mirror of
https://github.com/streamich/react-use.git
synced 2025-12-08 18:02:14 +00:00
256 lines
8.0 KiB
TypeScript
256 lines
8.0 KiB
TypeScript
import useLocalStorage from '../src/useLocalStorage';
|
|
import 'jest-localstorage-mock';
|
|
import { renderHook, act } from '@testing-library/react-hooks';
|
|
|
|
describe(useLocalStorage, () => {
|
|
afterEach(() => {
|
|
localStorage.clear();
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('retrieves an existing value from localStorage', () => {
|
|
localStorage.setItem('foo', '"bar"');
|
|
const { result } = renderHook(() => useLocalStorage('foo'));
|
|
const [state] = result.current;
|
|
expect(state).toEqual('bar');
|
|
});
|
|
|
|
it('should return initialValue if localStorage empty and set that to localStorage', () => {
|
|
const { result } = renderHook(() => useLocalStorage('foo', 'bar'));
|
|
const [state] = result.current;
|
|
expect(state).toEqual('bar');
|
|
expect(localStorage.__STORE__.foo).toEqual('"bar"');
|
|
});
|
|
|
|
it('prefers existing value over initial state', () => {
|
|
localStorage.setItem('foo', '"bar"');
|
|
const { result } = renderHook(() => useLocalStorage('foo', 'baz'));
|
|
const [state] = result.current;
|
|
expect(state).toEqual('bar');
|
|
});
|
|
|
|
it('does not clobber existing localStorage with initialState', () => {
|
|
localStorage.setItem('foo', '"bar"');
|
|
const { result } = renderHook(() => useLocalStorage('foo', 'buzz'));
|
|
expect(result.current).toBeTruthy();
|
|
expect(localStorage.__STORE__.foo).toEqual('"bar"');
|
|
});
|
|
|
|
it('correctly updates localStorage', () => {
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar'));
|
|
|
|
const [, setFoo] = result.current;
|
|
act(() => setFoo('baz'));
|
|
rerender();
|
|
|
|
expect(localStorage.__STORE__.foo).toEqual('"baz"');
|
|
});
|
|
|
|
it('should return undefined if no initialValue provided and localStorage empty', () => {
|
|
const { result } = renderHook(() => useLocalStorage('some_key'));
|
|
|
|
expect(result.current[0]).toBeUndefined();
|
|
});
|
|
|
|
it('returns and allows null setting', () => {
|
|
localStorage.setItem('foo', 'null');
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo'));
|
|
const [foo1, setFoo] = result.current;
|
|
act(() => setFoo(null));
|
|
rerender();
|
|
|
|
const [foo2] = result.current;
|
|
expect(foo1).toEqual(null);
|
|
expect(foo2).toEqual(null);
|
|
});
|
|
|
|
it('sets initialState if initialState is an object', () => {
|
|
renderHook(() => useLocalStorage('foo', { bar: true }));
|
|
expect(localStorage.__STORE__.foo).toEqual('{"bar":true}');
|
|
});
|
|
|
|
it('correctly and promptly returns a new value', () => {
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar'));
|
|
|
|
const [, setFoo] = result.current;
|
|
act(() => setFoo('baz'));
|
|
rerender();
|
|
|
|
const [foo] = result.current;
|
|
expect(foo).toEqual('baz');
|
|
});
|
|
|
|
it('reinitializes state when key changes', () => {
|
|
let key = 'foo';
|
|
const { result, rerender } = renderHook(() => useLocalStorage(key, 'bar'));
|
|
|
|
const [, setState] = result.current;
|
|
act(() => setState('baz'));
|
|
key = 'bar';
|
|
rerender();
|
|
|
|
const [state] = result.current;
|
|
expect(state).toEqual('bar');
|
|
});
|
|
|
|
/*
|
|
it('keeps multiple hooks accessing the same key in sync', () => {
|
|
localStorage.setItem('foo', 'bar');
|
|
const { result: r1, rerender: rerender1 } = renderHook(() => useLocalStorage('foo'));
|
|
const { result: r2, rerender: rerender2 } = renderHook(() => useLocalStorage('foo'));
|
|
|
|
const [, setFoo] = r1.current;
|
|
act(() => setFoo('potato'));
|
|
rerender1();
|
|
rerender2();
|
|
|
|
const [val1] = r1.current;
|
|
const [val2] = r2.current;
|
|
|
|
expect(val1).toEqual(val2);
|
|
expect(val1).toEqual('potato');
|
|
expect(val2).toEqual('potato');
|
|
});
|
|
*/
|
|
|
|
it('parses out objects from localStorage', () => {
|
|
localStorage.setItem('foo', JSON.stringify({ ok: true }));
|
|
const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo'));
|
|
const [foo] = result.current;
|
|
expect(foo!.ok).toEqual(true);
|
|
});
|
|
|
|
it('safely initializes objects to localStorage', () => {
|
|
const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo', { ok: true }));
|
|
const [foo] = result.current;
|
|
expect(foo!.ok).toEqual(true);
|
|
});
|
|
|
|
it('safely sets objects to localStorage', () => {
|
|
const { result, rerender } = renderHook(() =>
|
|
useLocalStorage<{ ok: any }>('foo', { ok: true })
|
|
);
|
|
|
|
const [, setFoo] = result.current;
|
|
act(() => setFoo({ ok: 'bar' }));
|
|
rerender();
|
|
|
|
const [foo] = result.current;
|
|
expect(foo!.ok).toEqual('bar');
|
|
});
|
|
|
|
it('safely returns objects from updates', () => {
|
|
const { result, rerender } = renderHook(() =>
|
|
useLocalStorage<{ ok: any }>('foo', { ok: true })
|
|
);
|
|
|
|
const [, setFoo] = result.current;
|
|
act(() => setFoo({ ok: 'bar' }));
|
|
rerender();
|
|
|
|
const [foo] = result.current;
|
|
expect(foo).toBeInstanceOf(Object);
|
|
expect(foo!.ok).toEqual('bar');
|
|
});
|
|
|
|
it('sets localStorage from the function updater', () => {
|
|
const { result, rerender } = renderHook(() =>
|
|
useLocalStorage<{ foo: string; fizz?: string }>('foo', { foo: 'bar' })
|
|
);
|
|
|
|
const [, setFoo] = result.current;
|
|
act(() => setFoo((state) => ({ ...state!, fizz: 'buzz' })));
|
|
rerender();
|
|
|
|
const [value] = result.current;
|
|
expect(value!.foo).toEqual('bar');
|
|
expect(value!.fizz).toEqual('buzz');
|
|
});
|
|
|
|
it('rejects nullish or undefined keys', () => {
|
|
const { result } = renderHook(() => useLocalStorage(null as any));
|
|
try {
|
|
(() => {
|
|
return result.current;
|
|
})();
|
|
fail('hook should have thrown');
|
|
} catch (e) {
|
|
expect(String(e)).toMatch(/key may not be/i);
|
|
}
|
|
});
|
|
|
|
/* Enforces proper eslint react-hooks/rules-of-hooks usage */
|
|
describe('eslint react-hooks/rules-of-hooks', () => {
|
|
it('memoizes an object between rerenders', () => {
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true }));
|
|
(() => {
|
|
return result.current; // if localStorage isn't set then r1 and r2 will be different
|
|
})();
|
|
rerender();
|
|
const [r2] = result.current;
|
|
rerender();
|
|
const [r3] = result.current;
|
|
expect(r2).toBe(r3);
|
|
});
|
|
|
|
it('memoizes an object immediately if localStorage is already set', () => {
|
|
localStorage.setItem('foo', JSON.stringify({ ok: true }));
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true }));
|
|
|
|
const [r1] = result.current; // if localStorage isn't set then r1 and r2 will be different
|
|
rerender();
|
|
const [r2] = result.current;
|
|
expect(r1).toBe(r2);
|
|
});
|
|
|
|
it('memoizes the setState function', () => {
|
|
localStorage.setItem('foo', JSON.stringify({ ok: true }));
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true }));
|
|
const [, s1] = result.current;
|
|
rerender();
|
|
const [, s2] = result.current;
|
|
expect(s1).toBe(s2);
|
|
});
|
|
});
|
|
|
|
describe('Options: raw', () => {
|
|
it('returns a string when localStorage is a stringified object', () => {
|
|
localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' }));
|
|
const { result } = renderHook(() => useLocalStorage('foo', null, { raw: true }));
|
|
const [foo] = result.current;
|
|
expect(typeof foo).toBe('string');
|
|
});
|
|
|
|
it('returns a string after an update', () => {
|
|
localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' }));
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo', null, { raw: true }));
|
|
|
|
const [, setFoo] = result.current;
|
|
|
|
act(() => setFoo({ fizz: 'bang' } as any));
|
|
rerender();
|
|
|
|
const [foo] = result.current;
|
|
expect(typeof foo).toBe('string');
|
|
|
|
expect(JSON.parse(foo!)).toBeInstanceOf(Object);
|
|
|
|
// expect(JSON.parse(foo!).fizz).toEqual('bang');
|
|
});
|
|
|
|
it('still forces setState to a string', () => {
|
|
localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' }));
|
|
const { result, rerender } = renderHook(() => useLocalStorage('foo', null, { raw: true }));
|
|
|
|
const [, setFoo] = result.current;
|
|
|
|
act(() => setFoo({ fizz: 'bang' } as any));
|
|
rerender();
|
|
|
|
const [value] = result.current;
|
|
|
|
expect(JSON.parse(value!).fizz).toEqual('bang');
|
|
});
|
|
});
|
|
});
|