import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createDeque, createMinHeap } from '../../src/lib/canvas-runtime/structures.ts'; import { createLedger, compressChanges } from '../../src/lib/canvas-runtime/ledger.ts'; const node = { id: 'a', incarnation: 1, ownerCanvasId: 'root', componentId: 'text', dataVersion: 1, data: { title: 'a', text: '' }, structure: 'node', geometry: { x: 0, y: 0, width: 20, height: 20, rotation: 0 } }; const intent = (id, changes = [], dependencies = []) => ({ id, operationType: 'document.change', operationSchemaVersion: 1, sequence: 1, origin: 'user', changes, dependencies }); const frozen = ledger => ledger.freeze({ id: 'request', operationIds: ['create'], canvasIds: ['root'], expectedVersions: { root: 1 }, payload: { changes: [] } }); test('FIFO retains order across compaction; retry heap orders deadline then sequence', () => { const queue = createDeque(); for (let i = 0; i < 4000; i++) queue.push(i); for (let i = 0; i < 4000; i++) assert.equal(queue.pop(), i); assert.equal(queue.size, 0); assert.equal(queue.pop(), undefined); const heap = createMinHeap((a, b) => a.due - b.due || a.sequence - b.sequence); for (const value of [{ due: 5, sequence: 2 }, { due: 2, sequence: 9 }, { due: 5, sequence: 1 }, { due: 1, sequence: 8 }]) heap.push(value); assert.deepEqual([heap.pop(), heap.pop(), heap.pop(), heap.pop()].map(item => [item.due, item.sequence]), [[1,8],[2,9],[5,1],[5,2]]); assert.equal(heap.pop(), undefined); }); test('wholly unsent create-data-delete disappears but a position update preserves the title', () => { const create = { kind: 'element', id: 'a', before: null, after: node }; const rename = { kind: 'field', target: { id: 'a', incarnation: 1 }, field: 'data.title', before: 'a', after: 'renamed', beforePresent: true, afterPresent: true }; const move = { kind: 'field', target: { id: 'a', incarnation: 1 }, field: 'geometry', before: node.geometry, after: { ...node.geometry, x: 100 } }; const compressed = compressChanges([create, rename, move]); assert.equal(compressed.length, 1); assert.equal(compressed[0].after.data.title, 'renamed'); assert.deepEqual(compressChanges([...compressed, { kind: 'element', id: 'a', before: compressed[0].after, after: null }]), []); }); test('frozen identity remains exact after unknown/accepted outcomes and deletion is a separate pending operation', () => { const ledger = createLedger(); ledger.add(intent('create'), { a: 'absent' }); const request = frozen(ledger), body = request.body; ledger.markDurable(ledger.getSnapshot().generation); ledger.markDispatch('request'); ledger.result('request', 'unresolved', 'transport_unknown'); ledger.add(intent('delete', [], ['create']), { a: 'exists' }); assert.equal(ledger.eligible('delete'), false); assert.equal(ledger.possiblyApplied('request'), true); ledger.result('request', 'accepted'); assert.equal(ledger.eligible('delete'), false); assert.equal(ledger.getSnapshot().requests.get('request').body, body); ledger.markDispatch('request'); assert.equal(ledger.getSnapshot().requests.get('request').body, body); ledger.result('request', 'applied'); assert.equal(ledger.eligible('delete'), true); }); test('dependencies are explicit, duplicate identities conflict and late durable completion does not mark newer work durable', () => { const ledger = createLedger(); assert.throws(() => ledger.add(intent('child', [], ['missing']), {}), /dependency_missing/); ledger.add(intent('first'), {}); const oldGeneration = ledger.getSnapshot().generation; ledger.add(intent('second', [], ['first']), {}); ledger.markDurable(oldGeneration); assert.ok(ledger.getSnapshot().durableGeneration < ledger.getSnapshot().generation); assert.throws(() => ledger.add(intent('first', [], ['second']), {}), /operation_identity_conflict/); assert.equal(ledger.eligible('second'), false); }); test('recovery retains possible application; explicit terminal zero-write does not pretend dependent writes succeeded', () => { const original = createLedger(); original.add(intent('create'), {}); frozen(original); original.markDispatch('request'); const restored = createLedger(original.getSnapshot()); assert.equal(restored.possiblyApplied('request'), true); restored.result('request', 'not-applied', 'permission'); assert.equal(restored.possiblyApplied('request'), false); assert.equal(restored.getSnapshot().operations.get('create').phase, 'blocked'); assert.throws(() => restored.result('request', 'applied'), /receipt_conflict/); });