Files
yellowjacket/frontend/test/components/shortcuts-overlay.test.ts
T
logan 1aa1598ecb
Build & publish Arch package / arch-package (push) Successful in 2m1s
CI / check (push) Successful in 2m24s
Search index maintenance / maintain-index (push) Successful in 6s
CI / e2e (push) Canceled after 8s
feat(shortcuts): tell the key story once, and give the arrows back
Decision 1 keeps the unmodified single-key bindings, and Settings was
the only place they were written down — three of the four categories of
them, because config-page listed the categories by hand, so the autotag
keys were written down nowhere at all. `?` now opens an overlay from
anywhere the app owns the keyboard, and both surfaces read one table
(services/shortcut-meta.ts, moved out of config-page's private static).

The other half is the same explanation from the other side. Phase 1
gave the arrow keys to the grid, correctly — but all six of them, and
no list in this app moves horizontally: track-list's own handler and
utils/roving-rows both take Up/Down/Home/End and ignore Left/Right. So
seeking stopped working from a focused row and nothing gained the keys.
Reproduced in the running app: two ArrowRights on a focused track row,
zero Player.Seek calls, against one per press from the body.

A shifted character no longer reports Shift, so the binding is `?` and
not `Shift+?` — the character already carries the shift, and a layout
where it does not is a layout where "Shift+?" is wrong anyway.
2026-08-12 12:33:50 -04:00

82 lines
2.7 KiB
TypeScript

/**
* The key story, told once.
*
* Decision 1 keeps the unmodified single-key bindings, and Settings was
* the only place they were written down — three of the four categories
* of them, so the autotag keys were written down nowhere at all.
*/
import { describe, expect, it } from 'vitest';
import '@components/shortcuts-overlay/shortcuts-overlay';
import { emit } from '@test/support/harness';
import { Events } from '../../src/events';
import { fixture, shadow, shadowAll } from '@test/support/render';
/** Open it the way the shortcut service does. */
async function openOverlay(el: HTMLElement): Promise<void> {
document.dispatchEvent(new CustomEvent('shortcut:app-shortcuts'));
await (el as HTMLElement & { updateComplete: Promise<boolean> })
.updateComplete;
}
describe('<shortcuts-overlay>', () => {
it('renders nothing until it is asked for', async () => {
const el = await fixture('shortcuts-overlay');
expect(shadow(el, 'wa-dialog')).toBeNull();
});
it('opens on the shortcut event, and stays open on a second one', async () => {
const el = await fixture('shortcuts-overlay');
await openOverlay(el);
expect(shadow(el, '[data-testid="shortcuts-overlay"]')).toBeTruthy();
// Deliberately not a toggle: a dialog owns every unmodified key
// while it is up, so a second `?` never reaches the service and a
// toggle would be a promise the shortcut layer cannot keep.
await openOverlay(el);
expect(shadow(el, '[data-testid="shortcuts-overlay"]')).toBeTruthy();
});
it('lists the autotag keys, which Settings never showed', async () => {
const el = await fixture('shortcuts-overlay');
await openOverlay(el);
const headings = shadowAll(el, 'h3').map((h) =>
h.textContent!.trim().split(' ')[0],
);
expect(headings).toContain('Autotag');
});
it('shows the bound key, not the default it shipped with', async () => {
const el = await fixture('shortcuts-overlay');
emit(Events.ShortcutsConfigChanged, { 'player.playPause': 'K' });
await openOverlay(el);
const playPause = shadowAll(el, '.row').find((row) =>
row.textContent!.includes('Play / Pause'),
);
expect(playPause?.querySelector('kbd')?.textContent).toBe('K');
});
it('splits a combination into one key each', async () => {
const el = await fixture('shortcuts-overlay');
emit(Events.ShortcutsConfigChanged, { 'app.selectAll': 'Ctrl+A' });
await openOverlay(el);
const selectAll = shadowAll(el, '.row').find((row) =>
row.textContent!.includes('Select All'),
);
expect(
[...selectAll!.querySelectorAll('kbd')].map((k) => k.textContent),
).toEqual(['Ctrl', 'A']);
});
});