mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:50:46 +00:00
test: merge config view browser checks
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user