Files
openidea/src/features/macros/model.test.ts
T
Jaw 55836e1b49 chore: Format the codebase with oxfmt and check formatting in CI (#135)
Run oxfmt once over the whole repository (formatting only, no logic changes) and add `pnpm format:check` to the frontend CI job so unformatted code no longer builds up. `.oxfmtrc.json` now skips the generated theme docs, test fixtures and code blocks inside Markdown.

Co-authored-by: jaw52 <[email protected]>
2026-10-28 19:35:16 +08:00

116 lines
4.0 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { editorKeyName } from "./editorKeys";
import {
_reloadMacros,
deleteMacro,
describeStep,
playSteps,
recordStep,
renameMacro,
saveMacro,
startRecording,
stopRecording,
storableArg,
STORAGE_KEY,
useMacros,
} from "./model";
beforeEach(() => {
localStorage.clear();
_reloadMacros();
});
describe("macros", () => {
it("records actions, merged typing and keys only while recording", () => {
recordStep({ kind: "text", text: "ignored" });
startRecording();
recordStep({ kind: "text", text: "he" });
recordStep({ kind: "text", text: "llo" });
recordStep({ kind: "key", key: "Enter" });
recordStep({ kind: "action", id: "editor.duplicateLine" });
const steps = stopRecording();
expect(steps).toEqual([
{ kind: "text", text: "hello" },
{ kind: "key", key: "Enter" },
{ kind: "action", id: "editor.duplicateLine" },
]);
expect(useMacros.getState().last).toEqual(steps);
// An empty recording keeps the last macro.
startRecording();
stopRecording();
expect(useMacros.getState().last).toEqual(steps);
});
it("persists saved macros; renames and deletes them", () => {
saveMacro("Dup", [{ kind: "action", id: "editor.duplicateLine" }]);
saveMacro("Two", []);
expect(renameMacro("Dup", "Two")).toBe(false);
expect(renameMacro("Dup", "Duplicate")).toBe(true);
_reloadMacros();
expect(useMacros.getState().saved.map((m) => m.name)).toEqual(["Duplicate", "Two"]);
deleteMacro("Two");
_reloadMacros();
expect(useMacros.getState().saved.map((m) => m.name)).toEqual(["Duplicate"]);
});
it("drops malformed stored steps", () => {
localStorage.setItem(
STORAGE_KEY,
JSON.stringify({
last: [{ kind: "text", text: "a" }, { kind: "action" }, { kind: "action", id: "x", arg: {} }],
saved: [{ name: "" }],
}),
);
_reloadMacros();
expect(useMacros.getState().last).toEqual([{ kind: "text", text: "a" }]);
expect(useMacros.getState().saved).toEqual([]);
});
it("plays steps in order and stops at the first that cannot run; nothing is recorded meanwhile", async () => {
const log: string[] = [];
startRecording();
const ok = await playSteps(
[
{ kind: "action", id: "a", arg: 1 },
{ kind: "text", text: "t" },
{ kind: "key", key: "Tab" },
{ kind: "action", id: "b" },
],
{
action: async (id, arg) => {
recordStep({ kind: "action", id });
log.push(`${id}${arg ?? ""}`);
return id !== "b";
},
text: (t) => (log.push(t), true),
key: (k) => (log.push(k), true),
},
);
expect(ok).toBe(false);
expect(log).toEqual(["a1", "t", "Tab", "b"]);
expect(useMacros.getState().recording).toEqual([]);
expect(useMacros.getState().playing).toBe(false);
});
it("stores plain action arguments only and describes steps", () => {
expect(storableArg("x")).toBe("x");
expect(storableArg(2)).toBe(2);
expect(storableArg(undefined)).toBeUndefined();
expect(storableArg({ path: "/a" })).toBeNull();
expect(storableArg(Number.NaN)).toBeNull();
const title = (id: string) => (id === "a" ? "Action A" : id);
expect(describeStep({ kind: "action", id: "a", arg: "x" }, title)).toBe("Action A (x)");
expect(describeStep({ kind: "text", text: "a\nb" }, title)).toBe('Type "a↵b"');
expect(describeStep({ kind: "key", key: "Enter" }, title)).toBe("Press Enter");
});
it("names the editor keys a macro records", () => {
const k = (key: string, mods: Partial<Record<"altKey" | "ctrlKey" | "metaKey" | "shiftKey", boolean>> = {}) =>
editorKeyName({ key, altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, ...mods });
expect(k("Enter")).toBe("Enter");
expect(k("ArrowLeft", { shiftKey: true, altKey: true })).toBe("Alt+Shift+ArrowLeft");
expect(k("a")).toBeNull();
expect(k("s", { metaKey: true })).toBeNull();
});
});