UNPKG

@coveord/plasma-mantine

Version:

A Plasma flavoured Mantine theme

219 lines (198 loc) 8.92 kB
import {act, renderHook} from '@test-utils'; import {useUrlSyncedState} from '../use-url-synced-state'; /** * Split a url into its parts. * Note that this method is purposefully different from the implementation, * and it splits to an object instead of an Array. * * @param href The url to extract the parts from. * @returns The separate parts, all are an empty string if not present. */ const extractParts = (href: string) => /^(?<pathname>[^?#]*)(?<search>\?[^#]*|)(?<hash>#[^?]*|)(?<hashSearch>\?.*|)$/.exec(href).groups as { pathname: string; search: string; hash: string; hashSearch: string; }; describe.each(['/', '/#', '/#?', '/#hash?with-question=mark', '/sub/path', '/?leave=untouched#/', '/?dev#/sub/path/'])( 'useUrlSyncedState with location %s', (location) => { const locationParts = extractParts(location); const isHashRoute = locationParts.hash.startsWith('#/'); beforeEach(() => { window.history.replaceState(null, '', location); }); afterEach(() => { window.history.replaceState(null, '', '/'); }); /** * Utility that will set the search to be a specific value, taking into account hash routes. * * @param search The search value to set. */ const setSearch = (search: string) => { const parts = extractParts(location); if (isHashRoute) { parts.hashSearch = search; } else { parts.search = search; } window.history.replaceState(null, '', `${parts.pathname}${parts.search}${parts.hash}${parts.hashSearch}`); }; /** * Utility expect that will automatically check either the hash route search or normal search, * and also validates the other part is left as-is, if necessary. * * @param expectedSearch The expected search */ const expectSearch = (expectedSearch: string) => { expect(window.location.pathname).toBe(locationParts.pathname); if (isHashRoute) { expect(window.location.search).toBe(locationParts.search); expect(window.location.hash).toBe(`${locationParts.hash}${expectedSearch}`); } else { expect(window.location.search).toBe(expectedSearch); const hash = `${locationParts.hash}${locationParts.hashSearch}`; // "fun" quirk: if the hash is only #, location.hash is empty. expect(window.location.hash).toBe(hash === '#' ? '' : hash); } }; it('serializes the state value as url parameter when the state changes', () => { const {result} = renderHook(() => useUrlSyncedState({ initialState: '', serializer: (state) => [['key', state]], deserializer: (params) => params.get('key') ?? '', }), ); act(() => result.current[1]('value')); expect(result.current[0]).toBe('value'); expectSearch('?key=value'); }); it('allows to serialize the state value into multiple parameters', () => { const {result} = renderHook(() => useUrlSyncedState({ initialState: new Date(), serializer: (state) => { const iso = state.toISOString(); return [ ['date', iso.substring(0, 10)], ['time', iso.substring(11, 24)], ]; }, deserializer: (params) => new Date(`${params.get('date') ?? '2025-01-01'}T${params.get('time') ?? '00:00:00.000Z'}`), }), ); act(() => result.current[1](new Date(Date.UTC(2025, 0, 31, 12, 34, 56, 789)))); expectSearch('?date=2025-01-31&time=12%3A34%3A56.789Z'); }); it('removes the parameter from the url if the state serializes to the same value as the initial state', () => { const {result} = renderHook(() => useUrlSyncedState({ initialState: true, serializer: (state) => [['key', state ? 'true' : 'false']], deserializer: (params, initialState) => params.has('key') ? params.get('key') === 'true' : initialState, sync: true, }), ); act(() => result.current[1](false)); expectSearch('?key=false'); act(() => result.current[1](true)); expectSearch(''); }); it('removes the parameter from the url if the state serializes to the empty string', () => { const {result} = renderHook(() => useUrlSyncedState({ initialState: 'initial', serializer: (state) => [['key', state]], deserializer: (params) => params.get('key') ?? '', }), ); act(() => result.current[1]('value')); expectSearch('?key=value'); act(() => result.current[1]('')); expectSearch(''); }); it('does not sync with the url if the sync parameter is set to false', () => { const {result} = renderHook(() => useUrlSyncedState({ initialState: '', serializer: (state) => [['key', state]], deserializer: (params) => params.get('key') ?? '', sync: false, }), ); act(() => result.current[1]('value')); expect(result.current[0]).toBe('value'); expectSearch(''); }); it('derives the initial state from the url on first render', () => { setSearch('?key=value'); const {result} = renderHook(() => useUrlSyncedState({ initialState: 'initial', serializer: (state) => [['key', state]], deserializer: (params) => params.get('key') ?? '', }), ); expect(result.current[0]).toBe('value'); }); it('does not derive the initial state from the url on first render if sync option is false', () => { setSearch('?key=value'); const {result} = renderHook(() => useUrlSyncedState({ initialState: 'initial', serializer: (state) => [['key', state]], deserializer: (params) => params.get('key') ?? '', sync: false, }), ); expect(result.current[0]).toBe('initial'); }); it('does not serializes initial state for "always emit" value, if sync option is false', () => { const {result} = renderHook(() => useUrlSyncedState({ initialState: 'initial', serializer: (state) => [['key', state, true]], deserializer: (params) => params.get('key') ?? '', sync: false, }), ); expect(result.current[0]).toBe('initial'); expectSearch(''); }); it('serializes initial state for "always emit" value', () => { setSearch('?keep=as-is'); const {result} = renderHook(() => useUrlSyncedState({ initialState: 'initial', serializer: (state) => [['key', state, true]], deserializer: (params, initial) => params.get('key') ?? initial, }), ); expect(result.current[0]).toBe('initial'); expectSearch('?keep=as-is&key=initial'); }); it('deserializes initial state for "always emit" value, without changing it', () => { setSearch('?text=value'); const {result} = renderHook(() => useUrlSyncedState({ initialState: {text: 'initial', nr: 42}, serializer: (state) => [ ['text', state.text, true], ['number', String(state.nr), true], ], deserializer: (params, initial) => ({ text: params.get('text') ?? initial.text, nr: params.has('number') ? Number.parseInt(params.get('number'), 10) : initial.nr, }), }), ); expect(result.current[0]).toStrictEqual({text: 'value', nr: 42}); expectSearch('?text=value&number=42'); }); }, );