From 873db518f13c2b74530eb8a8261cbaf75f33f44f Mon Sep 17 00:00:00 2001 From: Filip Lindahl Date: Tue, 27 Feb 2024 21:53:39 +0100 Subject: [PATCH 1/3] first test for hash --- __tests__/atomWithLocation_spec.tsx | 160 ++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 7 deletions(-) diff --git a/__tests__/atomWithLocation_spec.tsx b/__tests__/atomWithLocation_spec.tsx index 3a26cb4..302c99c 100644 --- a/__tests__/atomWithLocation_spec.tsx +++ b/__tests__/atomWithLocation_spec.tsx @@ -12,12 +12,27 @@ function assertPathNameAndHistoryLength( expect(window.history.length).toEqual(expectedHistoryLength); } +function assertHashAndHistoryLength( + expectedHash: string, + expectedHistoryLength: number, +) { + expect(window.location.hash).toEqual(expectedHash); + expect(window.history.length).toEqual(expectedHistoryLength); +} + async function assertStartState( startTestHistoryLength: number, findByText: any, + assert: 'pathname' | 'hash' | 'both' = 'pathname', ) { - await findByText('current pathname in atomWithLocation: /'); - assertPathNameAndHistoryLength('/', startTestHistoryLength); + if (assert === 'pathname' || assert === 'both') { + await findByText('current pathname in atomWithLocation: /'); + assertPathNameAndHistoryLength('/', startTestHistoryLength); + } + if (assert === 'hash' || assert === 'both') { + await findByText('current hash in atomWithLocation: '); + assertHashAndHistoryLength('', startTestHistoryLength); + } } function clickButtonAndAssertTemplate(localFindByText: any) { @@ -25,26 +40,49 @@ function clickButtonAndAssertTemplate(localFindByText: any) { target: `button${number}` | 'back' | 'buttonWithReplace' | 'buttonWithPush', historyLength: number, targetPathName?: string, + assert: 'pathname' | 'hash' | 'both' = 'pathname', ) { let expectedPathname: string = '/'; + let expectedHash: string = ''; if (target === 'buttonWithReplace') { expectedPathname = '/123'; + expectedHash = '#tab=1'; } else if (target === 'buttonWithPush') { expectedPathname = '/234'; + expectedHash = '#tab=2'; } else if (target.startsWith('button')) { expectedPathname = `/${target.slice(-1)}`; + expectedHash = `#tab=${target.slice(-1)}`; } else if (target === 'back' && targetPathName) { expectedPathname = targetPathName; } await userEvent.click(await localFindByText(target)); - await localFindByText( - `current pathname in atomWithLocation: ${expectedPathname}`, - ); - assertPathNameAndHistoryLength(expectedPathname, historyLength); + if (assert === 'pathname') { + await localFindByText( + `current pathname in atomWithLocation: ${expectedPathname}`, + ); + assertPathNameAndHistoryLength(expectedPathname, historyLength); + } + if (assert === 'hash') { + await localFindByText( + `current hash in atomWithLocation: ${expectedHash}`, + ); + assertHashAndHistoryLength(expectedHash, historyLength); + } + if (assert === 'both') { + await localFindByText( + `current pathname in atomWithLocation: ${expectedPathname}`, + ); + assertPathNameAndHistoryLength(expectedPathname, historyLength); + await localFindByText( + `current hash in atomWithLocation: ${expectedHash}`, + ); + assertHashAndHistoryLength(expectedHash, historyLength); + } }; } -describe('atomWithLocation', () => { +describe('atomWithLocation, pathName', () => { beforeEach(() => { window.history.pushState(null, '', '/'); }); @@ -244,6 +282,114 @@ describe('atomWithLocation', () => { }); }); +describe('atomWithLocation, hash', () => { + it('can push state with hash', async () => { + const locationAtom = atomWithLocation({ replace: false }); + + const Navigation = () => { + const [location, setLocation] = useAtom(locationAtom); + return ( + <> +
current hash in atomWithLocation: #{location.hash}
+ + + + + ); + }; + + const { findByText } = render( + + + , + ); + + const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); + const startHistoryLength = window.history.length; + assertStartState(startHistoryLength, findByText); + + await clickButtonAndAssert( + 'button1', + startHistoryLength + 1, + undefined, + 'hash', + ); + await clickButtonAndAssert( + 'button2', + startHistoryLength + 2, + undefined, + 'hash', + ); + }); + + it('can replace state with hash', async () => { + const locationAtom = atomWithLocation({ replace: true }); + + const Navigation = () => { + const [location, setLocation] = useAtom(locationAtom); + return ( + <> +
current hash in atomWithLocation: #{location.hash}
+ + + + + ); + }; + + const { findByText } = render( + + + , + ); + + const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); + const startHistoryLength = window.history.length; + assertStartState(startHistoryLength, findByText); + + await clickButtonAndAssert( + 'button1', + startHistoryLength, + undefined, + 'hash', + ); + await clickButtonAndAssert( + 'button2', + startHistoryLength, + undefined, + 'hash', + ); + + await clickButtonAndAssert('back', startHistoryLength, undefined, 'hash'); + }); +}); + describe('atomWithLocation without window', () => { let savedWindow: any; beforeEach(() => { From 3fc0aff4e95f035e2da5c3adecb7278b17918f27 Mon Sep 17 00:00:00 2001 From: Filip Lindahl Date: Thu, 29 Feb 2024 22:31:48 +0100 Subject: [PATCH 2/3] add tests for searchparams --- __tests__/atomWithLocation_spec.tsx | 301 ++++++++++++++++++++++++++-- 1 file changed, 280 insertions(+), 21 deletions(-) diff --git a/__tests__/atomWithLocation_spec.tsx b/__tests__/atomWithLocation_spec.tsx index 302c99c..9944f63 100644 --- a/__tests__/atomWithLocation_spec.tsx +++ b/__tests__/atomWithLocation_spec.tsx @@ -20,41 +20,69 @@ function assertHashAndHistoryLength( expect(window.history.length).toEqual(expectedHistoryLength); } +function assertSearchParamAndHistoryLength( + expectedSearchParams: URLSearchParams, + expectedHistoryLength: number, +) { + expect(window.location.search).toEqual(`?${expectedSearchParams.toString()}`); + expect(window.history.length).toEqual(expectedHistoryLength); +} + async function assertStartState( startTestHistoryLength: number, findByText: any, - assert: 'pathname' | 'hash' | 'both' = 'pathname', + assert: 'pathname' | 'hash' | 'searchParams' | 'all', ) { - if (assert === 'pathname' || assert === 'both') { + if (assert === 'pathname' || assert === 'all') { await findByText('current pathname in atomWithLocation: /'); assertPathNameAndHistoryLength('/', startTestHistoryLength); } - if (assert === 'hash' || assert === 'both') { - await findByText('current hash in atomWithLocation: '); + if (assert === 'hash' || assert === 'all') { + await findByText('current hash in atomWithLocation: #'); assertHashAndHistoryLength('', startTestHistoryLength); } + if (assert === 'searchParams' || assert === 'all') { + await findByText('current searchParams in atomWithLocation:'); + expect(window.location.search).toEqual(''); + expect(window.history.length).toEqual(startTestHistoryLength); + } } function clickButtonAndAssertTemplate(localFindByText: any) { return async function clickButtonAndAssert( target: `button${number}` | 'back' | 'buttonWithReplace' | 'buttonWithPush', historyLength: number, - targetPathName?: string, - assert: 'pathname' | 'hash' | 'both' = 'pathname', + { + targetPathName, + targetHash, + targetSearchParams, + }: { + targetPathName?: string; + targetHash?: string; + targetSearchParams?: string; + } = {}, + assert: 'pathname' | 'hash' | 'search' | 'all' = 'pathname', ) { let expectedPathname: string = '/'; let expectedHash: string = ''; + let expectedSearchParams = new URLSearchParams(); if (target === 'buttonWithReplace') { expectedPathname = '/123'; expectedHash = '#tab=1'; + expectedSearchParams.set('tab', '1'); } else if (target === 'buttonWithPush') { expectedPathname = '/234'; expectedHash = '#tab=2'; + expectedSearchParams.set('tab', '2'); } else if (target.startsWith('button')) { expectedPathname = `/${target.slice(-1)}`; expectedHash = `#tab=${target.slice(-1)}`; - } else if (target === 'back' && targetPathName) { - expectedPathname = targetPathName; + expectedSearchParams.set('tab', target.slice(-1)); + } else if (target === 'back') { + expectedPathname = targetPathName ?? ''; + expectedHash = `#${targetHash ?? ''}`; + expectedSearchParams = new URLSearchParams(); + expectedSearchParams.set('tab', targetSearchParams ?? ''); } await userEvent.click(await localFindByText(target)); if (assert === 'pathname') { @@ -67,9 +95,16 @@ function clickButtonAndAssertTemplate(localFindByText: any) { await localFindByText( `current hash in atomWithLocation: ${expectedHash}`, ); - assertHashAndHistoryLength(expectedHash, historyLength); + assertHashAndHistoryLength( + expectedHash === '#' ? '' : expectedHash, + historyLength, + ); } - if (assert === 'both') { + if (assert === 'search') { + await localFindByText(`${expectedSearchParams.toString()}`); + assertSearchParamAndHistoryLength(expectedSearchParams, historyLength); + } + if (assert === 'all') { await localFindByText( `current pathname in atomWithLocation: ${expectedPathname}`, ); @@ -78,13 +113,24 @@ function clickButtonAndAssertTemplate(localFindByText: any) { `current hash in atomWithLocation: ${expectedHash}`, ); assertHashAndHistoryLength(expectedHash, historyLength); + await localFindByText( + `current searchParams in atomWithLocation: ${expectedSearchParams}`, + ); + assertSearchParamAndHistoryLength(expectedSearchParams, historyLength); } }; } +const defaultLocation = { + pathname: '/', + search: '', + hash: '', + state: null, +}; + describe('atomWithLocation, pathName', () => { beforeEach(() => { - window.history.pushState(null, '', '/'); + resetWindow(); }); it('can replace state', async () => { @@ -111,6 +157,12 @@ describe('atomWithLocation, pathName', () => { > button2 + ); }; @@ -123,10 +175,12 @@ describe('atomWithLocation, pathName', () => { const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); const startHistoryLength = window.history.length; - assertStartState(startHistoryLength, findByText); + await assertStartState(startHistoryLength, findByText, 'pathname'); await clickButtonAndAssert('button1', startHistoryLength); await clickButtonAndAssert('button2', startHistoryLength); + + await userEvent.click(await findByText('reset-button')); }); it('can push state', async () => { @@ -153,6 +207,12 @@ describe('atomWithLocation, pathName', () => { > button2 + ); }; @@ -165,11 +225,15 @@ describe('atomWithLocation, pathName', () => { const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); const startHistoryLength = window.history.length; - assertStartState(startHistoryLength, findByText); + assertStartState(startHistoryLength, findByText, 'pathname'); await clickButtonAndAssert('button1', startHistoryLength + 1); await clickButtonAndAssert('button2', startHistoryLength + 2); - await clickButtonAndAssert('back', startHistoryLength + 2, '/1'); + await clickButtonAndAssert('back', startHistoryLength + 2, { + targetPathName: '/1', + }); + + await userEvent.click(await findByText('reset-button')); }); it('can override atomOptions, from replace=false to replace=true', async () => { @@ -202,6 +266,12 @@ describe('atomWithLocation, pathName', () => { > buttonWithReplace + ); }; @@ -215,11 +285,13 @@ describe('atomWithLocation, pathName', () => { const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); const startHistoryLength = window.history.length; - assertStartState(startHistoryLength, findByText); + assertStartState(startHistoryLength, findByText, 'pathname'); await clickButtonAndAssert('buttonWithPush', startHistoryLength + 1); await clickButtonAndAssert('buttonWithReplace', startHistoryLength + 1); - await clickButtonAndAssert('back', startHistoryLength + 1, '/'); + await clickButtonAndAssert('back', startHistoryLength + 1, { + targetPathName: '/', + }); // This click overwrites the history entry we // went back from. The history length remains the same. @@ -227,6 +299,8 @@ describe('atomWithLocation, pathName', () => { // The second click adds a new history entry, which now increments the history length. await clickButtonAndAssert('buttonWithPush', startHistoryLength + 2); + + await userEvent.click(await findByText('reset-button')); }); it('can override atomOptions, from replace=true to replace=false', async () => { @@ -259,6 +333,12 @@ describe('atomWithLocation, pathName', () => { > buttonWithPush + ); }; @@ -271,18 +351,26 @@ describe('atomWithLocation, pathName', () => { const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); const startTestHistoryLength = window.history.length; - assertStartState(startTestHistoryLength, findByText); + assertStartState(startTestHistoryLength, findByText, 'pathname'); await clickButtonAndAssert('buttonWithReplace', startTestHistoryLength); await clickButtonAndAssert('buttonWithPush', startTestHistoryLength + 1); - await clickButtonAndAssert('back', startTestHistoryLength + 1, '/123'); + await clickButtonAndAssert('back', startTestHistoryLength + 1, { + targetPathName: '/123', + }); await clickButtonAndAssert('buttonWithReplace', startTestHistoryLength + 1); await clickButtonAndAssert('buttonWithPush', startTestHistoryLength + 1); await clickButtonAndAssert('buttonWithPush', startTestHistoryLength + 2); + + await userEvent.click(await findByText('reset-button')); }); }); describe('atomWithLocation, hash', () => { + beforeEach(() => { + resetWindow(); + }); + it('can push state with hash', async () => { const locationAtom = atomWithLocation({ replace: false }); @@ -307,6 +395,12 @@ describe('atomWithLocation, hash', () => { > button2 + ); }; @@ -319,7 +413,7 @@ describe('atomWithLocation, hash', () => { const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); const startHistoryLength = window.history.length; - assertStartState(startHistoryLength, findByText); + assertStartState(startHistoryLength, findByText, 'hash'); await clickButtonAndAssert( 'button1', @@ -333,6 +427,8 @@ describe('atomWithLocation, hash', () => { undefined, 'hash', ); + + await userEvent.click(await findByText('reset-button')); }); it('can replace state with hash', async () => { @@ -359,6 +455,12 @@ describe('atomWithLocation, hash', () => { > button2 + ); }; @@ -371,7 +473,7 @@ describe('atomWithLocation, hash', () => { const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); const startHistoryLength = window.history.length; - assertStartState(startHistoryLength, findByText); + assertStartState(startHistoryLength, findByText, 'hash'); await clickButtonAndAssert( 'button1', @@ -386,7 +488,164 @@ describe('atomWithLocation, hash', () => { 'hash', ); - await clickButtonAndAssert('back', startHistoryLength, undefined, 'hash'); + await clickButtonAndAssert( + 'back', + startHistoryLength, + { targetHash: '' }, + 'hash', + ); + + await userEvent.click(await findByText('reset-button')); + }); +}); + +function resetWindow() { + window.history.pushState(null, '', '/'); + window.location.search = ''; + window.location.hash = ''; +} + +describe('atomWithLocation, searchParams', () => { + beforeEach(() => { + resetWindow(); + }); + it('can push state with searchParams', async () => { + const locationAtom = atomWithLocation({ replace: false }); + + const Navigation = () => { + const [location, setLocation] = useAtom(locationAtom); + const tab1Params = new URLSearchParams(); + tab1Params.set('tab', '1'); + const tab2Params = new URLSearchParams(); + tab2Params.set('tab', '2'); + + return ( + <> +
+ current searchParams in atomWithLocation: +
{location.searchParams?.toString()}
+
+ + + + + + ); + }; + + const { findByText, debug } = render( + + + , + ); + + const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); + const startHistoryLength = window.history.length; + await assertStartState(startHistoryLength, findByText, 'searchParams'); + debug(); + await clickButtonAndAssert( + 'button1', + startHistoryLength + 1, + undefined, + 'search', + ); + await clickButtonAndAssert( + 'button2', + startHistoryLength + 2, + undefined, + 'search', + ); + + await userEvent.click(await findByText('reset-button')); + }); + + it('can replace state with searchParams', async () => { + const locationAtom = atomWithLocation({ replace: true }); + + const Navigation = () => { + const [location, setLocation] = useAtom(locationAtom); + const tab1Params = new URLSearchParams(); + tab1Params.set('tab', '1'); + const tab2Params = new URLSearchParams(); + tab2Params.set('tab', '2'); + + return ( + <> +
current searchParams in atomWithLocation:
+
{location.searchParams?.toString()}
+ + + + + + ); + }; + + const { findByText } = render( + + + , + ); + + const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); + const startHistoryLength = window.history.length; + assertStartState(startHistoryLength, findByText, 'searchParams'); + + await clickButtonAndAssert( + 'button1', + startHistoryLength, + undefined, + 'search', + ); + await clickButtonAndAssert( + 'button2', + startHistoryLength, + undefined, + 'search', + ); + + await clickButtonAndAssert( + 'back', + startHistoryLength, + { targetSearchParams: '2' }, + 'search', + ); + + await userEvent.click(await findByText('reset-button')); }); }); From f2d65b4070867fc28dcde1f3a329d5aafdbf05eb Mon Sep 17 00:00:00 2001 From: Filip Lindahl Date: Sat, 9 Mar 2024 17:26:11 +0100 Subject: [PATCH 3/3] remove unused variable --- __tests__/atomWithLocation_spec.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/__tests__/atomWithLocation_spec.tsx b/__tests__/atomWithLocation_spec.tsx index 9944f63..05864ab 100644 --- a/__tests__/atomWithLocation_spec.tsx +++ b/__tests__/atomWithLocation_spec.tsx @@ -550,7 +550,7 @@ describe('atomWithLocation, searchParams', () => { ); }; - const { findByText, debug } = render( + const { findByText } = render( , @@ -559,7 +559,6 @@ describe('atomWithLocation, searchParams', () => { const clickButtonAndAssert = clickButtonAndAssertTemplate(findByText); const startHistoryLength = window.history.length; await assertStartState(startHistoryLength, findByText, 'searchParams'); - debug(); await clickButtonAndAssert( 'button1', startHistoryLength + 1,