mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-25 08:02:04 +00:00
234 lines
6.3 KiB
TypeScript
234 lines
6.3 KiB
TypeScript
import { render } from "lit";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { renderConfig } from "./config.ts";
|
|
|
|
describe("config view", () => {
|
|
const baseProps = () => ({
|
|
raw: "{\n}\n",
|
|
originalRaw: "{\n}\n",
|
|
valid: true,
|
|
issues: [],
|
|
loading: false,
|
|
saving: false,
|
|
applying: false,
|
|
updating: false,
|
|
connected: true,
|
|
schema: {
|
|
type: "object",
|
|
properties: {},
|
|
},
|
|
schemaLoading: false,
|
|
uiHints: {},
|
|
formMode: "form" as const,
|
|
formValue: {},
|
|
originalValue: {},
|
|
searchQuery: "",
|
|
activeSection: null,
|
|
activeSubsection: null,
|
|
onRawChange: vi.fn(),
|
|
onFormModeChange: vi.fn(),
|
|
onFormPatch: vi.fn(),
|
|
onSearchChange: vi.fn(),
|
|
onSectionChange: vi.fn(),
|
|
onReload: vi.fn(),
|
|
onSave: vi.fn(),
|
|
onApply: vi.fn(),
|
|
onUpdate: vi.fn(),
|
|
onSubsectionChange: vi.fn(),
|
|
});
|
|
|
|
function findActionButtons(container: HTMLElement): {
|
|
saveButton?: HTMLButtonElement;
|
|
applyButton?: HTMLButtonElement;
|
|
} {
|
|
const buttons = Array.from(container.querySelectorAll("button"));
|
|
return {
|
|
saveButton: buttons.find((btn) => btn.textContent?.trim() === "Save"),
|
|
applyButton: buttons.find((btn) => btn.textContent?.trim() === "Apply"),
|
|
};
|
|
}
|
|
|
|
it("allows save when form is unsafe", () => {
|
|
const container = document.createElement("div");
|
|
render(
|
|
renderConfig({
|
|
...baseProps(),
|
|
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",
|
|
);
|
|
expect(saveButton).not.toBeUndefined();
|
|
expect(saveButton?.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",
|
|
);
|
|
expect(saveButton).not.toBeUndefined();
|
|
expect(saveButton?.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);
|
|
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);
|
|
expect(saveButton).not.toBeUndefined();
|
|
expect(applyButton).not.toBeUndefined();
|
|
expect(saveButton?.disabled).toBe(false);
|
|
expect(applyButton?.disabled).toBe(false);
|
|
});
|
|
|
|
it("switches mode via the sidebar toggle", () => {
|
|
const container = document.createElement("div");
|
|
const onFormModeChange = vi.fn();
|
|
render(
|
|
renderConfig({
|
|
...baseProps(),
|
|
onFormModeChange,
|
|
}),
|
|
container,
|
|
);
|
|
|
|
const btn = Array.from(container.querySelectorAll("button")).find(
|
|
(b) => b.textContent?.trim() === "Raw",
|
|
);
|
|
expect(btn).toBeTruthy();
|
|
btn?.click();
|
|
expect(onFormModeChange).toHaveBeenCalledWith("raw");
|
|
});
|
|
|
|
it("switches sections from the sidebar", () => {
|
|
const container = document.createElement("div");
|
|
const onSectionChange = vi.fn();
|
|
render(
|
|
renderConfig({
|
|
...baseProps(),
|
|
onSectionChange,
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
gateway: { type: "object", properties: {} },
|
|
agents: { type: "object", properties: {} },
|
|
},
|
|
},
|
|
}),
|
|
container,
|
|
);
|
|
|
|
const btn = Array.from(container.querySelectorAll("button")).find(
|
|
(b) => b.textContent?.trim() === "Gateway",
|
|
);
|
|
expect(btn).toBeTruthy();
|
|
btn?.click();
|
|
expect(onSectionChange).toHaveBeenCalledWith("gateway");
|
|
});
|
|
|
|
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("shows all tag options in compact tag picker", () => {
|
|
const container = document.createElement("div");
|
|
render(renderConfig(baseProps()), container);
|
|
|
|
const options = Array.from(container.querySelectorAll(".config-search__tag-option")).map(
|
|
(option) => option.textContent?.trim(),
|
|
);
|
|
expect(options).toContain("tag:security");
|
|
expect(options).toContain("tag:advanced");
|
|
expect(options).toHaveLength(15);
|
|
});
|
|
|
|
it("updates search query when toggling a tag option", () => {
|
|
const container = document.createElement("div");
|
|
const onSearchChange = vi.fn();
|
|
render(
|
|
renderConfig({
|
|
...baseProps(),
|
|
onSearchChange,
|
|
}),
|
|
container,
|
|
);
|
|
|
|
const option = container.querySelector<HTMLButtonElement>(
|
|
'.config-search__tag-option[data-tag="security"]',
|
|
);
|
|
expect(option).toBeTruthy();
|
|
option?.click();
|
|
expect(onSearchChange).toHaveBeenCalledWith("tag:security");
|
|
});
|
|
});
|