Files
cinny/src/app/state/list.test.ts
T

105 lines
4.0 KiB
TypeScript
Raw Normal View History

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createStore } from 'jotai';
import { createListAtom } from './list';
// `createListAtom` produces a write atom whose reducer handles PUT (append a
// single item or an array of items), DELETE (identity-based removal of a single
// item or array of items) and REPLACE (identity match -> replacement). We drive
// the pure reducer through a real jotai store. The exported `TListAtom` type is
// a type alias only, nothing to test at runtime.
test('starts as an empty array', () => {
const store = createStore();
const listAtom = createListAtom<string>();
assert.deepEqual(store.get(listAtom), []);
});
test('PUT appends a single item', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: 'a' });
store.set(listAtom, { type: 'PUT', item: 'b' });
assert.deepEqual(store.get(listAtom), ['a', 'b']);
});
test('PUT appends an array of items in order', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: 'a' });
store.set(listAtom, { type: 'PUT', item: ['b', 'c'] });
assert.deepEqual(store.get(listAtom), ['a', 'b', 'c']);
});
test('PUT does not deduplicate', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: 'a' });
store.set(listAtom, { type: 'PUT', item: 'a' });
assert.deepEqual(store.get(listAtom), ['a', 'a']);
});
test('DELETE removes a single item by identity', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: ['a', 'b', 'c'] });
store.set(listAtom, { type: 'DELETE', item: 'b' });
assert.deepEqual(store.get(listAtom), ['a', 'c']);
});
test('DELETE removes an array of items', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: ['a', 'b', 'c', 'd'] });
store.set(listAtom, { type: 'DELETE', item: ['a', 'c'] });
assert.deepEqual(store.get(listAtom), ['b', 'd']);
});
test('DELETE of an absent item is a no-op', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: ['a', 'b'] });
store.set(listAtom, { type: 'DELETE', item: 'z' });
assert.deepEqual(store.get(listAtom), ['a', 'b']);
});
test('DELETE uses reference identity for object items', () => {
const store = createStore();
const listAtom = createListAtom<{ id: number }>();
const a = { id: 1 };
const b = { id: 2 };
store.set(listAtom, { type: 'PUT', item: [a, b] });
// A structurally-equal but distinct object is not removed.
store.set(listAtom, { type: 'DELETE', item: { id: 1 } });
assert.deepEqual(store.get(listAtom), [a, b]);
// The same reference is removed.
store.set(listAtom, { type: 'DELETE', item: a });
assert.deepEqual(store.get(listAtom), [b]);
});
test('REPLACE swaps the matching item, preserving position', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: ['a', 'b', 'c'] });
store.set(listAtom, { type: 'REPLACE', item: 'b', replacement: 'B' });
assert.deepEqual(store.get(listAtom), ['a', 'B', 'c']);
});
test('REPLACE matches by identity and replaces every match', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: ['a', 'b', 'a'] });
store.set(listAtom, { type: 'REPLACE', item: 'a', replacement: 'X' });
assert.deepEqual(store.get(listAtom), ['X', 'b', 'X']);
});
test('REPLACE with no match leaves the list unchanged', () => {
const store = createStore();
const listAtom = createListAtom<string>();
store.set(listAtom, { type: 'PUT', item: ['a', 'b'] });
store.set(listAtom, { type: 'REPLACE', item: 'z', replacement: 'Z' });
assert.deepEqual(store.get(listAtom), ['a', 'b']);
});