mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
b0418bf920
Two small file-private bits move out of playerStore.ts:
- `src/store/queueVisibilityStorage.ts` — `readInitialQueueVisibility`
and `persistQueueVisibility` (the QueuePanel show/hide toggle
survives reloads via a localStorage round-trip; SSR / private-mode
failures swallowed silently).
- `src/store/queueUndoHotkey.ts` — `installQueueUndoHotkey` (Ctrl+Z /
Cmd+Z queue undo, Ctrl+Shift+Z redo, document-capture; skips text
fields so native text undo stays; idempotent via window-scoped
flag; mini-player window skipped). Added a `_resetQueueUndoHotkeyForTest`
that removes the keydown listener too (needed so vitest specs
don't accumulate handlers across tests).
playerStore re-exports `installQueueUndoHotkey` so bootstrap + tests
keep their existing `from './playerStore'` imports. The
queue-visibility helpers were file-private; no re-exports.
Side-cleanup: `getWindowKind` import is gone from playerStore (only
the moved hotkey installer used it).
16 focused tests pin the storage round-trip, the idempotency flag, the
modifier + key matching, the editable-target skip, and the
preventDefault contract.
playerStore 1940 → 1879 LOC.
131 lines
3.9 KiB
TypeScript
131 lines
3.9 KiB
TypeScript
/**
|
|
* Smoke + behaviour tests for the queue-undo hotkey installer. Verifies
|
|
* idempotency (the second call is a no-op), Ctrl+Z → undo, Ctrl+Shift+Z
|
|
* → redo, and the editable-field guard that lets the browser keep
|
|
* native text undo inside input/textarea/contenteditable.
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const hoisted = vi.hoisted(() => ({
|
|
windowKindMock: vi.fn(() => 'main' as 'main' | 'mini'),
|
|
undoMock: vi.fn(() => true),
|
|
redoMock: vi.fn(() => true),
|
|
}));
|
|
|
|
vi.mock('../app/windowKind', () => ({ getWindowKind: hoisted.windowKindMock }));
|
|
vi.mock('./playerStore', () => ({
|
|
usePlayerStore: {
|
|
getState: () => ({
|
|
undoLastQueueEdit: hoisted.undoMock,
|
|
redoLastQueueEdit: hoisted.redoMock,
|
|
}),
|
|
},
|
|
}));
|
|
|
|
import { _resetQueueUndoHotkeyForTest, installQueueUndoHotkey } from './queueUndoHotkey';
|
|
|
|
beforeEach(() => {
|
|
hoisted.windowKindMock.mockReturnValue('main');
|
|
hoisted.undoMock.mockReset();
|
|
hoisted.undoMock.mockReturnValue(true);
|
|
hoisted.redoMock.mockReset();
|
|
hoisted.redoMock.mockReturnValue(true);
|
|
_resetQueueUndoHotkeyForTest();
|
|
});
|
|
|
|
afterEach(() => {
|
|
_resetQueueUndoHotkeyForTest();
|
|
});
|
|
|
|
function fireKey(key: string, opts: Partial<KeyboardEventInit> = {}, target?: HTMLElement): boolean {
|
|
const event = new KeyboardEvent('keydown', {
|
|
key,
|
|
code: 'Key' + key.toUpperCase(),
|
|
bubbles: true,
|
|
cancelable: true,
|
|
composed: true,
|
|
...opts,
|
|
});
|
|
(target ?? document.body).dispatchEvent(event);
|
|
return event.defaultPrevented;
|
|
}
|
|
|
|
describe('installQueueUndoHotkey', () => {
|
|
it('is a no-op when window kind is mini', () => {
|
|
hoisted.windowKindMock.mockReturnValue('mini');
|
|
installQueueUndoHotkey();
|
|
fireKey('z', { ctrlKey: true });
|
|
expect(hoisted.undoMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('fires undoLastQueueEdit on Ctrl+Z', () => {
|
|
installQueueUndoHotkey();
|
|
const prevented = fireKey('z', { ctrlKey: true });
|
|
expect(hoisted.undoMock).toHaveBeenCalledTimes(1);
|
|
expect(prevented).toBe(true);
|
|
});
|
|
|
|
it('fires redoLastQueueEdit on Ctrl+Shift+Z', () => {
|
|
installQueueUndoHotkey();
|
|
fireKey('z', { ctrlKey: true, shiftKey: true });
|
|
expect(hoisted.redoMock).toHaveBeenCalledTimes(1);
|
|
expect(hoisted.undoMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('honours Cmd+Z (metaKey) on macOS', () => {
|
|
installQueueUndoHotkey();
|
|
fireKey('z', { metaKey: true });
|
|
expect(hoisted.undoMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('is idempotent — second install attaches no second listener', () => {
|
|
installQueueUndoHotkey();
|
|
installQueueUndoHotkey();
|
|
fireKey('z', { ctrlKey: true });
|
|
expect(hoisted.undoMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does NOT preventDefault when undoLastQueueEdit returns false', () => {
|
|
hoisted.undoMock.mockReturnValueOnce(false);
|
|
installQueueUndoHotkey();
|
|
const prevented = fireKey('z', { ctrlKey: true });
|
|
expect(prevented).toBe(false);
|
|
});
|
|
|
|
it('skips when the target is an INPUT element (native text undo)', () => {
|
|
installQueueUndoHotkey();
|
|
const input = document.createElement('input');
|
|
document.body.appendChild(input);
|
|
try {
|
|
fireKey('z', { ctrlKey: true }, input);
|
|
expect(hoisted.undoMock).not.toHaveBeenCalled();
|
|
} finally {
|
|
input.remove();
|
|
}
|
|
});
|
|
|
|
it('skips when the target is a TEXTAREA', () => {
|
|
installQueueUndoHotkey();
|
|
const ta = document.createElement('textarea');
|
|
document.body.appendChild(ta);
|
|
try {
|
|
fireKey('z', { ctrlKey: true }, ta);
|
|
expect(hoisted.undoMock).not.toHaveBeenCalled();
|
|
} finally {
|
|
ta.remove();
|
|
}
|
|
});
|
|
|
|
it('does nothing for keys other than Z', () => {
|
|
installQueueUndoHotkey();
|
|
fireKey('a', { ctrlKey: true });
|
|
expect(hoisted.undoMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('does nothing without Ctrl / Cmd modifier', () => {
|
|
installQueueUndoHotkey();
|
|
fireKey('z');
|
|
expect(hoisted.undoMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|