This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/web/src/help.test.ts
T
Miguel Palhas f0851a62ed
ci / web (push) Successful in 37s
ci / rust (push) Successful in 4m13s
ci / flake (push) Successful in 8m48s
feat(maestro): keyboard shortcut overlay on web
`?` toggles a dialog listing every web binding, mirroring the TUI's help
overlay. A `?` button in the header covers pointer-only clients, and the
welcome screen points at the key.

One registry (SHORTCUTS) drives the rendering, so a binding added without
an entry reads as missing. main.ts and the overlay now share one
isTypingTarget guard instead of two copies.
2026-08-31 14:37:53 +01:00

113 lines
3.5 KiB
TypeScript

// @vitest-environment happy-dom
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
SHORTCUTS,
closeHelp,
installHelpKeybind,
isHelpOpen,
isTypingTarget,
openHelp,
} from "./help.js";
beforeEach(() => {
installHelpKeybind();
});
afterEach(() => {
closeHelp();
document.body.replaceChildren();
});
function press(key: string, init: KeyboardEventInit = {}): void {
window.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, ...init }));
}
function rows(): string[] {
return [...document.querySelectorAll(".help-row")].map((r) =>
[...r.querySelectorAll("kbd")].map((k) => k.textContent).join(" "),
);
}
describe("help overlay", () => {
it("renders every registered shortcut, grouped", () => {
openHelp();
const groups = [...document.querySelectorAll(".help-group-title")].map((h) => h.textContent);
expect(groups).toEqual(SHORTCUTS.map((g) => g.title));
expect(rows()).toEqual(SHORTCUTS.flatMap((g) => g.items.map((s) => s.keys.join(" "))));
expect(document.querySelectorAll(".help-desc")).toHaveLength(
SHORTCUTS.reduce((n, g) => n + g.items.length, 0),
);
});
it("opens on ? and closes on a second ?", () => {
press("?", { shiftKey: true });
expect(isHelpOpen()).toBe(true);
press("?", { shiftKey: true });
expect(isHelpOpen()).toBe(false);
});
it("closes on Escape, on the close button, and on a click outside the panel", () => {
openHelp();
press("Escape");
expect(isHelpOpen()).toBe(false);
openHelp();
(document.querySelector(".help-close") as HTMLButtonElement).click();
expect(isHelpOpen()).toBe(false);
openHelp();
(document.querySelector(".help-overlay") as HTMLElement).dispatchEvent(
new MouseEvent("click", { bubbles: true }),
);
expect(isHelpOpen()).toBe(false);
});
it("stays open for a click inside the panel", () => {
openHelp();
(document.querySelector(".help-panel") as HTMLElement).dispatchEvent(
new MouseEvent("click", { bubbles: true }),
);
expect(isHelpOpen()).toBe(true);
});
it("opening twice leaves one overlay", () => {
openHelp();
openHelp();
expect(document.querySelectorAll(".help-overlay")).toHaveLength(1);
});
it("ignores ? while typing and when a non-shift modifier is held", () => {
const input = document.createElement("input");
document.body.appendChild(input);
input.dispatchEvent(new KeyboardEvent("keydown", { key: "?", bubbles: true, shiftKey: true }));
expect(isHelpOpen()).toBe(false);
press("?", { ctrlKey: true });
expect(isHelpOpen()).toBe(false);
});
it("installing the keybind twice still toggles once", () => {
installHelpKeybind();
press("?", { shiftKey: true });
expect(isHelpOpen()).toBe(true);
expect(document.querySelectorAll(".help-overlay")).toHaveLength(1);
});
});
describe("isTypingTarget", () => {
it("is true for form fields and editable nodes, false otherwise", () => {
const el = (html: string) => {
const d = document.createElement("div");
d.innerHTML = html;
return d.firstElementChild as HTMLElement;
};
expect(isTypingTarget(el("<input>"))).toBe(true);
expect(isTypingTarget(el("<textarea></textarea>"))).toBe(true);
expect(isTypingTarget(el("<select></select>"))).toBe(true);
expect(isTypingTarget(el('<div contenteditable="true"></div>'))).toBe(true);
expect(isTypingTarget(el("<button></button>"))).toBe(false);
expect(isTypingTarget(null)).toBe(false);
});
});