Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
392 lines
11 KiB
TypeScript
392 lines
11 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
applyCustomAccentToTerminalTheme,
|
|
applySessionFontSizeToHost,
|
|
getFollowAppTerminalThemeIds,
|
|
getFollowAppTerminalThemeSelectionUpdate,
|
|
getFollowAppThemePickExpectedSettledId,
|
|
isFollowAppTerminalThemeId,
|
|
isFollowAppThemePickSettled,
|
|
mergeTerminalHostUpdate,
|
|
resolveFollowedTerminalThemeId,
|
|
resolveFollowAppTerminalThemeId,
|
|
resolveManualTerminalThemeId,
|
|
TERMINAL_THEME_AUTO,
|
|
} from "./terminalAppearance";
|
|
import type { Host, TerminalTheme } from "./models";
|
|
|
|
const baseTheme: TerminalTheme = {
|
|
id: "ui-snow",
|
|
name: "Snow",
|
|
type: "light",
|
|
colors: {
|
|
background: "#f1f4f8",
|
|
foreground: "#24292f",
|
|
cursor: "#0969da",
|
|
selection: "#add6ff",
|
|
black: "#24292f",
|
|
red: "#cf222e",
|
|
green: "#116329",
|
|
yellow: "#9a6700",
|
|
blue: "#0969da",
|
|
magenta: "#8250df",
|
|
cyan: "#0e7574",
|
|
white: "#6e7781",
|
|
brightBlack: "#57606a",
|
|
brightRed: "#a40e26",
|
|
brightGreen: "#1a7f37",
|
|
brightYellow: "#7d4e00",
|
|
brightBlue: "#218bff",
|
|
brightMagenta: "#a475f9",
|
|
brightCyan: "#0c7875",
|
|
brightWhite: "#8c959f",
|
|
},
|
|
};
|
|
|
|
test("applies a custom accent to terminal cursor and selection colors", () => {
|
|
const accented = applyCustomAccentToTerminalTheme(baseTheme, "custom", "160 70% 40%");
|
|
|
|
assert.notEqual(accented, baseTheme);
|
|
assert.equal(accented.colors.cursor, "#1fad7e");
|
|
assert.equal(accented.colors.selection, "#b1f1dc");
|
|
assert.equal(baseTheme.colors.cursor, "#0969da");
|
|
assert.equal(baseTheme.colors.selection, "#add6ff");
|
|
});
|
|
|
|
test("keeps terminal theme unchanged without a valid custom accent", () => {
|
|
assert.equal(applyCustomAccentToTerminalTheme(baseTheme, "theme", "160 70% 40%"), baseTheme);
|
|
assert.equal(applyCustomAccentToTerminalTheme(baseTheme, "custom", "not-a-color"), baseTheme);
|
|
});
|
|
|
|
const savedHost: Host = {
|
|
id: "host-1",
|
|
label: "Core switch",
|
|
hostname: "10.0.0.2",
|
|
username: "admin",
|
|
port: 22,
|
|
os: "linux",
|
|
group: "",
|
|
tags: [],
|
|
protocol: "ssh",
|
|
moshEnabled: true,
|
|
telnetEnabled: true,
|
|
telnetPort: 23,
|
|
};
|
|
|
|
test("terminal updates preserve saved connection protocol and port", () => {
|
|
const telnetSessionHost: Host = {
|
|
...savedHost,
|
|
protocol: "telnet",
|
|
port: 23,
|
|
moshEnabled: false,
|
|
fontFamily: "jetbrains-mono",
|
|
fontFamilyOverride: true,
|
|
};
|
|
|
|
const merged = mergeTerminalHostUpdate(savedHost, telnetSessionHost);
|
|
|
|
assert.equal(merged.protocol, "ssh");
|
|
assert.equal(merged.port, 22);
|
|
assert.equal(merged.moshEnabled, true);
|
|
assert.equal(merged.telnetEnabled, true);
|
|
assert.equal(merged.telnetPort, 23);
|
|
assert.equal(merged.fontFamily, "jetbrains-mono");
|
|
assert.equal(merged.fontFamilyOverride, true);
|
|
});
|
|
|
|
test("terminal updates still persist credentials entered during connection", () => {
|
|
const credentialUpdate: Host = {
|
|
...savedHost,
|
|
protocol: "telnet",
|
|
port: 23,
|
|
moshEnabled: false,
|
|
username: "deploy",
|
|
authMethod: "password",
|
|
password: "secret",
|
|
};
|
|
|
|
const merged = mergeTerminalHostUpdate(savedHost, credentialUpdate);
|
|
|
|
assert.equal(merged.protocol, "ssh");
|
|
assert.equal(merged.port, 22);
|
|
assert.equal(merged.moshEnabled, true);
|
|
assert.equal(merged.username, "deploy");
|
|
assert.equal(merged.authMethod, "password");
|
|
assert.equal(merged.password, "secret");
|
|
});
|
|
|
|
test("terminal updates still persist SFTP bookmarks", () => {
|
|
const bookmarkUpdate: Host = {
|
|
...savedHost,
|
|
protocol: "telnet",
|
|
port: 23,
|
|
moshEnabled: false,
|
|
sftpBookmarks: [{ id: "bookmark-1", path: "/srv/www", label: "/srv/www" }],
|
|
};
|
|
|
|
const merged = mergeTerminalHostUpdate(savedHost, bookmarkUpdate);
|
|
|
|
assert.equal(merged.protocol, "ssh");
|
|
assert.equal(merged.port, 22);
|
|
assert.equal(merged.moshEnabled, true);
|
|
assert.deepEqual(merged.sftpBookmarks, [
|
|
{ id: "bookmark-1", path: "/srv/www", label: "/srv/www" },
|
|
]);
|
|
});
|
|
|
|
test("partial terminal updates preserve unrelated saved host fields", () => {
|
|
const hostWithAppearance: Host = {
|
|
...savedHost,
|
|
fontSize: undefined,
|
|
fontSizeOverride: false,
|
|
showLineTimestamps: false,
|
|
};
|
|
|
|
const merged = mergeTerminalHostUpdate(hostWithAppearance, {
|
|
id: hostWithAppearance.id,
|
|
showLineTimestamps: true,
|
|
});
|
|
|
|
assert.equal(merged.showLineTimestamps, true);
|
|
assert.equal(merged.fontSize, undefined);
|
|
assert.equal(merged.fontSizeOverride, false);
|
|
assert.equal(merged.hostname, hostWithAppearance.hostname);
|
|
});
|
|
|
|
test("applySessionFontSizeToHost overlays workspace pane font size", () => {
|
|
const host: Host = {
|
|
id: "host-1",
|
|
label: "Server",
|
|
hostname: "example.com",
|
|
username: "root",
|
|
port: 22,
|
|
group: "",
|
|
tags: [],
|
|
};
|
|
const session = {
|
|
id: "session-1",
|
|
hostId: "host-1",
|
|
hostLabel: "Server",
|
|
username: "root",
|
|
hostname: "example.com",
|
|
status: "connected" as const,
|
|
workspaceId: "workspace-1",
|
|
fontSize: 18,
|
|
fontSizeOverride: true,
|
|
};
|
|
|
|
const merged = applySessionFontSizeToHost(host, session);
|
|
|
|
assert.equal(merged.fontSize, 18);
|
|
assert.equal(merged.fontSizeOverride, true);
|
|
});
|
|
|
|
test("terminal appearance reset clears only appearance fields", () => {
|
|
const hostWithAppearance: Host = {
|
|
...savedHost,
|
|
fontSize: 16,
|
|
fontSizeOverride: true,
|
|
};
|
|
const resetUpdate: Host = {
|
|
...hostWithAppearance,
|
|
protocol: "telnet",
|
|
port: 23,
|
|
moshEnabled: false,
|
|
fontSize: undefined,
|
|
fontSizeOverride: false,
|
|
};
|
|
|
|
const merged = mergeTerminalHostUpdate(hostWithAppearance, resetUpdate);
|
|
|
|
assert.equal(merged.protocol, "ssh");
|
|
assert.equal(merged.port, 22);
|
|
assert.equal(merged.moshEnabled, true);
|
|
assert.equal(merged.fontSize, undefined);
|
|
assert.equal(merged.fontSizeOverride, false);
|
|
});
|
|
|
|
test("follow-theme resolver: dark + auto follows the active dark UI preset", () => {
|
|
assert.equal(
|
|
resolveFollowedTerminalThemeId({
|
|
resolvedTheme: "dark",
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "midnight",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"ui-midnight",
|
|
);
|
|
});
|
|
|
|
test("follow-theme resolver: light + auto follows the active light UI preset", () => {
|
|
assert.equal(
|
|
resolveFollowedTerminalThemeId({
|
|
resolvedTheme: "light",
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "midnight",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"ui-snow",
|
|
);
|
|
});
|
|
|
|
test("follow-theme resolver: imported system presets follow the active light or dark mode", () => {
|
|
assert.equal(
|
|
resolveFollowedTerminalThemeId({
|
|
resolvedTheme: "light",
|
|
lightUiThemeId: "github",
|
|
darkUiThemeId: "github",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"system-github-light",
|
|
);
|
|
assert.equal(
|
|
resolveFollowedTerminalThemeId({
|
|
resolvedTheme: "dark",
|
|
lightUiThemeId: "github",
|
|
darkUiThemeId: "github",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"system-github-dark",
|
|
);
|
|
});
|
|
|
|
test("follow-theme resolver: explicit dark override is ignored while following app theme", () => {
|
|
assert.equal(
|
|
resolveFollowedTerminalThemeId({
|
|
resolvedTheme: "dark",
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "midnight",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"ui-midnight",
|
|
);
|
|
});
|
|
|
|
test("manual-theme resolver: explicit per-mode theme wins when not following app theme", () => {
|
|
assert.equal(
|
|
resolveManualTerminalThemeId({
|
|
resolvedTheme: "dark",
|
|
terminalThemeDarkId: "dracula",
|
|
terminalThemeLightId: TERMINAL_THEME_AUTO,
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "midnight",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"dracula",
|
|
);
|
|
assert.equal(
|
|
resolveManualTerminalThemeId({
|
|
resolvedTheme: "light",
|
|
terminalThemeDarkId: TERMINAL_THEME_AUTO,
|
|
terminalThemeLightId: "solarized-light",
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "midnight",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"solarized-light",
|
|
);
|
|
});
|
|
|
|
test("manual-theme resolver: auto preserves the saved global terminal theme", () => {
|
|
assert.equal(
|
|
resolveManualTerminalThemeId({
|
|
resolvedTheme: "dark",
|
|
terminalThemeDarkId: TERMINAL_THEME_AUTO,
|
|
terminalThemeLightId: TERMINAL_THEME_AUTO,
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "midnight",
|
|
fallbackThemeId: "dracula",
|
|
}),
|
|
"dracula",
|
|
);
|
|
});
|
|
|
|
test("follow-app sidebar pick resolves with the target mode before resolvedTheme catches up", () => {
|
|
assert.equal(
|
|
resolveFollowAppTerminalThemeId("system-flexoki-light", {
|
|
resolvedTheme: "dark",
|
|
lightUiThemeId: "flexoki",
|
|
darkUiThemeId: "github",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"system-flexoki-light",
|
|
);
|
|
});
|
|
|
|
test("follow-app pick settles once ui theme and resolved mode match the selection", () => {
|
|
assert.equal(
|
|
isFollowAppThemePickSettled("system-flexoki-light", {
|
|
resolvedTheme: "dark",
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "github",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
isFollowAppThemePickSettled("system-flexoki-light", {
|
|
resolvedTheme: "light",
|
|
lightUiThemeId: "snow",
|
|
darkUiThemeId: "github",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
isFollowAppThemePickSettled("system-flexoki-light", {
|
|
resolvedTheme: "light",
|
|
lightUiThemeId: "flexoki",
|
|
darkUiThemeId: "github",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
getFollowAppThemePickExpectedSettledId("system-flexoki-light"),
|
|
"system-flexoki-light",
|
|
);
|
|
});
|
|
|
|
test("follow-app theme selection updates the matching mode and app theme", () => {
|
|
assert.deepEqual(
|
|
getFollowAppTerminalThemeSelectionUpdate("ui-snow"),
|
|
{
|
|
appTheme: "light",
|
|
uiThemeId: "snow",
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
getFollowAppTerminalThemeSelectionUpdate("system-github-dark"),
|
|
{
|
|
appTheme: "dark",
|
|
uiThemeId: "github",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("follow-app theme list only includes terminal themes backed by UI themes", () => {
|
|
const lightIds = getFollowAppTerminalThemeIds("light");
|
|
const darkIds = getFollowAppTerminalThemeIds("dark");
|
|
|
|
assert.ok(lightIds.includes("ui-snow"));
|
|
assert.ok(lightIds.includes("system-github-light"));
|
|
assert.ok(darkIds.includes("ui-midnight"));
|
|
assert.ok(darkIds.includes("system-github-dark"));
|
|
assert.equal(isFollowAppTerminalThemeId("dracula"), false);
|
|
assert.equal(isFollowAppTerminalThemeId("custom-theme"), false);
|
|
});
|
|
|
|
test("follow-theme resolver: auto with no UI match falls back to fallbackThemeId", () => {
|
|
assert.equal(
|
|
resolveFollowedTerminalThemeId({
|
|
resolvedTheme: "dark",
|
|
lightUiThemeId: "no-such-ui-theme",
|
|
darkUiThemeId: "no-such-ui-theme",
|
|
fallbackThemeId: "netcatty-dark",
|
|
}),
|
|
"netcatty-dark",
|
|
);
|
|
});
|