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,686 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ /* eslint-disable jest/no-export */
3
+ import React from 'react';
4
+ import { Text, 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 { ChannelList } from '../../components/ChannelList/ChannelList';
11
+ import { Chat } from '../../components/Chat/Chat';
12
+ import { useChannelsContext } from '../../contexts/channelsContext/ChannelsContext';
13
+ import { getOrCreateChannelApi } from '../../mock-builders/api/getOrCreateChannel';
14
+ import { queryChannelsApi } from '../../mock-builders/api/queryChannels';
15
+ import { useMockedApis } from '../../mock-builders/api/useMockedApis';
16
+ import dispatchChannelTruncatedEvent from '../../mock-builders/event/channelTruncated';
17
+ import dispatchChannelUpdatedEvent from '../../mock-builders/event/channelUpdated';
18
+ import dispatchConnectionChangedEvent from '../../mock-builders/event/connectionChanged';
19
+ import dispatchMemberAddedEvent from '../../mock-builders/event/memberAdded';
20
+ import dispatchMemberRemovedEvent from '../../mock-builders/event/memberRemoved';
21
+ import dispatchMemberUpdatedEvent from '../../mock-builders/event/memberUpdated';
22
+ import dispatchMessageNewEvent from '../../mock-builders/event/messageNew';
23
+ import dispatchMessageReadEvent from '../../mock-builders/event/messageRead';
24
+ import dispatchMessageUpdatedEvent from '../../mock-builders/event/messageUpdated';
25
+ import dispatchNotificationAddedToChannel from '../../mock-builders/event/notificationAddedToChannel';
26
+ import dispatchNotificationMessageNewEvent from '../../mock-builders/event/notificationMessageNew';
27
+ import dispatchNotificationRemovedFromChannel from '../../mock-builders/event/notificationRemovedFromChannel';
28
+ import dispatchReactionDeletedEvent from '../../mock-builders/event/reactionDeleted';
29
+ import dispatchReactionNewEvent from '../../mock-builders/event/reactionNew';
30
+ import dispatchReactionUpdatedEvent from '../../mock-builders/event/reactionUpdated';
31
+ import { generateChannelResponse } from '../../mock-builders/generator/channel';
32
+ import { generateMember } from '../../mock-builders/generator/member';
33
+ import { generateMessage } from '../../mock-builders/generator/message';
34
+ import { generateReaction } from '../../mock-builders/generator/reaction';
35
+ import { generateUser } from '../../mock-builders/generator/user';
36
+ import { getTestClientWithUser } from '../../mock-builders/mock';
37
+ import { convertFilterSortToQuery } from '../../store/apis/utils/convertFilterSortToQuery';
38
+ import { QuickSqliteClient } from '../../store/QuickSqliteClient';
39
+ import { tables } from '../../store/schema';
40
+ import { BetterSqlite } from '../../test-utils/BetterSqlite';
41
+
42
+ /**
43
+ * We are gonna use following custom UI components for preview and list.
44
+ * If we use ChannelPreviewMessenger or ChannelPreviewLastMessage here, then changes
45
+ * to those components might end up breaking tests for ChannelList, which will be quite painful
46
+ * to debug.
47
+ */
48
+ const ChannelPreviewComponent = ({ channel, setActiveChannel }) => (
49
+ <View accessibilityRole='list-item' onPress={setActiveChannel} testID={channel.cid}>
50
+ <Text>{channel.data.name}</Text>
51
+ <Text>{channel.state.messages[0]?.text}</Text>
52
+ </View>
53
+ );
54
+
55
+ const ChannelListComponent = (props) => {
56
+ const { channels, onSelect } = useChannelsContext();
57
+ if (!channels) return null;
58
+
59
+ return (
60
+ <View testID='channel-list'>
61
+ {channels?.map((channel) => (
62
+ <ChannelPreviewComponent
63
+ {...props}
64
+ channel={channel}
65
+ key={channel.id}
66
+ setActiveChannel={onSelect}
67
+ />
68
+ ))}
69
+ </View>
70
+ );
71
+ };
72
+
73
+ test('Workaround to allow exporting tests', () => expect(true).toBe(true));
74
+
75
+ export const Generic = () => {
76
+ describe('Offline support is disabled', () => {
77
+ let chatClient;
78
+
79
+ beforeEach(async () => {
80
+ jest.clearAllMocks();
81
+ chatClient = await getTestClientWithUser({ id: 'dan' });
82
+ QuickSqliteClient.dropTables();
83
+ QuickSqliteClient.closeDB();
84
+ });
85
+
86
+ afterEach(() => {
87
+ QuickSqliteClient.dropTables();
88
+ QuickSqliteClient.closeDB();
89
+ cleanup();
90
+ });
91
+
92
+ it('should NOT create tables on first load if offline feature is disabled', async () => {
93
+ const { getByTestId } = render(
94
+ <Chat client={chatClient}>
95
+ <View testID='test-child'></View>
96
+ </Chat>,
97
+ );
98
+ await waitFor(() => expect(getByTestId('test-child')).toBeTruthy());
99
+
100
+ const tablesInDb = BetterSqlite.getTables();
101
+ const tableNamesInDB = tablesInDb.map((table) => table.name);
102
+ const tablesNamesInSchema = Object.keys(tables);
103
+
104
+ tablesNamesInSchema.forEach((name) => expect(tableNamesInDB.includes(name)).toBe(false));
105
+ });
106
+ });
107
+
108
+ describe('Offline support is enabled', () => {
109
+ let chatClient;
110
+ let channels;
111
+
112
+ let allUsers;
113
+ let allMessages;
114
+ let allMembers;
115
+ let allReactions;
116
+ let allReads;
117
+ const getRandomInt = (lower, upper) => Math.floor(lower + Math.random() * (upper - lower + 1));
118
+ const createChannel = () => {
119
+ const id = uuidv4();
120
+ const cid = `messaging:${id}`;
121
+ const begin = getRandomInt(0, allUsers.length - 2); // begin shouldn't be the end of users.length
122
+ const end = getRandomInt(begin + 1, allUsers.length - 1);
123
+ const usersForMembers = allUsers.slice(begin, end);
124
+ const members = usersForMembers.map((user) =>
125
+ generateMember({
126
+ cid,
127
+ user,
128
+ }),
129
+ );
130
+ const messages = Array(10)
131
+ .fill(1)
132
+ .map(() => {
133
+ const id = uuidv4();
134
+ const user = usersForMembers[getRandomInt(0, usersForMembers.length - 1)];
135
+
136
+ const begin = getRandomInt(0, usersForMembers.length - 2); // begin shouldn't be the end of users.length
137
+ const end = getRandomInt(begin + 1, usersForMembers.length - 1);
138
+
139
+ const usersForReactions = usersForMembers.slice(begin, end);
140
+ const reactions = usersForReactions.map((user) =>
141
+ generateReaction({
142
+ message_id: id,
143
+ user,
144
+ }),
145
+ );
146
+ allReactions.push(...reactions);
147
+ return generateMessage({
148
+ cid,
149
+ id,
150
+ latest_reactions: reactions,
151
+ user,
152
+ userId: user.id,
153
+ });
154
+ });
155
+
156
+ const reads = members.map((member) => ({
157
+ last_read: new Date(new Date().setDate(new Date().getDate() - getRandomInt(0, 20))),
158
+ unread_messages: getRandomInt(0, messages.length),
159
+ user: member.user,
160
+ }));
161
+
162
+ allMessages.push(...messages);
163
+ allMembers.push(...members);
164
+ allReads.push(...reads);
165
+
166
+ return generateChannelResponse({
167
+ cid,
168
+ id,
169
+ members,
170
+ messages,
171
+ });
172
+ };
173
+
174
+ beforeEach(async () => {
175
+ jest.clearAllMocks();
176
+ allUsers = Array(20).fill(1).map(generateUser);
177
+ allMessages = [];
178
+ allMembers = [];
179
+ allReactions = [];
180
+ allReads = [];
181
+ channels = Array(10)
182
+ .fill(1)
183
+ .map(() => createChannel());
184
+
185
+ chatClient = await getTestClientWithUser({ id: 'dan' });
186
+ BetterSqlite.dropAllTables();
187
+ });
188
+
189
+ afterEach(() => {
190
+ BetterSqlite.dropAllTables();
191
+ cleanup();
192
+ });
193
+
194
+ const filters = {
195
+ foo: 'bar',
196
+ type: 'messaging',
197
+ };
198
+ const sort = { last_message_at: 1 };
199
+
200
+ const renderComponent = () =>
201
+ render(
202
+ <Chat client={chatClient} enableOfflineSupport>
203
+ <ChannelList
204
+ filters={filters}
205
+ List={ChannelListComponent}
206
+ Preview={ChannelPreviewComponent}
207
+ sort={sort}
208
+ />
209
+ </Chat>,
210
+ );
211
+
212
+ const expectCIDsOnUIToBeInDB = (queryAllByA11yRole) => {
213
+ const channelIdsOnUI = queryAllByA11yRole('list-item').map(
214
+ (node) => node._fiber.pendingProps.testID,
215
+ );
216
+
217
+ const channelQueriesRows = BetterSqlite.selectFromTable('channelQueries');
218
+ const cidsInDB = JSON.parse(channelQueriesRows[0].cids);
219
+ const filterSortQueryInDB = channelQueriesRows[0].id;
220
+ const actualFilterSortQueryInDB = convertFilterSortToQuery({ filters, sort });
221
+
222
+ expect(channelQueriesRows.length).toBe(1);
223
+ expect(filterSortQueryInDB).toBe(actualFilterSortQueryInDB);
224
+
225
+ expect(cidsInDB.length).toBe(channelIdsOnUI.length);
226
+ channelIdsOnUI.forEach((cidOnUi, index) => {
227
+ expect(cidsInDB.includes(cidOnUi)).toBe(true);
228
+ expect(index).toBe(cidsInDB.indexOf(cidOnUi));
229
+ });
230
+ };
231
+
232
+ const expectAllChannelsWithStateToBeInDB = (queryAllByA11yRole) => {
233
+ const channelIdsOnUI = queryAllByA11yRole('list-item').map(
234
+ (node) => node._fiber.pendingProps.testID,
235
+ );
236
+
237
+ const channelsRows = BetterSqlite.selectFromTable('channels');
238
+ const messagesRows = BetterSqlite.selectFromTable('messages');
239
+ const membersRows = BetterSqlite.selectFromTable('members');
240
+ const usersRows = BetterSqlite.selectFromTable('users');
241
+ const reactionsRows = BetterSqlite.selectFromTable('reactions');
242
+ const readsRows = BetterSqlite.selectFromTable('reads');
243
+
244
+ expect(channelIdsOnUI.length).toBe(channels.length);
245
+ expect(channelsRows.length).toBe(channels.length);
246
+ expect(messagesRows.length).toBe(allMessages.length);
247
+ expect(membersRows.length).toBe(allMembers.length);
248
+ expect(reactionsRows.length).toBe(allReactions.length);
249
+
250
+ channelsRows.forEach((row) => {
251
+ expect(channelIdsOnUI.includes(row.cid)).toBe(true);
252
+ });
253
+
254
+ messagesRows.forEach((row) => {
255
+ expect(allMessages.filter((m) => m.id === row.id)).toHaveLength(1);
256
+ });
257
+ membersRows.forEach((row) =>
258
+ expect(
259
+ allMembers.filter((m) => m.cid === row.cid && m.user.id === row.userId),
260
+ ).toHaveLength(1),
261
+ );
262
+ usersRows.forEach((row) => expect(allUsers.filter((u) => u.id === row.id)).toHaveLength(1));
263
+ reactionsRows.forEach((row) =>
264
+ expect(
265
+ allReactions.filter((r) => r.message_id === row.messageId && row.userId === r.user_id),
266
+ ).toHaveLength(1),
267
+ );
268
+ readsRows.forEach((row) =>
269
+ expect(
270
+ allReads.filter(
271
+ (r) =>
272
+ r.last_read === row.lastRead &&
273
+ r.user.id === row.userId &&
274
+ r.unread_messages === row.unreadMessages,
275
+ ),
276
+ ).toHaveLength(1),
277
+ );
278
+ };
279
+
280
+ it('should create tables on first load if offline feature is enabled', async () => {
281
+ const { getByTestId } = render(
282
+ <Chat client={chatClient} enableOfflineSupport>
283
+ <View testID='test-child'></View>
284
+ </Chat>,
285
+ );
286
+ await waitFor(() => expect(getByTestId('test-child')).toBeTruthy());
287
+
288
+ const tablesInDb = BetterSqlite.getTables();
289
+ const tableNamesInDB = tablesInDb.map((table) => table.name);
290
+ const tablesNamesInSchema = Object.keys(tables);
291
+
292
+ tablesNamesInSchema.forEach((name) => expect(tableNamesInDB.includes(name)).toBe(true));
293
+ });
294
+
295
+ it('should store filter-sort query and cids on ChannelList in channelQueries table', async () => {
296
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
297
+ const { getByTestId, queryAllByA11yRole } = renderComponent();
298
+ act(() => dispatchConnectionChangedEvent(chatClient));
299
+ // await waiter();
300
+ act(() => dispatchConnectionChangedEvent(chatClient));
301
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
302
+
303
+ expectCIDsOnUIToBeInDB(queryAllByA11yRole);
304
+ });
305
+
306
+ it('should store channels and its state in tables', async () => {
307
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
308
+
309
+ const { getByTestId, queryAllByA11yRole } = renderComponent();
310
+ act(() => dispatchConnectionChangedEvent(chatClient));
311
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
312
+
313
+ expectAllChannelsWithStateToBeInDB(queryAllByA11yRole);
314
+ });
315
+
316
+ it('should add a new message to database', async () => {
317
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
318
+
319
+ const { getByTestId } = renderComponent();
320
+ act(() => dispatchConnectionChangedEvent(chatClient));
321
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
322
+ const newMessage = generateMessage({
323
+ cid: channels[0].channel.cid,
324
+ user: generateUser(),
325
+ });
326
+ act(() => dispatchMessageNewEvent(chatClient, newMessage, channels[0].channel));
327
+
328
+ const messagesRows = BetterSqlite.selectFromTable('messages');
329
+ const matchingRows = messagesRows.filter((m) => m.id === newMessage.id);
330
+
331
+ expect(matchingRows.length).toBe(1);
332
+ });
333
+
334
+ it('should add a new channel and a new message to database from notification event', async () => {
335
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
336
+
337
+ const { getByTestId, queryAllByA11yRole } = renderComponent();
338
+ act(() => dispatchConnectionChangedEvent(chatClient));
339
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
340
+
341
+ const newChannel = createChannel();
342
+ channels.push(newChannel);
343
+ useMockedApis(chatClient, [getOrCreateChannelApi(newChannel)]);
344
+
345
+ act(() => dispatchNotificationMessageNewEvent(chatClient, newChannel.channel));
346
+ await waitFor(() => {
347
+ const channelIdsOnUI = queryAllByA11yRole('list-item').map(
348
+ (node) => node._fiber.pendingProps.testID,
349
+ );
350
+ expect(channelIdsOnUI.includes(newChannel.channel.cid)).toBeTruthy();
351
+ });
352
+
353
+ expectAllChannelsWithStateToBeInDB(queryAllByA11yRole);
354
+ });
355
+
356
+ it('should update a message in database', async () => {
357
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
358
+
359
+ const { getByTestId } = renderComponent();
360
+ act(() => dispatchConnectionChangedEvent(chatClient));
361
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
362
+
363
+ const updatedMessage = { ...channels[0].messages[0] };
364
+ updatedMessage.text = uuidv4();
365
+
366
+ act(() => dispatchMessageUpdatedEvent(chatClient, updatedMessage, channels[0].channel));
367
+
368
+ const messagesRows = BetterSqlite.selectFromTable('messages');
369
+ const matchingRows = messagesRows.filter((m) => m.id === updatedMessage.id);
370
+
371
+ expect(matchingRows.length).toBe(1);
372
+ expect(matchingRows[0].text).toBe(updatedMessage.text);
373
+ });
374
+
375
+ it('should remove the channel from DB when user is removed as member', async () => {
376
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
377
+
378
+ const { getByTestId, queryAllByA11yRole } = renderComponent();
379
+ act(() => dispatchConnectionChangedEvent(chatClient));
380
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
381
+ const removedChannel = channels[getRandomInt(0, channels.length - 1)].channel;
382
+ act(() => dispatchNotificationRemovedFromChannel(chatClient, removedChannel));
383
+ await waitFor(() => {
384
+ const channelIdsOnUI = queryAllByA11yRole('list-item').map(
385
+ (node) => node._fiber.pendingProps.testID,
386
+ );
387
+ expect(channelIdsOnUI.includes(removedChannel.cid)).toBeFalsy();
388
+ });
389
+
390
+ expectCIDsOnUIToBeInDB(queryAllByA11yRole);
391
+
392
+ const channelsRows = BetterSqlite.selectFromTable('channels');
393
+ const matchingRows = channelsRows.filter((c) => c.id === removedChannel.id);
394
+
395
+ const messagesRows = BetterSqlite.selectFromTable('messages');
396
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === removedChannel.cid);
397
+
398
+ expect(matchingRows.length).toBe(0);
399
+ expect(matchingMessagesRows.length).toBe(0);
400
+ });
401
+
402
+ it('should add the channel to DB when user is added as member', async () => {
403
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
404
+
405
+ const { getByTestId, queryAllByA11yRole } = renderComponent();
406
+ act(() => dispatchConnectionChangedEvent(chatClient));
407
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
408
+
409
+ const newChannel = createChannel();
410
+ useMockedApis(chatClient, [getOrCreateChannelApi(newChannel)]);
411
+
412
+ act(() => dispatchNotificationAddedToChannel(chatClient, newChannel.channel));
413
+
414
+ await waitFor(() => {
415
+ const channelIdsOnUI = queryAllByA11yRole('list-item').map(
416
+ (node) => node._fiber.pendingProps.testID,
417
+ );
418
+ expect(channelIdsOnUI.includes(newChannel.channel.cid)).toBeTruthy();
419
+ });
420
+
421
+ expectCIDsOnUIToBeInDB(queryAllByA11yRole);
422
+ const channelsRows = BetterSqlite.selectFromTable('channels');
423
+ const matchingChannelsRows = channelsRows.filter((c) => c.id === newChannel.channel.id);
424
+
425
+ const messagesRows = BetterSqlite.selectFromTable('messages');
426
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === newChannel.channel.cid);
427
+
428
+ expect(matchingChannelsRows.length).toBe(1);
429
+ expect(matchingMessagesRows.length).toBe(newChannel.messages.length);
430
+ });
431
+
432
+ it('should remove the channel messages from DB when channel is truncated', async () => {
433
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
434
+
435
+ const { getByTestId, queryAllByA11yRole } = renderComponent();
436
+ act(() => dispatchConnectionChangedEvent(chatClient));
437
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
438
+
439
+ const channelToTruncate = channels[getRandomInt(0, channels.length - 1)].channel;
440
+ act(() => dispatchChannelTruncatedEvent(chatClient, channelToTruncate));
441
+
442
+ await waitFor(() => {
443
+ const channelIdsOnUI = queryAllByA11yRole('list-item').map(
444
+ (node) => node._fiber.pendingProps.testID,
445
+ );
446
+ expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy();
447
+ });
448
+
449
+ expectCIDsOnUIToBeInDB(queryAllByA11yRole);
450
+
451
+ const messagesRows = BetterSqlite.selectFromTable('messages');
452
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === channelToTruncate.cid);
453
+
454
+ expect(matchingMessagesRows.length).toBe(0);
455
+ });
456
+
457
+ it('should add a reaction to DB when a new reaction is added', async () => {
458
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
459
+
460
+ const { getByTestId } = renderComponent();
461
+ act(() => dispatchConnectionChangedEvent(chatClient));
462
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
463
+
464
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
465
+ const targetMessage =
466
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
467
+ const reactionMember =
468
+ targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
469
+
470
+ const newReaction = generateReaction({
471
+ message_id: targetMessage.id,
472
+ type: 'wow',
473
+ user: reactionMember.user,
474
+ });
475
+ const messageWithNewReaction = {
476
+ ...targetMessage,
477
+ latest_reactions: [...targetMessage.latest_reactions, newReaction],
478
+ };
479
+
480
+ act(() =>
481
+ dispatchReactionNewEvent(
482
+ chatClient,
483
+ newReaction,
484
+ messageWithNewReaction,
485
+ targetChannel.channel,
486
+ ),
487
+ );
488
+
489
+ const reactionsRows = BetterSqlite.selectFromTable('reactions');
490
+ const matchingReactionsRows = reactionsRows.filter(
491
+ (r) =>
492
+ r.type === newReaction.type &&
493
+ r.userId === reactionMember.user.id &&
494
+ r.messageId === messageWithNewReaction.id,
495
+ );
496
+
497
+ expect(matchingReactionsRows.length).toBe(1);
498
+ });
499
+
500
+ it('should remove a reaction from DB when reaction is deleted', async () => {
501
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
502
+
503
+ const { getByTestId } = renderComponent();
504
+ act(() => dispatchConnectionChangedEvent(chatClient));
505
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
506
+
507
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
508
+ const targetMessage =
509
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
510
+ const reactionsOnTargetMessage = targetMessage.latest_reactions;
511
+ const reactionToBeRemoved =
512
+ reactionsOnTargetMessage[getRandomInt(0, reactionsOnTargetMessage.length - 1)];
513
+
514
+ const reactionsRows = BetterSqlite.selectFromTable('reactions');
515
+ const matchingReactionsRows = reactionsRows.filter(
516
+ (r) =>
517
+ r.type === reactionToBeRemoved.type &&
518
+ r.userId === reactionToBeRemoved.user_id &&
519
+ r.messageId === targetMessage.id,
520
+ );
521
+
522
+ expect(matchingReactionsRows.length).toBe(1);
523
+ const messageWithoutDeletedReaction = {
524
+ ...targetMessage,
525
+ latest_reactions: reactionsOnTargetMessage.filter((r) => r !== reactionToBeRemoved),
526
+ };
527
+
528
+ act(() =>
529
+ dispatchReactionDeletedEvent(
530
+ chatClient,
531
+ reactionToBeRemoved,
532
+ messageWithoutDeletedReaction,
533
+ targetChannel.channel,
534
+ ),
535
+ );
536
+
537
+ const reactionsRowsAfterEvent = BetterSqlite.selectFromTable('reactions');
538
+ const matchingReactionsRowsAfterEvent = reactionsRowsAfterEvent.filter(
539
+ (r) =>
540
+ r.type === reactionToBeRemoved.type &&
541
+ r.userId === reactionToBeRemoved.user_id &&
542
+ r.messageId === messageWithoutDeletedReaction.id,
543
+ );
544
+
545
+ expect(matchingReactionsRowsAfterEvent.length).toBe(0);
546
+ });
547
+
548
+ it('should update a reaction in DB when reaction is updated', async () => {
549
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
550
+
551
+ const { getByTestId } = renderComponent();
552
+ act(() => dispatchConnectionChangedEvent(chatClient));
553
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
554
+
555
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
556
+ const targetMessage =
557
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
558
+ const reactionsOnTargetMessage = targetMessage.latest_reactions;
559
+ const reactionToBeUpdated =
560
+ reactionsOnTargetMessage[getRandomInt(0, reactionsOnTargetMessage.length - 1)];
561
+ reactionToBeUpdated.type = 'wow';
562
+
563
+ act(() =>
564
+ dispatchReactionUpdatedEvent(
565
+ chatClient,
566
+ reactionToBeUpdated,
567
+ targetMessage,
568
+ targetChannel.channel,
569
+ ),
570
+ );
571
+ const reactionsRows = BetterSqlite.selectFromTable('reactions');
572
+ const matchingReactionsRows = reactionsRows.filter(
573
+ (r) =>
574
+ r.type === reactionToBeUpdated.type &&
575
+ r.userId === reactionToBeUpdated.user_id &&
576
+ r.messageId === targetMessage.id,
577
+ );
578
+
579
+ expect(matchingReactionsRows.length).toBe(1);
580
+ });
581
+
582
+ it('should add a member to DB when a new member is added to channel', async () => {
583
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
584
+
585
+ const { getByTestId } = renderComponent();
586
+ act(() => dispatchConnectionChangedEvent(chatClient));
587
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
588
+
589
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
590
+ const newMember = generateMember();
591
+ act(() => dispatchMemberAddedEvent(chatClient, newMember, targetChannel.channel));
592
+
593
+ const membersRows = BetterSqlite.selectFromTable('members');
594
+ const matchingMembersRows = membersRows.filter(
595
+ (m) => m.cid === targetChannel.channel.cid && m.userId === newMember.user_id,
596
+ );
597
+
598
+ expect(matchingMembersRows.length).toBe(1);
599
+ });
600
+
601
+ it('should remove a member from DB when a member is removed from channel', async () => {
602
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
603
+
604
+ const { getByTestId } = renderComponent();
605
+ act(() => dispatchConnectionChangedEvent(chatClient));
606
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
607
+
608
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
609
+ const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
610
+ act(() => dispatchMemberRemovedEvent(chatClient, targetMember, targetChannel.channel));
611
+
612
+ const membersRows = BetterSqlite.selectFromTable('members');
613
+ const matchingMembersRows = membersRows.filter(
614
+ (m) => m.cid === targetChannel.channel.cid && m.userId === targetMember.user_id,
615
+ );
616
+
617
+ expect(matchingMembersRows.length).toBe(0);
618
+ });
619
+
620
+ it('should update the member in DB when a member of a channel is updated', async () => {
621
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
622
+
623
+ const { getByTestId } = renderComponent();
624
+ act(() => dispatchConnectionChangedEvent(chatClient));
625
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
626
+
627
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
628
+ const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
629
+ targetMember.role = 'admin';
630
+ act(() => dispatchMemberUpdatedEvent(chatClient, targetMember, targetChannel.channel));
631
+
632
+ const membersRows = BetterSqlite.selectFromTable('members');
633
+ const matchingMembersRows = membersRows.filter(
634
+ (m) =>
635
+ m.cid === targetChannel.channel.cid &&
636
+ m.userId === targetMember.user_id &&
637
+ m.role === targetMember.role,
638
+ );
639
+
640
+ expect(matchingMembersRows.length).toBe(1);
641
+ expect(matchingMembersRows[0].role).toBe(targetMember.role);
642
+ });
643
+
644
+ it('should update the channel data in DB when a channel is updated', async () => {
645
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
646
+
647
+ const { getByTestId } = renderComponent();
648
+ act(() => dispatchConnectionChangedEvent(chatClient));
649
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
650
+
651
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
652
+ targetChannel.channel.name = uuidv4();
653
+ act(() => dispatchChannelUpdatedEvent(chatClient, targetChannel.channel));
654
+
655
+ const channelsRows = BetterSqlite.selectFromTable('channels');
656
+ const matchingChannelsRows = channelsRows.filter((c) => c.cid === targetChannel.channel.cid);
657
+
658
+ expect(matchingChannelsRows.length).toBe(1);
659
+
660
+ const extraData = JSON.parse(matchingChannelsRows[0].extraData);
661
+
662
+ expect(extraData.name).toBe(targetChannel.channel.name);
663
+ });
664
+
665
+ it('should update reads in DB when channel is read', async () => {
666
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
667
+
668
+ const { getByTestId } = renderComponent();
669
+ act(() => dispatchConnectionChangedEvent(chatClient));
670
+ await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy());
671
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
672
+ const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
673
+
674
+ act(() => {
675
+ dispatchMessageReadEvent(chatClient, targetMember.user, targetChannel.channel);
676
+ });
677
+ const readsRows = BetterSqlite.selectFromTable('reads');
678
+ const matchingReadRows = readsRows.filter(
679
+ (r) => r.userId === targetMember.user_id && r.cid === targetChannel.cid,
680
+ );
681
+
682
+ expect(matchingReadRows.length).toBe(1);
683
+ expect(matchingReadRows[0].unreadMessages).toBe(0);
684
+ });
685
+ });
686
+ };