import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createStore } from 'jotai'; import type { ScheduledMessage } from './scheduledMessages'; // scheduledMessagesAtom is backed by jotai's atomWithStorage over // `createJSONStorage(() => localStorage)`, which dereferences `localStorage` // (absent in node) lazily on first store access. We install an in-memory mock // before importing the module so both module init and the storage reads/writes // resolve against it. const STORAGE_KEY = 'cinny_scheduled_messages_v1'; const installStorage = (): Map => { const map = new Map(); (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; }; installStorage(); const { scheduledMessagesAtom } = await import('./scheduledMessages'); // jotai's `atomWithStorage` binds to the `localStorage` captured at module // evaluation. To exercise hydration from pre-existing storage we install seeded // storage and then import a *fresh* (cache-busted) copy of the module. let freshCounter = 0; const importWithStorage = async ( seed?: Record, ): Promise => { const backing = installStorage(); if (seed) backing.set(STORAGE_KEY, JSON.stringify(seed)); freshCounter += 1; const mod = await import(`./scheduledMessages?fresh=${freshCounter}`); return mod.scheduledMessagesAtom; }; const msg = (delayId: string, roomId: string): ScheduledMessage => ({ delayId, roomId, content: { body: delayId, msgtype: 'm.text' }, sendAt: 1000, }); test('starts as an empty Map', () => { installStorage(); const store = createStore(); const map = store.get(scheduledMessagesAtom); assert.ok(map instanceof Map); assert.equal(map.size, 0); }); test('setting a Map is readable back as an equivalent Map (round-trip)', () => { installStorage(); const store = createStore(); const next = new Map([['!room:s', [msg('d1', '!room:s')]]]); store.set(scheduledMessagesAtom, next); const got = store.get(scheduledMessagesAtom); assert.ok(got instanceof Map); assert.deepEqual(got.get('!room:s'), [msg('d1', '!room:s')]); }); test('functional-updater form receives the previous Map', () => { installStorage(); const store = createStore(); store.set(scheduledMessagesAtom, new Map([['!a:s', [msg('d1', '!a:s')]]])); let seenPrev: Map | undefined; store.set(scheduledMessagesAtom, (prev) => { seenPrev = prev; const copy = new Map(prev); copy.set('!b:s', [msg('d2', '!b:s')]); return copy; }); assert.ok(seenPrev instanceof Map); assert.deepEqual(seenPrev?.get('!a:s'), [msg('d1', '!a:s')]); const got = store.get(scheduledMessagesAtom); assert.deepEqual([...got.keys()].sort(), ['!a:s', '!b:s']); }); test('persists to localStorage as a plain Record keyed by roomId', () => { const backing = installStorage(); const store = createStore(); store.set( scheduledMessagesAtom, new Map([ ['!a:s', [msg('d1', '!a:s')]], ['!b:s', [msg('d2', '!b:s'), msg('d3', '!b:s')]], ]), ); const raw = backing.get(STORAGE_KEY); assert.ok(raw, 'expected the storage key to be written'); const parsed = JSON.parse(raw!) as Record; assert.deepEqual(Object.keys(parsed).sort(), ['!a:s', '!b:s']); assert.equal(parsed['!b:s'].length, 2); assert.equal(parsed['!a:s'][0].delayId, 'd1'); }); test('hydrates the Map from a previously stored Record once the atom is mounted', async () => { const freshAtom = await importWithStorage({ '!room:s': [msg('stored', '!room:s')] }); const store = createStore(); // The underlying jotai atomWithStorage is created without `getOnInit`, so a // bare `store.get` returns the default ({}); storage is synced on mount. We // model the React mount with `store.sub`, which fires the onMount hydration. const unsub = store.sub(freshAtom, () => {}); try { assert.deepEqual(store.get(freshAtom).get('!room:s'), [msg('stored', '!room:s')]); } finally { unsub(); } }); test('supports multiple rooms independently', () => { installStorage(); const store = createStore(); store.set(scheduledMessagesAtom, (prev) => { const copy = new Map(prev); copy.set('!r1:s', [msg('a', '!r1:s')]); return copy; }); store.set(scheduledMessagesAtom, (prev) => { const copy = new Map(prev); copy.set('!r2:s', [msg('b', '!r2:s')]); return copy; }); const map = store.get(scheduledMessagesAtom); assert.deepEqual(map.get('!r1:s'), [msg('a', '!r1:s')]); assert.deepEqual(map.get('!r2:s'), [msg('b', '!r2:s')]); });