From 20cf51169b4fb84c89042960753aa4be9a488948 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 17 Apr 2026 18:37:49 +0100 Subject: [PATCH] test: merge config view browser checks --- ui/src/ui/views/config.browser.test.ts | 210 ++++++++----------------- 1 file changed, 68 insertions(+), 142 deletions(-) diff --git a/ui/src/ui/views/config.browser.test.ts b/ui/src/ui/views/config.browser.test.ts index faa9472abae..6658471cd02 100644 --- a/ui/src/ui/views/config.browser.test.ts +++ b/ui/src/ui/views/config.browser.test.ts @@ -102,86 +102,59 @@ describe("config view", () => { resetRawRevealState(); }); - it("allows save when form is unsafe", () => { + it("updates save/apply disabled state from form safety and raw dirtiness", () => { const container = document.createElement("div"); - render( - renderConfig({ - ...baseProps(), - schema: { - type: "object", - properties: { - mixed: { - anyOf: [{ type: "string" }, { type: "object", properties: {} }], - }, + + const renderCase = (overrides: Partial) => + render(renderConfig({ ...baseProps(), ...overrides }), container); + + renderCase({ + schema: { + type: "object", + properties: { + mixed: { + anyOf: [{ type: "string" }, { type: "object", properties: {} }], }, }, - schemaLoading: false, - uiHints: {}, - formMode: "form", - formValue: { mixed: "x" }, - }), - container, - ); - - const saveButton = Array.from(container.querySelectorAll("button")).find( - (btn) => btn.textContent?.trim() === "Save", - ); + }, + schemaLoading: false, + uiHints: {}, + formMode: "form", + formValue: { mixed: "x" }, + }); + let { saveButton, applyButton } = findActionButtons(container); expect(saveButton).not.toBeUndefined(); expect(saveButton?.disabled).toBe(false); - }); + expect(applyButton?.disabled).toBe(false); - it("disables save when schema is missing", () => { - const container = document.createElement("div"); - render( - renderConfig({ - ...baseProps(), - schema: null, - formMode: "form", - formValue: { gateway: { mode: "local" } }, - originalValue: {}, - }), - container, - ); - - const saveButton = Array.from(container.querySelectorAll("button")).find( - (btn) => btn.textContent?.trim() === "Save", - ); + renderCase({ + schema: null, + formMode: "form", + formValue: { gateway: { mode: "local" } }, + originalValue: {}, + }); + ({ saveButton, applyButton } = findActionButtons(container)); expect(saveButton).not.toBeUndefined(); expect(saveButton?.disabled).toBe(true); - }); + expect(applyButton?.disabled).toBe(true); - it("disables save and apply when raw is unchanged", () => { - const container = document.createElement("div"); - render( - renderConfig({ - ...baseProps(), - formMode: "raw", - raw: "{\n}\n", - originalRaw: "{\n}\n", - }), - container, - ); - - const { saveButton, applyButton } = findActionButtons(container); + renderCase({ + formMode: "raw", + raw: "{\n}\n", + originalRaw: "{\n}\n", + }); + ({ saveButton, applyButton } = findActionButtons(container)); expect(saveButton).not.toBeUndefined(); expect(applyButton).not.toBeUndefined(); expect(saveButton?.disabled).toBe(true); expect(applyButton?.disabled).toBe(true); - }); - it("enables save and apply when raw changes", () => { - const container = document.createElement("div"); - render( - renderConfig({ - ...baseProps(), - formMode: "raw", - raw: '{\n gateway: { mode: "local" }\n}\n', - originalRaw: "{\n}\n", - }), - container, - ); - - const { saveButton, applyButton } = findActionButtons(container); + renderCase({ + formMode: "raw", + raw: '{\n gateway: { mode: "local" }\n}\n', + originalRaw: "{\n}\n", + }); + ({ saveButton, applyButton } = findActionButtons(container)); expect(saveButton).not.toBeUndefined(); expect(applyButton).not.toBeUndefined(); expect(saveButton?.disabled).toBe(false); @@ -245,7 +218,7 @@ describe("config view", () => { expect(onFormModeChange).not.toHaveBeenCalled(); }); - it("switches sections from the sidebar", () => { + it("renders section tabs and switches sections from the sidebar", () => { const container = document.createElement("div"); const onSectionChange = vi.fn(); render( @@ -263,6 +236,13 @@ describe("config view", () => { container, ); + const tabs = Array.from(container.querySelectorAll(".config-top-tabs__tab")).map((tab) => + tab.textContent?.trim(), + ); + expect(tabs).toContain("Settings"); + expect(tabs).toContain("Agents"); + expect(tabs).toContain("Gateway"); + const btn = Array.from(container.querySelectorAll("button")).find( (b) => b.textContent?.trim() === "Gateway", ); @@ -329,61 +309,7 @@ describe("config view", () => { expect(content.scrollLeft).toBe(0); }); - it("wires search input to onSearchChange", () => { - const container = document.createElement("div"); - const onSearchChange = vi.fn(); - render( - renderConfig({ - ...baseProps(), - onSearchChange, - }), - container, - ); - - const input = container.querySelector(".config-search__input"); - expect(input).not.toBeNull(); - if (!input) { - return; - } - (input as HTMLInputElement).value = "gateway"; - input.dispatchEvent(new Event("input", { bubbles: true })); - expect(onSearchChange).toHaveBeenCalledWith("gateway"); - }); - - it("renders the top search icon inside the search input row", () => { - const container = document.createElement("div"); - render(renderConfig(baseProps()), container); - - const icon = container.querySelector(".config-search__icon"); - expect(icon).not.toBeNull(); - expect(icon?.closest(".config-search__input-row")).not.toBeNull(); - }); - - it("renders top tabs for root and available sections", () => { - const container = document.createElement("div"); - render( - renderConfig({ - ...baseProps(), - schema: { - type: "object", - properties: { - gateway: { type: "object", properties: {} }, - agents: { type: "object", properties: {} }, - }, - }, - }), - container, - ); - - const tabs = Array.from(container.querySelectorAll(".config-top-tabs__tab")).map((tab) => - tab.textContent?.trim(), - ); - expect(tabs).toContain("Settings"); - expect(tabs).toContain("Agents"); - expect(tabs).toContain("Gateway"); - }); - - it("clears the active search query", () => { + it("renders and wires the search field controls", () => { const container = document.createElement("div"); const onSearchChange = vi.fn(); render( @@ -395,32 +321,26 @@ describe("config view", () => { container, ); + const icon = container.querySelector(".config-search__icon"); + expect(icon).not.toBeNull(); + expect(icon?.closest(".config-search__input-row")).not.toBeNull(); + + const input = container.querySelector(".config-search__input"); + expect(input).not.toBeNull(); + if (!input) { + return; + } + (input as HTMLInputElement).value = "gateway"; + input.dispatchEvent(new Event("input", { bubbles: true })); + expect(onSearchChange).toHaveBeenCalledWith("gateway"); + const clearButton = container.querySelector(".config-search__clear"); expect(clearButton).toBeTruthy(); clearButton?.click(); expect(onSearchChange).toHaveBeenCalledWith(""); }); - it("keeps sensitive raw config hidden until reveal", () => { - const { container } = renderConfigView({ - formMode: "raw", - raw: '{\n "openai": { "apiKey": "supersecret" }\n}\n', - originalRaw: '{\n "openai": { "apiKey": "supersecret" }\n}\n', - formValue: { - openai: { - apiKey: "supersecret", - }, - }, - }); - - const text = normalizedText(container); - expect(text).toContain("1 secret redacted"); - expect(text).toContain("Use the reveal button above to edit the raw config."); - expect(text).not.toContain("supersecret"); - expect(container.querySelector("textarea")).toBeNull(); - }); - - it("reveals sensitive raw config before editing", () => { + it("keeps sensitive raw config hidden until reveal before editing", () => { const onRawChange = vi.fn(); const { container } = renderConfigView({ formMode: "raw", @@ -434,6 +354,12 @@ describe("config view", () => { onRawChange, }); + const text = normalizedText(container); + expect(text).toContain("1 secret redacted"); + expect(text).toContain("Use the reveal button above to edit the raw config."); + expect(text).not.toContain("supersecret"); + expect(container.querySelector("textarea")).toBeNull(); + const revealButton = container.querySelector(".config-raw-toggle"); expect(revealButton).toBeTruthy(); revealButton?.click();