114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
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 }
|
|
);
|
|
});
|
|
});
|