67 lines
2.8 KiB
TypeScript
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);
|
|
});
|
|
});
|