useLocalStorageQuery.test.ts113 lines · main
| 1 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query' |
| 2 | import { act, renderHook, waitFor } from '@testing-library/react' |
| 3 | import React from 'react' |
| 4 | import { afterEach, beforeEach, describe, expect, it } from 'vitest' |
| 5 | |
| 6 | import { useLocalStorageQuery } from '../useLocalStorage' |
| 7 | import { customRenderHook } from '@/tests/lib/custom-render' |
| 8 | |
| 9 | function makeQueryClient() { |
| 10 | return new QueryClient({ defaultOptions: { queries: { retry: false } } }) |
| 11 | } |
| 12 | |
| 13 | function makeWrapper(queryClient: QueryClient) { |
| 14 | return ({ children }: { children: React.ReactNode }) => |
| 15 | React.createElement(QueryClientProvider, { client: queryClient }, children) |
| 16 | } |
| 17 | |
| 18 | describe('useLocalStorageQuery', () => { |
| 19 | beforeEach(() => { |
| 20 | window.localStorage.clear() |
| 21 | }) |
| 22 | |
| 23 | afterEach(() => { |
| 24 | window.localStorage.clear() |
| 25 | }) |
| 26 | |
| 27 | it('returns initialValue when localStorage has no entry for the key', async () => { |
| 28 | const { result } = customRenderHook(() => useLocalStorageQuery('test-key', 'default')) |
| 29 | |
| 30 | await waitFor(() => expect(result.current[2].isSuccess).toBe(true)) |
| 31 | expect(result.current[0]).toBe('default') |
| 32 | }) |
| 33 | |
| 34 | it('returns the stored value from localStorage on mount', async () => { |
| 35 | window.localStorage.setItem('test-key', JSON.stringify('persisted')) |
| 36 | |
| 37 | const { result } = customRenderHook(() => useLocalStorageQuery('test-key', 'default')) |
| 38 | |
| 39 | await waitFor(() => expect(result.current[0]).toBe('persisted')) |
| 40 | }) |
| 41 | |
| 42 | it('updates the value in state and localStorage when setValue is called', async () => { |
| 43 | const { result } = customRenderHook(() => useLocalStorageQuery('test-key', 'default')) |
| 44 | await waitFor(() => expect(result.current[2].isSuccess).toBe(true)) |
| 45 | |
| 46 | act(() => { |
| 47 | result.current[1]('updated') |
| 48 | }) |
| 49 | |
| 50 | await waitFor(() => expect(result.current[0]).toBe('updated')) |
| 51 | expect(window.localStorage.getItem('test-key')).toBe(JSON.stringify('updated')) |
| 52 | }) |
| 53 | |
| 54 | it('supports a function updater that receives the current value', async () => { |
| 55 | window.localStorage.setItem('count', JSON.stringify(5)) |
| 56 | |
| 57 | const { result } = customRenderHook(() => useLocalStorageQuery('count', 0)) |
| 58 | await waitFor(() => expect(result.current[0]).toBe(5)) |
| 59 | |
| 60 | act(() => { |
| 61 | result.current[1]((prev: number) => prev + 1) |
| 62 | }) |
| 63 | |
| 64 | await waitFor(() => expect(result.current[0]).toBe(6)) |
| 65 | expect(window.localStorage.getItem('count')).toBe(JSON.stringify(6)) |
| 66 | }) |
| 67 | |
| 68 | it('works with object values', async () => { |
| 69 | const stored = { count: 3, label: 'hello' } |
| 70 | window.localStorage.setItem('obj-key', JSON.stringify(stored)) |
| 71 | |
| 72 | const { result } = customRenderHook(() => |
| 73 | useLocalStorageQuery('obj-key', { count: 0, label: '' }) |
| 74 | ) |
| 75 | |
| 76 | await waitFor(() => expect(result.current[0]).toEqual(stored)) |
| 77 | }) |
| 78 | |
| 79 | it('syncs two hooks sharing the same key via the same QueryClient', async () => { |
| 80 | const queryClient = makeQueryClient() |
| 81 | |
| 82 | const { result } = renderHook( |
| 83 | () => ({ |
| 84 | a: useLocalStorageQuery('shared-key', 'initial'), |
| 85 | b: useLocalStorageQuery('shared-key', 'initial'), |
| 86 | }), |
| 87 | { wrapper: makeWrapper(queryClient) } |
| 88 | ) |
| 89 | |
| 90 | await waitFor(() => expect(result.current.a[2].isSuccess).toBe(true)) |
| 91 | |
| 92 | act(() => { |
| 93 | result.current.a[1]('from-a') |
| 94 | }) |
| 95 | |
| 96 | await waitFor(() => { |
| 97 | expect(result.current.a[0]).toBe('from-a') |
| 98 | expect(result.current.b[0]).toBe('from-a') |
| 99 | }) |
| 100 | }) |
| 101 | |
| 102 | it('uses the initialValue as fallback when the function updater runs before any stored value', async () => { |
| 103 | const { result } = customRenderHook(() => useLocalStorageQuery('new-key', 10)) |
| 104 | await waitFor(() => expect(result.current[2].isSuccess).toBe(true)) |
| 105 | |
| 106 | act(() => { |
| 107 | result.current[1]((prev: number) => prev * 2) |
| 108 | }) |
| 109 | |
| 110 | await waitFor(() => expect(result.current[0]).toBe(20)) |
| 111 | expect(window.localStorage.getItem('new-key')).toBe(JSON.stringify(20)) |
| 112 | }) |
| 113 | }) |