Skip to content
Source
Sign inSign up
Open History to see the last commit

KeybindingsSettings.browser.test.tsx

146 lines5.2 KB

1import { RegistryProvider } from "@effect/atom-react";
2import { describe, expect, it, vi } from "vite-plus/test";
3import { page, userEvent, type Locator } from "vite-plus/test/browser";
4​
5import { render } from "../../../test/render";
6​
7vi.mock(import("../../state/session"), async (original) => ({
8 ...(await original()),
9 useEnvironmentScope: () => true,
10 useEnvironmentsWithScope: ((environments: ReadonlyArray<{ environmentId: string }>) =>
11 new Set(environments.map(({ environmentId }) => environmentId))) as never,
12 readEnvironmentScope: () => true,
13}));
14vi.mock(import("../../state/use-atom-command"), async (original) => ({
15 ...(await original()),
16 useAtomCommand: (() => vi.fn()) as never,
17}));
18vi.mock(import("./SettingsScopeContext"), async (original) => ({
19 ...(await original()),
20 useSettingsScope: (() => {
21 const environment = {
22 environmentId: "env",
23 label: "Build workstation",
24 serverConfig: { keybindings: [], keybindingsConfigPath: null, availableEditors: [] },
25 };
26 return {
27 scope: { kind: "environment" },
28 environment,
29 environments: [environment],
30 connectedEnvironments: [environment],
31 targets: [],
32 target: null,
33 };
34 }) as never,
35}));
36​
37import { Dialog, DialogPopup, DialogTitle } from "../ui/dialog";
38import { KeybindingsSettingsPanel } from "./KeybindingsSettings";
39​
40// The modal dialog's inert layer swallows pointer events in this harness, so drive the DOM.
41async function activate(locator: Locator, action: "click" | "focus") {
42 await expect.element(locator).toBeVisible();
43 (locator.element() as HTMLElement)[action]();
45​
46const existingField = page.getByRole("textbox", { name: "Keybinding for Sidebar: Toggle" });
47const existingPill = page.getByRole("button", { name: /^Edit shortcut for Sidebar: Toggle:/ });
48const newField = page.getByRole("textbox", { name: "Keybinding for new keybinding" });
49​
50describe("Esc in a shortcut recorder", () => {
51 it.each<{
52 name: string;
53 field: Locator;
54 open: () => Promise<void>;
55 expectReset: () => Promise<void>;
56 }>([
57 {
58 name: "existing binding",
59 field: existingField,
60 open: () => activate(existingPill, "click"),
61 expectReset: async () => {
62 await expect.element(existingField).not.toBeInTheDocument();
63 await expect.element(existingPill).toHaveFocus();
64 await expect.element(page.getByRole("button", { name: "Save" })).not.toBeInTheDocument();
65 },
66 },
67 {
68 name: "new binding",
69 field: newField,
70 open: async () => {
71 await activate(page.getByRole("button", { name: "Add keybinding" }), "click");
72 await activate(newField, "focus");
73 },
74 expectReset: async () => {
75 await expect.element(newField).toHaveValue("");
76 await expect.element(newField).toHaveAttribute("placeholder", "Unassigned");
77 },
78 },
79 ])("leaves the $name field without closing Settings", async ({ field, open, expectReset }) => {
80 const onOpenChange = vi.fn();
81 await using _screen = await render(
82 <Dialog open onOpenChange={onOpenChange}>
83 <DialogPopup>
84 <DialogTitle>Settings</DialogTitle>
85 <KeybindingsSettingsPanel />
86 </DialogPopup>
87 </Dialog>,
88 );
89​
90 await open();
91 await userEvent.keyboard("{Control>}k{/Control}");
92 await expect.element(field).not.toHaveValue("");
93 await userEvent.tab();
94 await activate(field, "focus");
95 await expect.element(field).toHaveAttribute("placeholder", "Press shortcut");
96​
97 await userEvent.keyboard("{Escape}");
98​
99 expect(onOpenChange).not.toHaveBeenCalled();
100 expect(document.activeElement?.matches("[data-keybinding-capture]")).toBe(false);
101 await expectReset();
102​
103 await userEvent.keyboard("{Escape}");
104 expect(onOpenChange).toHaveBeenCalledWith(false, expect.anything());
105 });
106});
107​
108it("Esc in the keybindings search clears the query without closing Settings", async () => {
109 const onOpenChange = vi.fn();
110 await using _screen = await render(
111 <Dialog open onOpenChange={onOpenChange}>
112 <DialogPopup>
113 <DialogTitle>Settings</DialogTitle>
114 <KeybindingsSettingsPanel />
115 </DialogPopup>
116 </Dialog>,
117 );
118 const search = page.getByRole("searchbox", { name: "Search keybindings" });
119 await search.fill("sidebar");
120​
121 await userEvent.keyboard("{Escape}");
122​
123 await expect.element(search).toHaveValue("");
124 expect(onOpenChange).not.toHaveBeenCalled();
125});
126​
127it("re-enables Save when a When editor closes on an expression it could not parse", async () => {
128 await using screen = await render(
129 <RegistryProvider>
130 <KeybindingsSettingsPanel />
131 </RegistryProvider>,
132 );
133 const whenTrigger = screen.getByRole("button", { name: "Edit when clause for Terminal: Toggle" });
134 const expression = screen.getByRole("textbox", { name: "When expression" });
135​
136 await whenTrigger.click();
137 await userEvent.type(expression, "terminalFocus");
138 await userEvent.type(expression, " &&");
139 await expect.element(screen.getByRole("button", { name: "Save" })).toBeDisabled();
140​
141 await userEvent.keyboard("{Escape}");
142 await whenTrigger.click();
143​
144 await expect.element(expression).toHaveValue("terminalFocus");
145 await expect.element(screen.getByRole("button", { name: "Save" })).toBeEnabled();
146});