


























@@ -102,86 +102,59 @@ describe("config view", () => {
102102resetRawRevealState();
103103});
104104105-it("allows save when form is unsafe", () => {
105+it("updates save/apply disabled state from form safety and raw dirtiness", () => {
106106const container = document.createElement("div");
107-render(
108-renderConfig({
109- ...baseProps(),
110-schema: {
111-type: "object",
112-properties: {
113-mixed: {
114-anyOf: [{ type: "string" }, { type: "object", properties: {} }],
115-},
107+108+const renderCase = (overrides: Partial<ConfigProps>) =>
109+render(renderConfig({ ...baseProps(), ...overrides }), container);
110+111+renderCase({
112+schema: {
113+type: "object",
114+properties: {
115+mixed: {
116+anyOf: [{ type: "string" }, { type: "object", properties: {} }],
116117},
117118},
118-schemaLoading: false,
119-uiHints: {},
120-formMode: "form",
121-formValue: { mixed: "x" },
122-}),
123-container,
124-);
125-126-const saveButton = Array.from(container.querySelectorAll("button")).find(
127-(btn) => btn.textContent?.trim() === "Save",
128-);
119+},
120+schemaLoading: false,
121+uiHints: {},
122+formMode: "form",
123+formValue: { mixed: "x" },
124+});
125+let { saveButton, applyButton } = findActionButtons(container);
129126expect(saveButton).not.toBeUndefined();
130127expect(saveButton?.disabled).toBe(false);
131-});
132-133-it("disables save when schema is missing", () => {
134-const container = document.createElement("div");
135-render(
136-renderConfig({
137- ...baseProps(),
138-schema: null,
139-formMode: "form",
140-formValue: { gateway: { mode: "local" } },
141-originalValue: {},
142-}),
143-container,
144-);
128+expect(applyButton?.disabled).toBe(false);
145129146-const saveButton = Array.from(container.querySelectorAll("button")).find(
147-(btn) => btn.textContent?.trim() === "Save",
148-);
130+renderCase({
131+schema: null,
132+formMode: "form",
133+formValue: { gateway: { mode: "local" } },
134+originalValue: {},
135+});
136+({ saveButton, applyButton } = findActionButtons(container));
149137expect(saveButton).not.toBeUndefined();
150138expect(saveButton?.disabled).toBe(true);
151-});
152-153-it("disables save and apply when raw is unchanged", () => {
154-const container = document.createElement("div");
155-render(
156-renderConfig({
157- ...baseProps(),
158-formMode: "raw",
159-raw: "{\n}\n",
160-originalRaw: "{\n}\n",
161-}),
162-container,
163-);
139+expect(applyButton?.disabled).toBe(true);
164140165-const { saveButton, applyButton } = findActionButtons(container);
141+renderCase({
142+formMode: "raw",
143+raw: "{\n}\n",
144+originalRaw: "{\n}\n",
145+});
146+({ saveButton, applyButton } = findActionButtons(container));
166147expect(saveButton).not.toBeUndefined();
167148expect(applyButton).not.toBeUndefined();
168149expect(saveButton?.disabled).toBe(true);
169150expect(applyButton?.disabled).toBe(true);
170-});
171-172-it("enables save and apply when raw changes", () => {
173-const container = document.createElement("div");
174-render(
175-renderConfig({
176- ...baseProps(),
177-formMode: "raw",
178-raw: '{\n gateway: { mode: "local" }\n}\n',
179-originalRaw: "{\n}\n",
180-}),
181-container,
182-);
183151184-const { saveButton, applyButton } = findActionButtons(container);
152+renderCase({
153+formMode: "raw",
154+raw: '{\n gateway: { mode: "local" }\n}\n',
155+originalRaw: "{\n}\n",
156+});
157+({ saveButton, applyButton } = findActionButtons(container));
185158expect(saveButton).not.toBeUndefined();
186159expect(applyButton).not.toBeUndefined();
187160expect(saveButton?.disabled).toBe(false);
@@ -245,7 +218,7 @@ describe("config view", () => {
245218expect(onFormModeChange).not.toHaveBeenCalled();
246219});
247220248-it("switches sections from the sidebar", () => {
221+it("renders section tabs and switches sections from the sidebar", () => {
249222const container = document.createElement("div");
250223const onSectionChange = vi.fn();
251224render(
@@ -263,6 +236,13 @@ describe("config view", () => {
263236container,
264237);
265238239+const tabs = Array.from(container.querySelectorAll(".config-top-tabs__tab")).map((tab) =>
240+tab.textContent?.trim(),
241+);
242+expect(tabs).toContain("Settings");
243+expect(tabs).toContain("Agents");
244+expect(tabs).toContain("Gateway");
245+266246const btn = Array.from(container.querySelectorAll("button")).find(
267247(b) => b.textContent?.trim() === "Gateway",
268248);
@@ -329,17 +309,22 @@ describe("config view", () => {
329309expect(content.scrollLeft).toBe(0);
330310});
331311332-it("wires search input to onSearchChange", () => {
312+it("renders and wires the search field controls", () => {
333313const container = document.createElement("div");
334314const onSearchChange = vi.fn();
335315render(
336316renderConfig({
337317 ...baseProps(),
318+searchQuery: "gateway",
338319 onSearchChange,
339320}),
340321container,
341322);
342323324+const icon = container.querySelector<SVGElement>(".config-search__icon");
325+expect(icon).not.toBeNull();
326+expect(icon?.closest(".config-search__input-row")).not.toBeNull();
327+343328const input = container.querySelector(".config-search__input");
344329expect(input).not.toBeNull();
345330if (!input) {
@@ -348,60 +333,15 @@ describe("config view", () => {
348333(input as HTMLInputElement).value = "gateway";
349334input.dispatchEvent(new Event("input", { bubbles: true }));
350335expect(onSearchChange).toHaveBeenCalledWith("gateway");
351-});
352-353-it("renders the top search icon inside the search input row", () => {
354-const container = document.createElement("div");
355-render(renderConfig(baseProps()), container);
356-357-const icon = container.querySelector<SVGElement>(".config-search__icon");
358-expect(icon).not.toBeNull();
359-expect(icon?.closest(".config-search__input-row")).not.toBeNull();
360-});
361-362-it("renders top tabs for root and available sections", () => {
363-const container = document.createElement("div");
364-render(
365-renderConfig({
366- ...baseProps(),
367-schema: {
368-type: "object",
369-properties: {
370-gateway: { type: "object", properties: {} },
371-agents: { type: "object", properties: {} },
372-},
373-},
374-}),
375-container,
376-);
377-378-const tabs = Array.from(container.querySelectorAll(".config-top-tabs__tab")).map((tab) =>
379-tab.textContent?.trim(),
380-);
381-expect(tabs).toContain("Settings");
382-expect(tabs).toContain("Agents");
383-expect(tabs).toContain("Gateway");
384-});
385-386-it("clears the active search query", () => {
387-const container = document.createElement("div");
388-const onSearchChange = vi.fn();
389-render(
390-renderConfig({
391- ...baseProps(),
392-searchQuery: "gateway",
393- onSearchChange,
394-}),
395-container,
396-);
397336398337const clearButton = container.querySelector<HTMLButtonElement>(".config-search__clear");
399338expect(clearButton).toBeTruthy();
400339clearButton?.click();
401340expect(onSearchChange).toHaveBeenCalledWith("");
402341});
403342404-it("keeps sensitive raw config hidden until reveal", () => {
343+it("keeps sensitive raw config hidden until reveal before editing", () => {
344+const onRawChange = vi.fn();
405345const { container } = renderConfigView({
406346formMode: "raw",
407347raw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
@@ -411,28 +351,14 @@ describe("config view", () => {
411351apiKey: "supersecret",
412352},
413353},
354+ onRawChange,
414355});
415356416357const text = normalizedText(container);
417358expect(text).toContain("1 secret redacted");
418359expect(text).toContain("Use the reveal button above to edit the raw config.");
419360expect(text).not.toContain("supersecret");
420361expect(container.querySelector("textarea")).toBeNull();
421-});
422-423-it("reveals sensitive raw config before editing", () => {
424-const onRawChange = vi.fn();
425-const { container } = renderConfigView({
426-formMode: "raw",
427-raw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
428-originalRaw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
429-formValue: {
430-openai: {
431-apiKey: "supersecret",
432-},
433-},
434- onRawChange,
435-});
436362437363const revealButton = container.querySelector<HTMLButtonElement>(".config-raw-toggle");
438364expect(revealButton).toBeTruthy();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。