agent-ecosystem/test/renderer/utils/keyboardUtils.test.ts
iliya cb8017b0db feat: enhance file icon handling and keyboard shortcuts
- Introduced a new FileIcon component to render file-type icons with support for Devicon CDN and fallback to lucide-react icons.
- Added a glow effect for file icons in dark mode to improve visibility.
- Updated various components to utilize the new FileIcon component for consistent icon rendering.
- Refined keyboard shortcut handling to use event.code for layout-independent key detection, improving cross-platform compatibility.
- Enhanced the EditorTabBar with a context menu for tab management, including options to close tabs and manage open files more efficiently.
2026-03-01 14:24:24 +02:00

269 lines
8.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { beforeEach, describe, expect, it } from 'vitest';
import {
formatModifierShortcut,
getModifierKeyName,
getModifierKeySymbol,
isMacOS,
physicalKey,
} from '../../../src/renderer/utils/keyboardUtils';
describe('keyboardUtils', () => {
describe('isMacOS', () => {
beforeEach(() => {
// Reset userAgent before each test
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: '',
});
});
it('should return true when userAgent contains "mac"', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
});
expect(isMacOS()).toBe(true);
});
it('should return false when userAgent does not contain "mac"', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
});
expect(isMacOS()).toBe(false);
});
it('should be case-insensitive', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (MAC OS)',
});
expect(isMacOS()).toBe(true);
});
});
describe('getModifierKeyName', () => {
it('should return "Cmd" on macOS', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
});
expect(getModifierKeyName()).toBe('Cmd');
});
it('should return "Ctrl" on Windows', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
});
expect(getModifierKeyName()).toBe('Ctrl');
});
it('should return "Ctrl" on Linux', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (X11; Linux x86_64)',
});
expect(getModifierKeyName()).toBe('Ctrl');
});
});
describe('getModifierKeySymbol', () => {
it('should return "⌘" on macOS', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
});
expect(getModifierKeySymbol()).toBe('⌘');
});
it('should return "Ctrl" on Windows', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
});
expect(getModifierKeySymbol()).toBe('Ctrl');
});
it('should return "Ctrl" on Linux', () => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (X11; Linux x86_64)',
});
expect(getModifierKeySymbol()).toBe('Ctrl');
});
});
describe('formatModifierShortcut', () => {
describe('macOS', () => {
beforeEach(() => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
});
});
it('should format with symbol by default', () => {
expect(formatModifierShortcut('K')).toBe('⌘K');
});
it('should format with text when useSymbol is false', () => {
expect(formatModifierShortcut('K', false)).toBe('Cmd+K');
});
it('should work with different keys', () => {
expect(formatModifierShortcut('G')).toBe('⌘G');
expect(formatModifierShortcut('S')).toBe('⌘S');
expect(formatModifierShortcut('Enter')).toBe('⌘Enter');
});
});
describe('Windows/Linux', () => {
beforeEach(() => {
Object.defineProperty(navigator, 'userAgent', {
writable: true,
configurable: true,
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
});
});
it('should format with symbol by default', () => {
expect(formatModifierShortcut('K')).toBe('Ctrl+K');
});
it('should format with text when useSymbol is false', () => {
expect(formatModifierShortcut('K', false)).toBe('Ctrl+K');
});
it('should work with different keys', () => {
expect(formatModifierShortcut('G')).toBe('Ctrl+G');
expect(formatModifierShortcut('S')).toBe('Ctrl+S');
expect(formatModifierShortcut('Enter')).toBe('Ctrl+Enter');
});
it('should always include + separator', () => {
expect(formatModifierShortcut('K')).toContain('+');
expect(formatModifierShortcut('K', false)).toContain('+');
});
});
});
describe('physicalKey', () => {
function makeEvent(
key: string,
code: string,
mods: Partial<KeyboardEventInit> = {}
): KeyboardEvent {
return new KeyboardEvent('keydown', { key, code, ...mods, bubbles: true, cancelable: true });
}
describe('letter keys — English layout', () => {
it('resolves KeyF to f', () => {
expect(physicalKey(makeEvent('f', 'KeyF'))).toBe('f');
});
it('resolves KeyW to w', () => {
expect(physicalKey(makeEvent('w', 'KeyW'))).toBe('w');
});
it('always returns lowercase even with Shift', () => {
expect(physicalKey(makeEvent('F', 'KeyF', { shiftKey: true }))).toBe('f');
});
});
describe('letter keys — Russian layout', () => {
it('resolves Cyrillic а (physical F) to f', () => {
expect(physicalKey(makeEvent('а', 'KeyF'))).toBe('f');
});
it('resolves Cyrillic ц (physical W) to w', () => {
expect(physicalKey(makeEvent('ц', 'KeyW'))).toBe('w');
});
it('resolves Cyrillic з (physical P) to p', () => {
expect(physicalKey(makeEvent('з', 'KeyP'))).toBe('p');
});
it('resolves Cyrillic и (physical B) to b', () => {
expect(physicalKey(makeEvent('и', 'KeyB'))).toBe('b');
});
it('resolves Cyrillic л (physical K) to k', () => {
expect(physicalKey(makeEvent('л', 'KeyK'))).toBe('k');
});
it('resolves Cyrillic ы (physical S) to s', () => {
expect(physicalKey(makeEvent('ы', 'KeyS'))).toBe('s');
});
});
describe('digit keys', () => {
it('resolves Digit1 to 1', () => {
expect(physicalKey(makeEvent('1', 'Digit1'))).toBe('1');
});
it('resolves Digit9 to 9', () => {
expect(physicalKey(makeEvent('9', 'Digit9'))).toBe('9');
});
it('resolves shifted digit (e.g. !) back to digit', () => {
expect(physicalKey(makeEvent('!', 'Digit1', { shiftKey: true }))).toBe('1');
});
});
describe('punctuation keys', () => {
it('resolves BracketLeft to [', () => {
expect(physicalKey(makeEvent('[', 'BracketLeft'))).toBe('[');
});
it('resolves Russian х (physical [) to [', () => {
expect(physicalKey(makeEvent('х', 'BracketLeft'))).toBe('[');
});
it('resolves BracketRight to ]', () => {
expect(physicalKey(makeEvent(']', 'BracketRight'))).toBe(']');
});
it('resolves Backslash to \\', () => {
expect(physicalKey(makeEvent('\\', 'Backslash'))).toBe('\\');
});
it('resolves Comma to ,', () => {
expect(physicalKey(makeEvent(',', 'Comma'))).toBe(',');
// Russian: physical , produces б
expect(physicalKey(makeEvent('б', 'Comma'))).toBe(',');
});
});
describe('special keys (pass-through)', () => {
it('returns event.key for Tab', () => {
expect(physicalKey(makeEvent('Tab', 'Tab'))).toBe('Tab');
});
it('returns event.key for Enter', () => {
expect(physicalKey(makeEvent('Enter', 'Enter'))).toBe('Enter');
});
it('returns event.key for Escape', () => {
expect(physicalKey(makeEvent('Escape', 'Escape'))).toBe('Escape');
});
it('returns event.key for Arrow keys', () => {
expect(physicalKey(makeEvent('ArrowUp', 'ArrowUp'))).toBe('ArrowUp');
expect(physicalKey(makeEvent('ArrowDown', 'ArrowDown'))).toBe('ArrowDown');
});
});
});
});