124 lines
4.3 KiB
TypeScript
124 lines
4.3 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createStore } from 'jotai';
|
|
import { enableMapSet } from 'immer';
|
|
import type { Path } from 'react-router';
|
|
|
|
// `makeNavToActivePathAtom(userId)` is a factory: localStorage is read when the
|
|
// returned atom is first created/accessed (not at module load), but we still
|
|
// install the mock up front. The reducers `produce` over an immer-managed Map,
|
|
// so immer's Map/Set plugin must be enabled.
|
|
const NAV_TO_ACTIVE_PATH = 'navToActivePath';
|
|
const storeKeyFor = (userId: string): string => `${NAV_TO_ACTIVE_PATH}${userId}`;
|
|
|
|
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;
|
|
};
|
|
|
|
enableMapSet();
|
|
installStorage();
|
|
// eslint-disable-next-line import/first
|
|
import { makeNavToActivePathAtom } from './navToActivePath';
|
|
|
|
const USER = '@user:server';
|
|
const path = (pathname: string): Path => ({ pathname, search: '', hash: '' });
|
|
|
|
test('starts as an empty Map', () => {
|
|
installStorage();
|
|
const navAtom = makeNavToActivePathAtom(USER);
|
|
const store = createStore();
|
|
const map = store.get(navAtom);
|
|
assert.ok(map instanceof Map);
|
|
assert.equal(map.size, 0);
|
|
});
|
|
|
|
test('PUT stores a path under its navId', () => {
|
|
installStorage();
|
|
const navAtom = makeNavToActivePathAtom(USER);
|
|
const store = createStore();
|
|
store.set(navAtom, { type: 'PUT', navId: 'home', path: path('/home') });
|
|
assert.deepEqual(store.get(navAtom).get('home'), path('/home'));
|
|
});
|
|
|
|
test('PUT overwrites the path for an existing navId', () => {
|
|
installStorage();
|
|
const navAtom = makeNavToActivePathAtom(USER);
|
|
const store = createStore();
|
|
store.set(navAtom, { type: 'PUT', navId: 'home', path: path('/old') });
|
|
store.set(navAtom, { type: 'PUT', navId: 'home', path: path('/new') });
|
|
const map = store.get(navAtom);
|
|
assert.equal(map.size, 1);
|
|
assert.deepEqual(map.get('home'), path('/new'));
|
|
});
|
|
|
|
test('DELETE removes a navId', () => {
|
|
installStorage();
|
|
const navAtom = makeNavToActivePathAtom(USER);
|
|
const store = createStore();
|
|
store.set(navAtom, { type: 'PUT', navId: 'home', path: path('/home') });
|
|
store.set(navAtom, { type: 'PUT', navId: 'dms', path: path('/dms') });
|
|
store.set(navAtom, { type: 'DELETE', navId: 'home' });
|
|
|
|
const map = store.get(navAtom);
|
|
assert.equal(map.has('home'), false);
|
|
assert.deepEqual(map.get('dms'), path('/dms'));
|
|
});
|
|
|
|
test('DELETE of an absent navId is a no-op', () => {
|
|
installStorage();
|
|
const navAtom = makeNavToActivePathAtom(USER);
|
|
const store = createStore();
|
|
store.set(navAtom, { type: 'PUT', navId: 'home', path: path('/home') });
|
|
store.set(navAtom, { type: 'DELETE', navId: 'ghost' });
|
|
assert.deepEqual([...store.get(navAtom).keys()], ['home']);
|
|
});
|
|
|
|
test('persists to localStorage as an Object keyed per user', () => {
|
|
const backing = installStorage();
|
|
const navAtom = makeNavToActivePathAtom(USER);
|
|
const store = createStore();
|
|
store.set(navAtom, { type: 'PUT', navId: 'home', path: path('/home') });
|
|
|
|
const raw = backing.get(storeKeyFor(USER));
|
|
assert.ok(raw);
|
|
const obj = JSON.parse(raw!) as Record<string, Path>;
|
|
assert.deepEqual(obj, { home: path('/home') });
|
|
});
|
|
|
|
test('hydrates the Map from a stored Object', () => {
|
|
const backing = installStorage();
|
|
backing.set(storeKeyFor(USER), JSON.stringify({ home: path('/home') }));
|
|
|
|
const navAtom = makeNavToActivePathAtom(USER);
|
|
const store = createStore();
|
|
assert.deepEqual(store.get(navAtom).get('home'), path('/home'));
|
|
});
|
|
|
|
test('storage is scoped per userId', () => {
|
|
const backing = installStorage();
|
|
const userA = '@a:s';
|
|
const userB = '@b:s';
|
|
|
|
const atomA = makeNavToActivePathAtom(userA);
|
|
const atomB = makeNavToActivePathAtom(userB);
|
|
const store = createStore();
|
|
|
|
store.set(atomA, { type: 'PUT', navId: 'home', path: path('/a-home') });
|
|
store.set(atomB, { type: 'PUT', navId: 'home', path: path('/b-home') });
|
|
|
|
assert.ok(backing.has(storeKeyFor(userA)));
|
|
assert.ok(backing.has(storeKeyFor(userB)));
|
|
assert.deepEqual(store.get(atomA).get('home'), path('/a-home'));
|
|
assert.deepEqual(store.get(atomB).get('home'), path('/b-home'));
|
|
});
|