返回 DeepSeek-Reasonix
provider-models-editor.test.tsx
根目录 / desktop / frontend / src / __tests__ / provider-models-editor.test.tsx
1 import assert from "node:assert/strict";
2 import { JSDOM } from "jsdom";
3 import { applyModelDraft, modelDraftError } from "../lib/providerModelDraft";
4 import { providerVisionModelsForView } from "../lib/providerVisionCapability";
5 import { modelCapabilityForModel } from "../lib/providerImageInput";
6 import type { ProviderView, ProviderModelCapabilityView } from "../lib/types";
7
8 const dom = new JSDOM('<!doctype html><div id="root"></div>', { url: "http://localhost", pretendToBeVisual: true });
9 Object.assign(globalThis, { window: dom.window, document: dom.window.document, HTMLElement: dom.window.HTMLElement, Node: dom.window.Node, localStorage: dom.window.localStorage, IS_REACT_ACT_ENVIRONMENT: true });
10 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
11 const { default: React, act } = await import("react");
12 const { createRoot } = await import("react-dom/client");
13 const { LocaleProvider } = await import("../lib/i18n");
14 const { ProviderModelsEditor } = await import("../components/ProviderModelsEditor");
15 const root = createRoot(document.getElementById("root")!);
16 const override = { model: "existing", contextWindow: 32768, maxOutputTokens: 4096, vision: null, reasoningProtocol: "kimi", supportedEfforts: ["high"], defaultEffort: "high" };
17 const auto = { model: "renamed", context: "", output: "-1", vision: "auto" as const };
18 const result = applyModelDraft([override], auto, "existing")[0];
19 assert.equal(result.reasoningProtocol, "kimi");
20 assert.deepEqual(result.supportedEfforts, ["high"]);
21 assert.equal(result.contextWindow, 0);
22 assert.equal(result.maxOutputTokens, -1);
23 assert.equal(result.vision, null);
24 assert.equal(modelDraftError({ ...auto, model: "existing" }, ["existing"]), "duplicate");
25 assert.equal(modelDraftError({ ...auto, model: "Existing" }, ["existing"]), null);
26 assert.equal(modelDraftError({ ...auto, model: "bad model" }, []), "syntax");
27 const distinct = applyModelDraft([override], {...auto, model:"Existing", vision:"yes"});
28 assert.equal(distinct.length, 2, "case-distinct add must not replace another override");
29 assert.deepEqual(distinct[0], override);
30 assert.equal(distinct[1].reasoningProtocol, "", "new ID must not inherit another model's protocol");
31 assert.deepEqual(providerVisionModelsForView({models:["existing","Existing"],visionModels:[],modelOverrides:[distinct[1]],modelCapabilities:[]}), ["Existing"]);
32 assert.equal(modelCapabilityForModel([{model:"Existing",state:"supported"} as ProviderModelCapabilityView],"existing"), undefined);
33 assert.equal(modelDraftError({ ...auto, context: "1.5" }, []), "context");
34 assert.equal(modelDraftError({ ...auto, output: "0" }, []), "output");
35
36 let provider = { name: "test", kind: "openai", baseUrl: "https://example.test/v1", models: ["existing"], modelOverrides: [override], modelCapabilities: [], visionModels: [], visionCapability: "configurable" } as unknown as ProviderView;
37 let changes = 0;
38 let resolveFetch: ((value: ProviderModelCapabilityView[]) => void) | undefined;
39 let rejectTest: ((reason: Error) => void) | undefined;
40 const render = () => root.render(<LocaleProvider><ProviderModelsEditor provider={provider} disabled={false} canFetch draft
41 onFetch={() => new Promise((resolve) => { resolveFetch = resolve; })}
42 onTest={() => new Promise((_, reject) => { rejectTest = reject; })}
43 onChange={(models, modelOverrides, modelCapabilities) => { changes++; provider = { ...provider, models, modelOverrides, modelCapabilities }; render(); }}
44 /></LocaleProvider>);
45 const click = async (text: string) => {
46 const button = Array.from(document.querySelectorAll("button")).find((b) => b.textContent?.trim() === text || b.getAttribute("aria-label") === text);
47 assert.ok(button, `button ${text}`);
48 await act(async () => { button.focus(); button.click(); });
49 };
50 const fill = async (input: HTMLInputElement, value: string) => {
51 await act(async () => {
52 Object.getOwnPropertyDescriptor(dom.window.HTMLInputElement.prototype, "value")!.set!.call(input, value);
53 input.dispatchEvent(new dom.window.Event("input", { bubbles: true }));
54 });
55 };
56 await act(async () => render());
57 await click("Add manually");
58 assert.ok(document.querySelector('[role="dialog"]'));
59 assert.equal(document.activeElement?.tagName, "INPUT");
60 await click("Save to draft");
61 assert.match(document.querySelector('[role="alert"]')!.textContent!, /required/);
62 const modelID = document.querySelector<HTMLInputElement>('[role="dialog"] input')!;
63 await fill(modelID, "existing");
64 await click("Save to draft");
65 assert.match(document.querySelector('[role="alert"]')!.textContent!, /already added/);
66 await fill(modelID, "manual");
67 await click("Save to draft");
68 assert.deepEqual(provider.models, ["existing", "manual"]);
69 assert.equal(provider.modelOverrides?.find((m) => m.model === "manual")?.contextWindow, 0);
70
71 await click("Edit model existing");
72 let parentEscapes = 0;
73 const parentEscape = () => { parentEscapes++; };
74 document.addEventListener("keydown", parentEscape);
75 await act(async () => { document.activeElement?.dispatchEvent(new dom.window.KeyboardEvent("keydown", { key: "Escape", bubbles: true })); });
76 assert.equal(parentEscapes, 0);
77 assert.equal(document.querySelector('[role="dialog"]'), null);
78 assert.equal(document.activeElement?.getAttribute("aria-label"), "Edit model existing");
79 document.removeEventListener("keydown", parentEscape);
80
81 await click("Test model existing");
82 await click("Refresh models");
83 await act(async () => { rejectTest!(new Error("authentication failed")); });
84 assert.match(document.querySelector('[role="status"]')!.textContent!, /authentication failed/);
85 const catalog = ["existing", "remote"].map((model) => ({ model, inputModalities: ["text"], state: "unknown", source: "catalog" }));
86 await act(async () => { resolveFetch!(catalog); });
87 const checkboxes = document.querySelectorAll<HTMLInputElement>('[role="dialog"] input[type="checkbox"]');
88 assert.equal(checkboxes[0].disabled, true);
89 assert.equal(checkboxes[0].checked, true);
90 await act(async () => checkboxes[1].click());
91 await click("Add selected models");
92 assert.deepEqual(provider.models, ["existing", "manual", "remote"]);
93 assert.deepEqual(provider.modelOverrides?.[0], override);
94 assert.equal(changes, 2);
95
96 await click("Refresh models");
97 await act(async () => { provider = { ...provider, baseUrl: "https://other.test/v1" }; render(); });
98 await act(async () => { resolveFetch!(catalog); });
99 assert.equal(document.querySelector('[role="dialog"]'), null, "old endpoint discovery must not reopen a dialog");
100 await act(async () => root.unmount());
101 dom.window.close();
102 console.log("PASS provider model drafts, modal validation/focus/Escape, concurrent probes, add-only discovery, stale results");
103
103 lines Plain Text