import assert from "node:assert/strict"; import { cloneDefaultConfig, mock, runTest } from "./test-helpers.test.ts"; mock.module("@earendil-works/pi-coding-agent", { namedExports: { getAgentDir: () => "/tmp/.pi/agent", getSettingsListTheme: () => ({}), }, }); const settingsListInputs: string[] = []; const settingsListUpdates: Array<{ id: string; value: string }> = []; mock.module("@earendil-works/pi-tui", { namedExports: { Box: class { addChild(): void {} }, Container: class { addChild(): void {} render(): string[] { return ["settings-content"]; } invalidate(): void {} }, SettingsList: class { handleInput(data: string): void { settingsListInputs.push(data); } updateValue(id: string, value: string): void { settingsListUpdates.push({ id, value }); } }, Spacer: class {}, Text: class {}, truncateToWidth: (text: string, width: number) => text.slice(0, width), visibleWidth: (text: string) => text.length, }, }); function stripAnsi(text: string): string { return text.replace(/\x1b\[[0-9;]*m/g, ""); } const { registerRtkIntegrationCommand } = await import("./command-register.ts"); const { ZellijModal, ZellijSettingsModal } = await import("./zellij-modal.ts"); const { getRtkArgumentCompletions } = await import("./command-completions.ts"); type Notification = { message: string; level: "info" | "warning" | "error" }; interface CommandContextStub { hasUI: boolean; ui: { notify(message: string, level: "info" | "warning" | "error"): void; custom(): Promise; }; } function createNotifyContext(hasUI: boolean): { ctx: CommandContextStub; notifications: Notification[] } { const notifications: Notification[] = []; return { ctx: { hasUI, ui: { notify(message: string, level: "info" | "warning" | "error") { notifications.push({ message, level }); }, async custom(): Promise { throw new Error("custom UI should not be invoked in config-modal tests"); }, }, }, notifications, }; } function lastNotification(notifications: Notification[]): Notification { return notifications[notifications.length - 1] as Notification; } function createThemeStub(): { fg: (_name: string, text: string) => string; bold: (text: string) => string } { return { fg: (_name: string, text: string) => text, bold: (text: string) => text, }; } runTest("zellij settings modal renders overlay frame and delegates non-enter input", () => { settingsListInputs.length = 0; settingsListUpdates.length = 0; const settingsModal = new ZellijSettingsModal( { title: "RTK Integration Settings", settings: [ { id: "enabled", label: "Enabled", description: "Enable integration", currentValue: "on", values: ["on", "off"], }, ], onChange: () => {}, onClose: () => {}, helpText: "Esc: close", }, createThemeStub() as never, ); const modal = new ZellijModal(settingsModal, { titleBar: { left: { text: "RTK Integration Settings", maxWidth: 30, color: "accent" }, right: { text: "pi-rtk-optimizer", maxWidth: 20, color: "dim" }, }, helpUndertitle: { text: "Esc: close", color: "dim" }, overlay: { anchor: "center", width: 86, maxHeight: "85%", margin: 1 }, }); const rendered = modal.renderModal(86); settingsModal.handleInput("\r"); settingsModal.handleInput("j"); settingsModal.updateValue("enabled", "off"); assert.equal(rendered.visibleWidth, 86); assert.equal(rendered.contentWidth, 82); assert.ok(stripAnsi(rendered.lines[0] ?? "").includes("RTK Integration Settings")); assert.ok(stripAnsi(rendered.lines[rendered.lines.length - 1] ?? "").includes("Esc: close")); assert.deepEqual(modal.getOverlayOptions(), { overlay: true, overlayOptions: { anchor: "center", width: 86, maxHeight: "85%", margin: 1 }, }); assert.deepEqual(settingsListInputs, ["j"]); assert.deepEqual(settingsListUpdates, [{ id: "enabled", value: "off" }]); }); runTest("command completions return top-level and filtered RTK subcommands", () => { const topLevel = getRtkArgumentCompletions(""); assert.ok(Array.isArray(topLevel)); assert.ok(topLevel.some((item) => item.value === "show")); assert.ok(topLevel.some((item) => item.value === "clear-stats")); const filtered = getRtkArgumentCompletions("st"); assert.deepEqual( filtered?.map((item) => item.value), ["stats"], ); assert.equal(getRtkArgumentCompletions("show extra"), null); assert.equal(getRtkArgumentCompletions("zzz"), null); }); await runTest("config modal command handlers route RTK subcommands to controller actions", async () => { const config = cloneDefaultConfig(); const controllerState = { config, cleared: 0, refreshed: 0, lastSavedMode: "", }; const controller = { getConfig: () => controllerState.config, setConfig: (next: typeof config, _ctx: unknown) => { controllerState.config = next; controllerState.lastSavedMode = next.mode; }, getConfigPath: () => "C:/tmp/pi-rtk-optimizer/config.json", getRuntimeStatus: () => ({ rtkAvailable: false, lastError: "not found" }), refreshRuntimeStatus: async () => { controllerState.refreshed += 1; return { rtkAvailable: true, rtkExecutablePath: "C:/Tools/rtk.exe" }; }, getMetricsSummary: () => "metrics summary", clearMetrics: () => { controllerState.cleared += 1; }, }; let registeredName = ""; type RegisteredCommandDefinition = { description: string; getArgumentCompletions?: (argumentPrefix: string) => Array<{ value: string; label: string; description?: string }> | null; handler: (args: string, ctx: CommandContextStub) => Promise; }; let definition: RegisteredCommandDefinition | undefined; registerRtkIntegrationCommand( { registerCommand(name: string, nextDefinition: RegisteredCommandDefinition) { registeredName = name; definition = nextDefinition; }, } as never, controller as never, ); assert.equal(registeredName, "rtk"); if (!definition) { throw new Error("Expected /rtk command definition to be registered"); } assert.ok(definition.description.includes("Configure RTK rewrite")); assert.ok(typeof definition.getArgumentCompletions === "function"); const infoCtx = createNotifyContext(true); await definition.handler("help", infoCtx.ctx); assert.ok(lastNotification(infoCtx.notifications).message.includes("Usage: /rtk")); await definition.handler("show", infoCtx.ctx); assert.ok(lastNotification(infoCtx.notifications).message.includes("mode=rewrite")); assert.ok(lastNotification(infoCtx.notifications).message.includes("rewriteSource=rtk")); assert.equal(lastNotification(infoCtx.notifications).message.includes("categories="), false); await definition.handler("path", infoCtx.ctx); assert.equal(lastNotification(infoCtx.notifications).message, "rtk config: C:/tmp/pi-rtk-optimizer/config.json"); await definition.handler("verify", infoCtx.ctx); assert.equal(controllerState.refreshed, 1); assert.equal(lastNotification(infoCtx.notifications).level, "info"); assert.ok(lastNotification(infoCtx.notifications).message.includes("available at C:/Tools/rtk.exe")); await definition.handler("stats", infoCtx.ctx); assert.equal(lastNotification(infoCtx.notifications).message, "metrics summary"); await definition.handler("clear-stats", infoCtx.ctx); assert.equal(controllerState.cleared, 1); assert.equal(lastNotification(infoCtx.notifications).message, "RTK metrics cleared."); await definition.handler("reset", infoCtx.ctx); assert.equal(controllerState.lastSavedMode, "rewrite"); assert.equal(lastNotification(infoCtx.notifications).message, "RTK integration settings reset to defaults."); await definition.handler("unknown", infoCtx.ctx); assert.equal(lastNotification(infoCtx.notifications).level, "warning"); assert.ok(lastNotification(infoCtx.notifications).message.includes("Usage: /rtk")); const headlessCtx = createNotifyContext(false); await definition.handler("", headlessCtx.ctx); assert.equal(lastNotification(headlessCtx.notifications).message, "/rtk requires interactive TUI mode."); }); console.log("All config-modal tests passed.");