Initial Commit
This commit is contained in:
@@ -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 }
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user