diff --git a/ui/desktop/src/hooks/useNavigationSessions.test.tsx b/ui/desktop/src/hooks/useNavigationSessions.test.tsx new file mode 100644 index 000000000..d3d933fc3 --- /dev/null +++ b/ui/desktop/src/hooks/useNavigationSessions.test.tsx @@ -0,0 +1,54 @@ +import { act, renderHook } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const navigateMock = vi.hoisted(() => vi.fn()); +const routerState = vi.hoisted(() => ({ searchParams: new URLSearchParams() })); + +vi.mock('react-router', () => ({ + useNavigate: () => navigateMock, + useLocation: () => ({ pathname: '/pair', search: routerState.searchParams.toString() }), + useSearchParams: () => [routerState.searchParams, vi.fn()], +})); + +vi.mock('../acp/sessions', () => ({ + acpGetSessionListItem: vi.fn(() => new Promise(() => {})), + acpListRecentSessions: vi.fn().mockResolvedValue([]), +})); + +vi.mock('../contexts/ChatContext', () => ({ + useChatContext: () => ({ chat: { sessionId: undefined } }), +})); + +import { useNavigationSessions } from './useNavigationSessions'; + +const injectedSessionId = 'session-1&shouldStartAgent=true'; + +function expectSingleSessionParameter(target: string) { + const url = new URL(target, 'http://localhost'); + expect(url.searchParams.get('resumeSessionId')).toBe(injectedSessionId); + expect(url.searchParams.get('shouldStartAgent')).toBeNull(); +} + +describe('useNavigationSessions', () => { + beforeEach(() => { + navigateMock.mockReset(); + routerState.searchParams = new URLSearchParams(); + }); + + it('keeps a selected session ID in one query parameter', () => { + const { result } = renderHook(() => useNavigationSessions()); + + act(() => result.current.handleSessionClick(injectedSessionId)); + + expectSingleSessionParameter(navigateMock.mock.calls[0][0]); + }); + + it('keeps a retained session ID in one query parameter', () => { + routerState.searchParams = new URLSearchParams({ resumeSessionId: injectedSessionId }); + const { result } = renderHook(() => useNavigationSessions()); + + act(() => result.current.handleNavClick('/pair')); + + expectSingleSessionParameter(navigateMock.mock.calls[0][0]); + }); +}); diff --git a/ui/desktop/src/hooks/useNavigationSessions.ts b/ui/desktop/src/hooks/useNavigationSessions.ts index 9eaad0b1d..3c180f7b3 100644 --- a/ui/desktop/src/hooks/useNavigationSessions.ts +++ b/ui/desktop/src/hooks/useNavigationSessions.ts @@ -13,6 +13,11 @@ import { groupSessionsByProject } from '../utils/projectSessions'; const MAX_RECENT_SESSIONS = 25; +function pairSessionPath(sessionId: string): string { + const searchParams = new URLSearchParams({ resumeSessionId: sessionId }); + return `/pair?${searchParams.toString()}`; +} + export function prependUnique( prev: SessionListItem[], session: SessionListItem @@ -188,7 +193,7 @@ export function useNavigationSessions() { const sessionId = currentSessionId || lastSessionIdRef.current || chatContext?.chat?.sessionId; if (sessionId && sessionId.length > 0) { - navigate(`/pair?resumeSessionId=${sessionId}`); + navigate(pairSessionPath(sessionId)); } else { navigate('/'); } @@ -201,7 +206,7 @@ export function useNavigationSessions() { const handleSessionClick = useCallback( (sessionId: string) => { - navigate(`/pair?resumeSessionId=${sessionId}`); + navigate(pairSessionPath(sessionId)); }, [navigate] );