stream-chat-react-native-core 5.13.0-beta.2 → 5.13.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/commonjs/mock-builders/api/sendReaction.js.map +1 -1
  2. package/lib/commonjs/version.json +1 -1
  3. package/lib/module/mock-builders/api/sendReaction.js.map +1 -1
  4. package/lib/module/version.json +1 -1
  5. package/package.json +1 -1
  6. package/src/__tests__/offline-support/index.test.ts +13 -0
  7. package/src/__tests__/offline-support/offline-feature.js +686 -0
  8. package/src/__tests__/offline-support/optimistic-update.js +374 -0
  9. package/src/version.json +1 -1
  10. package/lib/typescript/components/Attachment/__tests__/openUrlSafely.test.d.ts +0 -1
  11. package/lib/typescript/components/ChannelList/hooks/listeners/__tests__/useChannelUpdated.test.d.ts +0 -1
  12. package/lib/typescript/components/ChannelPreview/__tests__/ChannelPreview.test.d.ts +0 -1
  13. package/lib/typescript/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayName.test.d.ts +0 -1
  14. package/lib/typescript/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.d.ts +0 -1
  15. package/lib/typescript/components/Chat/hooks/__tests__/useAppSettings.test.d.ts +0 -1
  16. package/lib/typescript/components/ImageGallery/__tests__/AnimatedVideoGallery.test.d.ts +0 -1
  17. package/lib/typescript/components/ImageGallery/__tests__/ImageGallery.test.d.ts +0 -1
  18. package/lib/typescript/components/ImageGallery/__tests__/ImageGalleryFooter.test.d.ts +0 -1
  19. package/lib/typescript/components/ImageGallery/__tests__/ImageGalleryGrid.test.d.ts +0 -1
  20. package/lib/typescript/components/ImageGallery/__tests__/ImageGalleryGridHandle.test.d.ts +0 -1
  21. package/lib/typescript/components/ImageGallery/__tests__/ImageGalleryHeader.test.d.ts +0 -1
  22. package/lib/typescript/components/ImageGallery/__tests__/ImageGalleryOverlay.test.d.ts +0 -1
  23. package/lib/typescript/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.d.ts +0 -1
  24. package/lib/typescript/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.d.ts +0 -1
  25. package/lib/typescript/components/Message/MessageSimple/__tests__/MessageTextContainer.test.d.ts +0 -1
  26. package/lib/typescript/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.d.ts +0 -1
  27. package/lib/typescript/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.d.ts +0 -1
  28. package/lib/typescript/components/MessageList/__tests__/useMessageList.test.d.ts +0 -1
  29. package/lib/typescript/components/MessageList/utils/__tests__/getDateSeparators.test.d.ts +0 -1
  30. package/lib/typescript/components/Reply/__tests__/Reply.test.d.ts +0 -1
  31. package/lib/typescript/contexts/__tests__/index.test.d.ts +0 -1
  32. package/lib/typescript/contexts/messageInputContext/__tests__/MessageInputContext.test.d.ts +0 -1
  33. package/lib/typescript/contexts/messageInputContext/__tests__/isValidMessage.test.d.ts +0 -1
  34. package/lib/typescript/contexts/messageInputContext/__tests__/pickFile.test.d.ts +0 -1
  35. package/lib/typescript/contexts/messageInputContext/__tests__/removeFile.test.d.ts +0 -1
  36. package/lib/typescript/contexts/messageInputContext/__tests__/removeImage.test.d.ts +0 -1
  37. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessage.test.d.ts +0 -1
  38. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessageAsync.test.d.ts +0 -1
  39. package/lib/typescript/contexts/messageInputContext/__tests__/updateMessage.test.d.ts +0 -1
  40. package/lib/typescript/contexts/messageInputContext/__tests__/uploadFile.test.d.ts +0 -1
  41. package/lib/typescript/contexts/messageInputContext/__tests__/uploadImage.test.d.ts +0 -1
  42. package/lib/typescript/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.d.ts +0 -1
  43. package/lib/typescript/hooks/__tests__/useAppStateListener.test.d.ts +0 -1
  44. package/lib/typescript/hooks/__tests__/useTranslatedMessage.test.d.ts +0 -1
  45. package/lib/typescript/mock-builders/api/channelMocks.d.ts +0 -14
  46. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +0 -19
  47. package/lib/typescript/mock-builders/api/queryMembers.d.ts +0 -161
  48. package/lib/typescript/mock-builders/api/useMockedApis.d.ts +0 -1
  49. package/lib/typescript/mock-builders/api/utils.d.ts +0 -7
  50. package/lib/typescript/mock-builders/event/messageNew.d.ts +0 -2
  51. package/lib/typescript/mock-builders/event/messageRead.d.ts +0 -8
  52. package/lib/typescript/mock-builders/generator/attachment.d.ts +0 -10
  53. package/lib/typescript/mock-builders/generator/channel.d.ts +0 -120
  54. package/lib/typescript/mock-builders/generator/message.d.ts +0 -38
  55. package/lib/typescript/mock-builders/generator/user.d.ts +0 -30
  56. package/lib/typescript/mock-builders/mock.d.ts +0 -3
  57. package/lib/typescript/store/sqlite-utils/__tests__/createSelectQuery.test.d.ts +0 -1
  58. package/lib/typescript/utils/__tests__/patchMessageTextCommand.test.d.ts +0 -1
  59. package/src/__tests__/offline-feature.test.js +0 -675
  60. package/src/__tests__/optimistic-update.test.js +0 -369
  61. /package/src/mock-builders/api/{sendReaction.tsx → sendReaction.ts} +0 -0
@@ -0,0 +1,374 @@
1
+ /* eslint-disable jest/no-export */
2
+ /* eslint-disable no-underscore-dangle */
3
+ import React, { useContext, useEffect, useState } from 'react';
4
+ import { View } from 'react-native';
5
+
6
+ import { act, cleanup, render, waitFor } from '@testing-library/react-native';
7
+
8
+ import { v4 as uuidv4 } from 'uuid';
9
+
10
+ import { Channel } from '../../components/Channel/Channel';
11
+ import { Chat } from '../../components/Chat/Chat';
12
+ import { MessagesContext } from '../../contexts';
13
+ import { deleteMessageApi } from '../../mock-builders/api/deleteMessage';
14
+ import { deleteReactionApi } from '../../mock-builders/api/deleteReaction';
15
+ import { erroredDeleteApi, erroredPostApi } from '../../mock-builders/api/error';
16
+ import { getOrCreateChannelApi } from '../../mock-builders/api/getOrCreateChannel';
17
+ import { sendReactionApi } from '../../mock-builders/api/sendReaction';
18
+ import { useMockedApis } from '../../mock-builders/api/useMockedApis';
19
+ import dispatchConnectionChangedEvent from '../../mock-builders/event/connectionChanged';
20
+ import { generateChannelResponse } from '../../mock-builders/generator/channel';
21
+ import { generateMember } from '../../mock-builders/generator/member';
22
+ import { generateMessage } from '../../mock-builders/generator/message';
23
+ import { generateReaction } from '../../mock-builders/generator/reaction';
24
+ import { generateUser } from '../../mock-builders/generator/user';
25
+ import { getTestClientWithUser } from '../../mock-builders/mock';
26
+ import { upsertChannels } from '../../store/apis';
27
+ import { QuickSqliteClient } from '../../store/QuickSqliteClient';
28
+ import { BetterSqlite } from '../../test-utils/BetterSqlite';
29
+
30
+ test('Workaround to allow exporting tests', () => expect(true).toBe(true));
31
+
32
+ export const OptimisticUpdates = () => {
33
+ describe('Optimistic Updates', () => {
34
+ let chatClient;
35
+
36
+ const getRandomInt = (lower, upper) => Math.floor(lower + Math.random() * (upper - lower + 1));
37
+ const createChannel = () => {
38
+ const allUsers = Array(20).fill(1).map(generateUser);
39
+ const allMessages = [];
40
+ const allMembers = [];
41
+ const allReactions = [];
42
+ const allReads = [];
43
+ const id = uuidv4();
44
+ const cid = `messaging:${id}`;
45
+ const begin = getRandomInt(0, allUsers.length - 2); // begin shouldn't be the end of users.length
46
+ const end = getRandomInt(begin + 1, allUsers.length - 1);
47
+ const usersForMembers = allUsers.slice(begin, end);
48
+ const members = usersForMembers.map((user) =>
49
+ generateMember({
50
+ cid,
51
+ user,
52
+ }),
53
+ );
54
+ const messages = Array(10)
55
+ .fill(1)
56
+ .map(() => {
57
+ const id = uuidv4();
58
+ const user = usersForMembers[getRandomInt(0, usersForMembers.length - 1)];
59
+
60
+ const begin = getRandomInt(0, usersForMembers.length - 2); // begin shouldn't be the end of users.length
61
+ const end = getRandomInt(begin + 1, usersForMembers.length - 1);
62
+
63
+ const usersForReactions = usersForMembers.slice(begin, end);
64
+ const reactions = usersForReactions.map((user) =>
65
+ generateReaction({
66
+ message_id: id,
67
+ user,
68
+ }),
69
+ );
70
+ allReactions.push(...reactions);
71
+ return generateMessage({
72
+ cid,
73
+ id,
74
+ latest_reactions: reactions,
75
+ user,
76
+ userId: user.id,
77
+ });
78
+ });
79
+
80
+ const reads = members.map((member) => ({
81
+ last_read: new Date(new Date().setDate(new Date().getDate() - getRandomInt(0, 20))),
82
+ unread_messages: getRandomInt(0, messages.length),
83
+ user: member.user,
84
+ }));
85
+
86
+ allMessages.push(...messages);
87
+ allMembers.push(...members);
88
+ allReads.push(...reads);
89
+
90
+ return generateChannelResponse({
91
+ cid,
92
+ id,
93
+ members,
94
+ messages,
95
+ });
96
+ };
97
+
98
+ beforeEach(async () => {
99
+ jest.clearAllMocks();
100
+
101
+ chatClient = await getTestClientWithUser({ id: 'dan' });
102
+ const channelResponse = createChannel();
103
+ useMockedApis(chatClient, [getOrCreateChannelApi(channelResponse)]);
104
+ channel = chatClient.channel('messaging', channelResponse.id);
105
+ await channel.watch();
106
+
107
+ channel.cid = channelResponse.channel.cid;
108
+ channel.id = channelResponse.channel.id;
109
+
110
+ // Populate the DB with channel
111
+ QuickSqliteClient.initializeDatabase();
112
+ upsertChannels({
113
+ channels: [channelResponse],
114
+ isLatestMessagesSet: true,
115
+ });
116
+ });
117
+
118
+ afterEach(() => {
119
+ QuickSqliteClient.dropTables();
120
+ QuickSqliteClient.closeDB();
121
+ cleanup();
122
+ });
123
+
124
+ let channel;
125
+ // This component is used for performing effects in a component that consumes ChannelContext,
126
+ // i.e. making use of the callbacks & values provided by the Channel component.
127
+ // the effect is called every time channelContext changes
128
+ const CallbackEffectWithContext = ({ callback, children, context }) => {
129
+ const ctx = useContext(context);
130
+ const [ready, setReady] = useState(false);
131
+ useEffect(() => {
132
+ const call = async () => {
133
+ await callback(ctx);
134
+ setReady(true);
135
+ };
136
+
137
+ call();
138
+ }, []);
139
+
140
+ if (!ready) return null;
141
+
142
+ return children;
143
+ };
144
+
145
+ describe('delete message', () => {
146
+ it('pending task should exist if deleteMessage request fails', async () => {
147
+ const message = generateMessage();
148
+
149
+ const { getByTestId } = render(
150
+ <Chat client={chatClient} enableOfflineSupport>
151
+ <Channel channel={channel} initialValue={message.text}>
152
+ <CallbackEffectWithContext
153
+ callback={async ({ deleteMessage }) => {
154
+ useMockedApis(chatClient, [erroredPostApi()]);
155
+ try {
156
+ await deleteMessage(message);
157
+ } catch (e) {
158
+ // do nothing
159
+ }
160
+ }}
161
+ context={MessagesContext}
162
+ >
163
+ <View testID='children' />
164
+ </CallbackEffectWithContext>
165
+ </Channel>
166
+ </Chat>,
167
+ );
168
+ await waitFor(() => expect(getByTestId('children')).toBeTruthy());
169
+ await waitFor(() => {
170
+ const pendingTasksRows = BetterSqlite.selectFromTable('pendingTasks');
171
+ const pendingTaskType = pendingTasksRows?.[0]?.type;
172
+ const pendingTaskPayload = JSON.parse(pendingTasksRows?.[0]?.payload || '{}');
173
+ expect(pendingTaskType).toBe('delete-message');
174
+ expect(pendingTaskPayload[0]).toBe(message.id);
175
+ });
176
+ });
177
+
178
+ it('pending task should be cleared if deleteMessage request is succesful', async () => {
179
+ const message = generateMessage();
180
+ const { getByTestId } = render(
181
+ <Chat client={chatClient} enableOfflineSupport>
182
+ <Channel channel={channel} initialValue={message.text}>
183
+ <CallbackEffectWithContext
184
+ callback={({ deleteMessage }) => {
185
+ useMockedApis(chatClient, [deleteMessageApi(message)]);
186
+ deleteMessage(message);
187
+ }}
188
+ context={MessagesContext}
189
+ >
190
+ <View testID='children' />
191
+ </CallbackEffectWithContext>
192
+ </Channel>
193
+ </Chat>,
194
+ );
195
+ await waitFor(() => expect(getByTestId('children')).toBeTruthy());
196
+ await waitFor(() => {
197
+ const pendingTasksRows = BetterSqlite.selectFromTable('pendingTasks');
198
+ expect(pendingTasksRows.length).toBe(0);
199
+ });
200
+ });
201
+ });
202
+
203
+ describe('send reaction', () => {
204
+ it('pending task should exist if sendReaction request fails', async () => {
205
+ const reaction = generateReaction();
206
+ const targetMessage = channel.state.messages[0];
207
+
208
+ const { getByTestId } = render(
209
+ <Chat client={chatClient} enableOfflineSupport>
210
+ <Channel channel={channel}>
211
+ <CallbackEffectWithContext
212
+ callback={async ({ sendReaction }) => {
213
+ useMockedApis(chatClient, [erroredPostApi()]);
214
+ try {
215
+ await sendReaction(reaction.type, targetMessage.id);
216
+ } catch (e) {
217
+ // do nothing
218
+ }
219
+ }}
220
+ context={MessagesContext}
221
+ >
222
+ <View testID='children' />
223
+ </CallbackEffectWithContext>
224
+ </Channel>
225
+ </Chat>,
226
+ );
227
+ await waitFor(() => expect(getByTestId('children')).toBeTruthy());
228
+ await waitFor(() => {
229
+ const pendingTasksRows = BetterSqlite.selectFromTable('pendingTasks');
230
+ const pendingTaskType = pendingTasksRows?.[0]?.type;
231
+ const pendingTaskPayload = JSON.parse(pendingTasksRows?.[0]?.payload || '{}');
232
+ expect(pendingTaskType).toBe('send-reaction');
233
+ expect(pendingTaskPayload[0]).toBe(targetMessage.id);
234
+ });
235
+ });
236
+
237
+ it('pending task should be cleared if sendReaction request is succesful', async () => {
238
+ const reaction = generateReaction();
239
+ const targetMessage = channel.state.messages[0];
240
+
241
+ const { getByTestId } = render(
242
+ <Chat client={chatClient} enableOfflineSupport>
243
+ <Channel channel={channel}>
244
+ <CallbackEffectWithContext
245
+ callback={({ sendReaction }) => {
246
+ useMockedApis(chatClient, [sendReactionApi(targetMessage, reaction)]);
247
+ sendReaction(reaction.type, targetMessage.id);
248
+ }}
249
+ context={MessagesContext}
250
+ >
251
+ <View testID='children' />
252
+ </CallbackEffectWithContext>
253
+ </Channel>
254
+ </Chat>,
255
+ );
256
+ await waitFor(() => expect(getByTestId('children')).toBeTruthy());
257
+ await waitFor(() => {
258
+ const pendingTasksRows = BetterSqlite.selectFromTable('pendingTasks');
259
+ expect(pendingTasksRows.length).toBe(0);
260
+ });
261
+ });
262
+ });
263
+
264
+ describe('delete reaction', () => {
265
+ it('pending task should exist if deleteReaction request fails', async () => {
266
+ const reaction = generateReaction();
267
+ const targetMessage = channel.state.messages[0];
268
+
269
+ const { getByTestId } = render(
270
+ <Chat client={chatClient} enableOfflineSupport>
271
+ <Channel channel={channel}>
272
+ <CallbackEffectWithContext
273
+ callback={async ({ deleteReaction }) => {
274
+ useMockedApis(chatClient, [erroredPostApi()]);
275
+ try {
276
+ await deleteReaction(reaction.type, targetMessage.id);
277
+ } catch (e) {
278
+ // do nothing
279
+ }
280
+ }}
281
+ context={MessagesContext}
282
+ >
283
+ <View testID='children' />
284
+ </CallbackEffectWithContext>
285
+ </Channel>
286
+ </Chat>,
287
+ );
288
+ await waitFor(() => expect(getByTestId('children')).toBeTruthy());
289
+ await waitFor(() => {
290
+ const pendingTasksRows = BetterSqlite.selectFromTable('pendingTasks');
291
+ const pendingTaskType = pendingTasksRows?.[0]?.type;
292
+ const pendingTaskPayload = JSON.parse(pendingTasksRows?.[0]?.payload || '{}');
293
+ expect(pendingTaskType).toBe('delete-reaction');
294
+ expect(pendingTaskPayload[0]).toBe(targetMessage.id);
295
+ });
296
+ });
297
+
298
+ it('pending task should be cleared if deleteReaction request is succesful', async () => {
299
+ const reaction = generateReaction();
300
+ const targetMessage = channel.state.messages[0];
301
+
302
+ const { getByTestId } = render(
303
+ <Chat client={chatClient} enableOfflineSupport>
304
+ <Channel channel={channel}>
305
+ <CallbackEffectWithContext
306
+ callback={async ({ deleteReaction }) => {
307
+ useMockedApis(chatClient, [deleteReactionApi(targetMessage, reaction)]);
308
+ await deleteReaction(reaction.type, targetMessage.id);
309
+ }}
310
+ context={MessagesContext}
311
+ >
312
+ <View testID='children' />
313
+ </CallbackEffectWithContext>
314
+ </Channel>
315
+ </Chat>,
316
+ );
317
+ await waitFor(() => expect(getByTestId('children')).toBeTruthy());
318
+
319
+ await waitFor(() => {
320
+ const pendingTasksRows = BetterSqlite.selectFromTable('pendingTasks');
321
+ expect(pendingTasksRows.length).toBe(0);
322
+ });
323
+ });
324
+ });
325
+
326
+ it('pending task should be executed after connection is recovered', async () => {
327
+ const message = channel.state.messages[0];
328
+ const reaction = generateReaction();
329
+
330
+ const { getByTestId } = render(
331
+ <Chat client={chatClient} enableOfflineSupport>
332
+ <Channel channel={channel} initialValue={message.text}>
333
+ <CallbackEffectWithContext
334
+ callback={async ({ deleteMessage, sendReaction }) => {
335
+ useMockedApis(chatClient, [erroredDeleteApi()]);
336
+ try {
337
+ await deleteMessage(reaction);
338
+ } catch (e) {
339
+ // do nothing
340
+ }
341
+
342
+ useMockedApis(chatClient, [erroredPostApi()]);
343
+ try {
344
+ await sendReaction(reaction.type, message.id);
345
+ } catch (e) {
346
+ // do nothing
347
+ }
348
+ }}
349
+ context={MessagesContext}
350
+ >
351
+ <View testID='children' />
352
+ </CallbackEffectWithContext>
353
+ </Channel>
354
+ </Chat>,
355
+ );
356
+ await waitFor(() => expect(getByTestId('children')).toBeTruthy());
357
+
358
+ await waitFor(() => {
359
+ const pendingTasksRows = BetterSqlite.selectFromTable('pendingTasks');
360
+ expect(pendingTasksRows.length).toBe(2);
361
+ });
362
+
363
+ chatClient.deleteMessage = jest.fn();
364
+ channel.sendReaction = jest.fn();
365
+
366
+ act(() => dispatchConnectionChangedEvent(chatClient, true));
367
+
368
+ await waitFor(() => {
369
+ expect(chatClient.deleteMessage).toHaveBeenCalled();
370
+ expect(channel.sendReaction).toHaveBeenCalled();
371
+ });
372
+ });
373
+ });
374
+ };
package/src/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "5.13.0-beta.2"
2
+ "version": "5.13.0-beta.3"
3
3
  }
@@ -1 +0,0 @@
1
- import '@testing-library/jest-native/extend-expect';
@@ -1 +0,0 @@
1
- export {};
@@ -1,14 +0,0 @@
1
- import type { Channel, FormatMessageResponse, MessageResponse } from 'stream-chat';
2
- import type { DefaultStreamChatGenerics } from '../../types/types';
3
- declare const CHANNEL: Channel<DefaultStreamChatGenerics>;
4
- declare const CHANNEL_WITH_MESSAGES_TEXT: Channel<DefaultStreamChatGenerics>;
5
- declare const CHANNEL_WITH_DELETED_MESSAGES: Channel<DefaultStreamChatGenerics>;
6
- declare const CHANNEL_WITH_NO_MESSAGES: Channel<DefaultStreamChatGenerics>;
7
- declare const CHANNEL_WITH_MESSAGE_COMMAND: Channel<DefaultStreamChatGenerics>;
8
- declare const CHANNEL_WITH_MESSAGES_ATTACHMENTS: Channel<DefaultStreamChatGenerics>;
9
- declare const LATEST_MESSAGE: MessageResponse<DefaultStreamChatGenerics>;
10
- declare const FORMATTED_MESSAGE: FormatMessageResponse<DefaultStreamChatGenerics>;
11
- declare const CHANNEL_WITH_MENTIONED_USERS: Channel<DefaultStreamChatGenerics>;
12
- declare const CHANNEL_WITH_EMPTY_MESSAGE: Channel<DefaultStreamChatGenerics>;
13
- declare const CHANNEL_WITH_MESSAGES: Channel<DefaultStreamChatGenerics>;
14
- export { CHANNEL, CHANNEL_WITH_EMPTY_MESSAGE, CHANNEL_WITH_MESSAGES, CHANNEL_WITH_MENTIONED_USERS, FORMATTED_MESSAGE, LATEST_MESSAGE, CHANNEL_WITH_MESSAGES_ATTACHMENTS, CHANNEL_WITH_MESSAGE_COMMAND as CHANNEL_WITH_MESSAGES_COMMAND, CHANNEL_WITH_NO_MESSAGES, CHANNEL_WITH_DELETED_MESSAGES, CHANNEL_WITH_MESSAGES_TEXT, };
@@ -1,19 +0,0 @@
1
- export declare type GetOrCreateChannelApiParams = {
2
- channel?: Record<string, any>;
3
- members?: Record<string, any>[];
4
- messages?: Record<string, any>[];
5
- };
6
- /**
7
- * Returns the api response for queryChannel api.
8
- *
9
- * api - /channels/{type}/{id}/query
10
- *
11
- * @param {*} channel
12
- */
13
- export declare const getOrCreateChannelApi: (channel?: GetOrCreateChannelApiParams) => {
14
- response: {
15
- data: any;
16
- status: number;
17
- };
18
- type: string;
19
- };