diff --git a/frontend/src/components/SessionPanel.tsx b/frontend/src/components/SessionPanel.tsx index 2afd994d..80c491f4 100644 --- a/frontend/src/components/SessionPanel.tsx +++ b/frontend/src/components/SessionPanel.tsx @@ -23,7 +23,7 @@ function readViewMode(): ViewMode { } export function SessionPanel({ activeSessionId, onSelectSession, onNewChat }: SessionPanelProps) { - const { sessions, loading, dismissSession } = useSessionList(); + const { sessions, loading, loadingMore, hasMore, loadMore, dismissSession } = useSessionList(); const { attendCount } = useSessionOverview(); const [viewMode, setViewMode] = useState(readViewMode); @@ -99,6 +99,11 @@ export function SessionPanel({ activeSessionId, onSelectSession, onNewChat }: Se ))} + {hasMore && ( + + )} )} diff --git a/frontend/src/components/__tests__/SessionPanel.test.tsx b/frontend/src/components/__tests__/SessionPanel.test.tsx index 877fbb91..f4827eb9 100644 --- a/frontend/src/components/__tests__/SessionPanel.test.tsx +++ b/frontend/src/components/__tests__/SessionPanel.test.tsx @@ -7,6 +7,15 @@ vi.mock('../../hooks/useSessionList', () => ({ useSessionList: vi.fn(), })); +// Mock useSessionOverview to avoid EventSource dependency +vi.mock('../../hooks/useSessionOverview', () => ({ + useSessionOverview: vi.fn(() => ({ + activities: [], + attendCount: 0, + connected: false, + })), +})); + import { SessionPanel } from '../SessionPanel'; import { useSessionList } from '../../hooks/useSessionList'; @@ -32,11 +41,15 @@ function makeDefaultReturn(overrides = {}) { beforeEach(() => { mockUseSessionList.mockReturnValue(makeDefaultReturn()); + // Default to "All" view so session list tests work (the "Active" view + // renders ActiveSessionsList which is tested separately). + localStorage.setItem('mitzo-session-view-mode', 'all'); }); afterEach(() => { cleanup(); vi.clearAllMocks(); + localStorage.clear(); }); describe('SessionPanel', () => { @@ -143,4 +156,64 @@ describe('SessionPanel', () => { fireEvent.click(deleteBtn); expect(dismiss).toHaveBeenCalledWith('s1'); }); + + describe('Load More button', () => { + it('does not render Load More when hasMore is false', () => { + mockUseSessionList.mockReturnValue( + makeDefaultReturn({ + sessions: [{ id: 's1', summary: 'A session', lastModified: Date.now() }], + hasMore: false, + }), + ); + render( + , + ); + expect(screen.queryByText('Load More')).toBeNull(); + }); + + it('renders when hasMore is true', () => { + mockUseSessionList.mockReturnValue( + makeDefaultReturn({ + sessions: [{ id: 's1', summary: 'A session', lastModified: Date.now() }], + hasMore: true, + }), + ); + render( + , + ); + expect(screen.getByText('Load More')).toBeTruthy(); + }); + + it('shows disabled loading state when loadingMore is true', () => { + mockUseSessionList.mockReturnValue( + makeDefaultReturn({ + sessions: [{ id: 's1', summary: 'A session', lastModified: Date.now() }], + hasMore: true, + loadingMore: true, + }), + ); + render( + , + ); + const btn = screen.getByText('Loading...'); + expect(btn).toBeTruthy(); + expect((btn as HTMLButtonElement).disabled).toBe(true); + }); + + it('calls loadMore when clicked', () => { + const loadMore = vi.fn(); + mockUseSessionList.mockReturnValue( + makeDefaultReturn({ + sessions: [{ id: 's1', summary: 'A session', lastModified: Date.now() }], + hasMore: true, + loadMore, + }), + ); + render( + , + ); + fireEvent.click(screen.getByText('Load More')); + expect(loadMore).toHaveBeenCalledOnce(); + }); + }); });