Files
LocalMind/__tests__/debouncedStorage.test.ts
evandro 0e5b38fd15 perf(chat): debounce persisted writes and re-render only the streaming message
- the Zustand persist middleware serialized every saved session and wrote to AsyncStorage on each streamed token (appendToMessage -> set); writes are now debounced (400 ms) and flushed when the app leaves the foreground
- every token re-rendered every MessageBubble, re-parsing the Markdown of the whole conversation; rows are memoized so only the changed message renders (covered by MessageList.test.tsx)
2026-10-10 04:28:38 -03:00

73 lines
2.4 KiB
TypeScript

import { createDebouncedStorage } from '../store/debouncedStorage';
const makeBase = () => ({
getItem: jest.fn(async (_name: string): Promise<string | null> => 'from-disk'),
setItem: jest.fn(async (_name: string, _value: string) => {}),
removeItem: jest.fn(async (_name: string) => {}),
});
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
describe('createDebouncedStorage', () => {
it('collapses a burst of writes into one trailing write with the latest value', () => {
const base = makeBase();
const storage = createDebouncedStorage(base, 400);
storage.setItem('k', '1');
jest.advanceTimersByTime(300);
storage.setItem('k', '2');
jest.advanceTimersByTime(300);
storage.setItem('k', '3');
expect(base.setItem).not.toHaveBeenCalled();
jest.advanceTimersByTime(400);
expect(base.setItem).toHaveBeenCalledTimes(1);
expect(base.setItem).toHaveBeenCalledWith('k', '3');
});
it('reads a pending value before it reaches the disk, and falls back to the base storage', async () => {
const base = makeBase();
const storage = createDebouncedStorage(base);
await expect(storage.getItem('k')).resolves.toBe('from-disk');
storage.setItem('k', 'fresh');
await expect(storage.getItem('k')).resolves.toBe('fresh');
});
it('flush writes immediately and cancels the timer', async () => {
const base = makeBase();
const storage = createDebouncedStorage(base);
storage.setItem('a', '1');
storage.setItem('b', '2');
await storage.flush();
expect(base.setItem.mock.calls).toEqual([['a', '1'], ['b', '2']]);
jest.advanceTimersByTime(1000);
expect(base.setItem).toHaveBeenCalledTimes(2);
});
it('flush with nothing pending does not write', async () => {
const base = makeBase();
await createDebouncedStorage(base).flush();
expect(base.setItem).not.toHaveBeenCalled();
});
it('removeItem drops a pending write and removes from the base storage', async () => {
const base = makeBase();
const storage = createDebouncedStorage(base);
storage.setItem('k', '1');
await storage.removeItem('k');
jest.advanceTimersByTime(1000);
expect(base.setItem).not.toHaveBeenCalled();
expect(base.removeItem).toHaveBeenCalledWith('k');
});
});