116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { handleCanvasShortcut, type CanvasCommands } from '../../src/react';
|
|
|
|
function commands(): CanvasCommands {
|
|
return {
|
|
clearSelection: vi.fn(),
|
|
selectAll: vi.fn(),
|
|
deleteSelection: vi.fn(),
|
|
lockSelection: vi.fn(),
|
|
copySelection: vi.fn(),
|
|
cutSelection: vi.fn(),
|
|
pasteElements: vi.fn(),
|
|
unlockTarget: vi.fn(),
|
|
toggleGroup: vi.fn(),
|
|
reorderTarget: vi.fn(),
|
|
alignSelection: vi.fn(),
|
|
};
|
|
}
|
|
|
|
function keyEvent(
|
|
key: string,
|
|
options: {
|
|
ctrlKey?: boolean;
|
|
metaKey?: boolean;
|
|
altKey?: boolean;
|
|
repeat?: boolean;
|
|
target?: unknown;
|
|
} = {},
|
|
) {
|
|
return {
|
|
key,
|
|
ctrlKey: options.ctrlKey ?? false,
|
|
metaKey: options.metaKey ?? false,
|
|
altKey: options.altKey ?? false,
|
|
repeat: options.repeat ?? false,
|
|
target: options.target ?? null,
|
|
preventDefault: vi.fn(),
|
|
};
|
|
}
|
|
|
|
describe('handleCanvasShortcut', () => {
|
|
it.each([
|
|
['Delete', 'deleteSelection'],
|
|
['Backspace', 'deleteSelection'],
|
|
['Escape', 'clearSelection'],
|
|
] as const)('maps %s to %s', (key, command) => {
|
|
const available = commands();
|
|
const event = keyEvent(key);
|
|
|
|
expect(handleCanvasShortcut(event, available)).toBe(true);
|
|
expect(available[command]).toHaveBeenCalledOnce();
|
|
expect(event.preventDefault).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it.each([
|
|
['a', 'selectAll'],
|
|
['c', 'copySelection'],
|
|
['x', 'cutSelection'],
|
|
['v', 'pasteElements'],
|
|
['l', 'lockSelection'],
|
|
['g', 'toggleGroup'],
|
|
] as const)('maps Ctrl/Cmd+%s to %s', (key, command) => {
|
|
for (const modifier of [{ ctrlKey: true }, { metaKey: true }]) {
|
|
const available = commands();
|
|
const event = keyEvent(key, modifier);
|
|
|
|
expect(handleCanvasShortcut(event, available)).toBe(true);
|
|
expect(available[command]).toHaveBeenCalledOnce();
|
|
expect(event.preventDefault).toHaveBeenCalledOnce();
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
{ tagName: 'INPUT' },
|
|
{ tagName: 'TEXTAREA' },
|
|
{ tagName: 'SELECT' },
|
|
{ tagName: 'DIV', isContentEditable: true },
|
|
{
|
|
tagName: 'DIV',
|
|
closest: (selector: string) => (selector.includes('ProseMirror') ? {} : null),
|
|
},
|
|
])('ignores editable targets', (target) => {
|
|
const available = commands();
|
|
const event = keyEvent('Delete', { target });
|
|
|
|
expect(handleCanvasShortcut(event, available)).toBe(false);
|
|
expect(available.deleteSelection).not.toHaveBeenCalled();
|
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each([
|
|
['pick mode', { pickActive: true }],
|
|
['disabled shortcuts', { enabled: false }],
|
|
] as const)('ignores shortcuts during %s', (_label, options) => {
|
|
const available = commands();
|
|
const event = keyEvent('Delete');
|
|
|
|
expect(handleCanvasShortcut(event, available, options)).toBe(false);
|
|
expect(available.deleteSelection).not.toHaveBeenCalled();
|
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('ignores unmodified letters, Alt combinations, and keyboard auto-repeat', () => {
|
|
for (const event of [
|
|
keyEvent('a'),
|
|
keyEvent('g', { ctrlKey: true, altKey: true }),
|
|
keyEvent('g', { ctrlKey: true, repeat: true }),
|
|
]) {
|
|
const available = commands();
|
|
expect(handleCanvasShortcut(event, available)).toBe(false);
|
|
expect(available.toggleGroup).not.toHaveBeenCalled();
|
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
}
|
|
});
|
|
});
|