109 lines
3.5 KiB
TypeScript
109 lines
3.5 KiB
TypeScript
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<Counter>(
|
|
`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<Counter>({ 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<Counter>({ value: 0 });
|
|
s.push(mkCmd(1));
|
|
expect(() => s.undo()).not.toThrow();
|
|
});
|
|
|
|
it("UndoStack redo increments counter without throwing", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const s = new UndoStack<Counter>({ 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<Counter>({ 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<Counter>({ value: 0 });
|
|
const aCmd = createAsyncCommand<Counter>(
|
|
"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<Counter>({ 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<Counter>({ 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<Counter>({ 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();
|
|
}
|
|
});
|
|
});
|