- 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)
73 lines
2.4 KiB
TypeScript
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');
|
|
});
|
|
});
|