diff --git a/packages/app/src/screens/settings/keyboard-shortcuts-section.tsx b/packages/app/src/screens/settings/keyboard-shortcuts-section.tsx index 8bba11a1c4..3798a5f9bd 100644 --- a/packages/app/src/screens/settings/keyboard-shortcuts-section.tsx +++ b/packages/app/src/screens/settings/keyboard-shortcuts-section.tsx @@ -377,8 +377,7 @@ export function KeyboardShortcutsSection() { event.preventDefault(); event.stopPropagation(); - const key = event.key ?? ""; - if (key === "Backspace") { + if (event.code === "Backspace" && heldModifiersFromEvent(event) === null) { setCapturedCombos((current) => (current.length > 0 ? current.slice(0, -1) : current)); return; } diff --git a/packages/desktop/e2e/keyboard-shortcut-unassign.spec.ts b/packages/desktop/e2e/keyboard-shortcut-unassign.spec.ts index ac40c7a3f6..1d0b829f67 100644 --- a/packages/desktop/e2e/keyboard-shortcut-unassign.spec.ts +++ b/packages/desktop/e2e/keyboard-shortcut-unassign.spec.ts @@ -14,10 +14,13 @@ const SHORTCUTS_ROW = "show-shortcuts"; * 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 { - await page.addInitScript(() => { +async function installDesktopBridge( + page: Page, + platform: "darwin" | "win32" = "darwin", +): Promise { + await page.addInitScript((desktopPlatform) => { window.paseoDesktop = { - platform: "darwin", + platform: desktopPlatform, events: { on: () => () => {} }, invoke: async (command: string) => { if (command === "get_desktop_settings") { @@ -29,11 +32,11 @@ async function installDesktopBridge(page: Page): Promise { return null; }, }; - }); + }, platform); } -async function openShortcutsSettings(page: Page) { - await installDesktopBridge(page); +async function openShortcutsSettings(page: Page, platform: "darwin" | "win32" = "darwin") { + await installDesktopBridge(page, platform); await gotoAppShell(page); await openSettings(page); await openSettingsSection(page, "shortcuts"); @@ -67,6 +70,38 @@ async function closeRowMenu(page: Page) { await expect(page.getByTestId(`shortcut-bind-${SHORTCUTS_ROW}`)).toHaveCount(0); } +async function clearShortcut(page: Page, label: string, action: string) { + await page.getByRole("button", { name: `Actions for ${label}` }).click(); + await page.getByTestId(`shortcut-clear-${action}`).click(); +} + +async function captureShortcut(page: Page, label: string, action: string, keys: string) { + await page.getByRole("button", { name: `Actions for ${label}` }).click(); + await page.getByTestId(`shortcut-bind-${action}`).click(); + await page.keyboard.press(keys); +} + +async function expectInterruptShortcut(page: Page, chord: string) { + const row = page.getByText("Interrupt agent", { exact: true }).locator(".."); + await expect(row.getByText(chord, { exact: true })).toBeVisible(); +} + +async function finishInterruptCapture(page: Page, action: "Done" | "Cancel") { + const row = page.getByText("Interrupt agent", { exact: true }).locator(".."); + await row.getByRole("button", { name: action }).click(); +} + +async function eraseLastCapturedCombo(page: Page, firstCombo: string, secondCombo: string) { + await captureShortcut(page, "Interrupt agent", "agent-interrupt", "Alt+K"); + await page.keyboard.press("Alt+J"); + await expectInterruptShortcut(page, firstCombo); + await expectInterruptShortcut(page, secondCombo); + await page.keyboard.press("Backspace"); + await expectInterruptShortcut(page, firstCombo); + const row = page.getByText("Interrupt agent", { exact: true }).locator(".."); + await expect(row.getByText(secondCombo, { exact: true })).toHaveCount(0); +} + test("unassigning a shortcut leaves it inert until it is reset", async ({ page }) => { await openShortcutsSettings(page); @@ -146,3 +181,35 @@ test("unassigning a shortcut leaves it inert until it is reset", async ({ page } await page.keyboard.press("Escape"); await expect(dialog).not.toBeVisible({ timeout: 10_000 }); }); + +for (const { platform, keys, label, firstCombo, secondCombo } of [ + { + platform: "darwin", + keys: "Meta+Shift+Backspace", + label: "⇧⌘⌫", + firstCombo: "⌥K", + secondCombo: "⌥J", + }, + { + platform: "win32", + keys: "Control+Shift+Backspace", + label: "Ctrl+Shift+⌫", + firstCombo: "Alt+K", + secondCombo: "Alt+J", + }, +] as const) { + test(`binds modified Backspace and erases a combo with bare Backspace on ${platform}`, async ({ + page, + }) => { + await openShortcutsSettings(page, platform); + await clearShortcut(page, "Archive workspace", "archive-workspace"); + await captureShortcut(page, "Interrupt agent", "agent-interrupt", keys); + await expectInterruptShortcut(page, label); + await finishInterruptCapture(page, "Done"); + await expectInterruptShortcut(page, label); + + await eraseLastCapturedCombo(page, firstCombo, secondCombo); + await finishInterruptCapture(page, "Cancel"); + await expectInterruptShortcut(page, label); + }); +}