169 lines
6.8 KiB
TypeScript
169 lines
6.8 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
import { test, expect } from "../../app/e2e/support/fixtures";
|
|
import { gotoAppShell, openSettings } from "../../app/e2e/support/helpers/app";
|
|
import { openSettingsSection } from "../../app/e2e/support/helpers/settings";
|
|
|
|
// Settings > Keyboard Shortcuts is desktop-only (`desktopOnly` in
|
|
// settings-screen.tsx), and the gate reads `getIsElectronRuntime()`, which only
|
|
// checks for `window.paseoDesktop` -- so this belongs in the desktop suite even
|
|
// though no `.electron.*` module sits in the surface's import path.
|
|
const SHORTCUTS_ROW = "show-shortcuts";
|
|
|
|
/**
|
|
* The smallest bridge that makes the app believe it is Electron. The built-in
|
|
* daemon is left unmanaged so the app talks to the E2E daemon instead of trying
|
|
* to start one of its own.
|
|
*/
|
|
async function installDesktopBridge(page: Page): Promise<void> {
|
|
await page.addInitScript(() => {
|
|
window.paseoDesktop = {
|
|
platform: "darwin",
|
|
events: { on: () => () => {} },
|
|
invoke: async (command: string) => {
|
|
if (command === "get_desktop_settings") {
|
|
return {
|
|
releaseChannel: "stable",
|
|
daemon: { manageBuiltInDaemon: false, keepRunningAfterQuit: true },
|
|
};
|
|
}
|
|
return null;
|
|
},
|
|
};
|
|
});
|
|
}
|
|
|
|
async function openShortcutsSettings(page: Page) {
|
|
await installDesktopBridge(page);
|
|
await gotoAppShell(page);
|
|
await openSettings(page);
|
|
await openSettingsSection(page, "shortcuts");
|
|
await expect(page.getByText("Show keyboard shortcuts", { exact: true })).toBeVisible({
|
|
timeout: 30_000,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Every row action lives behind the row's actions menu, so which actions a row
|
|
* offers can only be asserted while that menu is open.
|
|
*/
|
|
async function openRowMenu(page: Page) {
|
|
await page.getByTestId(`shortcut-actions-${SHORTCUTS_ROW}`).click();
|
|
await expect(page.getByTestId(`shortcut-bind-${SHORTCUTS_ROW}`)).toBeVisible();
|
|
}
|
|
|
|
/** Reachable from the sidebar even when the cheat sheet's own shortcut is gone. */
|
|
async function openCheatSheet(page: Page) {
|
|
await gotoAppShell(page);
|
|
await page.getByTestId("sidebar-help").click();
|
|
await expect(page.getByTestId("sidebar-help-menu")).toBeVisible();
|
|
await page.getByTestId("sidebar-help-shortcuts").click();
|
|
const dialog = page.getByTestId("keyboard-shortcuts-dialog");
|
|
await expect(dialog).toBeVisible({ timeout: 10_000 });
|
|
return dialog;
|
|
}
|
|
|
|
async function closeRowMenu(page: Page) {
|
|
await page.keyboard.press("Escape");
|
|
await expect(page.getByTestId(`shortcut-bind-${SHORTCUTS_ROW}`)).toHaveCount(0);
|
|
}
|
|
|
|
test("unassigning a shortcut leaves it inert until it is reset", async ({ page }) => {
|
|
await openShortcutsSettings(page);
|
|
|
|
const clear = page.getByTestId(`shortcut-clear-${SHORTCUTS_ROW}`);
|
|
const reset = page.getByTestId(`shortcut-reset-${SHORTCUTS_ROW}`);
|
|
const bind = page.getByTestId(`shortcut-bind-${SHORTCUTS_ROW}`);
|
|
const notSet = page.getByText("Not set", { exact: true });
|
|
const dialog = page.getByTestId("keyboard-shortcuts-dialog");
|
|
|
|
// The shortcut fires before it is cleared, so the assertion after clearing
|
|
// measures the change rather than a shortcut that never worked.
|
|
await page.keyboard.press("Shift+?");
|
|
await expect(dialog).toBeVisible({ timeout: 10_000 });
|
|
await page.keyboard.press("Escape");
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
await openRowMenu(page);
|
|
await expect(clear).toBeVisible();
|
|
await expect(reset).toHaveCount(0);
|
|
await expect(bind).toHaveText("Rebind");
|
|
await clear.click();
|
|
|
|
await expect(notSet).toBeVisible();
|
|
await openRowMenu(page);
|
|
await expect(clear).toHaveCount(0);
|
|
await expect(reset).toBeVisible();
|
|
// Nothing is bound now, so the item stops offering to *re*-bind.
|
|
await expect(bind).toHaveText("Bind");
|
|
await closeRowMenu(page);
|
|
|
|
await page.keyboard.press("Shift+?");
|
|
await expect(dialog).not.toBeVisible({ timeout: 5_000 });
|
|
|
|
// The unassignment has to survive a restart, or "cleared" is only a UI state.
|
|
// A reload lands back on the app shell, so Settings has to be reopened before
|
|
// the section is reachable.
|
|
await page.reload();
|
|
await openSettings(page);
|
|
await openSettingsSection(page, "shortcuts");
|
|
await expect(notSet).toBeVisible({ timeout: 30_000 });
|
|
await page.keyboard.press("Shift+?");
|
|
await expect(dialog).not.toBeVisible({ timeout: 5_000 });
|
|
|
|
await openRowMenu(page);
|
|
await page.getByTestId(`shortcut-reset-${SHORTCUTS_ROW}`).click();
|
|
await expect(notSet).toHaveCount(0);
|
|
await openRowMenu(page);
|
|
await expect(page.getByTestId(`shortcut-clear-${SHORTCUTS_ROW}`)).toBeVisible();
|
|
await expect(page.getByTestId(`shortcut-bind-${SHORTCUTS_ROW}`)).toHaveText("Rebind");
|
|
await closeRowMenu(page);
|
|
|
|
await page.keyboard.press("Shift+?");
|
|
await expect(dialog).toBeVisible({ timeout: 10_000 });
|
|
await page.keyboard.press("Escape");
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test("an unassigned shortcut lists no keys in the shortcuts cheat sheet", async ({ page }) => {
|
|
await openShortcutsSettings(page);
|
|
|
|
await openRowMenu(page);
|
|
await page.getByTestId(`shortcut-clear-${SHORTCUTS_ROW}`).click();
|
|
await expect(page.getByText("Not set", { exact: true })).toBeVisible();
|
|
|
|
const dialog = await openCheatSheet(page);
|
|
|
|
// Addressed by testID rather than by filtering on its own text: an anchored
|
|
// `hasText` matches only the innermost node holding exactly that label, so a
|
|
// row-scoped assertion about anything *else* in the row finds nothing.
|
|
const row = dialog.getByTestId(`shortcut-help-row-${SHORTCUTS_ROW}`);
|
|
await expect(row).toBeVisible();
|
|
// No badge pill, blank or otherwise, for a shortcut with no keys.
|
|
await expect(dialog.getByText("?", { exact: true })).toHaveCount(0);
|
|
// It says so, rather than leaving a silent gap where the keys were. Same words
|
|
// the settings row uses for the same state.
|
|
await expect(row.getByText("Not set", { exact: true })).toBeVisible();
|
|
});
|
|
|
|
test("a rebound shortcut lists its new keys in the shortcuts cheat sheet", async ({ page }) => {
|
|
await openShortcutsSettings(page);
|
|
|
|
const defaultKeys = page.getByText("?", { exact: true });
|
|
await expect(defaultKeys).toBeVisible();
|
|
|
|
await page.getByTestId(`shortcut-actions-${SHORTCUTS_ROW}`).click();
|
|
await page.getByTestId(`shortcut-bind-${SHORTCUTS_ROW}`).click();
|
|
await page.keyboard.press("Alt+Shift+K");
|
|
await page.getByText("Done", { exact: true }).click();
|
|
|
|
const reboundKeys = page.getByText("⌥⇧K", { exact: true });
|
|
await expect(reboundKeys).toBeVisible();
|
|
await expect(defaultKeys).toHaveCount(0);
|
|
|
|
const dialog = await openCheatSheet(page);
|
|
const row = dialog.getByTestId(`shortcut-help-row-${SHORTCUTS_ROW}`);
|
|
await expect(row.getByText("⌥⇧K", { exact: true })).toBeVisible();
|
|
// The whole point: the cheat sheet stops advertising the shipped default.
|
|
await expect(row.getByText("?", { exact: true })).toHaveCount(0);
|
|
});
|