Skip to content

Commit 6da2891

Browse files
author
Marvy
committed
feat(frontend): add keyboard shortcuts to ChatInput.tsx
- Add Cmd/Ctrl+N for new chat - Add Cmd/Ctrl+H for open history - Add Cmd/Ctrl+B for open bridge modal - Add Cmd/Ctrl+Shift+C for cancel request - Add comprehensive unit tests for all keyboard shortcuts - Ensure no regressions in existing functionality Closes #561
1 parent 347f6b3 commit 6da2891

2 files changed

Lines changed: 147 additions & 1 deletion

File tree

dex_with_fiat_frontend/src/components/ChatInput.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,26 @@ export default function ChatInput({
181181
event.preventDefault();
182182
setShowPalette((prev: boolean) => !prev);
183183
}
184+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'n' && !event.shiftKey) {
185+
event.preventDefault();
186+
onNewChat?.();
187+
}
188+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'h') {
189+
event.preventDefault();
190+
onOpenHistory?.();
191+
}
192+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'b') {
193+
event.preventDefault();
194+
onOpenBridgeModal?.();
195+
}
196+
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === 'c') {
197+
event.preventDefault();
198+
onCancelRequest?.();
199+
}
184200
};
185201
window.addEventListener('keydown', handler);
186202
return () => window.removeEventListener('keydown', handler);
187-
}, []);
203+
}, [onNewChat, onOpenHistory, onOpenBridgeModal, onCancelRequest]);
188204

189205
// Load draft when session changes
190206
useEffect(() => {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
3+
import ChatInput from '../ChatInput';
4+
5+
// Mock the translation context
6+
jest.mock('@/contexts/TranslationContext', () => ({
7+
useTranslation: () => ({
8+
t: (key: string) => key,
9+
}),
10+
}));
11+
12+
// Mock draft utils
13+
jest.mock('@/lib/draftUtils', () => ({
14+
saveDraft: jest.fn(),
15+
getDraft: jest.fn(() => ''),
16+
clearDraft: jest.fn(),
17+
}));
18+
19+
// Mock StellarWalletContext
20+
jest.mock('@/contexts/StellarWalletContext', () => ({
21+
useStellarWallet: () => ({
22+
connection: {
23+
isConnected: true,
24+
address: 'GABC123',
25+
publicKey: 'GABC123',
26+
network: 'testnet',
27+
networkPassphrase: '',
28+
},
29+
}),
30+
}));
31+
32+
describe('ChatInput - Keyboard Shortcuts', () => {
33+
const mockOnSendMessage = jest.fn();
34+
const mockOnCancelRequest = jest.fn();
35+
const mockOnNewChat = jest.fn();
36+
const mockOnOpenHistory = jest.fn();
37+
const mockOnOpenBridgeModal = jest.fn();
38+
39+
const defaultProps = {
40+
onSendMessage: mockOnSendMessage,
41+
onCancelRequest: mockOnCancelRequest,
42+
onNewChat: mockOnNewChat,
43+
onOpenHistory: mockOnOpenHistory,
44+
onOpenBridgeModal: mockOnOpenBridgeModal,
45+
isLoading: false,
46+
placeholder: 'Type a message...',
47+
};
48+
49+
beforeEach(() => {
50+
jest.clearAllMocks();
51+
});
52+
53+
it('should trigger new chat with Cmd+N', () => {
54+
render(<ChatInput {...defaultProps} />);
55+
fireEvent.keyDown(window, { key: 'n', metaKey: true });
56+
expect(mockOnNewChat).toHaveBeenCalledTimes(1);
57+
});
58+
59+
it('should trigger new chat with Ctrl+N', () => {
60+
render(<ChatInput {...defaultProps} />);
61+
fireEvent.keyDown(window, { key: 'n', ctrlKey: true });
62+
expect(mockOnNewChat).toHaveBeenCalledTimes(1);
63+
});
64+
65+
it('should not trigger new chat with Shift+Cmd+N', () => {
66+
render(<ChatInput {...defaultProps} />);
67+
fireEvent.keyDown(window, { key: 'n', metaKey: true, shiftKey: true });
68+
expect(mockOnNewChat).not.toHaveBeenCalled();
69+
});
70+
71+
it('should trigger open history with Cmd+H', () => {
72+
render(<ChatInput {...defaultProps} />);
73+
fireEvent.keyDown(window, { key: 'h', metaKey: true });
74+
expect(mockOnOpenHistory).toHaveBeenCalledTimes(1);
75+
});
76+
77+
it('should trigger open history with Ctrl+H', () => {
78+
render(<ChatInput {...defaultProps} />);
79+
fireEvent.keyDown(window, { key: 'h', ctrlKey: true });
80+
expect(mockOnOpenHistory).toHaveBeenCalledTimes(1);
81+
});
82+
83+
it('should trigger open bridge modal with Cmd+B', () => {
84+
render(<ChatInput {...defaultProps} />);
85+
fireEvent.keyDown(window, { key: 'b', metaKey: true });
86+
expect(mockOnOpenBridgeModal).toHaveBeenCalledTimes(1);
87+
});
88+
89+
it('should trigger open bridge modal with Ctrl+B', () => {
90+
render(<ChatInput {...defaultProps} />);
91+
fireEvent.keyDown(window, { key: 'b', ctrlKey: true });
92+
expect(mockOnOpenBridgeModal).toHaveBeenCalledTimes(1);
93+
});
94+
95+
it('should trigger cancel request with Cmd+Shift+C', () => {
96+
render(<ChatInput {...defaultProps} />);
97+
fireEvent.keyDown(window, { key: 'c', metaKey: true, shiftKey: true });
98+
expect(mockOnCancelRequest).toHaveBeenCalledTimes(1);
99+
});
100+
101+
it('should trigger cancel request with Ctrl+Shift+C', () => {
102+
render(<ChatInput {...defaultProps} />);
103+
fireEvent.keyDown(window, { key: 'c', ctrlKey: true, shiftKey: true });
104+
expect(mockOnCancelRequest).toHaveBeenCalledTimes(1);
105+
});
106+
107+
it('should not trigger cancel request with Cmd+C', () => {
108+
render(<ChatInput {...defaultProps} />);
109+
fireEvent.keyDown(window, { key: 'c', metaKey: true });
110+
expect(mockOnCancelRequest).not.toHaveBeenCalled();
111+
});
112+
113+
it('should toggle command palette with Cmd+K', () => {
114+
render(<ChatInput {...defaultProps} />);
115+
expect(screen.queryByPlaceholderText('Type a command...')).not.toBeInTheDocument();
116+
fireEvent.keyDown(window, { key: 'k', metaKey: true });
117+
expect(screen.getByPlaceholderText('Type a command...')).toBeInTheDocument();
118+
fireEvent.keyDown(window, { key: 'k', metaKey: true });
119+
expect(screen.queryByPlaceholderText('Type a command...')).not.toBeInTheDocument();
120+
});
121+
122+
it('should toggle command palette with Ctrl+K', () => {
123+
render(<ChatInput {...defaultProps} />);
124+
expect(screen.queryByPlaceholderText('Type a command...')).not.toBeInTheDocument();
125+
fireEvent.keyDown(window, { key: 'k', ctrlKey: true });
126+
expect(screen.getByPlaceholderText('Type a command...')).toBeInTheDocument();
127+
fireEvent.keyDown(window, { key: 'k', ctrlKey: true });
128+
expect(screen.queryByPlaceholderText('Type a command...')).not.toBeInTheDocument();
129+
});
130+
});

0 commit comments

Comments
 (0)