дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
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
200 рядки
8.8 KiB
TypeScript
200 рядки
8.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { RunConfig } from "@/platform/ipc";
|
|
import {
|
|
commandLine,
|
|
duplicateConfig,
|
|
formatArgs,
|
|
groupConfigs,
|
|
mergeConfigs,
|
|
newConfig,
|
|
parseArgs,
|
|
removeConfig,
|
|
upsertConfig,
|
|
userConfigs,
|
|
validateConfig,
|
|
} from "./configs";
|
|
import { contextConfig, fileRole } from "./context";
|
|
import { envFromRows } from "./EnvDialog";
|
|
import { defaultSelection } from "./store";
|
|
|
|
const cfg = (over: Partial<RunConfig> & { id: string }): RunConfig => ({
|
|
name: over.id,
|
|
kind: "shell",
|
|
command: "echo",
|
|
args: [],
|
|
cwd: null,
|
|
env: {},
|
|
detected: true,
|
|
isTest: false,
|
|
debugAdapter: null,
|
|
...over,
|
|
});
|
|
|
|
describe("config merge / override", () => {
|
|
const detected = [cfg({ id: "npm:dev", kind: "npm" }), cfg({ id: "npm:test", kind: "npm", isTest: true })];
|
|
|
|
it("user configs override detected ones with the same id", () => {
|
|
const user = [cfg({ id: "npm:dev", kind: "npm", name: "dev (custom)", detected: false })];
|
|
const merged = mergeConfigs(detected, user);
|
|
expect(merged.map((c) => [c.id, c.name])).toEqual([
|
|
["npm:test", "npm:test"],
|
|
["npm:dev", "dev (custom)"],
|
|
]);
|
|
});
|
|
|
|
it("editing a detected config creates a user copy with the same id", () => {
|
|
const edited = upsertConfig(detected, { ...detected[0]!, args: ["run", "dev", "--port", "3001"] });
|
|
expect(edited[0]).toMatchObject({ id: "npm:dev", detected: false, args: ["run", "dev", "--port", "3001"] });
|
|
expect(userConfigs(edited).map((c) => c.id)).toEqual(["npm:dev"]);
|
|
expect(edited[1]!.detected).toBe(true);
|
|
});
|
|
|
|
it("adds, duplicates and removes user configs", () => {
|
|
const added = newConfig(detected);
|
|
expect(added.id.startsWith("user:")).toBe(true);
|
|
expect(added.detected).toBe(false);
|
|
let list = upsertConfig(detected, added);
|
|
const copy = duplicateConfig(detected[0]!, list);
|
|
expect(copy).toMatchObject({ name: "npm:dev (copy)", detected: false });
|
|
expect(copy.id).not.toBe(detected[0]!.id);
|
|
list = upsertConfig(list, copy);
|
|
expect(userConfigs(list)).toHaveLength(2);
|
|
list = removeConfig(list, added.id);
|
|
expect(userConfigs(list).map((c) => c.id)).toEqual([copy.id]);
|
|
});
|
|
|
|
it("groups by kind in order", () => {
|
|
const groups = groupConfigs([cfg({ id: "a", kind: "npm" }), cfg({ id: "b", kind: "cargo" }), cfg({ id: "c", kind: "npm" })]);
|
|
expect(groups.map((g) => [g.label, g.configs.map((c) => c.id)])).toEqual([
|
|
["npm", ["a", "c"]],
|
|
["Cargo", ["b"]],
|
|
]);
|
|
});
|
|
|
|
it("prefers an application config as the default selection", () => {
|
|
const list = [
|
|
cfg({ id: "cargo:build" }),
|
|
cfg({ id: "cargo:test", isTest: true }),
|
|
cfg({ id: "cargo:run:app", name: "cargo run app" }),
|
|
];
|
|
expect(defaultSelection(list)?.id).toBe("cargo:run:app");
|
|
});
|
|
});
|
|
|
|
describe("validateConfig", () => {
|
|
it("requires a name and command", () => {
|
|
expect(validateConfig(cfg({ id: "x", name: " ", command: "" }))).toEqual({
|
|
name: "Name is required",
|
|
command: "Command is required",
|
|
});
|
|
});
|
|
|
|
it("rejects duplicate names and bad env keys", () => {
|
|
const all = [cfg({ id: "a", name: "Server" })];
|
|
expect(validateConfig(cfg({ id: "b", name: "Server" }), all).name).toMatch(/Another/);
|
|
expect(validateConfig(cfg({ id: "c", env: { "BAD KEY": "1" } })).env).toMatch(/Invalid/);
|
|
expect(validateConfig(cfg({ id: "d", env: { RUST_LOG: "debug" } }))).toEqual({});
|
|
});
|
|
|
|
it("validates env table rows", () => {
|
|
expect(envFromRows([{ key: "A", value: "1" }, { key: "", value: "" }])).toEqual({ env: { A: "1" } });
|
|
expect(envFromRows([{ key: "", value: "x" }]).error).toMatch(/empty/);
|
|
expect(envFromRows([{ key: "A", value: "1" }, { key: "A", value: "2" }]).error).toMatch(/Duplicate/);
|
|
});
|
|
});
|
|
|
|
describe("argument parsing", () => {
|
|
it("splits with quotes and escapes", () => {
|
|
expect(parseArgs(`run --name "hello world" 'it''s' a\\ b ""`)).toEqual(["run", "--name", "hello world", "its", "a b", ""]);
|
|
expect(parseArgs(" ")).toEqual([]);
|
|
});
|
|
|
|
it("round-trips through formatArgs", () => {
|
|
const args = ["test", "--", "it's here", "", "$HOME", "plain"];
|
|
expect(parseArgs(formatArgs(args))).toEqual(args);
|
|
expect(commandLine({ command: "cargo", args: ["run", "--bin", "my app"] })).toBe("cargo run --bin 'my app'");
|
|
});
|
|
});
|
|
|
|
describe("context configuration", () => {
|
|
const configs = [
|
|
cfg({ id: "npm:test", kind: "npm", command: "pnpm", args: ["run", "test"], isTest: true }),
|
|
cfg({ id: "npm:web:test", kind: "npm", command: "pnpm", args: ["run", "test"], cwd: "web", isTest: true }),
|
|
cfg({ id: "cargo:test", kind: "cargo", command: "cargo", args: ["test"], isTest: true }),
|
|
cfg({ id: "cargo:run:server", kind: "cargo", command: "cargo", args: ["run", "--bin", "server"] }),
|
|
cfg({ id: "go:test", kind: "go", command: "go", args: ["test", "./..."], isTest: true }),
|
|
cfg({ id: "pytest", kind: "pytest", command: "uv", args: ["run", "pytest"], isTest: true }),
|
|
cfg({ id: "python:module:app", kind: "python", command: ".venv/bin/python", args: ["-m", "app"] }),
|
|
cfg({ id: "gradle:test", kind: "gradle", command: "./gradlew", args: ["test"], isTest: true }),
|
|
];
|
|
|
|
it("classifies files", () => {
|
|
expect(fileRole("src/a.test.ts")).toBe("test");
|
|
expect(fileRole("pkg/x_test.go")).toBe("test");
|
|
expect(fileRole("tests/test_api.py")).toBe("test");
|
|
expect(fileRole("src/test/kotlin/OrderControllerTest.kt")).toBe("test");
|
|
expect(fileRole("tests/integration.rs")).toBe("test");
|
|
expect(fileRole("src/main.rs")).toBe("main");
|
|
expect(fileRole("cmd/main.go", "package main\nfunc main() {}")).toBe("main");
|
|
expect(fileRole("tools/gen.py")).toBe("script");
|
|
expect(fileRole("README.md")).toBeNull();
|
|
});
|
|
|
|
it("runs a JS test file through the closest test script", () => {
|
|
const c = contextConfig("web/src/cart.test.ts", configs)!;
|
|
expect(c).toMatchObject({ args: ["run", "test", "--", "src/cart.test.ts"], cwd: "web", isTest: true, name: "cart.test.ts" });
|
|
expect(c.id).toBe("context:npm:web:test:web/src/cart.test.ts");
|
|
expect(contextConfig("src/a.spec.js", configs)!.args).toEqual(["run", "test", "--", "src/a.spec.js"]);
|
|
});
|
|
|
|
it("narrows go, cargo, pytest and gradle runs", () => {
|
|
expect(contextConfig("internal/calc/calc_test.go", configs)!.args).toEqual(["test", "./internal/calc"]);
|
|
expect(contextConfig("tests/api.rs", configs)!.args).toEqual(["test", "--test", "api"]);
|
|
expect(contextConfig("tests/test_math.py", configs)!.args).toEqual(["run", "pytest", "tests/test_math.py"]);
|
|
expect(contextConfig("src/test/kotlin/OrderControllerTest.kt", configs)!.args).toEqual([
|
|
"test",
|
|
"--tests",
|
|
"*OrderControllerTest",
|
|
]);
|
|
});
|
|
|
|
it("finds run targets and script invocations", () => {
|
|
expect(contextConfig("src/bin/server.rs", configs)!.id).toBe("cargo:run:server");
|
|
expect(contextConfig("tools/gen.py", configs)).toMatchObject({ command: ".venv/bin/python", args: ["tools/gen.py"] });
|
|
expect(contextConfig("scripts/build.sh", configs)).toMatchObject({ command: "sh", args: ["scripts/build.sh"] });
|
|
expect(contextConfig("docs/readme.md", configs)).toBeNull();
|
|
});
|
|
|
|
it("runs pytest for a test file even without a detected pytest config", () => {
|
|
expect(contextConfig("worker/test_worker.py", [])).toMatchObject({
|
|
command: "python3",
|
|
args: ["-m", "pytest", "worker/test_worker.py"],
|
|
isTest: true,
|
|
debugAdapter: "debugpy",
|
|
});
|
|
const venv = [cfg({ id: "python:module:app", kind: "python", command: ".venv/bin/python", args: ["-m", "app"] })];
|
|
expect(contextConfig("worker/test_worker.py", venv)).toMatchObject({
|
|
command: ".venv/bin/python",
|
|
args: ["-m", "pytest", "worker/test_worker.py"],
|
|
});
|
|
});
|
|
|
|
it("runs a Go main package of a nested module from the module directory", () => {
|
|
const nested = [
|
|
cfg({ id: "go:run:api", kind: "go", command: "go", args: ["run", "."], cwd: "api" }),
|
|
cfg({ id: "go:test:api", kind: "go", command: "go", args: ["test", "./..."], cwd: "api", isTest: true }),
|
|
];
|
|
const main = "package main\nfunc main() {}";
|
|
expect(contextConfig("api/main.go", nested, main)!.id).toBe("go:run:api");
|
|
expect(contextConfig("api/cmd/tool/main.go", nested, main)).toMatchObject({ args: ["run", "./cmd/tool"], cwd: "api" });
|
|
expect(contextConfig("api/main_test.go", nested)).toMatchObject({ args: ["test", "."], cwd: "api" });
|
|
});
|
|
|
|
it("derives the interpreter from a venv pytest config", () => {
|
|
const venv = [cfg({ id: "pytest", kind: "pytest", command: ".venv/bin/pytest", args: [], isTest: true })];
|
|
expect(contextConfig("tools/gen.py", venv)).toMatchObject({ command: ".venv/bin/python", args: ["tools/gen.py"] });
|
|
const uv = [cfg({ id: "pytest", kind: "pytest", command: "uv", args: ["run", "pytest"], isTest: true })];
|
|
expect(contextConfig("tools/gen.py", uv)).toMatchObject({ command: "uv", args: ["run", "python", "tools/gen.py"] });
|
|
});
|
|
});
|