Closes #250: kill confirmation/force-kill (dialogs/KillDialog), the ? shortcuts overlay (dialogs/HelpOverlay), the general-purpose right-click menu (common/ContextMenu, closes on scroll/resize unlike the sidebar's own), the mobile drawer with swipe support (layout/Drawer), and the visual-viewport hook (hooks/useViewport). Each is a standalone, tested port of its vanilla-DOM source; wiring into the shell is a later issue.
83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { keyCaps } from "../../commands.js";
|
|
import { groupedCommands } from "../../help.js";
|
|
import { HelpOverlay } from "./HelpOverlay.js";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
function press(key: string, init: KeyboardEventInit = {}): void {
|
|
fireEvent.keyDown(window, { key, ...init });
|
|
}
|
|
|
|
describe("HelpOverlay", () => {
|
|
it("opens on ? and closes on a second ?", () => {
|
|
render(<HelpOverlay />);
|
|
expect(screen.queryByRole("dialog")).toBeNull();
|
|
press("?", { shiftKey: true });
|
|
expect(screen.queryByRole("dialog")).not.toBeNull();
|
|
press("?", { shiftKey: true });
|
|
expect(screen.queryByRole("dialog")).toBeNull();
|
|
});
|
|
|
|
it("ignores ? while typing and when a non-shift modifier is held", () => {
|
|
render(<HelpOverlay />);
|
|
const input = document.createElement("input");
|
|
document.body.appendChild(input);
|
|
fireEvent.keyDown(input, { key: "?", shiftKey: true });
|
|
expect(screen.queryByRole("dialog")).toBeNull();
|
|
|
|
press("?", { ctrlKey: true });
|
|
expect(screen.queryByRole("dialog")).toBeNull();
|
|
input.remove();
|
|
});
|
|
|
|
it("closes on Escape, on the close button, and on a click outside the panel", () => {
|
|
render(<HelpOverlay />);
|
|
|
|
press("?", { shiftKey: true });
|
|
press("Escape");
|
|
expect(screen.queryByRole("dialog")).toBeNull();
|
|
|
|
press("?", { shiftKey: true });
|
|
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
|
expect(screen.queryByRole("dialog")).toBeNull();
|
|
|
|
press("?", { shiftKey: true });
|
|
fireEvent.click(screen.getByRole("dialog"));
|
|
expect(screen.queryByRole("dialog")).toBeNull();
|
|
});
|
|
|
|
it("stays open for a click inside the panel", () => {
|
|
render(<HelpOverlay />);
|
|
press("?", { shiftKey: true });
|
|
fireEvent.click(document.querySelector(".help-panel") as HTMLElement);
|
|
expect(screen.queryByRole("dialog")).not.toBeNull();
|
|
});
|
|
|
|
it("renders exactly the commands the registry has bound, grouped", () => {
|
|
render(<HelpOverlay />);
|
|
press("?", { shiftKey: true });
|
|
|
|
const groups = groupedCommands();
|
|
expect([...document.querySelectorAll(".help-group-title")].map((h) => h.textContent)).toEqual(
|
|
groups.map((g) => g.title),
|
|
);
|
|
expect(
|
|
[...document.querySelectorAll<HTMLElement>(".help-row")].map((r) => r.dataset.commandId),
|
|
).toEqual(groups.flatMap((g) => g.items.map((c) => c.id)));
|
|
expect([...document.querySelectorAll(".help-desc")].map((d) => d.textContent)).toEqual(
|
|
groups.flatMap((g) => g.items.map((c) => c.label)),
|
|
);
|
|
expect(
|
|
[...document.querySelectorAll(".help-row")].map((r) =>
|
|
[...r.querySelectorAll("kbd")].map((k) => k.textContent).join(" "),
|
|
),
|
|
).toEqual(groups.flatMap((g) => g.items.map((c) => keyCaps(c.keys[0]).join(" "))));
|
|
});
|
|
});
|