diff --git a/website/src/components/SearchPage/SearchFullUI.tsx b/website/src/components/SearchPage/SearchFullUI.tsx index dd69a6bb76..d40a87cf53 100644 --- a/website/src/components/SearchPage/SearchFullUI.tsx +++ b/website/src/components/SearchPage/SearchFullUI.tsx @@ -91,10 +91,66 @@ export const InnerSearchFullUI = ({ return consolidateGroupedFields(metadataSchemaWithExpandedRanges); }, [metadataSchema]); - const [previewedSeqId, setPreviewedSeqId] = useState(null); - const [previewHalfScreen, setPreviewHalfScreen] = useState(false); const [state, setState] = useQueryAsState(initialQueryDict); + // Initialize previewedSeqId from URL parameter if present + const [previewedSeqId, setPreviewedSeqIdInner] = useState(state.selectedSeq || null); + const [previewHalfScreen, setPreviewHalfScreenInner] = useState(state.halfScreen === 'true'); + + // Function to update both state and URL for half screen preference + const setPreviewHalfScreen = useCallback( + (isHalfScreen: boolean) => { + setPreviewHalfScreenInner(isHalfScreen); + setState((prev: QueryState) => { + if (!isHalfScreen) { + const withoutHalfScreenSet = { ...prev }; + delete withoutHalfScreenSet.halfScreen; + return withoutHalfScreenSet; + } else { + return { + ...prev, + halfScreen: 'true', + }; + } + }); + }, + [setState], + ); + + // Function to update both state and URL for selected sequence + const setPreviewedSeqId = useCallback( + (seqId: string | null) => { + setPreviewedSeqIdInner(seqId); + setState((prev: QueryState) => { + if (seqId === null) { + const withoutSeqIdSet = { ...prev }; + delete withoutSeqIdSet.selectedSeq; + return withoutSeqIdSet; + } else { + return { + ...prev, + selectedSeq: seqId, + }; + } + }); + }, + [setState], + ); + + // Update local state when URL parameters change + useEffect(() => { + if (state.selectedSeq !== undefined && state.selectedSeq !== previewedSeqId) { + setPreviewedSeqIdInner(state.selectedSeq); + } else if (state.selectedSeq === undefined && previewedSeqId !== null) { + setPreviewedSeqIdInner(null); + } + + const halfScreenFromUrl = state.halfScreen === 'true'; + if (halfScreenFromUrl !== previewHalfScreen) { + setPreviewHalfScreenInner(halfScreenFromUrl); + } + }, [state.selectedSeq, state.halfScreen, previewedSeqId, previewHalfScreen]); + const searchVisibilities = useMemo(() => { return getFieldVisibilitiesFromQuery(schema, state); }, [schema, state]); diff --git a/website/tests/pages/search/index.spec.ts b/website/tests/pages/search/index.spec.ts index 36577b91e3..2204a3f49a 100644 --- a/website/tests/pages/search/index.spec.ts +++ b/website/tests/pages/search/index.spec.ts @@ -157,4 +157,61 @@ test.describe('The search page', () => { void expect(page.getByRole('checkbox', { name: 'Pango lineage' })).toBeVisible(); void expect(page.getByRole('checkbox', { name: 'Hidden Field' })).not.toBeVisible(); }); + + test('should add selected sequence to URL when clicking a sequence', async ({ searchPage, page }) => { + // Go to search page + await searchPage.goto(); + + // Get accessions using the helper method that returns a Promise + const accessions = await searchPage.getAccessions(1); + const accessionId = accessions[0]; + + // Find the link with the accession ID + const accessionLink = page.getByRole('link', { name: accessionId }); + + // Click to show the sequence preview modal + await accessionLink.click(); + + // Wait for the modal to appear + await expect(page.getByText('Amino acid mutations')).toBeVisible({ timeout: 30000 }); + + // Verify URL contains the selectedSeq parameter + await expect(page).toHaveURL(new RegExp(`selectedSeq=${accessionId}`)); + }); + + test('should add halfScreen parameter to URL when toggling view mode', async ({ searchPage, page }) => { + // Go to search page and click a sequence + await searchPage.goto(); + const firstAccessionLink = page.getByRole('link', { name: /LOC_\d+\.\d+/ }); + await firstAccessionLink.click(); + + // Wait for the modal to appear + await expect(page.getByText('Amino acid mutations')).toBeVisible({ timeout: 30000 }); + + // Click the dock button (halfScreen toggle) + await page.getByTitle('Dock sequence details view').click(); + + // Verify URL contains the halfScreen parameter + await expect(page).toHaveURL(/halfScreen=true/); + + // Toggle back to full screen + await page.getByTitle('Expand sequence details view').click(); + + // Verify halfScreen parameter is removed from URL + await expect(page).not.toHaveURL(/halfScreen/); + }); + + test('should restore state from URL parameters', async ({ searchPage, page }) => { + // Get a valid sequence ID first by using the searchPage fixture + await searchPage.goto(); + + const accessions = await searchPage.getAccessions(1); + + const accessionId = /LOC_\d+\.\d+/.exec(accessions[0])[0]; + + await page.goto(`${baseUrl}${routes.searchPage(dummyOrganism.key)}?selectedSeq=${accessionId}&halfScreen=true`); + + await expect(page.getByText('Amino acid mutations')).toBeVisible({ timeout: 30000 }); + await expect(page.getByTitle('Expand sequence details view')).toBeVisible(); + }); });