返回 DeepSeek-Reasonix
settings-select.test.tsx
根目录 / desktop / frontend / src / __tests__ / settings-select.test.tsx
1 import assert from "node:assert/strict";
2 import { JSDOM } from "jsdom";
3 import React, { act } from "react";
4
5 const dom = new JSDOM('<div id="root"></div><button id="outside">Outside</button>', { url: "http://localhost", pretendToBeVisual: true });
6 Object.assign(globalThis, { window: dom.window, document: dom.window.document, Node: dom.window.Node,
7 HTMLElement: dom.window.HTMLElement, IS_REACT_ACT_ENVIRONMENT: true });
8 window.matchMedia = (() => ({ matches: true, addEventListener() {}, removeEventListener() {} })) as any;
9 dom.window.HTMLElement.prototype.scrollIntoView = function () {};
10 const { createRoot } = await import("react-dom/client");
11 const { SettingsSelect } = await import("../components/SettingsSelect");
12 const root = createRoot(document.getElementById("root")!);
13 const changes: string[] = [];
14 const options = [
15 { value: "one/shared", label: "shared", group: "one", groupLabel: "Connection one" },
16 { value: "unsupported", label: "Unsupported", disabled: true },
17 { value: "two/shared", label: "shared", group: "two", groupLabel: "Connection two" },
18 ];
19 async function render(search = false, disabled = false) {
20 await act(async () => root.render(<SettingsSelect aria-label="Model" name="model" value="one/shared"
21 options={options} disabled={disabled} searchPlaceholder={search ? "Search models" : undefined}
22 emptyLabel="No matches" onValueChange={value => changes.push(value)} />));
23 }
24 const trigger = () => document.querySelector<HTMLButtonElement>(".settings-select")!;
25 const list = () => document.querySelector<HTMLElement>('[role="listbox"]')!;
26 async function key(node: HTMLElement, value: string) {
27 await act(async () => node.dispatchEvent(new dom.window.KeyboardEvent("keydown", {key: value, bubbles: true, cancelable: true})));
28 }
29 await render();
30 await key(trigger(), "ArrowDown");
31 assert.equal(document.activeElement, list(), "plain selector opens with keyboard focus in its list");
32 await key(list(), "ArrowDown");
33 assert.equal(document.getElementById(list().getAttribute("aria-activedescendant")!)?.dataset.value, "two/shared", "keyboard navigation skips disabled options");
34 await key(list(), "Enter");
35 assert.deepEqual(changes, ["two/shared"], "identical labels preserve connection identity");
36 assert.equal(document.activeElement, trigger(), "selection returns focus to the trigger");
37 await act(async () => trigger().click());
38 await key(list(), "Escape");
39 assert.equal(trigger().getAttribute("aria-expanded"), "false");
40 assert.equal(changes.length, 1, "escape does not save");
41 await act(async () => trigger().click());
42 await act(async () => document.getElementById("outside")!.click());
43 assert.equal(trigger().getAttribute("aria-expanded"), "false", "outside click closes menu");
44 await render(true);
45 await act(async () => trigger().click());
46 const search = document.querySelector<HTMLInputElement>('[role="combobox"]')!;
47 async function searchFor(value: string) {
48 await act(async () => {
49 Object.getOwnPropertyDescriptor(dom.window.HTMLInputElement.prototype, "value")!.set!.call(search, value);
50 search.dispatchEvent(new dom.window.Event("input", { bubbles: true }));
51 });
52 }
53 const beforeComposition = changes.length;
54 for (const key of ["ArrowDown", "Enter", "Escape"]) {
55 const event = new dom.window.KeyboardEvent("keydown", { key, isComposing: true, bubbles: true, cancelable: true });
56 await act(async () => { search.dispatchEvent(event); });
57 assert.equal(event.defaultPrevented, false, "IME owns candidate keys");
58 }
59 assert.equal(trigger().getAttribute("aria-expanded"), "true", "IME confirmation/cancellation keeps the dropdown open");
60 assert.equal(document.getElementById(search.getAttribute("aria-activedescendant")!)?.dataset.value, "one/shared", "IME arrows do not change the active option");
61 await key(search, "ArrowDown");
62 await act(async () => { search.dispatchEvent(new dom.window.KeyboardEvent("keydown", { key: "Enter", keyCode: 229, bubbles: true, cancelable: true })); });
63 assert.equal(changes.length, beforeComposition, "WebKit IME confirmation does not select a model");
64 assert.equal(trigger().getAttribute("aria-expanded"), "true", "WebKit confirmation keeps the dropdown open");
65 await searchFor("Connection two");
66 assert.equal(document.querySelectorAll('[role="option"]').length, 1, "search matches connection labels");
67 await key(search, "Enter");
68 assert.equal(changes.at(-1), "two/shared");
69 await act(async () => trigger().click());
70 assert.equal(document.querySelector<HTMLInputElement>('[role="combobox"]')!.value, "", "reopening resets search");
71 await key(document.querySelector<HTMLElement>('[role="combobox"]')!, "Escape");
72 await render(false, true);
73 await act(async () => trigger().click());
74 assert.equal(trigger().getAttribute("aria-expanded"), "false", "disabled selector cannot open");
75 assert.equal(document.querySelector<HTMLInputElement>('input[name="model"]')!.disabled, true);
76 await act(async () => root.unmount());
77 console.log("PASS shared settings select: keyboard, disabled, search, connection identity, dismiss and focus");
78
78 lines Plain Text