103 lines
3.8 KiB
TypeScript
103 lines
3.8 KiB
TypeScript
import { test } from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { createStore } from 'jotai';
|
||
|
|
import { enableMapSet } from 'immer';
|
||
|
|
import { makeClosedLobbyCategoriesAtom, makeLobbyCategoryId } from './closedLobbyCategories';
|
||
|
|
|
||
|
|
// makeClosedLobbyCategoriesAtom(userId) builds a Set<string> atom whose reducer uses
|
||
|
|
// immer produce (PUT add / DELETE delete) and persists to a per-user localStorage
|
||
|
|
// key `closedLobbyCategories<userId>`. The reducers produce over a Set, so enable
|
||
|
|
// immer's Map/Set plugin (the app does this once at startup).
|
||
|
|
// makeLobbyCategoryId joins args with '|'. (Mirrors closedNavCategories.test.ts.)
|
||
|
|
enableMapSet();
|
||
|
|
|
||
|
|
type Store = Record<string, string>;
|
||
|
|
const installLocalStorage = (): Store => {
|
||
|
|
const data: Store = {};
|
||
|
|
const ls = {
|
||
|
|
getItem: (k: string) => (k in data ? data[k] : null),
|
||
|
|
setItem: (k: string, v: string) => {
|
||
|
|
data[k] = String(v);
|
||
|
|
},
|
||
|
|
removeItem: (k: string) => {
|
||
|
|
delete data[k];
|
||
|
|
},
|
||
|
|
};
|
||
|
|
(globalThis as { localStorage?: unknown }).localStorage = ls;
|
||
|
|
(globalThis as { window?: unknown }).window = {
|
||
|
|
addEventListener: () => undefined,
|
||
|
|
removeEventListener: () => undefined,
|
||
|
|
};
|
||
|
|
return data;
|
||
|
|
};
|
||
|
|
|
||
|
|
test('makeLobbyCategoryId joins args with "|"', () => {
|
||
|
|
assert.equal(makeLobbyCategoryId('space', 'catA'), 'space|catA');
|
||
|
|
assert.equal(makeLobbyCategoryId('only'), 'only');
|
||
|
|
assert.equal(makeLobbyCategoryId(), '');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('starts empty when nothing is stored', () => {
|
||
|
|
installLocalStorage();
|
||
|
|
const store = createStore();
|
||
|
|
const lobbyAtom = makeClosedLobbyCategoriesAtom('@u:server');
|
||
|
|
assert.equal(store.get(lobbyAtom).size, 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('hydrates the Set from the per-user localStorage key', () => {
|
||
|
|
const data = installLocalStorage();
|
||
|
|
data['closedLobbyCategories@u:server'] = JSON.stringify(['x', 'y']);
|
||
|
|
const store = createStore();
|
||
|
|
const lobbyAtom = makeClosedLobbyCategoriesAtom('@u:server');
|
||
|
|
assert.deepEqual(Array.from(store.get(lobbyAtom)).sort(), ['x', 'y']);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PUT adds a category and DELETE removes it', () => {
|
||
|
|
installLocalStorage();
|
||
|
|
const store = createStore();
|
||
|
|
const lobbyAtom = makeClosedLobbyCategoriesAtom('@u:server');
|
||
|
|
|
||
|
|
store.set(lobbyAtom, { type: 'PUT', categoryId: 'cat1' });
|
||
|
|
assert.deepEqual(Array.from(store.get(lobbyAtom)), ['cat1']);
|
||
|
|
|
||
|
|
store.set(lobbyAtom, { type: 'DELETE', categoryId: 'cat1' });
|
||
|
|
assert.equal(store.get(lobbyAtom).has('cat1'), false);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PUT of an existing category is idempotent', () => {
|
||
|
|
installLocalStorage();
|
||
|
|
const store = createStore();
|
||
|
|
const lobbyAtom = makeClosedLobbyCategoriesAtom('@u:server');
|
||
|
|
store.set(lobbyAtom, { type: 'PUT', categoryId: 'cat1' });
|
||
|
|
store.set(lobbyAtom, { type: 'PUT', categoryId: 'cat1' });
|
||
|
|
assert.equal(store.get(lobbyAtom).size, 1);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('DELETE of an absent category is a no-op', () => {
|
||
|
|
installLocalStorage();
|
||
|
|
const store = createStore();
|
||
|
|
const lobbyAtom = makeClosedLobbyCategoriesAtom('@u:server');
|
||
|
|
store.set(lobbyAtom, { type: 'DELETE', categoryId: 'missing' });
|
||
|
|
assert.equal(store.get(lobbyAtom).size, 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('writes persist to localStorage as an array', () => {
|
||
|
|
const data = installLocalStorage();
|
||
|
|
const store = createStore();
|
||
|
|
const lobbyAtom = makeClosedLobbyCategoriesAtom('@u:server');
|
||
|
|
store.set(lobbyAtom, { type: 'PUT', categoryId: 'cat1' });
|
||
|
|
assert.deepEqual(JSON.parse(data['closedLobbyCategories@u:server']), ['cat1']);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('the storage key is namespaced per user', () => {
|
||
|
|
const data = installLocalStorage();
|
||
|
|
const store = createStore();
|
||
|
|
const aAtom = makeClosedLobbyCategoriesAtom('@a:server');
|
||
|
|
const bAtom = makeClosedLobbyCategoriesAtom('@b:server');
|
||
|
|
|
||
|
|
store.set(aAtom, { type: 'PUT', categoryId: 'only-a' });
|
||
|
|
assert.deepEqual(JSON.parse(data['closedLobbyCategories@a:server']), ['only-a']);
|
||
|
|
assert.equal(data['closedLobbyCategories@b:server'], undefined);
|
||
|
|
assert.equal(store.get(bAtom).size, 0);
|
||
|
|
});
|