Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
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<Counter>({ 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<Counter>(
{ 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 }
);
});
});