75 lines
4.7 KiB
TypeScript
75 lines
4.7 KiB
TypeScript
import React, { act } from "react";
|
|
(globalThis as Record<string, unknown>).React = React;
|
|
import assert from "node:assert/strict";
|
|
import { afterEach, it } from "node:test";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { parseHTML } from "linkedom";
|
|
import { AgentSection } from "../components/settings/agent-section.tsx";
|
|
import { I18nProvider } from "../components/i18n.tsx";
|
|
import type { ModelCatalogEntry, ModelsSettings } from "../src/core/index.ts";
|
|
|
|
const dom = parseHTML("<html><body></body></html>");
|
|
Object.assign(globalThis, { window: dom.window, document: dom.document, Element: dom.window.Element, HTMLElement: dom.window.HTMLElement, CustomEvent: dom.window.CustomEvent, IS_REACT_ACT_ENVIRONMENT: true });
|
|
const originalFetch = globalThis.fetch;
|
|
const chatgpt = { provider: "chatgpt", id: "gpt-5" };
|
|
const api = { provider: "openai", id: "gpt-5" };
|
|
const fixture: ModelCatalogEntry = { ...chatgpt, name: "Offline model", reasoning: true, input: ["text", "image"], supportedLevels: ["low", "high"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 8192, maxTokens: 2048 };
|
|
let root: Root | null = null;
|
|
let host: HTMLDivElement;
|
|
let writes: ModelsSettings[];
|
|
let saved: ModelsSettings;
|
|
let waitForSave: (() => Promise<void>) | undefined;
|
|
let failSave = false;
|
|
function button(slot: string, kind = "fast"): HTMLButtonElement {
|
|
const found = document.querySelector<HTMLButtonElement>(`[data-testid="slot-${kind}-${slot}"]`);
|
|
assert(found); return found;
|
|
}
|
|
async function click(target: HTMLButtonElement) { await act(async () => target.click()); }
|
|
async function mount() {
|
|
writes = []; waitForSave = undefined; failSave = false;
|
|
saved = { strong: { model: chatgpt, thinkingLevel: "high" }, cheap: { model: chatgpt, thinkingLevel: "low" }, vision: { model: api } };
|
|
const responses: Record<string, unknown> = {
|
|
"/api/models": { models: [fixture, { ...fixture, provider: "openai" }], slots: saved },
|
|
"/api/search": { available: false, engine: null, reason: "No search engine is configured." },
|
|
"/api/keys": { keys: [] }, "/api/settings/prompts": { prober: [], teacher: [] },
|
|
};
|
|
globalThis.fetch = async input => { const name = String(input); assert(name in responses); return Response.json(responses[name]); };
|
|
host = document.createElement("div"); document.body.appendChild(host); root = createRoot(host);
|
|
function Harness() {
|
|
const [models, setModels] = React.useState(saved);
|
|
return <I18nProvider initialGeneral={{ language: "ru" }}><AgentSection agent={undefined} models={models} graph={undefined} compaction={undefined} onSectionChange={() => {}} onCompactionChange={() => {}} onModelsChange={async next => {
|
|
writes.push(next); await waitForSave?.();
|
|
if (failSave) throw new Error("offline save failure");
|
|
saved = next; setModels(next);
|
|
}} /></I18nProvider>;
|
|
}
|
|
await act(async () => { root!.render(<Harness />); });
|
|
}
|
|
afterEach(async () => { await act(async () => root?.unmount()); root = null; host?.remove(); globalThis.fetch = originalFetch; });
|
|
|
|
it("Fast controls wait for stored truth, lock related edits while saving, and preserve other tiers", async () => {
|
|
await mount();
|
|
assert.equal(button("strong").getAttribute("aria-pressed"), "false");
|
|
assert.equal(button("vision").disabled, true);
|
|
assert.equal(button("strong").textContent, "Быстро");
|
|
let release!: () => void;
|
|
const pending = new Promise<void>(resolve => { release = resolve; }); waitForSave = () => pending;
|
|
await click(button("strong"));
|
|
assert.equal(writes.length, 1);
|
|
for (const slot of ["strong", "cheap"]) for (const kind of ["fast", "model", "thinking"]) assert.equal(button(slot, kind).disabled, true);
|
|
assert.equal(button("strong").getAttribute("aria-pressed"), "false", "unconfirmed billing choices must not appear saved");
|
|
await act(async () => { release(); }); waitForSave = undefined;
|
|
assert.equal(button("strong").getAttribute("aria-pressed"), "true");
|
|
await click(button("cheap"));
|
|
assert.equal(saved.strong?.fastMode, true); assert.equal(saved.cheap?.fastMode, true);
|
|
assert.equal(saved.strong?.thinkingLevel, "high"); assert.equal(saved.vision?.fastMode, undefined);
|
|
});
|
|
it("failed Fast saves retain the previous choice, show a Russian error, and allow retry", async () => {
|
|
await mount(); failSave = true;
|
|
await click(button("strong"));
|
|
assert.equal(button("strong").getAttribute("aria-pressed"), "false"); assert.equal(button("strong").disabled, false);
|
|
assert.equal(document.querySelector('[role="alert"]')?.textContent, "Не удалось сохранить настройки моделей");
|
|
failSave = false; await click(button("strong"));
|
|
assert.equal(button("strong").getAttribute("aria-pressed"), "true"); assert.equal(document.querySelector('[role="alert"]'), null);
|
|
});
|