@@ -4,10 +4,13 @@ import { View } from 'react-native';
44import { act , cleanup , render , screen , waitFor } from '@testing-library/react-native' ;
55
66import type {
7+ Channel as ChannelLLC ,
78 ChannelAPIResponse ,
89 ChannelMemberResponse ,
910 LocalMessage ,
1011 ReactionResponse ,
12+ StreamChat ,
13+ UserResponse ,
1114} from 'stream-chat' ;
1215import { v4 as uuidv4 } from 'uuid' ;
1316
@@ -41,13 +44,40 @@ const Channel = ChannelRaw as unknown as React.ComponentType<
4144 React . ComponentProps < typeof ChannelRaw > & { initialValue ?: string }
4245> ;
4346
47+ // Tests reach into internal / private StreamChat + LLC Channel APIs (sync manager, legacy
48+ // `wsConnection`, `_deleteMessage`, `_sendReaction`, `_sendMessage`). Helpers narrow at the
49+ // call sites without sprinkling `any` everywhere.
50+ type TestPendingTask = { id : number ; type : string ; payload : unknown } ;
51+ type TestSyncManager = {
52+ invokeSyncStatusListeners : ( recovered : boolean ) => Promise < void > ;
53+ } ;
54+ // Intentionally not intersected with the real `StreamChat['offlineDb']` — the
55+ // real `syncManager` member is a class with `invokeSyncStatusListeners` marked
56+ // private, which conflicts with the test-only accessor. Kept as a standalone
57+ // test shim shape.
58+ type TestOfflineDb = {
59+ addPendingTask : ( task : {
60+ channelId : string | undefined ;
61+ channelType : string ;
62+ messageId : string ;
63+ payload : unknown ;
64+ type : string ;
65+ } ) => Promise < void > ;
66+ deletePendingTask : ( params : { id : number } ) => Promise < void > ;
67+ getPendingTasks : ( ) => Promise < TestPendingTask [ ] > ;
68+ syncManager : TestSyncManager ;
69+ } ;
70+ const getOfflineDb = ( client : StreamChat ) : TestOfflineDb =>
71+ client . offlineDb as unknown as TestOfflineDb ;
72+
4473test ( 'Workaround to allow exporting tests' , ( ) => expect ( true ) . toBe ( true ) ) ;
4574
4675export const OptimisticUpdates = ( ) => {
4776 describe ( 'Optimistic Updates' , ( ) => {
48- let chatClient ;
77+ let chatClient : StreamChat ;
4978
50- const getRandomInt = ( lower , upper ) => Math . floor ( lower + Math . random ( ) * ( upper - lower + 1 ) ) ;
79+ const getRandomInt = ( lower : number , upper : number ) =>
80+ Math . floor ( lower + Math . random ( ) * ( upper - lower + 1 ) ) ;
5181 const createChannel = ( ) => {
5282 const allUsers = Array ( 20 ) . fill ( 1 ) . map ( generateUser ) ;
5383 const allMessages : LocalMessage [ ] = [ ] ;
@@ -63,7 +93,7 @@ export const OptimisticUpdates = () => {
6393 const begin = getRandomInt ( 0 , allUsers . length - 2 ) ; // begin shouldn't be the end of users.length
6494 const end = getRandomInt ( begin + 1 , allUsers . length - 1 ) ;
6595 const usersForMembers = allUsers . slice ( begin , end ) ;
66- const members = usersForMembers . map ( ( user ) =>
96+ const members = usersForMembers . map ( ( user : UserResponse ) =>
6797 generateMember ( {
6898 user,
6999 } ) ,
@@ -78,7 +108,7 @@ export const OptimisticUpdates = () => {
78108 const end = getRandomInt ( begin + 1 , usersForMembers . length - 1 ) ;
79109
80110 const usersForReactions = usersForMembers . slice ( begin , end ) ;
81- const reactions = usersForReactions . map ( ( user ) =>
111+ const reactions = usersForReactions . map ( ( user : UserResponse ) =>
82112 generateReaction ( {
83113 message_id : id ,
84114 user,
@@ -94,7 +124,7 @@ export const OptimisticUpdates = () => {
94124 } ) ;
95125 } ) ;
96126
97- const reads = members . map ( ( member ) => ( {
127+ const reads = members . map ( ( member : ChannelMemberResponse ) => ( {
98128 last_read : new Date ( new Date ( ) . setDate ( new Date ( ) . getDate ( ) - getRandomInt ( 0 , 20 ) ) ) ,
99129 unread_messages : getRandomInt ( 0 , messages . length ) ,
100130 user : member . user ,
@@ -136,7 +166,10 @@ export const OptimisticUpdates = () => {
136166 channels : [ channelResponse ] as unknown as ChannelAPIResponse [ ] ,
137167 isLatestMessagesSet : true ,
138168 } ) ;
139- chatClient . wsConnection = { isHealthy : true , onlineStatusChanged : jest . fn ( ) } ;
169+ chatClient . wsConnection = {
170+ isHealthy : true ,
171+ onlineStatusChanged : jest . fn ( ) ,
172+ } as unknown as StreamChat [ 'wsConnection' ] ;
140173 } ) ;
141174
142175 afterEach ( ( ) => {
@@ -146,11 +179,19 @@ export const OptimisticUpdates = () => {
146179 jest . clearAllMocks ( ) ;
147180 } ) ;
148181
149- let channel ;
182+ let channel : ChannelLLC ;
150183 // This component is used for performing effects in a component that consumes ChannelContext,
151184 // i.e. making use of the callbacks & values provided by the Channel component.
152185 // the effect is called every time channelContext changes
153- const CallbackEffectWithContext = ( { callback, children, context } ) => {
186+ const CallbackEffectWithContext = < T , > ( {
187+ callback,
188+ children,
189+ context,
190+ } : {
191+ callback : ( ctx : T ) => Promise < void > | void ;
192+ children : React . ReactNode ;
193+ context : React . Context < T > ;
194+ } ) => {
154195 const ctx = useContext ( context ) ;
155196 const [ ready , setReady ] = useState ( false ) ;
156197 useEffect ( ( ) => {
@@ -166,7 +207,7 @@ export const OptimisticUpdates = () => {
166207 return null ;
167208 }
168209
169- return children ;
210+ return < > { children } </ > ;
170211 } ;
171212
172213 describe ( 'delete message' , ( ) => {
@@ -297,7 +338,7 @@ export const OptimisticUpdates = () => {
297338 localMessage : newMessage ,
298339 message : newMessage ,
299340 options : { } ,
300- } ) ;
341+ } as unknown as Awaited < ReturnType < typeof channel . messageComposer . compose > > ) ;
301342
302343 render (
303344 < Chat client = { chatClient } enableOfflineSupport >
@@ -340,7 +381,7 @@ export const OptimisticUpdates = () => {
340381 < CallbackEffectWithContext
341382 callback = { async ( { sendMessage } ) => {
342383 useMockedApis ( chatClient , [ sendMessageApi ( newMessage ) ] ) ;
343- await sendMessage ( { customMessageData : newMessage } ) ;
384+ await sendMessage ( ) ;
344385 } }
345386 context = { MessageInputContext }
346387 >
@@ -430,8 +471,8 @@ export const OptimisticUpdates = () => {
430471 < Channel
431472 channel = { channel }
432473 doUpdateMessageRequest = {
433- ( async ( _channelId , localMessage , options ) => {
434- await chatClient . offlineDb . addPendingTask ( {
474+ ( async ( _channelId : string , localMessage : LocalMessage , options : unknown ) => {
475+ await getOfflineDb ( chatClient ) . addPendingTask ( {
435476 channelId : channel . id ,
436477 channelType : channel . type ,
437478 messageId : message . id ,
@@ -475,8 +516,8 @@ export const OptimisticUpdates = () => {
475516 const dbMessages = await BetterSqlite . selectFromTable ( 'messages' ) ;
476517 const dbMessage = dbMessages . find ( ( row ) => row . id === message . id ) ;
477518
478- expect ( updatedMessage . text ) . toBe ( editedText ) ;
479- expect ( updatedMessage . message_text_updated_at ) . toBeTruthy ( ) ;
519+ expect ( updatedMessage ! . text ) . toBe ( editedText ) ;
520+ expect ( updatedMessage ! . message_text_updated_at ) . toBeTruthy ( ) ;
480521 expect ( pendingTasksRows ) . toHaveLength ( 1 ) ;
481522 expect ( pendingTasksRows [ 0 ] . type ) . toBe ( 'update-message' ) ;
482523 expect ( dbMessage ! . text ) . toBe ( editedText ) ;
@@ -527,7 +568,7 @@ export const OptimisticUpdates = () => {
527568 const dbMessages = await BetterSqlite . selectFromTable ( 'messages' ) ;
528569 const dbMessage = dbMessages . find ( ( row ) => row . id === message . id ) ;
529570
530- expect ( updatedMessage . text ) . toBe ( editedText ) ;
571+ expect ( updatedMessage ! . text ) . toBe ( editedText ) ;
531572 expect ( pendingTasksRows ) . toHaveLength ( 0 ) ;
532573 expect ( dbMessage ! . text ) . toBe ( editedText ) ;
533574 } ) ;
@@ -638,8 +679,8 @@ export const OptimisticUpdates = () => {
638679 const dbMessage = dbMessages . find ( ( row ) => row . id === message . id ) ;
639680 const storedAttachments = JSON . parse ( dbMessage ! . attachments as string ) ;
640681
641- expect ( updatedMessage . text ) . toBe ( editedText ) ;
642- expect ( updatedMessage . attachments [ 0 ] . asset_url ) . toBe ( localUri ) ;
682+ expect ( updatedMessage ! . text ) . toBe ( editedText ) ;
683+ expect ( updatedMessage ! . attachments ! [ 0 ] . asset_url ) . toBe ( localUri ) ;
643684 expect ( pendingTasksRows ) . toHaveLength ( 0 ) ;
644685 expect ( dbMessage ! . text ) . toBe ( editedText ) ;
645686 expect ( storedAttachments [ 0 ] . asset_url ) . toBe ( localUri ) ;
@@ -706,7 +747,7 @@ export const OptimisticUpdates = () => {
706747 localMessage : newMessage ,
707748 message : newMessage ,
708749 options : { } ,
709- } ) ;
750+ } as unknown as Awaited < ReturnType < typeof channel . messageComposer . compose > > ) ;
710751
711752 // initialValue is needed as a prop to trick the message input ctx into thinking
712753 // we are sending a message.
@@ -760,11 +801,11 @@ export const OptimisticUpdates = () => {
760801 user_id : chatClient . userID ,
761802 } ) ;
762803
763- jest . spyOn ( channel . messageComposer , 'compose' ) . mockResolvedValue ( {
764- localMessage ,
765- message : localMessage ,
766- options : { } ,
767- } ) ;
804+ jest
805+ . spyOn ( channel . messageComposer , 'compose' )
806+ . mockResolvedValue ( { localMessage , message : localMessage } as unknown as Awaited <
807+ ReturnType < typeof channel . messageComposer . compose >
808+ > ) ;
768809
769810 render (
770811 < Chat client = { chatClient } enableOfflineSupport >
@@ -783,23 +824,25 @@ export const OptimisticUpdates = () => {
783824 ) ;
784825 await waitFor ( ( ) => expect ( screen . getByTestId ( 'children' ) ) . toBeTruthy ( ) ) ;
785826
786- let pendingTask ;
827+ let pendingTask : TestPendingTask | undefined ;
787828 await waitFor ( async ( ) => {
788- const pendingTasks = await chatClient . offlineDb . getPendingTasks ( ) ;
829+ const pendingTasks = await getOfflineDb ( chatClient ) . getPendingTasks ( ) ;
789830 expect ( pendingTasks ) . toHaveLength ( 1 ) ;
790831 pendingTask = pendingTasks [ 0 ] ;
791832 } ) ;
792833
793834 expect ( channel . state . messages . some ( ( message ) => message . id === localMessage . id ) ) . toBe ( true ) ;
794835
795- jest . spyOn ( channel , 'watch' ) . mockResolvedValue ( { } ) ;
836+ jest
837+ . spyOn ( channel , 'watch' )
838+ . mockResolvedValue ( { } as Awaited < ReturnType < typeof channel . watch > > ) ;
796839
797840 channel . state . removeMessage ( localMessage ) ;
798841 channel . state . addMessageSorted ( serverMessage , true ) ;
799- await chatClient . offlineDb . deletePendingTask ( { id : pendingTask . id } ) ;
842+ await getOfflineDb ( chatClient ) . deletePendingTask ( { id : pendingTask ! . id } ) ;
800843
801844 await act ( async ( ) => {
802- await chatClient . offlineDb . syncManager . invokeSyncStatusListeners ( true ) ;
845+ await getOfflineDb ( chatClient ) . syncManager . invokeSyncStatusListeners ( true ) ;
803846 } ) ;
804847
805848 await waitFor ( ( ) => {
@@ -821,11 +864,11 @@ export const OptimisticUpdates = () => {
821864 user_id : chatClient . userID ,
822865 } ) ;
823866
824- jest . spyOn ( channel . messageComposer , 'compose' ) . mockResolvedValue ( {
825- localMessage ,
826- message : localMessage ,
827- options : { } ,
828- } ) ;
867+ jest
868+ . spyOn ( channel . messageComposer , 'compose' )
869+ . mockResolvedValue ( { localMessage , message : localMessage } as unknown as Awaited <
870+ ReturnType < typeof channel . messageComposer . compose >
871+ > ) ;
829872
830873 render (
831874 < Chat client = { chatClient } enableOfflineSupport >
@@ -844,20 +887,22 @@ export const OptimisticUpdates = () => {
844887 ) ;
845888 await waitFor ( ( ) => expect ( screen . getByTestId ( 'children' ) ) . toBeTruthy ( ) ) ;
846889
847- let pendingTask ;
890+ let pendingTask : TestPendingTask | undefined ;
848891 await waitFor ( async ( ) => {
849- const pendingTasks = await chatClient . offlineDb . getPendingTasks ( ) ;
892+ const pendingTasks = await getOfflineDb ( chatClient ) . getPendingTasks ( ) ;
850893 expect ( pendingTasks ) . toHaveLength ( 1 ) ;
851894 pendingTask = pendingTasks [ 0 ] ;
852895 } ) ;
853896
854- jest . spyOn ( channel , 'watch' ) . mockResolvedValue ( { } ) ;
897+ jest
898+ . spyOn ( channel , 'watch' )
899+ . mockResolvedValue ( { } as Awaited < ReturnType < typeof channel . watch > > ) ;
855900
856901 channel . state . removeMessage ( localMessage ) ;
857- await chatClient . offlineDb . deletePendingTask ( { id : pendingTask . id } ) ;
902+ await getOfflineDb ( chatClient ) . deletePendingTask ( { id : pendingTask ! . id } ) ;
858903
859904 await act ( async ( ) => {
860- await chatClient . offlineDb . syncManager . invokeSyncStatusListeners ( true ) ;
905+ await getOfflineDb ( chatClient ) . syncManager . invokeSyncStatusListeners ( true ) ;
861906 } ) ;
862907
863908 await waitFor ( ( ) => {
0 commit comments