import { describe, it } from "@suaveplan/testing/runner"; import * as fc from "fast-check"; import { BranchingHistory } from "../../src/branch/branch.js"; import { createCommand } from "../../src/command/command.js"; interface Counter { value: number; } describe("property: BranchingHistory invariants", () => { it("branch isolation: pushing on one branch doesn't change another", { timeout: 30000, }, () => { fc.assert( fc.property( fc.array(fc.integer({ min: 1, max: 5 }), { minLength: 2, maxLength: 10, }), fc.array(fc.integer({ min: 1, max: 5 }), { minLength: 1, maxLength: 5, }), (mainDeltas, extraDeltas) => { const h = new BranchingHistory({ value: 0 }); for (const d of mainDeltas) { h.push( createCommand( "D", (s) => ({ value: s.value + d }), (s) => ({ value: s.value - d }) ) ); } h.undo(); // set up diverge const _stateBeforeFork = h.current.value; h.push( createCommand( "Fork", (s) => ({ value: s.value + 99 }), (s) => ({ value: s.value - 99 }) ) ); const forked = h.branches.find((b) => b.label.startsWith("fork-") ); if (!forked) return true; // nothing to test const mainId = h.branches.find( (b) => b.label === "main" )?.id; if (!mainId) return true; const mainCurrent = h.current.value; // on main now // Switch to forked and push more h.switchBranch(forked.id); for (const d of extraDeltas) { h.push( createCommand( "E", (s) => ({ value: s.value + d }), (s) => ({ value: s.value - d }) ) ); } // Switch back to main — must be unchanged h.switchBranch(mainId); return h.current.value === mainCurrent; } ), { numRuns: 100 } ); }); it("undo/redo cursor: canUndo + canRedo invariant holds", { timeout: 30000, }, () => { fc.assert( fc.property( fc.array(fc.integer({ min: 1, max: 5 }), { minLength: 1, maxLength: 20, }), fc.integer({ min: 0, max: 20 }), (deltas, undoSteps) => { const h = new BranchingHistory( { value: 0 }, { branchOnDiverge: false } ); for (const d of deltas) { h.push( createCommand( "D", (s) => ({ value: s.value + d }), (s) => ({ value: s.value - d }) ) ); } const steps = Math.min(undoSteps, deltas.length); for (let i = 0; i < steps; i++) h.undo(); const branch = h.getBranch(h.activeBranchId)!; return ( branch.undoStack.length + branch.redoStack.length === deltas.length ); } ), { numRuns: 100 } ); }); });