Skip to content
Source
Sign inSign up

ProviderModelPicker.composition.browser.test.tsx

120 lines4.2 KB

1import {
2 ProviderDriverKind,
3 ProviderInstanceId,
4 type ResolvedKeybindingsConfig,
5} from "@t3tools/contracts";
6import { compileResolvedKeybindingsConfig } from "@t3tools/shared/keybindings";
7import { useState } from "react";
8import { expect, it } from "vite-plus/test";
9import { page, userEvent } from "vite-plus/test/browser";
10​
11import { act, render } from "../../../test/render";
12import { deriveProviderInstanceEntries } from "../../providerInstances";
13import { ProviderModelPicker } from "./ProviderModelPicker";
14​
15const instanceId = ProviderInstanceId.make("codex");
16​
17function Picker({ keybindings = [] }: { keybindings?: ResolvedKeybindingsConfig }) {
18 const [model, setModel] = useState("original");
19 return (
20 <>
21 <output aria-label="Selected model">{model}</output>
22 <ProviderModelPicker
23 environmentId={null}
24 activeInstanceId={instanceId}
25 model={model}
26 lockedProvider={null}
27 instanceEntries={deriveProviderInstanceEntries([
28 {
29 instanceId,
30 driver: ProviderDriverKind.make("codex"),
31 enabled: true,
32 installed: true,
33 version: null,
34 status: "ready",
35 auth: { status: "authenticated" },
36 checkedAt: "2026-01-01T00:00:00.000Z",
37 models: [],
38 slashCommands: [],
39 skills: [],
40 },
41 ])}
42 keybindings={keybindings}
43 modelOptionsByInstance={
44 new Map([
45 [
46 instanceId,
47 [
48 { slug: "original", name: "Original model" },
49 { slug: "target", name: "Target model" },
50 ],
51 ],
52 ])
53 }
54 triggerAriaLabel="Choose model"
55 onInstanceModelChange={(_, selected) => setModel(selected)}
56 />
57 </>
58 );
60​
61it("keeps composition keys out of model selection and dismissal", async () => {
62 await using screen = await render(<Picker />);
63 await page.getByRole("button", { name: "Choose model" }).click();
64 const search = page.getByPlaceholder("Search models...");
65 await search.fill("Target");
66 await expect.element(page.getByRole("option", { name: /Target model/ })).toBeVisible();
67​
68 for (const nativeEvent of [{ isComposing: true }, { keyCode: 229 }]) {
69 for (const key of ["Enter", "Escape"]) {
70 const event = new KeyboardEvent("keydown", {
71 key,
72 ...nativeEvent,
73 bubbles: true,
74 cancelable: true,
75 });
76 await act(() => search.element().dispatchEvent(event));
77 expect(event.defaultPrevented).toBe(false);
78 await expect.element(search).toBeVisible();
79 await expect
80 .element(screen.getByRole("status", { name: "Selected model" }))
81 .toHaveTextContent("original");
82 }
83 }
84​
85 await userEvent.keyboard("{Enter}");
86 await expect.element(search).not.toBeInTheDocument();
87 await expect
88 .element(screen.getByRole("status", { name: "Selected model" }))
89 .toHaveTextContent("target");
90 await page.getByRole("button", { name: "Choose model" }).click();
91 await expect.element(search).toBeVisible();
92 await userEvent.keyboard("{Escape}");
93 await expect.element(search).not.toBeInTheDocument();
94});
95​
96it("keeps composing keys from triggering model jump shortcuts", async () => {
97 const keybindings = compileResolvedKeybindingsConfig([
98 { key: "enter", command: "modelPicker.jump.1", when: "modelPickerOpen" },
99 ]);
100 await using screen = await render(<Picker keybindings={keybindings} />);
101 await page.getByRole("button", { name: "Choose model" }).click();
102 const search = page.getByPlaceholder("Search models...");
103 await search.fill("Target");
104 await expect.element(page.getByRole("option", { name: /Target model/ })).toBeVisible();
105​
106 for (const nativeEvent of [{ isComposing: true }, { keyCode: 229 }]) {
107 const event = new KeyboardEvent("keydown", {
108 key: "Enter",
109 ...nativeEvent,
110 bubbles: true,
111 cancelable: true,
112 });
113 await act(() => search.element().dispatchEvent(event));
114 expect(event.defaultPrevented).toBe(false);
115 await expect.element(search).toBeVisible();
116 await expect
117 .element(screen.getByRole("status", { name: "Selected model" }))
118 .toHaveTextContent("original");
119 }
120});