it('renders source list, detail and a completed bounded discovery refresh',async()=>{constuser=userEvent.setup();mockGet.mockImplementation((path: string)=>Promise.resolve({data: path==='/api/energy/sources'?{items:[source]}:path.includes('channels')?{items:[]}:source}));mockPost.mockResolvedValue({data:{status:'completed'}});renderWithProviders(<SourceManager/>);awaituser.click(awaitscreen.findByText('WarmteLink'));expect(awaitscreen.findByText(/No channels discovered yet/)).toBeInTheDocument();awaituser.click(screen.getByRole('button',{name:/Refresh discovered channels/}));expect(awaitscreen.findByText(/Discovery completed/)).toBeInTheDocument()})
it('explains that an enabled serial source reconnects automatically',async()=>{constuser=userEvent.setup();mockGet.mockImplementation((path: string)=>Promise.resolve({data: path==='/api/energy/sources'?{items:[{...source,status:'error',last_error:'serial unavailable'}]}:path.includes('channels')?{items:[]}:{...source,status:'error',last_error:'serial unavailable'}}));renderWithProviders(<SourceManager/>);awaituser.click(awaitscreen.findByText('WarmteLink'));expect(awaitscreen.findAllByText(/reconnecting automatically/i)).not.toHaveLength(0);expect(screen.getByRole('button',{name:/Refresh discovered channels/})).toBeInTheDocument()})
it('renders source list empty and error states',async()=>{mockGet.mockResolvedValueOnce({data:{items:[]}});const{unmount}=renderWithProviders(<SourceManager/>);expect(awaitscreen.findByText(/No sources configured/)).toBeInTheDocument();unmount();mockGet.mockRejectedValueOnce(newError('offline'));renderWithProviders(<SourceManager/>);expect(awaitscreen.findByText(/Failed to load sources/)).toBeInTheDocument()})
it('contains the wide four-column source table in a scroll area',async()=>{mockGet.mockResolvedValue({data:{items:[source]}});renderWithProviders(<SourceManager/>);expect(awaitscreen.findByTestId('sources-table-scrollarea')).toBeInTheDocument();expect(screen.getByTestId('sources-table')).toHaveStyle({minWidth:'640px'})})
it('safely deletes an unreferenced source and clears its selection',async()=>{constuser=userEvent.setup();letitems=[source];mockGet.mockImplementation((path: string)=>Promise.resolve({data: path==='/api/energy/sources'?{items}:path.includes('channels')?{items:[]}:source}));mockDelete.mockImplementation(async()=>{items=[];return{data: undefined}});renderWithProviders(<SourceManager/>);awaituser.click(awaitscreen.findByText('WarmteLink'));awaituser.click(screen.getByRole('button',{name:'Delete source'}));awaitwaitFor(()=>expect(mockDelete).toHaveBeenCalledWith('/api/energy/sources/{source_uuid}',{params:{path:{source_uuid:'s1'}}}));expect(awaitscreen.findByText(/No sources configured/)).toBeInTheDocument();expect(screen.queryByRole('button',{name:'Delete source'})).not.toBeInTheDocument()})
it('keeps source visible and explains dependencies when deletion returns 409',async()=>{constuser=userEvent.setup();mockGet.mockImplementation((path: string)=>Promise.resolve({data: path==='/api/energy/sources'?{items:[source]}:path.includes('channels')?{items:[]}:source}));const{ApiError}=awaitimport('../api/client');mockDelete.mockRejectedValue(newApiError(409,{detail:'dependent readings'}));renderWithProviders(<SourceManager/>);awaituser.click(awaitscreen.findByText('WarmteLink'));awaituser.click(screen.getByRole('button',{name:'Delete source'}));expect(awaitscreen.findByText(/dependent channels, readings, or meter bindings/)).toBeInTheDocument();expect(screen.getByRole('button',{name:'WarmteLink'})).toBeInTheDocument()})