test: merge config view browser checks

This commit is contained in:
Peter Steinberger
2026-04-17 18:37:49 +01:00
parent eed71160ae
commit 20cf51169b

View File

@@ -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<ConfigProps>) =>
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<SVGElement>(".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<SVGElement>(".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<HTMLButtonElement>(".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<HTMLButtonElement>(".config-raw-toggle");
expect(revealButton).toBeTruthy();
revealButton?.click();