@@ -33,17 +33,17 @@ describe('ChatInput - Rapid Click Protection', () => {
3333 jest . restoreAllMocks ( ) ;
3434 } ) ;
3535
36- it ( 'should prevent duplicate message submissions on rapid Enter key presses' , async ( ) => {
36+ it ( 'should prevent duplicate message submissions on rapid keyboard shortcut presses' , async ( ) => {
3737 render ( < ChatInput { ...defaultProps } /> ) ;
3838 const textarea = screen . getByPlaceholderText ( 'Type a message...' ) ;
3939
4040 // Type a message
4141 fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
4242
43- // Rapidly press Enter multiple times
44- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
45- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
46- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
43+ // Rapidly press Ctrl+ Enter multiple times
44+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
45+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
46+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
4747
4848 await waitFor ( ( ) => {
4949 // Should only send once
@@ -59,7 +59,9 @@ describe('ChatInput - Rapid Click Protection', () => {
5959 // Type a message
6060 fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
6161
62- const submitButton = screen . getByRole ( 'button' , { name : / s u b m i t / i } ) ;
62+ const submitButton = screen . getByRole ( 'button' , {
63+ name : / s e n d m e s s a g e \( c t r l \+ e n t e r \) / i,
64+ } ) ;
6365
6466 // Rapidly click the submit button
6567 fireEvent . click ( submitButton ) ;
@@ -79,7 +81,9 @@ describe('ChatInput - Rapid Click Protection', () => {
7981
8082 fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
8183
82- const submitButton = screen . getByRole ( 'button' , { name : / s u b m i t / i } ) ;
84+ const submitButton = screen . getByRole ( 'button' , {
85+ name : / s e n d m e s s a g e \( c t r l \+ e n t e r \) / i,
86+ } ) ;
8387
8488 // Click submit
8589 fireEvent . click ( submitButton ) ;
@@ -96,7 +100,7 @@ describe('ChatInput - Rapid Click Protection', () => {
96100
97101 // First submission
98102 fireEvent . change ( textarea , { target : { value : 'First message' } } ) ;
99- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
103+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
100104
101105 await waitFor ( ( ) => {
102106 expect ( mockOnSendMessage ) . toHaveBeenCalledTimes ( 1 ) ;
@@ -107,7 +111,7 @@ describe('ChatInput - Rapid Click Protection', () => {
107111
108112 // Second submission
109113 fireEvent . change ( textarea , { target : { value : 'Second message' } } ) ;
110- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
114+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
111115
112116 await waitFor ( ( ) => {
113117 expect ( mockOnSendMessage ) . toHaveBeenCalledTimes ( 2 ) ;
@@ -120,7 +124,7 @@ describe('ChatInput - Rapid Click Protection', () => {
120124 const textarea = screen . getByPlaceholderText ( 'Type a message...' ) ;
121125
122126 fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
123- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
127+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
124128
125129 expect ( mockOnSendMessage ) . not . toHaveBeenCalled ( ) ;
126130 } ) ;
@@ -134,8 +138,8 @@ describe('ChatInput - Rapid Click Protection', () => {
134138 fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
135139
136140 // Rapid submissions
137- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
138- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
141+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
142+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
139143
140144 await waitFor ( ( ) => {
141145 expect ( consoleWarnSpy ) . toHaveBeenCalledWith (
@@ -152,7 +156,7 @@ describe('ChatInput - Rapid Click Protection', () => {
152156 fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
153157 expect ( textarea . value ) . toBe ( 'Test message' ) ;
154158
155- fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
159+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' , ctrlKey : true } ) ;
156160
157161 await waitFor ( ( ) => {
158162 expect ( textarea . value ) . toBe ( '' ) ;
@@ -177,4 +181,30 @@ describe('ChatInput - Rapid Click Protection', () => {
177181 expect ( mockOnSendMessage ) . toHaveBeenCalledTimes ( 1 ) ;
178182 } ) ;
179183 } ) ;
184+
185+ it ( 'should not submit on Enter without the modifier shortcut' , ( ) => {
186+ render ( < ChatInput { ...defaultProps } /> ) ;
187+ const textarea = screen . getByPlaceholderText ( 'Type a message...' ) ;
188+
189+ fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
190+ fireEvent . keyDown ( textarea , { key : 'Enter' , code : 'Enter' } ) ;
191+
192+ expect ( mockOnSendMessage ) . not . toHaveBeenCalled ( ) ;
193+ } ) ;
194+
195+ it ( 'should expose the shortcut through the submit button tooltip and label' , ( ) => {
196+ render ( < ChatInput { ...defaultProps } /> ) ;
197+ const textarea = screen . getByPlaceholderText ( 'Type a message...' ) ;
198+
199+ fireEvent . change ( textarea , { target : { value : 'Test message' } } ) ;
200+
201+ const submitButton = screen . getByRole ( 'button' , {
202+ name : / s e n d m e s s a g e \( c t r l \+ e n t e r \) / i,
203+ } ) ;
204+
205+ expect ( submitButton ) . toHaveAttribute ( 'title' , 'Send message (Ctrl+Enter)' ) ;
206+ expect ( submitButton ) . toHaveAttribute ( 'aria-keyshortcuts' , 'Control+Enter' ) ;
207+ expect ( textarea ) . toHaveAttribute ( 'aria-describedby' , 'chat-submit-shortcut' ) ;
208+ expect ( screen . getByText ( / s e n d m e s s a g e w i t h c t r l \+ e n t e r / i) ) . toBeInTheDocument ( ) ;
209+ } ) ;
180210} ) ;
0 commit comments