import { describe, expect, it } from "vitest"; import { History, HISTORY_LIMIT } from "./history.js"; import type { BlockDto } from "./types.js"; function block(id: string, text: string, version = 1): BlockDto { return { id, type: "paragraph", content: { text }, parentId: null, position: "1", version, }; } describe("History (undo/redo, 100 steps)", () => { it("records, undoes and redoes", () => { const h = new History([block("a", "A")]); expect(h.canUndo).toBe(false); expect(h.canRedo).toBe(false); h.record(h.current(), [block("a", "AB")], "type"); expect(h.canUndo).toBe(true); expect(h.depth).toBe(1); const undo = h.undo(); expect(undo?.blocks.map((b) => (b.content as { text: string }).text)).toEqual(["A"]); expect(h.canRedo).toBe(true); const redo = h.redo(); expect(redo?.blocks.map((b) => (b.content as { text: string }).text)).toEqual(["AB"]); expect(h.canRedo).toBe(false); }); it("truncates the redo branch on a new edit after undo", () => { const h = new History([block("a", "A")]); h.record(h.current(), [block("a", "B")], "e1"); h.record(h.current(), [block("a", "C")], "e2"); h.undo(); // back to B h.record(h.current(), [block("a", "D")], "e3"); // new branch expect(h.canRedo).toBe(false); expect(h.depth).toBe(2); }); it("coalesces consecutive same-label steps (typing burst)", () => { const h = new History([block("a", "A")]); h.record(h.current(), [block("a", "AB")], "type"); h.record(h.current(), [block("a", "ABC")], "type"); expect(h.depth).toBe(1); // one step, not two const undo = h.undo(); expect(undo?.blocks.map((b) => (b.content as { text: string }).text)).toEqual(["A"]); }); it("caps the undo stack at 100 steps", () => { const h = new History([block("a", "A")]); for (let i = 0; i < HISTORY_LIMIT + 10; i += 1) { h.record(h.current(), [block("a", `T${i}`)], `edit-${i}`); } expect(h.depth).toBe(HISTORY_LIMIT); }); it("reset drops all history", () => { const h = new History([block("a", "A")]); h.record(h.current(), [block("a", "B")], "x"); h.reset([block("b", "fresh")]); expect(h.canUndo).toBe(false); expect(h.canRedo).toBe(false); expect(h.current().map((b) => b.id)).toEqual(["b"]); }); });