| 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 |