1import { describe, expect, it } from "vitest";
2import { OpStack } from "./history.js";
3
4describe("OpStack (T-167/T-170)", () => {
5 it("push/undo/redo walk a linear history", () => {
6 const s = new OpStack(0, 10);
7 s.push(1);
8 s.push(2);
9 expect(s.present).toBe(2);
10 expect(s.canUndo()).toBe(true);
11 expect(s.canRedo()).toBe(false);
12 expect(s.undo()).toBe(1);
13 expect(s.undo()).toBe(0);
14 expect(s.canUndo()).toBe(false);
15 expect(s.undo()).toBe(0);
16 expect(s.redo()).toBe(1);
17 expect(s.redo()).toBe(2);
18 expect(s.redo()).toBe(2);
19 });
20
21 it("push after undo clears the redo branch", () => {
22 const s = new OpStack("a", 10);
23 s.push("b");
24 s.push("c");
25 s.undo();
26 s.push("d");
27 expect(s.present).toBe("d");
28 expect(s.canRedo()).toBe(false);
29 expect(s.undo()).toBe("b");
30 expect(s.undo()).toBe("a");
31 });
32
33 it("is bounded: evicts oldest, reports truncation", () => {
34 const s = new OpStack(0, 3);
35 for (let i = 1; i <= 10; i++) s.push(i);
36 expect(s.present).toBe(10);
37 expect(s.depth).toBe(3);
38 expect(s.truncated).toBe(true);
39
40 expect(s.undo()).toBe(9);
41 expect(s.undo()).toBe(8);
42 expect(s.undo()).toBe(7);
43 expect(s.canUndo()).toBe(false);
44 });
45
46 it("no-op push (same reference) is ignored", () => {
47 const obj = { v: 1 };
48 const s = new OpStack(obj, 10);
49 s.push(obj);
50 expect(s.canUndo()).toBe(false);
51 expect(s.present).toBe(obj);
52 });
53
54 it("rejects capacity < 1", () => {
55 expect(() => new OpStack(0, 0)).toThrow();
56 });
57
58 it("reset replaces the present with NO undo step (seed/restore)", () => {
59 const s = new OpStack("a", 10);
60 s.push("b");
61 s.push("c");
62 expect(s.canUndo()).toBe(true);
63 s.reset("x");
64 expect(s.present).toBe("x");
65 expect(s.canUndo()).toBe(false);
66 expect(s.canRedo()).toBe(false);
67
68 s.push("y");
69 s.undo();
70 expect(s.present).toBe("x");
71 expect(s.canUndo()).toBe(false);
72 });
73});
74