import assert from "node:assert/strict"; import test from "node:test"; import { getSettingsListTheme, initTheme } from "@earendil-works/pi-coding-agent"; import { SettingsList, visibleWidth } from "@earendil-works/pi-tui"; import { FAST_BROWSER_SETTING_IDS, FAST_BROWSER_SETTING_LABELS, FastBrowserSettingsView, buildFastBrowserSettingItems, fastBrowserSettingValue, } from "./settings-view.ts"; const stripAnsi = (value: string): string => value.replace(/\u001b\[[0-9;]*m/g, ""); test.before(() => initTheme("dark", false)); function options( draft: Record = {}, inherited: Record = {}, onEvent: (event: any) => void = () => {}, ) { return { scope: "project" as const, draft, inherited, onEvent, }; } test("renders native SettingsList values subdued on the right", () => { const view = new FastBrowserSettingsView(options( { cdpUrl: "http://127.0.0.1:9333" }, { allowedOrigins: ["https://example.test"], textModel: "fixture/small" }, )); const lines = view.render(80); const endpoint = lines.find((line) => stripAnsi(line).includes("CDP endpoint")); assert(endpoint); assert.match(stripAnsi(endpoint), /CDP endpoint\s+http:\/\/127\.0\.0\.1:9333/); // The native theme wraps an unselected value in its muted ANSI color. assert.match(endpoint, /\u001b\[[0-9;]*mhttp:\/\/127\.0\.0\.1:9333\u001b\[[0-9;]*m/); const inherited = fastBrowserSettingValue( FAST_BROWSER_SETTING_IDS.textModel, {}, { textModel: "fixture/small" }, ); assert.equal(inherited, "(inherited) fixture/small"); }); test("displays unrestricted origin policy and inherited default values", () => { assert.equal(fastBrowserSettingValue( FAST_BROWSER_SETTING_IDS.allowedOrigins, { allowedOrigins: "*" }, {}, ), "All web origins"); assert.equal(fastBrowserSettingValue( FAST_BROWSER_SETTING_IDS.allowedOrigins, {}, { allowedOrigins: "*" }, ), "(inherited) All web origins"); }); test("shows named profiles as explicit or inherited browser settings", () => { const explicit = fastBrowserSettingValue( FAST_BROWSER_SETTING_IDS.browserProfile, { browser: { mode: "launch", profile: "evebot" } }, {}, ); const inherited = fastBrowserSettingValue( FAST_BROWSER_SETTING_IDS.browserProfile, {}, { browser: { mode: "launch", profile: "global-profile" } }, ); assert.equal(explicit, "evebot"); assert.equal(inherited, "(inherited) global-profile"); }); test("updates displayed rows with the latest draft after an edit", () => { const draft: Record = {}; const inherited = { textModel: "global/model" }; const view = new FastBrowserSettingsView(options(draft, inherited)); const before = view.render(80).join("\n"); assert.match(stripAnsi(before), /Auxiliary text model\s+\(inherited\) global\/model/); draft.textModel = "project/model"; view.updateValues(draft, inherited); const after = view.render(80).join("\n"); assert.match(stripAnsi(after), /Auxiliary text model\s+project\/model/); }); test("keeps long URLs, paths, and CJK labels within narrow terminal widths", () => { const view = new FastBrowserSettingsView(options( { cdpUrl: "http://127.0.0.1:9222", python: "/very/long/path/项目/with/a/credential-free/python-executable", browser: { mode: "launch", product: "chromium", executablePath: "/very/long/path/项目/with/a/browser-executable", startUrl: "https://example.test/非常長い開始URL/with/more/path", }, }, { allowedOrigins: ["https://example.test/非常長い起点"] }, )); for (const width of [60, 80, 120]) { for (const line of view.render(width)) assert(visibleWidth(line) <= width, `${width}: ${stripAnsi(line)}`); } }); test("dispatches by stable id even when a displayed label changes", () => { const events: any[] = []; const items = buildFastBrowserSettingItems(options({}, {}, (event) => events.push(event))); const first = items.find((item) => item.id === FAST_BROWSER_SETTING_IDS.cdpUrl); assert(first); first.label = "Endpoint label changed"; const list = new SettingsList(items, 10, getSettingsListTheme(), (id) => { events.push({ kind: "edit", id }); }, () => {}); list.handleInput("\r"); assert.deepEqual(events, [{ kind: "edit", id: FAST_BROWSER_SETTING_IDS.cdpUrl }]); assert.equal(FAST_BROWSER_SETTING_LABELS.cdpUrl, "CDP endpoint"); }); test("does not render explicit save or discard actions", () => { const labels = buildFastBrowserSettingItems(options()).map((item) => item.label); assert(!labels.includes("Save settings")); assert(!labels.includes("Cancel")); const view = new FastBrowserSettingsView(options()); assert.match(stripAnsi(view.render(120).join("\n")), /Changes save automatically/); }); test("Escape dispatches cancel without mutating the draft", () => { const draft = { textModel: "keep-me" }; const events: any[] = []; const view = new FastBrowserSettingsView(options(draft, {}, (event) => events.push(event))); view.handleInput("\u001b"); assert.deepEqual(events, [{ kind: "cancel" }]); assert.deepEqual(draft, { textModel: "keep-me" }); });