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 ba02b11f13 feat(maestro): serve the configured leader to the web client
The TUI read its leader from `[tui].leader`; the web used `DEFAULT_LEADER`,
a module constant. Rebinding one left the other on `ctrl-a`, so the two
escape hatches hanging off the leader (`leader ctrl-k`, `leader leader`)
moved on one client and not the other.

`GET /api/info` now carries `leader`. The web binds that when it arrives and
rebuilds the leader-owning `CommandSet`, so the `?` overlay and the command
bar render the configured chord; an absent or empty field — an older daemon,
or an unreachable `/api/info` — leaves it on `ctrl-a`.

Chose the existing `[tui].leader` served as-is over a new shared key: one
knob, no precedence rules between two spellings, and no migration for
operators who already set it. The name is a wart — it is the leader for every
client now, not the TUI's — and `docs/COMMAND-BAR.md` and `docs/INSTALL.md`
say so.
2026-09-03 18:33:01 +01:00

128 lines
4.2 KiB
TypeScript

// @vitest-environment happy-dom
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { defineCommands, keyCaps, publishCommands, publishedCommands } from "./commands.js";
import { closeHelp, groupedCommands, installHelpKeybind, isHelpOpen, 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 exactly the commands the registry has bound, grouped", () => {
openHelp();
const groups = groupedCommands();
expect([...document.querySelectorAll(".help-group-title")].map((h) => h.textContent)).toEqual(
groups.map((g) => g.title),
);
expect(rows()).toEqual(
groups.flatMap((g) => g.items.map((c) => keyCaps(c.keys[0]).join(" "))),
);
expect([...document.querySelectorAll(".help-desc")].map((d) => d.textContent)).toEqual(
groups.flatMap((g) => g.items.map((c) => c.label)),
);
});
it("names each row by its command id, so a row can't drift from its command", () => {
openHelp();
expect([...document.querySelectorAll<HTMLElement>(".help-row")].map((r) => r.dataset.commandId))
.toEqual(publishedCommands().map((c) => c.id));
});
// #159: the leader comes from the daemon, so the overlay must render the
// configured chord — a hardcoded `Ctrl A` is the visible half of the bug.
it("renders the configured leader's chord, not a hardcoded Ctrl A", () => {
const bound = defineCommands(
{ "pane.literalCtrlK": () => {}, "pane.literalLeader": () => {} },
"ctrl-b",
);
publishCommands(bound);
try {
openHelp();
const keysOf = (id: string) =>
[
...(document.querySelector(`.help-row[data-command-id="${id}"]`) as HTMLElement)
.querySelectorAll("kbd"),
]
.map((k) => k.textContent)
.join(" ");
expect(keysOf("pane.literalCtrlK")).toBe("Ctrl B then Ctrl K");
expect(keysOf("pane.literalLeader")).toBe("Ctrl B then Ctrl B");
} finally {
// Leave the registry as the rest of the suite expects it.
publishCommands(
defineCommands({ "pane.literalCtrlK": () => {}, "pane.literalLeader": () => {} }),
);
}
});
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);
});
});