334 lines
9.9 KiB
TypeScript
334 lines
9.9 KiB
TypeScript
import { describe, expect, it, beforeEach, vi } from 'vitest';
|
|
import { detectPlatform, isMac, formatShortcut, matchesShortcut, shortcutToCodeMirrorKeybinding } from './utils';
|
|
import { KEYS, SHORTCUTS } from './constants';
|
|
import type { KeyboardShortcut } from './constants';
|
|
|
|
describe('detectPlatform', () => {
|
|
beforeEach(() => {
|
|
// Reset navigator mock before each test
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it('detects Mac platform from navigator.platform', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'MacIntel',
|
|
userAgent: 'Mozilla/5.0',
|
|
});
|
|
expect(detectPlatform()).toBe('mac');
|
|
});
|
|
|
|
it('detects Mac platform from userAgent', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'unknown',
|
|
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
|
|
});
|
|
expect(detectPlatform()).toBe('mac');
|
|
});
|
|
|
|
it('detects Windows platform', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'Win32',
|
|
userAgent: 'Mozilla/5.0 (Windows NT 10.0)',
|
|
});
|
|
expect(detectPlatform()).toBe('windows');
|
|
});
|
|
|
|
it('detects Linux platform', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'Linux x86_64',
|
|
userAgent: 'Mozilla/5.0 (X11; Linux x86_64)',
|
|
});
|
|
expect(detectPlatform()).toBe('linux');
|
|
});
|
|
|
|
it('returns unknown when navigator is undefined', () => {
|
|
vi.stubGlobal('navigator', undefined);
|
|
expect(detectPlatform()).toBe('unknown');
|
|
});
|
|
|
|
it('returns unknown for unrecognized platform', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'FreeBSD',
|
|
userAgent: 'Mozilla/5.0',
|
|
});
|
|
expect(detectPlatform()).toBe('unknown');
|
|
});
|
|
});
|
|
|
|
describe('isMac', () => {
|
|
it('returns true on Mac platform', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'MacIntel',
|
|
userAgent: 'Mozilla/5.0',
|
|
});
|
|
expect(isMac()).toBe(true);
|
|
});
|
|
|
|
it('returns false on Windows platform', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'Win32',
|
|
userAgent: 'Mozilla/5.0',
|
|
});
|
|
expect(isMac()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('formatShortcut', () => {
|
|
it('formats Cmd+Enter on Mac', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ENTER,
|
|
modifiers: { ctrlOrCmd: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'mac')).toBe('Cmd+Enter');
|
|
});
|
|
|
|
it('formats Ctrl+Enter on Windows', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ENTER,
|
|
modifiers: { ctrlOrCmd: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'windows')).toBe('Ctrl+Enter');
|
|
});
|
|
|
|
it('formats Ctrl+Enter on Linux', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ENTER,
|
|
modifiers: { ctrlOrCmd: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'linux')).toBe('Ctrl+Enter');
|
|
});
|
|
|
|
it('formats Ctrl+S', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrl: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'windows')).toBe('Ctrl+S');
|
|
});
|
|
|
|
it('formats Cmd+S on Mac', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { cmd: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'mac')).toBe('Cmd+S');
|
|
});
|
|
|
|
it('formats Alt as Option on Mac', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.A,
|
|
modifiers: { alt: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'mac')).toBe('Option+A');
|
|
});
|
|
|
|
it('formats Alt on Windows', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.A,
|
|
modifiers: { alt: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'windows')).toBe('Alt+A');
|
|
});
|
|
|
|
it('formats Shift+Ctrl+S', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrl: true, shift: true },
|
|
};
|
|
expect(formatShortcut(shortcut, 'windows')).toBe('Ctrl+Shift+S');
|
|
});
|
|
|
|
it('formats key without modifiers', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ESCAPE,
|
|
};
|
|
expect(formatShortcut(shortcut, 'mac')).toBe('Esc');
|
|
});
|
|
|
|
it('formats special keys correctly', () => {
|
|
expect(formatShortcut({ key: KEYS.ESCAPE }, 'mac')).toBe('Esc');
|
|
expect(formatShortcut({ key: KEYS.SPACE }, 'mac')).toBe('Space');
|
|
expect(formatShortcut({ key: KEYS.TAB }, 'mac')).toBe('Tab');
|
|
expect(formatShortcut({ key: KEYS.DELETE }, 'mac')).toBe('Del');
|
|
});
|
|
|
|
it('formats arrow keys with symbols', () => {
|
|
expect(formatShortcut({ key: KEYS.ARROW_UP }, 'mac')).toBe('↑');
|
|
expect(formatShortcut({ key: KEYS.ARROW_DOWN }, 'mac')).toBe('↓');
|
|
expect(formatShortcut({ key: KEYS.ARROW_LEFT }, 'mac')).toBe('←');
|
|
expect(formatShortcut({ key: KEYS.ARROW_RIGHT }, 'mac')).toBe('→');
|
|
});
|
|
|
|
it('uppercases single letter keys', () => {
|
|
expect(formatShortcut({ key: KEYS.A }, 'mac')).toBe('A');
|
|
expect(formatShortcut({ key: KEYS.Z }, 'mac')).toBe('Z');
|
|
});
|
|
|
|
it('uses current platform when not specified', () => {
|
|
vi.stubGlobal('navigator', {
|
|
platform: 'MacIntel',
|
|
userAgent: 'Mozilla/5.0',
|
|
});
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ENTER,
|
|
modifiers: { ctrlOrCmd: true },
|
|
};
|
|
expect(formatShortcut(shortcut)).toBe('Cmd+Enter');
|
|
});
|
|
});
|
|
|
|
describe('matchesShortcut', () => {
|
|
it('matches exact key without modifiers', () => {
|
|
const shortcut: KeyboardShortcut = { key: KEYS.ESCAPE };
|
|
const event = new KeyboardEvent('keydown', { key: 'Escape' });
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('does not match different key', () => {
|
|
const shortcut: KeyboardShortcut = { key: KEYS.ESCAPE };
|
|
const event = new KeyboardEvent('keydown', { key: 'Enter' });
|
|
expect(matchesShortcut(event, shortcut)).toBe(false);
|
|
});
|
|
|
|
it('matches ctrlOrCmd with Ctrl key', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ENTER,
|
|
modifiers: { ctrlOrCmd: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('matches ctrlOrCmd with Meta key', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ENTER,
|
|
modifiers: { ctrlOrCmd: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 'Enter', metaKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('does not match ctrlOrCmd without modifier', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.ENTER,
|
|
modifiers: { ctrlOrCmd: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 'Enter' });
|
|
expect(matchesShortcut(event, shortcut)).toBe(false);
|
|
});
|
|
|
|
it('matches Ctrl modifier', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrl: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 's', ctrlKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('matches Cmd modifier', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { cmd: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 's', metaKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('matches Alt modifier', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.A,
|
|
modifiers: { alt: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 'a', altKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('matches Shift modifier', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.A,
|
|
modifiers: { shift: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 'a', shiftKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('matches multiple modifiers', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrl: true, shift: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', {
|
|
key: 's',
|
|
ctrlKey: true,
|
|
shiftKey: true,
|
|
});
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('does not match when extra modifier is pressed', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrl: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', {
|
|
key: 's',
|
|
ctrlKey: true,
|
|
shiftKey: true,
|
|
});
|
|
expect(matchesShortcut(event, shortcut)).toBe(false);
|
|
});
|
|
|
|
it('does not match when required modifier is missing', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrl: true, shift: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', { key: 's', ctrlKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(false);
|
|
});
|
|
|
|
it('matches ctrlOrCmd with additional modifiers', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrlOrCmd: true, shift: true },
|
|
};
|
|
const event = new KeyboardEvent('keydown', {
|
|
key: 's',
|
|
ctrlKey: true,
|
|
shiftKey: true,
|
|
});
|
|
expect(matchesShortcut(event, shortcut)).toBe(true);
|
|
});
|
|
|
|
it('does not match when no modifiers expected but some pressed', () => {
|
|
const shortcut: KeyboardShortcut = { key: KEYS.ESCAPE };
|
|
const event = new KeyboardEvent('keydown', { key: 'Escape', ctrlKey: true });
|
|
expect(matchesShortcut(event, shortcut)).toBe(false);
|
|
});
|
|
|
|
it('works with predefined SHORTCUTS', () => {
|
|
const event = new KeyboardEvent('keydown', { key: 'Enter', metaKey: true });
|
|
expect(matchesShortcut(event, SHORTCUTS.SUBMIT)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('shortcutToCodeMirrorKeybinding', () => {
|
|
it('formats ctrlOrCmd shortcuts with Mod', () => {
|
|
expect(shortcutToCodeMirrorKeybinding(SHORTCUTS.SUBMIT)).toBe('Mod-Enter');
|
|
});
|
|
|
|
it('formats explicit modifiers', () => {
|
|
const shortcut: KeyboardShortcut = {
|
|
key: KEYS.S,
|
|
modifiers: { ctrl: true, shift: true },
|
|
};
|
|
expect(shortcutToCodeMirrorKeybinding(shortcut)).toBe('Ctrl-Shift-s');
|
|
});
|
|
|
|
it('formats without modifiers', () => {
|
|
const shortcut: KeyboardShortcut = { key: KEYS.ESCAPE };
|
|
expect(shortcutToCodeMirrorKeybinding(shortcut)).toBe('Escape');
|
|
});
|
|
});
|