import { describe, it } from "@suaveplan/testing/runner"; import * as fc from "fast-check"; import { createCommand } from "../../src/command/command.js"; import { UndoStack } from "../../src/stack/stack.js"; interface Counter { value: number; } describe("property: UndoStack invariants", () => { it("push N, undo all, redo all: final state = after-push state", { timeout: 30000, }, () => { fc.assert( fc.property( fc.array(fc.integer({ min: 1, max: 10 }), { minLength: 1, maxLength: 20, }), (deltas) => { const s = new UndoStack({ value: 0 }); for (const d of deltas) { s.push( createCommand( "D", (st) => ({ value: st.value + d }), (st) => ({ value: st.value - d }) ) ); } const afterPush = s.current.value; for (let i = 0; i < deltas.length; i++) s.undo(); for (let i = 0; i < deltas.length; i++) s.redo(); return s.current.value === afterPush; } ), { numRuns: 200 } ); }); it("undoSize never exceeds maxEntries", { timeout: 30000 }, () => { fc.assert( fc.property( fc.integer({ min: 1, max: 20 }), fc.array(fc.integer({ min: 1, max: 5 }), { minLength: 0, maxLength: 50, }), (maxEntries, deltas) => { const s = new UndoStack( { value: 0 }, { maxEntries } ); for (const d of deltas) { s.push( createCommand( "D", (st) => ({ value: st.value + d }), (st) => ({ value: st.value - d }) ) ); } return s.undoSize <= maxEntries; } ), { numRuns: 200 } ); }); it("undoSize + redoSize = total pushed (within maxEntries limit)", { timeout: 30000, }, () => { fc.assert( fc.property( fc.array(fc.integer({ min: 1, max: 5 }), { minLength: 0, maxLength: 15, }), fc.integer({ min: 0, max: 15 }), (deltas, undoSteps) => { const s = new UndoStack({ value: 0 }); for (const d of deltas) { s.push( createCommand( "D", (st) => ({ value: st.value + d }), (st) => ({ value: st.value - d }) ) ); } const steps = Math.min(undoSteps, deltas.length); for (let i = 0; i < steps; i++) s.undo(); return s.undoSize + s.redoSize === deltas.length; } ), { numRuns: 200 } ); }); });