import { describe, expect, it } from "@suaveplan/testing/runner"; import { createAsyncCommand } from "../src/async/async-command.js"; import { AsyncUndoStack } from "../src/async/async-stack.js"; import { BranchingHistory } from "../src/branch/branch.js"; import { createCommand } from "../src/command/command.js"; import { compressHistory } from "../src/compression/history-compressor.js"; import { SelectiveUndoStack } from "../src/selective/selective-undo.js"; import { UndoStack } from "../src/stack/stack.js"; // OTel meters are no-ops by default (no exporter registered) — we just verify // that operations complete without throwing and that the meter API is exercised. // The default @opentelemetry/api ships a NoopMeterProvider. interface Counter { value: number; } const mkCmd = (n = 1) => createCommand( `Inc${n}`, (s) => ({ value: s.value + n }), (s) => ({ value: s.value - n }) ); describe("OTel instrumentation (no-op SDK)", () => { it("UndoStack push increments counter without throwing", { timeout: 5000, }, () => { const s = new UndoStack({ value: 0 }); expect(() => { s.push(mkCmd(1)); s.push(mkCmd(2)); }).not.toThrow(); }); it("UndoStack undo increments counter without throwing", { timeout: 5000, }, () => { const s = new UndoStack({ value: 0 }); s.push(mkCmd(1)); expect(() => s.undo()).not.toThrow(); }); it("UndoStack redo increments counter without throwing", { timeout: 5000, }, () => { const s = new UndoStack({ value: 0 }); s.push(mkCmd(1)); s.undo(); expect(() => s.redo()).not.toThrow(); }); it("BranchingHistory push, undo, redo without throwing", { timeout: 5000, }, () => { const h = new BranchingHistory({ value: 0 }); expect(() => { h.push(mkCmd(1)); h.undo(); h.redo(); }).not.toThrow(); }); it("AsyncUndoStack push/undo/redo without throwing", { timeout: 30000, }, async () => { const s = new AsyncUndoStack({ value: 0 }); const aCmd = createAsyncCommand( "AI", async (st) => ({ value: st.value + 1 }), async (st) => ({ value: st.value - 1 }) ); await s.push(aCmd); await s.undo(); await s.redo(); expect(s.current.value).toBe(1); }); it("compressHistory records compress counter without throwing", { timeout: 5000, }, () => { const s = new UndoStack({ value: 0 }); for (let i = 0; i < 20; i++) s.push(mkCmd(1)); expect(() => compressHistory(s, { compressionDepth: 5 })).not.toThrow(); }); it("SelectiveUndoStack undoById records undo counter", { timeout: 5000, }, () => { const s = new SelectiveUndoStack({ value: 0 }); const a = mkCmd(1); s.push(a); expect(() => s.undoById(a.id)).not.toThrow(); }); it("BranchingHistory switchBranch without throwing", { timeout: 5000, }, () => { const h = new BranchingHistory({ value: 0 }); h.push(mkCmd(1)); h.undo(); h.push(mkCmd(2)); // diverge → fork const forked = h.branches.find((b) => b.label.startsWith("fork-")); if (forked) { expect(() => h.switchBranch(forked.id)).not.toThrow(); } }); });