@@ -14,9 +14,17 @@ import { colors } from '../../lib/constants';
1414import { IRoomContext , RoomContext } from '../../views/RoomView/context' ;
1515import * as EmojiKeyboardHook from './hooks/useEmojiKeyboard' ;
1616import { initStore } from '../../lib/store/auxStore' ;
17+ import { search } from '../../lib/methods/search' ;
18+ import database from '../../lib/database' ;
19+ import { Services } from '../../lib/services' ;
1720
1821jest . useFakeTimers ( ) ;
1922
23+ // Ensure search returns at least one item so autocomplete renders
24+ jest . mock ( '../../lib/methods/search' , ( ) => ( {
25+ search : jest . fn ( ( ) => [ { _id : 'u1' , username : 'john' , name : 'John' } ] )
26+ } ) ) ;
27+
2028const user = userEvent . setup ( ) ;
2129
2230const initialStoreState = ( ) => {
@@ -128,6 +136,10 @@ let showEmojiSearchbar = false;
128136beforeEach ( ( ) => {
129137 showEmojiKeyboard = false ;
130138 showEmojiSearchbar = false ;
139+ // Default DB mocks used by autocomplete
140+ ( database . active . get as unknown as jest . Mock ) . mockImplementation ( ( ) => ( {
141+ query : jest . fn ( ( ) => ( { fetch : jest . fn ( ( ) => Promise . resolve ( [ ] ) ) } ) )
142+ } ) ) ;
131143 jest . spyOn ( EmojiKeyboardHook , 'useEmojiKeyboard' ) . mockReturnValue ( {
132144 showEmojiPickerSharedValue : sharedValue ,
133145 showEmojiKeyboard,
@@ -385,6 +397,132 @@ describe('MessageComposer', () => {
385397 } ) ;
386398 } ) ;
387399
400+ describe ( 'Autocomplete' , ( ) => {
401+ test ( 'typing @ opens autocomplete' , async ( ) => {
402+ render ( < Render /> ) ;
403+
404+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'focus' ) ;
405+ await fireEvent . changeText ( screen . getByTestId ( 'message-composer-input' ) , '@' ) ;
406+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'selectionChange' , {
407+ nativeEvent : { selection : { start : 1 , end : 1 } }
408+ } ) ;
409+
410+ jest . advanceTimersByTime ( 500 ) ;
411+
412+ await waitFor ( ( ) => expect ( screen . getByTestId ( 'autocomplete' ) ) . toBeOnTheScreen ( ) ) ;
413+ } ) ;
414+
415+ test ( 'select @ user inserts mention and sends, autocomplete hides' , async ( ) => {
416+ const onSendMessage = jest . fn ( ) ;
417+ ( search as unknown as jest . Mock ) . mockImplementationOnce ( ( ) => [ { _id : 'u1' , username : 'john' , name : 'John' } ] ) ;
418+ render ( < Render context = { { onSendMessage } } /> ) ;
419+
420+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'focus' ) ;
421+ await fireEvent . changeText ( screen . getByTestId ( 'message-composer-input' ) , '@' ) ;
422+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'selectionChange' , {
423+ nativeEvent : { selection : { start : 1 , end : 1 } }
424+ } ) ;
425+ jest . advanceTimersByTime ( 500 ) ;
426+ await waitFor ( ( ) => expect ( screen . getByTestId ( 'autocomplete-item-John' ) ) . toBeOnTheScreen ( ) ) ;
427+
428+ await user . press ( screen . getByTestId ( 'autocomplete-item-John' ) ) ;
429+ await waitFor ( ( ) => expect ( screen . queryByTestId ( 'autocomplete' ) ) . not . toBeOnTheScreen ( ) ) ;
430+
431+ await user . press ( screen . getByTestId ( 'message-composer-send' ) ) ;
432+ expect ( onSendMessage ) . toHaveBeenCalledTimes ( 1 ) ;
433+ expect ( onSendMessage ) . toHaveBeenCalledWith ( '@john' , undefined ) ;
434+ } ) ;
435+
436+ test ( 'select # room inserts channel and sends, autocomplete hides' , async ( ) => {
437+ const onSendMessage = jest . fn ( ) ;
438+ ( search as unknown as jest . Mock ) . mockImplementationOnce ( ( ) => [ { rid : 'r1' , name : 'general' , t : 'c' } ] ) ;
439+ render ( < Render context = { { onSendMessage } } /> ) ;
440+
441+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'focus' ) ;
442+ await fireEvent . changeText ( screen . getByTestId ( 'message-composer-input' ) , '#' ) ;
443+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'selectionChange' , {
444+ nativeEvent : { selection : { start : 1 , end : 1 } }
445+ } ) ;
446+ jest . advanceTimersByTime ( 500 ) ;
447+ await waitFor ( ( ) => expect ( screen . getByTestId ( 'autocomplete-item-general' ) ) . toBeOnTheScreen ( ) ) ;
448+
449+ await user . press ( screen . getByTestId ( 'autocomplete-item-general' ) ) ;
450+ await waitFor ( ( ) => expect ( screen . queryByTestId ( 'autocomplete' ) ) . not . toBeOnTheScreen ( ) ) ;
451+
452+ await user . press ( screen . getByTestId ( 'message-composer-send' ) ) ;
453+ expect ( onSendMessage ) . toHaveBeenCalledTimes ( 1 ) ;
454+ expect ( onSendMessage ) . toHaveBeenCalledWith ( '#general' , undefined ) ;
455+ } ) ;
456+
457+ test ( 'select : emoji inserts emoji and sends, autocomplete hides' , async ( ) => {
458+ const onSendMessage = jest . fn ( ) ;
459+ render ( < Render context = { { onSendMessage } } /> ) ;
460+
461+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'focus' ) ;
462+ await fireEvent . changeText ( screen . getByTestId ( 'message-composer-input' ) , ':smi' ) ;
463+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'selectionChange' , {
464+ nativeEvent : { selection : { start : 4 , end : 4 } }
465+ } ) ;
466+ jest . advanceTimersByTime ( 500 ) ;
467+ await waitFor ( ( ) => expect ( screen . getByTestId ( 'autocomplete-item-smile' ) ) . toBeOnTheScreen ( ) ) ;
468+
469+ await user . press ( screen . getByTestId ( 'autocomplete-item-smile' ) ) ;
470+ await waitFor ( ( ) => expect ( screen . queryByTestId ( 'autocomplete' ) ) . not . toBeOnTheScreen ( ) ) ;
471+
472+ await user . press ( screen . getByTestId ( 'message-composer-send' ) ) ;
473+ expect ( onSendMessage ) . toHaveBeenCalledTimes ( 1 ) ;
474+ expect ( onSendMessage ) . toHaveBeenCalledWith ( ':smile:' , undefined ) ;
475+ } ) ;
476+
477+ test ( 'select / command inserts command text and sends, autocomplete hides' , async ( ) => {
478+ const onSendMessage = jest . fn ( ) ;
479+ const getSpy = jest . spyOn ( database . active as any , 'get' ) ;
480+ ( getSpy as any ) . mockImplementation ( ( table : string ) => {
481+ if ( table === 'slash_commands' ) {
482+ return {
483+ query : jest . fn ( ( ) => ( { fetch : jest . fn ( ( ) => Promise . resolve ( [ { id : 'hello' , description : 'desc' } ] ) ) } ) )
484+ } ;
485+ }
486+ return { query : jest . fn ( ( ) => ( { fetch : jest . fn ( ( ) => Promise . resolve ( [ ] ) ) } ) ) } ;
487+ } ) ;
488+ render ( < Render context = { { onSendMessage } } /> ) ;
489+
490+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'focus' ) ;
491+ await fireEvent . changeText ( screen . getByTestId ( 'message-composer-input' ) , '/hello' ) ;
492+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'selectionChange' , {
493+ nativeEvent : { selection : { start : 6 , end : 6 } }
494+ } ) ;
495+ jest . advanceTimersByTime ( 500 ) ;
496+ await screen . findByTestId ( 'autocomplete' ) ;
497+ await user . press ( screen . getByTestId ( 'message-composer-send' ) ) ;
498+ await waitFor ( ( ) => expect ( screen . queryByTestId ( 'autocomplete' ) ) . not . toBeOnTheScreen ( ) ) ;
499+ } ) ;
500+
501+ test ( 'select ! canned response inserts text and sends, autocomplete hides' , async ( ) => {
502+ const onSendMessage = jest . fn ( ) ;
503+ jest . spyOn ( Services , 'getListCannedResponse' ) . mockResolvedValueOnce ( {
504+ success : true ,
505+ cannedResponses : [ { _id : '1' , shortcut : 'brb' , text : 'Be right back' } ]
506+ } as any ) ;
507+ render ( < Render context = { { onSendMessage, room : { ...initialContext . room , t : 'l' } } } /> ) ;
508+
509+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'focus' ) ;
510+ await fireEvent . changeText ( screen . getByTestId ( 'message-composer-input' ) , '!' ) ;
511+ await fireEvent ( screen . getByTestId ( 'message-composer-input' ) , 'selectionChange' , {
512+ nativeEvent : { selection : { start : 1 , end : 1 } }
513+ } ) ;
514+ jest . advanceTimersByTime ( 500 ) ;
515+ await waitFor ( ( ) => expect ( screen . getByTestId ( 'autocomplete-item-brb' ) ) . toBeOnTheScreen ( ) ) ;
516+
517+ await user . press ( screen . getByTestId ( 'autocomplete-item-brb' ) ) ;
518+ await waitFor ( ( ) => expect ( screen . queryByTestId ( 'autocomplete' ) ) . not . toBeOnTheScreen ( ) ) ;
519+
520+ await user . press ( screen . getByTestId ( 'message-composer-send' ) ) ;
521+ expect ( onSendMessage ) . toHaveBeenCalledTimes ( 1 ) ;
522+ expect ( onSendMessage ) . toHaveBeenCalledWith ( 'Be right back' , undefined ) ;
523+ } ) ;
524+ } ) ;
525+
388526 describe ( 'edit message' , ( ) => {
389527 const onSendMessage = jest . fn ( ) ;
390528 const editCancel = jest . fn ( ) ;
0 commit comments