110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
|
|
import { test } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { createStore } from 'jotai';
|
||
|
|
import {
|
||
|
|
getLocalStorageItem,
|
||
|
|
setLocalStorageItem,
|
||
|
|
atomWithLocalStorage,
|
||
|
|
} from './atomWithLocalStorage';
|
||
|
|
|
||
|
|
// These helpers read/write the real `localStorage` global, which node lacks, so
|
||
|
|
// we install a small in-memory mock before each case. `atomWithLocalStorage`
|
||
|
|
// also registers a `window` storage listener via `onMount`; we only drive the
|
||
|
|
// pure read/write path through a jotai store (no onMount), so `window` is not
|
||
|
|
// required here.
|
||
|
|
const installStorage = (): Map<string, string> => {
|
||
|
|
const map = new Map<string, string>();
|
||
|
|
(globalThis as { localStorage?: unknown }).localStorage = {
|
||
|
|
getItem: (k: string) => (map.has(k) ? map.get(k)! : null),
|
||
|
|
setItem: (k: string, v: string) => {
|
||
|
|
map.set(k, v);
|
||
|
|
},
|
||
|
|
removeItem: (k: string) => {
|
||
|
|
map.delete(k);
|
||
|
|
},
|
||
|
|
};
|
||
|
|
return map;
|
||
|
|
};
|
||
|
|
|
||
|
|
test('getLocalStorageItem returns the default when the key is absent', () => {
|
||
|
|
installStorage();
|
||
|
|
assert.deepEqual(getLocalStorageItem('missing', { a: 1 }), { a: 1 });
|
||
|
|
assert.equal(getLocalStorageItem('missing', 7), 7);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getLocalStorageItem maps the literal string "undefined" to undefined', () => {
|
||
|
|
const store = installStorage();
|
||
|
|
store.set('k', 'undefined');
|
||
|
|
assert.equal(getLocalStorageItem('k', 'fallback'), undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getLocalStorageItem parses stored JSON', () => {
|
||
|
|
const store = installStorage();
|
||
|
|
store.set('k', JSON.stringify({ nested: [1, 2, 3] }));
|
||
|
|
assert.deepEqual(getLocalStorageItem('k', null), { nested: [1, 2, 3] });
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getLocalStorageItem returns the default on malformed JSON', () => {
|
||
|
|
const store = installStorage();
|
||
|
|
store.set('k', '{ not valid json');
|
||
|
|
assert.equal(getLocalStorageItem('k', 'fallback'), 'fallback');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('setLocalStorageItem writes the JSON-serialized value', () => {
|
||
|
|
const store = installStorage();
|
||
|
|
setLocalStorageItem('k', { hello: 'world' });
|
||
|
|
assert.equal(store.get('k'), JSON.stringify({ hello: 'world' }));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('round-trips a value through set + get', () => {
|
||
|
|
installStorage();
|
||
|
|
setLocalStorageItem('k', [1, 'two', { three: true }]);
|
||
|
|
assert.deepEqual(getLocalStorageItem('k', null), [1, 'two', { three: true }]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('atomWithLocalStorage seeds the atom from getItem on creation', () => {
|
||
|
|
installStorage();
|
||
|
|
const seeded = atomWithLocalStorage<number>(
|
||
|
|
'k',
|
||
|
|
() => 42,
|
||
|
|
() => undefined,
|
||
|
|
);
|
||
|
|
const store = createStore();
|
||
|
|
assert.equal(store.get(seeded), 42);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('atomWithLocalStorage write-through updates BOTH the atom and storage', () => {
|
||
|
|
installStorage();
|
||
|
|
const writes: Array<[string, number]> = [];
|
||
|
|
const theAtom = atomWithLocalStorage<number>(
|
||
|
|
'k',
|
||
|
|
() => 0,
|
||
|
|
(key, value) => {
|
||
|
|
writes.push([key, value]);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const store = createStore();
|
||
|
|
store.set(theAtom, 5);
|
||
|
|
|
||
|
|
// Atom value reflects the write...
|
||
|
|
assert.equal(store.get(theAtom), 5);
|
||
|
|
// ...and setItem was invoked with the key + new value.
|
||
|
|
assert.deepEqual(writes, [['k', 5]]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('atomWithLocalStorage persists through the real setLocalStorageItem helper', () => {
|
||
|
|
const backing = installStorage();
|
||
|
|
const theAtom = atomWithLocalStorage<{ count: number }>(
|
||
|
|
'k',
|
||
|
|
(key) => getLocalStorageItem(key, { count: 0 }),
|
||
|
|
(key, value) => setLocalStorageItem(key, value),
|
||
|
|
);
|
||
|
|
|
||
|
|
const store = createStore();
|
||
|
|
store.set(theAtom, { count: 9 });
|
||
|
|
|
||
|
|
assert.equal(backing.get('k'), JSON.stringify({ count: 9 }));
|
||
|
|
assert.deepEqual(store.get(theAtom), { count: 9 });
|
||
|
|
});
|