The GIF picker was a bare Giphy search grid with no memory of what you've sent, so re-sending a go-to reaction GIF meant re-typing the search every time. Add a "Recent" row at the top of the picker (default view; hidden while searching) for one-click re-sending. - New persisted state state/recentGifs.ts: recentGifsAtom (localStorage, cinny_recent_gifs_v1, getOnInit) + pure addRecentGif (dedupe-by-url move-to-front, cap 16, ignore empty url), with 5 unit tests. - GifPicker records every sent GIF (from search or the Recent row) to the front, and renders a 3-up thumbnail grid of recents above the search grid when there are recents and no active search term. Section label matches the picker's existing `// GIF_SEARCH` treatment (lotusTerminal) or a muted label otherwise. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
1.7 KiB
TypeScript
57 lines
1.7 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
// The module evaluates atomWithStorage(..., { getOnInit: true }), which reads
|
|
// localStorage at load time. node has none, so install a no-op mock, then import
|
|
// dynamically (a static import would hoist above the mock and evaluate too early).
|
|
(globalThis as { localStorage?: unknown }).localStorage = {
|
|
getItem: () => null,
|
|
setItem: () => undefined,
|
|
removeItem: () => undefined,
|
|
};
|
|
|
|
const { addRecentGif } = await import('./recentGifs');
|
|
|
|
const gif = (url: string, width = 100, height = 100) => ({ url, width, height });
|
|
|
|
test('addRecentGif prepends a new gif', () => {
|
|
const out = addRecentGif([gif('a'), gif('b')], gif('c'));
|
|
assert.deepEqual(
|
|
out.map((g) => g.url),
|
|
['c', 'a', 'b'],
|
|
);
|
|
});
|
|
|
|
test('addRecentGif de-dupes by url, moving the entry to the front', () => {
|
|
const out = addRecentGif([gif('a'), gif('b'), gif('c')], gif('b'));
|
|
assert.deepEqual(
|
|
out.map((g) => g.url),
|
|
['b', 'a', 'c'],
|
|
);
|
|
});
|
|
|
|
test('addRecentGif ignores an empty url', () => {
|
|
assert.deepEqual(addRecentGif([gif('a')], gif('')), [gif('a')]);
|
|
});
|
|
|
|
test('addRecentGif caps the list at 16, dropping the oldest', () => {
|
|
const sixteen = Array.from({ length: 16 }, (_, i) => gif(`u${i}`));
|
|
const out = addRecentGif(sixteen, gif('new'));
|
|
assert.equal(out.length, 16);
|
|
assert.equal(out[0].url, 'new');
|
|
assert.equal(
|
|
out.some((g) => g.url === 'u15'),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('addRecentGif does not mutate its input', () => {
|
|
const input = [gif('a'), gif('b')];
|
|
const before = input.map((g) => g.url);
|
|
addRecentGif(input, gif('c'));
|
|
assert.deepEqual(
|
|
input.map((g) => g.url),
|
|
before,
|
|
);
|
|
});
|