105 lines
4.0 KiB
TypeScript
105 lines
4.0 KiB
TypeScript
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']);
|
||
|
|
});
|