import { describe, expect, it } from "vitest"; import { OpStack } from "./history.js"; describe("OpStack (T-167/T-170)", () => { it("push/undo/redo walk a linear history", () => { const s = new OpStack(0, 10); s.push(1); s.push(2); expect(s.present).toBe(2); expect(s.canUndo()).toBe(true); expect(s.canRedo()).toBe(false); expect(s.undo()).toBe(1); expect(s.undo()).toBe(0); expect(s.canUndo()).toBe(false); expect(s.undo()).toBe(0); // no-op at the floor expect(s.redo()).toBe(1); expect(s.redo()).toBe(2); expect(s.redo()).toBe(2); // no-op at the ceiling }); it("push after undo clears the redo branch", () => { const s = new OpStack("a", 10); s.push("b"); s.push("c"); s.undo(); // back to b s.push("d"); // new branch expect(s.present).toBe("d"); expect(s.canRedo()).toBe(false); // "c" branch discarded expect(s.undo()).toBe("b"); expect(s.undo()).toBe("a"); }); it("is bounded: evicts oldest, reports truncation", () => { const s = new OpStack(0, 3); for (let i = 1; i <= 10; i++) s.push(i); expect(s.present).toBe(10); expect(s.depth).toBe(3); // only the last 3 past states kept (7,8,9) expect(s.truncated).toBe(true); // can undo back to 7 but not further expect(s.undo()).toBe(9); expect(s.undo()).toBe(8); expect(s.undo()).toBe(7); expect(s.canUndo()).toBe(false); }); it("no-op push (same reference) is ignored", () => { const obj = { v: 1 }; const s = new OpStack(obj, 10); s.push(obj); // same ref expect(s.canUndo()).toBe(false); expect(s.present).toBe(obj); }); it("rejects capacity < 1", () => { expect(() => new OpStack(0, 0)).toThrow(); }); it("reset replaces the present with NO undo step (seed/restore)", () => { const s = new OpStack("a", 10); s.push("b"); s.push("c"); expect(s.canUndo()).toBe(true); s.reset("x"); // e.g. the server document arrived / a version was restored expect(s.present).toBe("x"); expect(s.canUndo()).toBe(false); expect(s.canRedo()).toBe(false); // edits after the reset are undoable to the reset state s.push("y"); s.undo(); expect(s.present).toBe("x"); expect(s.canUndo()).toBe(false); }); });