1import { describe, expect, it } from "vitest";
2
3import { History, HISTORY_LIMIT } from "./history.js";
4import type { BlockDto } from "./types.js";
5
6function block(id: string, text: string, version = 1): BlockDto {
7 return {
8 id,
9 type: "paragraph",
10 content: { text },
11 parentId: null,
12 position: "1",
13 version,
14 };
15}
16
17describe("History (undo/redo, 100 steps)", () => {
18 it("records, undoes and redoes", () => {
19 const h = new History([block("a", "A")]);
20 expect(h.canUndo).toBe(false);
21 expect(h.canRedo).toBe(false);
22
23 h.record(h.current(), [block("a", "AB")], "type");
24 expect(h.canUndo).toBe(true);
25 expect(h.depth).toBe(1);
26
27 const undo = h.undo();
28 expect(undo?.blocks.map((b) => (b.content as { text: string }).text)).toEqual(["A"]);
29 expect(h.canRedo).toBe(true);
30
31 const redo = h.redo();
32 expect(redo?.blocks.map((b) => (b.content as { text: string }).text)).toEqual(["AB"]);
33 expect(h.canRedo).toBe(false);
34 });
35
36 it("truncates the redo branch on a new edit after undo", () => {
37 const h = new History([block("a", "A")]);
38 h.record(h.current(), [block("a", "B")], "e1");
39 h.record(h.current(), [block("a", "C")], "e2");
40 h.undo();
41 h.record(h.current(), [block("a", "D")], "e3");
42 expect(h.canRedo).toBe(false);
43 expect(h.depth).toBe(2);
44 });
45
46 it("coalesces consecutive same-label steps (typing burst)", () => {
47 const h = new History([block("a", "A")]);
48 h.record(h.current(), [block("a", "AB")], "type");
49 h.record(h.current(), [block("a", "ABC")], "type");
50 expect(h.depth).toBe(1);
51 const undo = h.undo();
52 expect(undo?.blocks.map((b) => (b.content as { text: string }).text)).toEqual(["A"]);
53 });
54
55 it("caps the undo stack at 100 steps", () => {
56 const h = new History([block("a", "A")]);
57 for (let i = 0; i < HISTORY_LIMIT + 10; i += 1) {
58 h.record(h.current(), [block("a", `T${i}`)], `edit-${i}`);
59 }
60 expect(h.depth).toBe(HISTORY_LIMIT);
61 });
62
63 it("reset drops all history", () => {
64 const h = new History([block("a", "A")]);
65 h.record(h.current(), [block("a", "B")], "x");
66 h.reset([block("b", "fresh")]);
67 expect(h.canUndo).toBe(false);
68 expect(h.canRedo).toBe(false);
69 expect(h.current().map((b) => b.id)).toEqual(["b"]);
70 });
71});
72