storebound/apps/desktop/tests/edits.test.ts

67 lines
2.8 KiB
TypeScript

// Unsaved edits of the editors (src/lib/edits.ts): one pending value per field, typing back to the saved value drops
// it, undo / redo (typing into one field within COALESCE_MS is one step), discard can be undone, areas and apps stay
// apart, and the ops sent to project.edit.
import { describe, expect, test } from 'bun:test';
import { COALESCE_MS, EditStore, keyOf } from '../src/lib/edits';
const sub = (value: string) => ({ kind: 'listing' as const, store: 'apple' as const, lang: 'en-US', field: 'subtitle', value });
describe('EditStore', () => {
test('pending values, back to the saved value drops the edit', () => {
let now = 0;
const s = new EditStore(() => now);
s.set('p1', sub('Tidy'), 'listings', 'Old');
expect(s.count('p1')).toBe(1);
expect(s.get('p1', keyOf(sub('')))!.op).toEqual(sub('Tidy'));
now += 10;
s.set('p1', sub('Old'), 'listings', 'Old');
expect(s.count('p1')).toBe(0);
expect(s.count('p2')).toBe(0);
});
test('undo / redo, one step per field burst', () => {
let now = 0;
const s = new EditStore(() => now);
s.set('p', sub('T'), 'listings', 'Old');
now += 100; s.set('p', sub('Ti'), 'listings', 'Old');
now += 100; s.set('p', sub('Tid'), 'listings', 'Old');
now += COALESCE_MS + 1;
s.set('p', { kind: 'config', path: 'app.supportUrl', value: 'https://example.com/h' }, 'listings', 'https://example.com/s');
expect(s.count('p')).toBe(2);
s.undo('p');
expect(s.count('p')).toBe(1);
expect((s.get('p', keyOf(sub('')))!.op as { value: string }).value).toBe('Tid');
s.undo('p');
expect(s.count('p')).toBe(0);
expect(s.canUndo('p')).toBe(false);
s.redo('p');
s.redo('p');
expect(s.count('p')).toBe(2);
});
test('areas, discard (undoable), clear after a save, ops in a stable order', () => {
const s = new EditStore(() => 0);
s.set('p', sub('Tidy'), 'listings', 'Old');
s.set('p', { kind: 'config', path: 'apple.info.free', value: false }, 'appinfo', true);
expect(s.count('p', 'listings')).toBe(1);
expect(s.ops('p', 'appinfo')).toEqual([{ kind: 'config', path: 'apple.info.free', value: false }]);
expect(s.ops('p').map(keyOf)).toEqual(['config:apple.info.free', 'listing:apple:en-US:subtitle']);
s.clear('p', 'listings', { undoable: true });
expect(s.count('p')).toBe(1);
s.undo('p');
expect(s.count('p')).toBe(2);
expect(s.dirtyProjects()).toEqual(['p']);
s.clear('p');
expect(s.dirtyProjects()).toEqual([]);
expect(s.canUndo('p')).toBe(false);
});
test('subscribers hear every change', () => {
const s = new EditStore(() => 0);
let n = 0;
const off = s.subscribe(() => { n++; });
s.set('p', sub('x'), 'listings', '');
s.undo('p');
off();
s.set('p', sub('y'), 'listings', '');
expect(n).toBe(2);
expect(s.snapshot()).toBe(3);
});
});