mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-03-15 03:02:00 -07:00
* fix: different out path on macos * fix: let go hibernation promise, so it's faster on macos * log marker [test-id-TIDGI_MINI_WINDOW_CREATED] * Skip registerShortcutByKey in test * fix: mini window on mac * Remove useless log marker * Update handleAttachToTidgiMiniWindow.ts * fix: log marker check all log files * lint * fix: open in new window now showing wiki title * Update package.json * feat: basic load and save to sub wiki using in-tag-tree-of * fix: load sub-wiki content and prevent echo * fix: test and ui logic * test: refactor subwiki test logic to a file * refactor: shorten steps by using dedicated step, and test underlying micro steps * fix: review * refactor: remove outdated method signature * test: unit cover adaptor subwiki routing * Update FileSystemAdaptor.routing.test.ts * fix: merge issue
514 lines
15 KiB
TypeScript
514 lines
15 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import '@testing-library/jest-dom/vitest';
|
|
import { ThemeProvider } from '@mui/material/styles';
|
|
import { lightTheme } from '@services/theme/defaultTheme';
|
|
|
|
import { KeyboardShortcutRegister } from '../KeyboardShortcutRegister';
|
|
|
|
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
<ThemeProvider theme={lightTheme}>
|
|
{children}
|
|
</ThemeProvider>
|
|
);
|
|
|
|
// Helper to get the correct modifier key based on platform
|
|
// On macOS, ctrlKey is displayed as 'Cmd', on other platforms as 'Ctrl'
|
|
const getCtrlModifier = () => process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
|
|
|
|
describe('KeyboardShortcutRegister Component', () => {
|
|
let mockOnChange: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockOnChange = vi.fn();
|
|
|
|
// Mock window.service.context.get
|
|
Object.defineProperty(window.service.context, 'get', {
|
|
value: vi.fn().mockImplementation((key: string) => {
|
|
if (key === 'platform') {
|
|
// Return platform based on process.platform for testing
|
|
return Promise.resolve(process.platform === 'darwin' ? 'darwin' : 'win32');
|
|
}
|
|
return Promise.resolve('win32');
|
|
}),
|
|
writable: true,
|
|
});
|
|
});
|
|
|
|
const renderComponent = (overrides: {
|
|
value?: string;
|
|
onChange?: (value: string) => void;
|
|
label?: string;
|
|
disabled?: boolean;
|
|
} = {}) => {
|
|
const defaultProps = {
|
|
value: '',
|
|
onChange: mockOnChange,
|
|
label: 'Test Shortcut',
|
|
disabled: false,
|
|
...overrides,
|
|
};
|
|
|
|
return render(
|
|
<TestWrapper>
|
|
<KeyboardShortcutRegister {...defaultProps} />
|
|
</TestWrapper>,
|
|
);
|
|
};
|
|
|
|
describe('Initial state and rendering', () => {
|
|
it('should display button with label and current shortcut value', () => {
|
|
renderComponent({
|
|
value: 'Ctrl+Shift+T',
|
|
label: 'Toggle Window',
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
expect(button).toBeInTheDocument();
|
|
expect(button).toHaveTextContent('Toggle Window');
|
|
expect(button).toHaveTextContent('Ctrl+Shift+T');
|
|
});
|
|
|
|
it('should display "None" when no shortcut is set', () => {
|
|
renderComponent({
|
|
value: '',
|
|
label: 'Toggle Window',
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
expect(button).toHaveTextContent('KeyboardShortcut.None');
|
|
});
|
|
|
|
it('should respect disabled state', () => {
|
|
renderComponent({
|
|
disabled: true,
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
expect(button).toBeDisabled();
|
|
});
|
|
|
|
it('should not show dialog initially', () => {
|
|
renderComponent();
|
|
|
|
expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Dialog interaction', () => {
|
|
it('should open dialog when button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
expect(screen.getByText('KeyboardShortcut.RegisterShortcut')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display current shortcut in dialog initially', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent({
|
|
value: 'Ctrl+A',
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent('Ctrl+A');
|
|
});
|
|
});
|
|
|
|
it('should close dialog when cancel button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
// Press ESC to close dialog (MUI Dialog default behavior)
|
|
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument();
|
|
}, { timeout: 500 });
|
|
});
|
|
});
|
|
|
|
describe('Keyboard shortcut capture', () => {
|
|
it('should capture Ctrl key combination', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
const dialogContent = screen.getByTestId('shortcut-dialog-content');
|
|
|
|
// Simulate keyboard event with Ctrl+Shift+T
|
|
// On macOS, ctrlKey is displayed as 'Cmd'
|
|
fireEvent.keyDown(dialogContent, {
|
|
key: 'T',
|
|
ctrlKey: true,
|
|
shiftKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent(`${getCtrlModifier()}+Shift+T`);
|
|
});
|
|
});
|
|
|
|
it('should capture Cmd key combination on macOS', async () => {
|
|
const originalPlatform = process.platform;
|
|
Object.defineProperty(process, 'platform', {
|
|
value: 'darwin',
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
// Simulate Cmd+K key press on document
|
|
fireEvent.keyDown(document, {
|
|
key: 'K',
|
|
metaKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent('Cmd+K');
|
|
});
|
|
|
|
Object.defineProperty(process, 'platform', {
|
|
value: originalPlatform,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
});
|
|
|
|
it('should capture Alt key combination', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
const dialogContent = screen.getByTestId('shortcut-dialog-content');
|
|
|
|
fireEvent.keyDown(dialogContent, {
|
|
key: 'F4',
|
|
altKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent('Alt+F4');
|
|
});
|
|
});
|
|
|
|
it('should ignore modifier keys pressed alone', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
const dialogContent = screen.getByTestId('shortcut-dialog-content');
|
|
const display = screen.getByTestId('shortcut-display');
|
|
|
|
// Should show initial "Press keys" message
|
|
expect(display).toHaveTextContent('KeyboardShortcut.PressKeys');
|
|
|
|
const modifierKeys = ['Shift', 'Control', 'Alt', 'Meta'];
|
|
for (const key of modifierKeys) {
|
|
fireEvent.keyDown(dialogContent, {
|
|
key,
|
|
bubbles: true,
|
|
});
|
|
}
|
|
|
|
// Should still show "Press keys" message
|
|
expect(display).toHaveTextContent('KeyboardShortcut.PressKeys');
|
|
});
|
|
|
|
it('should update display when keys are pressed', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
const dialogContent = screen.getByTestId('shortcut-dialog-content');
|
|
|
|
// Press first combination
|
|
fireEvent.keyDown(dialogContent, {
|
|
key: 'A',
|
|
ctrlKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent(`${getCtrlModifier()}+A`);
|
|
});
|
|
|
|
// Press second combination - should replace
|
|
fireEvent.keyDown(dialogContent, {
|
|
key: 'B',
|
|
ctrlKey: true,
|
|
shiftKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent(`${getCtrlModifier()}+Shift+B`);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Clear functionality', () => {
|
|
it('should clear current shortcut when clear button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent({
|
|
value: 'Ctrl+T',
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
// Initially should show the current value
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent('Ctrl+T');
|
|
|
|
// Verify clear button exists and is enabled
|
|
const clearButton = screen.getByTestId('shortcut-clear-button');
|
|
expect(clearButton).toBeInTheDocument();
|
|
expect(clearButton).toBeEnabled();
|
|
});
|
|
|
|
it('should call onChange with empty string when cleared and confirmed', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent({
|
|
value: '', // Start with empty to test setting empty and confirming
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
// Don't set any keys, just press Enter to confirm empty
|
|
fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' });
|
|
|
|
await waitFor(() => {
|
|
expect(mockOnChange).toHaveBeenCalledWith('');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Confirm functionality', () => {
|
|
it('should call onChange with new shortcut on confirm', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent({
|
|
value: '',
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
const dialogContent = screen.getByTestId('shortcut-dialog-content');
|
|
|
|
fireEvent.keyDown(dialogContent, {
|
|
key: 'N',
|
|
ctrlKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent(`${getCtrlModifier()}+N`);
|
|
});
|
|
|
|
// Press Enter to confirm
|
|
fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' });
|
|
|
|
await waitFor(() => {
|
|
expect(mockOnChange).toHaveBeenCalledWith(`${getCtrlModifier()}+N`);
|
|
});
|
|
});
|
|
|
|
it('should not call onChange when cancel is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent({
|
|
value: 'Ctrl+A',
|
|
});
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
const dialogContent = screen.getByTestId('shortcut-dialog-content');
|
|
|
|
fireEvent.keyDown(dialogContent, {
|
|
key: 'B',
|
|
ctrlKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const display = screen.getByTestId('shortcut-display');
|
|
expect(display).toHaveTextContent(`${getCtrlModifier()}+B`);
|
|
});
|
|
|
|
// Press ESC to cancel without saving
|
|
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' });
|
|
|
|
await waitFor(() => {
|
|
expect(mockOnChange).not.toHaveBeenCalled();
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument();
|
|
}, { timeout: 500 });
|
|
});
|
|
|
|
it('should close dialog after confirm', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
const dialogContent = screen.getByTestId('shortcut-dialog-content');
|
|
|
|
fireEvent.keyDown(dialogContent, {
|
|
key: 'T',
|
|
ctrlKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
// Press Enter to confirm and close dialog
|
|
fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument();
|
|
}, { timeout: 500 });
|
|
});
|
|
});
|
|
|
|
describe('Help text', () => {
|
|
it('should display help text in dialog', async () => {
|
|
const user = userEvent.setup();
|
|
renderComponent();
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('KeyboardShortcut.HelpText')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Props validation', () => {
|
|
it('should use custom label', () => {
|
|
renderComponent({ label: 'Custom Label' });
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
expect(button).toHaveTextContent('Custom Label');
|
|
});
|
|
|
|
it('should handle onChange callback', async () => {
|
|
const customOnChange = vi.fn();
|
|
const user = userEvent.setup();
|
|
|
|
renderComponent({ onChange: customOnChange });
|
|
|
|
const button = screen.getByTestId('shortcut-register-button');
|
|
await user.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
// Simulate Ctrl+X key press on document
|
|
// On macOS, ctrlKey is displayed as 'Cmd', on other platforms as 'Ctrl'
|
|
const expectedModifier = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
|
|
fireEvent.keyDown(document, {
|
|
key: 'X',
|
|
ctrlKey: true,
|
|
bubbles: true,
|
|
});
|
|
|
|
// Wait for the key combination to be processed
|
|
await waitFor(() => {
|
|
expect(screen.getByText(`${expectedModifier}+X`)).toBeInTheDocument();
|
|
});
|
|
|
|
// Press Enter to confirm
|
|
fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' });
|
|
|
|
await waitFor(() => {
|
|
expect(customOnChange).toHaveBeenCalledWith(`${expectedModifier}+X`);
|
|
});
|
|
});
|
|
});
|
|
});
|