mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
AppKit puts the title bar container and window buttons back at their defaults on every live resize step, title change and fullscreen exit. They were corrected from window notifications after that layout had been drawn, so the buttons bounced between positions, or stayed at the defaults when no notification followed. The frames are now corrected in a main run loop observer ordered just before the Core Animation commit, project windows are created hidden on macOS so the buttons are placed before the first frame, and repeated title bar height updates are skipped. Choosing This Window for a local project in a remote window was routed to the remote host or skipped when the path matched the remote root. open_project and light_edit_open now run on the local backend and leave remote mode once they succeed, project trust for these opens is checked locally, and a failed open cancels the pending close.
188 lines
7.5 KiB
TypeScript
188 lines
7.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { CLIENT_SETTINGS_KEYS } from "./clientSettings";
|
|
import { commands, call, type Settings } from "./ipc";
|
|
import { loadSettings, useProject } from "./project";
|
|
import { connectRemote, disableRemote, enableRemote, isRemoteWindow, remoteRouting } from "./remote";
|
|
|
|
const handlers = vi.hoisted(() => new Map<string, () => void>());
|
|
vi.mock("./ipc", async (original) => ({
|
|
...(await original<typeof import("./ipc")>()),
|
|
onIdeEvent: (event: string, handler: () => void) => {
|
|
handlers.set(event, handler);
|
|
return () => handlers.delete(event);
|
|
},
|
|
}));
|
|
import { trackRuntimeSettings } from "@/features/settings/runtime";
|
|
|
|
const local = {
|
|
keymap: "my-map",
|
|
keymapOverrides: { "editor.copy": ["Meta+J"] },
|
|
customKeymaps: {
|
|
"my-map": { name: "Mine", parent: "vscode-macos", overrides: { "editor.paste": ["Meta+K"] } },
|
|
},
|
|
appearance: { theme: "dark" },
|
|
editor: { fontSize: 19, tabSize: 8 },
|
|
terminal: { fontSize: 18, shellPath: "/local" },
|
|
};
|
|
const remote = {
|
|
keymap: "intellij-linux",
|
|
keymapOverrides: {},
|
|
customKeymaps: {},
|
|
appearance: { theme: "light" },
|
|
editor: { fontSize: 13, tabSize: 2 },
|
|
terminal: { fontSize: 13, shellPath: "/remote" },
|
|
};
|
|
const served = new Set(["settings_get", "settings_set", "project_current"]);
|
|
let invoke: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
useProject.setState({ opening: true });
|
|
invoke = vi.fn(async (cmd: string, _args?: unknown, _options?: unknown) =>
|
|
cmd === "remote_invoke" ? JSON.stringify(remote) : local,
|
|
);
|
|
Object.assign(window, { __TAURI_INTERNALS__: { invoke, transformCallback: () => 1 } });
|
|
enableRemote({ sshConfig: "test", root: "/remote", host: "test", commands: [...served] });
|
|
});
|
|
afterEach(() => {
|
|
disableRemote();
|
|
useProject.setState({ opening: false, settings: null });
|
|
});
|
|
|
|
describe("remote client settings", () => {
|
|
it("ends remote routing before a successful local open is published, even at the same path", async () => {
|
|
invoke.mockResolvedValueOnce({ root: "/remote", settings: local });
|
|
await call(commands.openProject("/remote"));
|
|
expect(isRemoteWindow()).toBe(false);
|
|
await loadSettings();
|
|
expect(useProject.getState().settings).toEqual(local);
|
|
expect(invoke.mock.calls.map(([cmd]) => cmd)).toEqual(["open_project", "settings_get"]);
|
|
});
|
|
|
|
it("preserves remote routing when opening a local project fails", async () => {
|
|
invoke.mockRejectedValueOnce({ kind: "notFound", message: "missing folder" });
|
|
await expect(call(commands.openProject("/missing"))).rejects.toThrow("missing folder");
|
|
expect(isRemoteWindow()).toBe(true);
|
|
await loadSettings();
|
|
expect(useProject.getState().settings?.editor?.tabSize).toBe(2);
|
|
});
|
|
|
|
it.each([...CLIENT_SETTINGS_KEYS, "customKeymaps.mine", "keymapOverrides.editor.copy", "appearance.theme"])(
|
|
"keeps %s writes local in either scope",
|
|
(key) => {
|
|
for (const scope of ["global", "project"])
|
|
expect(remoteRouting("settings_set", { scope, key, value: "null" }, served)).toBe("local");
|
|
},
|
|
);
|
|
it.each([
|
|
"editor.tabSize",
|
|
"editor.formatOnSave",
|
|
"editor.codeStyle.languages",
|
|
"terminal.shellPath",
|
|
"terminal.env",
|
|
"tools.node",
|
|
"languages.python.interpreter",
|
|
"keymapExtra",
|
|
])("preserves routing for host setting %s", (key) => {
|
|
expect(remoteRouting("settings_set", { scope: "project", key }, served)).toBe("remote");
|
|
expect(remoteRouting("settings_set", { scope: "global", key }, served)).toBe("both");
|
|
});
|
|
it("merges settings before publishing a newly connected project", async () => {
|
|
disableRemote();
|
|
invoke.mockImplementation(async (cmd: string) =>
|
|
cmd === "remote_connect"
|
|
? {
|
|
sshConfig: "test",
|
|
root: "/remote",
|
|
host: "test",
|
|
commands: [...served],
|
|
project: { root: "/remote", name: "remote", settings: remote },
|
|
}
|
|
: cmd === "remote_invoke"
|
|
? "null"
|
|
: local,
|
|
);
|
|
await connectRemote("test", "/remote");
|
|
expect(useProject.getState().settings).toMatchObject({ keymap: "my-map", editor: { tabSize: 2, fontSize: 19 } });
|
|
});
|
|
it("overlays the project snapshot used by the settings window", async () => {
|
|
invoke.mockImplementation(async (cmd: string) =>
|
|
cmd === "remote_invoke" ? JSON.stringify({ root: "/remote", settings: remote }) : local,
|
|
);
|
|
expect((await commands.projectCurrent())?.settings).toMatchObject({
|
|
keymap: "my-map",
|
|
editor: { tabSize: 2, fontSize: 19 },
|
|
});
|
|
});
|
|
it("keeps client preferences after a remote tooling write", async () => {
|
|
expect(await call(commands.settingsSet("project", "editor.tabSize", "2"))).toMatchObject({
|
|
keymap: "my-map",
|
|
editor: { fontSize: 19, tabSize: 2 },
|
|
});
|
|
expect(invoke.mock.calls.some(([cmd]) => cmd === "settings_set")).toBe(false);
|
|
});
|
|
it("keeps local overrides through a settingsChanged runtime reload", async () => {
|
|
vi.useFakeTimers();
|
|
await loadSettings();
|
|
const stop = trackRuntimeSettings();
|
|
try {
|
|
handlers.get("settingsChanged")!();
|
|
await vi.advanceTimersByTimeAsync(40);
|
|
expect(useProject.getState().settings?.keymapOverrides).toEqual(local.keymapOverrides);
|
|
expect(useProject.getState().settings?.keymap).toBe("my-map");
|
|
} finally {
|
|
stop();
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
it("reloads local preferences instead of retaining stale or remote overrides", async () => {
|
|
await loadSettings();
|
|
invoke.mockImplementation(async (cmd: string) =>
|
|
cmd === "remote_invoke" ? JSON.stringify(remote) : { ...local, keymapOverrides: {} },
|
|
);
|
|
await loadSettings();
|
|
expect(useProject.getState().settings?.keymapOverrides).toEqual({});
|
|
});
|
|
it("does not inherit remote UI values when a local field is absent", async () => {
|
|
invoke.mockImplementation(async (cmd: string) => (cmd === "remote_invoke" ? JSON.stringify(remote) : {}));
|
|
await loadSettings();
|
|
expect(useProject.getState().settings?.keymap).toBeUndefined();
|
|
expect(useProject.getState().settings?.editor?.tabSize).toBe(2);
|
|
});
|
|
it("leaves local windows unchanged", async () => {
|
|
disableRemote();
|
|
await loadSettings();
|
|
expect(useProject.getState().settings).toEqual(local);
|
|
expect(invoke).toHaveBeenCalledTimes(1);
|
|
});
|
|
it("preserves client keymaps and UI settings when loading remote settings", async () => {
|
|
await loadSettings();
|
|
const settings = useProject.getState().settings as Settings;
|
|
expect(settings).toMatchObject({
|
|
...local,
|
|
editor: { fontSize: 19, tabSize: 2 },
|
|
terminal: { fontSize: 18, shellPath: "/remote" },
|
|
});
|
|
expect(invoke).toHaveBeenCalledWith("settings_get_global", {}, undefined);
|
|
});
|
|
it("shows client preferences in the global settings dialog snapshot", async () => {
|
|
expect(await commands.settingsGetGlobal()).toMatchObject({
|
|
keymap: "my-map",
|
|
editor: { fontSize: 19, tabSize: 8 },
|
|
});
|
|
});
|
|
it("writes a client preference only to local global settings and returns merged settings", async () => {
|
|
const settings = await call(commands.settingsSet("project", "editor.fontSize", "19"));
|
|
expect(invoke).toHaveBeenCalledWith(
|
|
"settings_set",
|
|
{ scope: "global", key: "editor.fontSize", value: "19" },
|
|
undefined,
|
|
);
|
|
expect(
|
|
invoke.mock.calls.filter(
|
|
([cmd, args]) => cmd === "remote_invoke" && (args as { cmd?: string })?.cmd === "settings_set",
|
|
),
|
|
).toHaveLength(0);
|
|
expect(settings).toMatchObject({ keymap: "my-map", editor: { fontSize: 19, tabSize: 2 } });
|
|
});
|
|
});
|