From ca3eae2412968d49d799ebe1671ddece4e74f7b6 Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Wed, 26 Feb 2025 00:34:29 +0000 Subject: [PATCH 1/6] preview in url --- .../components/SearchPage/SearchFullUI.tsx | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/website/src/components/SearchPage/SearchFullUI.tsx b/website/src/components/SearchPage/SearchFullUI.tsx index dd69a6bb7..5a74d3f15 100644 --- a/website/src/components/SearchPage/SearchFullUI.tsx +++ b/website/src/components/SearchPage/SearchFullUI.tsx @@ -91,9 +91,65 @@ 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); From 58051cbd1ca3ec79ea406dd2c1b7838b82bb1ab1 Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Wed, 26 Feb 2025 00:35:26 +0000 Subject: [PATCH 2/6] try an e2e test --- website/tests/pages/search/index.spec.ts | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/website/tests/pages/search/index.spec.ts b/website/tests/pages/search/index.spec.ts index 36577b91e..68410af0b 100644 --- a/website/tests/pages/search/index.spec.ts +++ b/website/tests/pages/search/index.spec.ts @@ -157,4 +157,57 @@ 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 and click a sequence + await searchPage.goto(); + const firstAccessionLink = await page.locator('tr').nth(1).locator('a').first(); + const accessionId = await firstAccessionLink.textContent(); + + // Click to show the sequence preview modal + await firstAccessionLink.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 = await page.locator('tr').nth(1).locator('a').first(); + 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 ({ page }) => { + // Get a valid sequence ID first + const searchPage = new SearchPage(page); + await searchPage.goto(); + const firstAccessionLink = await page.locator('tr').nth(1).locator('a').first(); + const accessionId = await firstAccessionLink.textContent(); + + // Go directly to a URL with parameters + await page.goto(`${baseUrl}${routes.searchPage(dummyOrganism.key)}?selectedSeq=${accessionId}&halfScreen=true`); + + // Verify the sequence preview is shown and in half-screen mode + await expect(page.getByText('Amino acid mutations')).toBeVisible({ timeout: 30000 }); + await expect(page.getByTitle('Expand sequence details view')).toBeVisible(); + }); }); From 3b00135136e40c413cbad990f342c760e965de07 Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Wed, 26 Feb 2025 00:41:44 +0000 Subject: [PATCH 3/6] lint --- .../components/SearchPage/SearchFullUI.tsx | 14 ++++---- website/tests/pages/search/index.spec.ts | 35 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/website/src/components/SearchPage/SearchFullUI.tsx b/website/src/components/SearchPage/SearchFullUI.tsx index 5a74d3f15..d40a87cf5 100644 --- a/website/src/components/SearchPage/SearchFullUI.tsx +++ b/website/src/components/SearchPage/SearchFullUI.tsx @@ -92,11 +92,11 @@ export const InnerSearchFullUI = ({ }, [metadataSchema]); 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) => { @@ -114,9 +114,9 @@ export const InnerSearchFullUI = ({ } }); }, - [setState] + [setState], ); - + // Function to update both state and URL for selected sequence const setPreviewedSeqId = useCallback( (seqId: string | null) => { @@ -134,9 +134,9 @@ export const InnerSearchFullUI = ({ } }); }, - [setState] + [setState], ); - + // Update local state when URL parameters change useEffect(() => { if (state.selectedSeq !== undefined && state.selectedSeq !== previewedSeqId) { @@ -144,7 +144,7 @@ export const InnerSearchFullUI = ({ } else if (state.selectedSeq === undefined && previewedSeqId !== null) { setPreviewedSeqIdInner(null); } - + const halfScreenFromUrl = state.halfScreen === 'true'; if (halfScreenFromUrl !== previewHalfScreen) { setPreviewHalfScreenInner(halfScreenFromUrl); diff --git a/website/tests/pages/search/index.spec.ts b/website/tests/pages/search/index.spec.ts index 68410af0b..25fcf6092 100644 --- a/website/tests/pages/search/index.spec.ts +++ b/website/tests/pages/search/index.spec.ts @@ -161,15 +161,15 @@ test.describe('The search page', () => { test('should add selected sequence to URL when clicking a sequence', async ({ searchPage, page }) => { // Go to search page and click a sequence await searchPage.goto(); - const firstAccessionLink = await page.locator('tr').nth(1).locator('a').first(); + const firstAccessionLink = page.locator('tr').nth(1).locator('a').first(); const accessionId = await firstAccessionLink.textContent(); - + // Click to show the sequence preview modal await firstAccessionLink.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}`)); }); @@ -177,35 +177,36 @@ test.describe('The search page', () => { 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 = await page.locator('tr').nth(1).locator('a').first(); + const firstAccessionLink = page.locator('tr').nth(1).locator('a').first(); 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 ({ page }) => { - // Get a valid sequence ID first - const searchPage = new SearchPage(page); + 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 firstAccessionLink = await page.locator('tr').nth(1).locator('a').first(); - const accessionId = await firstAccessionLink.textContent(); - + + // Get the first accession ID + const accessions = await searchPage.getAccessions(1); + const accessionId = accessions[0]; + // Go directly to a URL with parameters await page.goto(`${baseUrl}${routes.searchPage(dummyOrganism.key)}?selectedSeq=${accessionId}&halfScreen=true`); - + // Verify the sequence preview is shown and in half-screen mode await expect(page.getByText('Amino acid mutations')).toBeVisible({ timeout: 30000 }); await expect(page.getByTitle('Expand sequence details view')).toBeVisible(); From d5da7276a8b3beaa0ccb36ba5592d92f91932e6e Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Wed, 26 Feb 2025 00:47:27 +0000 Subject: [PATCH 4/6] fix --- website/tests/pages/search/index.spec.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/website/tests/pages/search/index.spec.ts b/website/tests/pages/search/index.spec.ts index 25fcf6092..a1144b341 100644 --- a/website/tests/pages/search/index.spec.ts +++ b/website/tests/pages/search/index.spec.ts @@ -159,13 +159,18 @@ test.describe('The search page', () => { }); test('should add selected sequence to URL when clicking a sequence', async ({ searchPage, page }) => { - // Go to search page and click a sequence + // Go to search page await searchPage.goto(); - const firstAccessionLink = page.locator('tr').nth(1).locator('a').first(); - const accessionId = await firstAccessionLink.textContent(); + + // 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 firstAccessionLink.click(); + await accessionLink.click(); // Wait for the modal to appear await expect(page.getByText('Amino acid mutations')).toBeVisible({ timeout: 30000 }); From 381a187e929c4e7ce7f25e8611403f5d75fe43ed Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Wed, 26 Feb 2025 01:03:35 +0000 Subject: [PATCH 5/6] fix maybe --- website/tests/pages/search/index.spec.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/website/tests/pages/search/index.spec.ts b/website/tests/pages/search/index.spec.ts index a1144b341..056087082 100644 --- a/website/tests/pages/search/index.spec.ts +++ b/website/tests/pages/search/index.spec.ts @@ -182,7 +182,7 @@ test.describe('The search page', () => { 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.locator('tr').nth(1).locator('a').first(); + const firstAccessionLink = page.getByRole('link', { name: /LOC_\d+\.\d+/ }); await firstAccessionLink.click(); // Wait for the modal to appear @@ -205,14 +205,13 @@ test.describe('The search page', () => { // Get a valid sequence ID first by using the searchPage fixture await searchPage.goto(); - // Get the first accession ID + const accessions = await searchPage.getAccessions(1); - const accessionId = accessions[0]; + + const accessionId = accessions[0].match(/LOC_\d+\.\d+/)[0]; - // Go directly to a URL with parameters await page.goto(`${baseUrl}${routes.searchPage(dummyOrganism.key)}?selectedSeq=${accessionId}&halfScreen=true`); - // Verify the sequence preview is shown and in half-screen mode await expect(page.getByText('Amino acid mutations')).toBeVisible({ timeout: 30000 }); await expect(page.getByTitle('Expand sequence details view')).toBeVisible(); }); From 137cdd125b1f6387adbd9458b6195f8e8de49989 Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Wed, 26 Feb 2025 01:15:30 +0000 Subject: [PATCH 6/6] u --- website/tests/pages/search/index.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/website/tests/pages/search/index.spec.ts b/website/tests/pages/search/index.spec.ts index 056087082..2204a3f49 100644 --- a/website/tests/pages/search/index.spec.ts +++ b/website/tests/pages/search/index.spec.ts @@ -205,10 +205,9 @@ test.describe('The search page', () => { // Get a valid sequence ID first by using the searchPage fixture await searchPage.goto(); - const accessions = await searchPage.getAccessions(1); - - const accessionId = accessions[0].match(/LOC_\d+\.\d+/)[0]; + + const accessionId = /LOC_\d+\.\d+/.exec(accessions[0])[0]; await page.goto(`${baseUrl}${routes.searchPage(dummyOrganism.key)}?selectedSeq=${accessionId}&halfScreen=true`);