返回 DeepSeek-Reasonix
web-search-model-settings.test.tsx
根目录 / desktop / frontend / src / __tests__ / web-search-model-settings.test.tsx
1 import assert from "node:assert/strict";
2 import { JSDOM } from "jsdom";
3 import React, { act } from "react";
4 import { createRoot } from "react-dom/client";
5 import { ModelsSection } from "../components/SettingsPanel";
6 import { LocaleProvider } from "../lib/i18n";
7 import type { AppBindings } from "../lib/bridge";
8 import { baseSettings, installCanvasMock } from "../test-support/settingsTestFixtures";
9 import { installDesktopHostStub } from "./desktopHostStub";
10 const dom = new JSDOM("<!doctype html><html><body><div id=\"root\"></div></body></html>", {
11 pretendToBeVisual: true,
12 url: "http://localhost/",
13 });
14 // React's legacy input-event fallback expects these IE hooks when JSDOM does
15 // not expose native input event support. The custom threshold editor focuses
16 // its input on open, so keep that production behavior testable without noise.
17 Object.defineProperty(dom.window.HTMLElement.prototype, "attachEvent", { configurable: true, value: () => {} });
18 Object.defineProperty(dom.window.HTMLElement.prototype, "detachEvent", { configurable: true, value: () => {} });
19 installCanvasMock(dom.window as unknown as Window);
20 (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
21 globalThis.window = dom.window as unknown as Window & typeof globalThis;
22 globalThis.document = dom.window.document;
23 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
24 globalThis.Node = dom.window.Node;
25 globalThis.HTMLElement = dom.window.HTMLElement;
26 globalThis.Event = dom.window.Event;
27 globalThis.CustomEvent = dom.window.CustomEvent;
28 globalThis.KeyboardEvent = dom.window.KeyboardEvent;
29 globalThis.MouseEvent = dom.window.MouseEvent;
30 globalThis.localStorage = dom.window.localStorage;
31 globalThis.sessionStorage = dom.window.sessionStorage;
32 globalThis.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window);
33 globalThis.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window);
34 window.scrollTo = () => {};
35 window.matchMedia = () => ({ matches: true, addEventListener() {}, removeEventListener() {} }) as unknown as MediaQueryList;
36 Object.defineProperty(dom.window.HTMLElement.prototype, "getBoundingClientRect", { configurable: true, value: () => ({x:10,y:10,top:10,left:10,right:310,bottom:54,width:300,height:44,toJSON(){return {};}}) });
37 localStorage.clear();
38
39
40 let selected = "";
41 installDesktopHostStub({ApplyModelSettings:async(change)=>{
42 assert.equal(change.kind,"preference");
43 if(change.kind === "preference" && change.field === "search") selected=change.ref;
44 return {requestId:change.requestId,persisted:true,revision:"saved",application:"not_required",targets:[],issues:[],appliedCatalogs:[]};
45 }} as Partial<AppBindings> as AppBindings);
46 const host=document.getElementById("root")!;
47 const root=createRoot(host);
48 const settings=baseSettings();
49 settings.webSearchModel="auto";
50 settings.webSearchModels=[];
51 const render=async()=>{await act(async()=>{root.render(<LocaleProvider><ModelsSection s={{...settings}} busy={false} subtab="usage" apply={async fn=>{await fn();return true;}} /></LocaleProvider>);});};
52 await render();
53 const picker=()=>host.querySelector<HTMLButtonElement>('button[aria-label="Web search"],button[aria-label="网页搜索"]')!;
54 assert.ok(picker(),"search picker rendered");
55 assert.equal(picker().disabled,false,"auto remains selectable without candidates");
56 settings.webSearchModel="removed/org/model";
57 settings.webSearchModelStatus="invalid";
58 settings.webSearchModelReason="Search connection is not added";
59 await render();
60 assert.ok(picker().textContent?.includes("removed/org/model"),"invalid ref retained");
61 assert.ok(host.querySelector('[role="status"]'),"invalid assignment explained");
62 await act(async()=>picker().click());
63 const auto=Array.from(document.querySelectorAll<HTMLElement>('[role="option"]')).find(el=>/auto|自动/i.test(el.textContent??""));
64 assert.ok(auto,"automatic option available");
65 await act(async()=>auto.click());
66 assert.equal(selected,"auto","automatic choice reaches binding");
67 settings.webSearchModelOverridden=true;
68 settings.effectiveWebSearchModel="project/m";
69 settings.webSearchModelStatus="ready";
70 await render();
71 assert.ok(host.textContent?.includes("project/m"),"project override visible");
72 assert.ok(host.querySelector('[role="status"]'),"stale global assignment remains visible under a valid project override");
73 const helpButtons = host.querySelectorAll<HTMLButtonElement>(".model-setting-help");
74 assert.equal(helpButtons.length, 6, "all model assignments and subagent effort have help");
75 assert.equal(document.querySelector('[role="tooltip"]'), null, "help starts hidden");
76 const searchHelp = Array.from(helpButtons).find(button => /Web search|网页搜索/.test(button.getAttribute("aria-label") ?? ""))!;
77 await act(async () => searchHelp.focus());
78 assert.equal(searchHelp.getAttribute("aria-expanded"), "true", "keyboard focus opens help");
79 assert.ok(document.querySelector('[role="tooltip"]')?.textContent?.includes("Running tasks"), "search activation explained in help");
80 await act(async () => window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" })));
81 assert.equal(searchHelp.getAttribute("aria-expanded"), "false", "Escape closes help");
82 await act(async () => searchHelp.click());
83 assert.equal(searchHelp.getAttribute("aria-expanded"), "true", "click pins help");
84 await act(async () => document.body.click());
85 assert.equal(searchHelp.getAttribute("aria-expanded"), "false", "outside click closes help");
86 await act(async()=>root.unmount());
87 console.log("web search model settings passed");
88
88 lines Plain Text