Файли
openidea/src/features/macros/model.test.ts
T
l0ng-ai 1e00ea5ee2 feat: Third pass aligning core experience with IntelliJ IDEA
Project & files
- Project view modes (Project Files / Open Files / All Changed Files /
  Scratches); External Libraries for Python, JDK (src.zip), Node, Cargo, Go,
  Maven/Gradle jars; read-only jar/zip browsing
- ⌘N New popup with type search and more file kinds; New Project, Rename
  Project, Reload All from Disk, Export/Import Settings, Power Save Mode
- File type associations, extra content roots and exclude patterns (indexed
  and watched without reopening the project)

Editor & navigation
- Find bar: search scope (comments/strings), Preserve Case, Exclude, multiline,
  history; Find in Files: Module scope, custom pattern scopes, multiline
- Search Everywhere filters, Assign Shortcut, settings results; named bookmark
  lists, file/folder bookmarks; Structure filters and Show Inherited; Recent
  Locations timestamps and breadcrumbs; Code Vision author
- Completion options, editor tab placement/layout, inline completion (LSP 3.18)
- Spell checking (Typo inspection, ide-spell) and Inspections settings with
  custom profiles; Code Style schemes, formatter markers, Do not format

Run, VCS, services, database
- Typed run configurations (Shell, Program, npm, Gradle, Maven, Docker,
  Compound), Modify options, Before launch, env dialog, Current File,
  Emulate terminal (PTY)
- Git log graph/view options, Go to Hash/Branch/Tag, date filter keeps the
  graph; Pull/Merge/Rebase dialogs, Manage Remotes, Worktrees
- Generic Services host (Docker connections/registries, run sessions); TODO
  scope/changes tabs with changelists; Coverage report import
- Data sources: wire-compatible vendors, URL import, SSL certs, Options,
  Schemas and Advanced tabs, color and comment

Workbench
- Run Anything, Customize Main Toolbar, named tool window layouts, menu
  completeness, Code Cleanup, macros, "Don't Show Again", editor banners
- Chinese translations for the new UI

Claude-Session: https://claude.ai/code/session_01SaPPsVPuk6ZTNsoUCNCbMv
2026-10-25 21:24:00 +08:00

113 рядки
3.9 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();
});
});