stream-chat-react-native-core 4.9.0-beta.4 → 4.10.0

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 (98) hide show
  1. package/lib/commonjs/components/Attachment/Gallery.js +246 -192
  2. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  3. package/lib/commonjs/components/ChannelPreview/ChannelPreview.js +7 -3
  4. package/lib/commonjs/components/ChannelPreview/ChannelPreview.js.map +1 -1
  5. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +2 -1
  6. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  7. package/lib/commonjs/components/Chat/Chat.js +9 -5
  8. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  9. package/lib/commonjs/components/ImageGallery/ImageGallery.js +25 -22
  10. package/lib/commonjs/components/ImageGallery/ImageGallery.js.map +1 -1
  11. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js +15 -15
  12. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  13. package/lib/commonjs/components/Message/Message.js +12 -8
  14. package/lib/commonjs/components/Message/Message.js.map +1 -1
  15. package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js +7 -4
  16. package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  17. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +16 -10
  18. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  19. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  20. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  21. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js +15 -13
  22. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js.map +1 -1
  23. package/lib/commonjs/contexts/translationContext/TranslationContext.js +8 -4
  24. package/lib/commonjs/contexts/translationContext/TranslationContext.js.map +1 -1
  25. package/lib/commonjs/hooks/useStreami18n.js.map +1 -1
  26. package/lib/commonjs/hooks/useTranslatedMessage.js +34 -0
  27. package/lib/commonjs/hooks/useTranslatedMessage.js.map +1 -0
  28. package/lib/commonjs/mock-builders/api/getOrCreateChannel.js.map +1 -1
  29. package/lib/commonjs/mock-builders/generator/channel.js +8 -4
  30. package/lib/commonjs/mock-builders/generator/channel.js.map +1 -1
  31. package/lib/commonjs/version.json +1 -1
  32. package/lib/module/components/Attachment/Gallery.js +246 -192
  33. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  34. package/lib/module/components/ChannelPreview/ChannelPreview.js +7 -3
  35. package/lib/module/components/ChannelPreview/ChannelPreview.js.map +1 -1
  36. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +2 -1
  37. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  38. package/lib/module/components/Chat/Chat.js +9 -5
  39. package/lib/module/components/Chat/Chat.js.map +1 -1
  40. package/lib/module/components/ImageGallery/ImageGallery.js +25 -22
  41. package/lib/module/components/ImageGallery/ImageGallery.js.map +1 -1
  42. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js +15 -15
  43. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  44. package/lib/module/components/Message/Message.js +12 -8
  45. package/lib/module/components/Message/Message.js.map +1 -1
  46. package/lib/module/components/Message/MessageSimple/MessageTextContainer.js +7 -4
  47. package/lib/module/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  48. package/lib/module/contexts/messageInputContext/MessageInputContext.js +16 -10
  49. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  50. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  51. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  52. package/lib/module/contexts/overlayContext/OverlayProvider.js +15 -13
  53. package/lib/module/contexts/overlayContext/OverlayProvider.js.map +1 -1
  54. package/lib/module/contexts/translationContext/TranslationContext.js +8 -4
  55. package/lib/module/contexts/translationContext/TranslationContext.js.map +1 -1
  56. package/lib/module/hooks/useStreami18n.js.map +1 -1
  57. package/lib/module/hooks/useTranslatedMessage.js +34 -0
  58. package/lib/module/hooks/useTranslatedMessage.js.map +1 -0
  59. package/lib/module/mock-builders/api/getOrCreateChannel.js.map +1 -1
  60. package/lib/module/mock-builders/generator/channel.js +8 -4
  61. package/lib/module/mock-builders/generator/channel.js.map +1 -1
  62. package/lib/module/version.json +1 -1
  63. package/lib/typescript/components/ChannelPreview/__tests__/ChannelPreview.test.d.ts +1 -0
  64. package/lib/typescript/components/Message/MessageSimple/__tests__/MessageTextContainer.test.d.ts +1 -0
  65. package/lib/typescript/contexts/messageOverlayContext/MessageOverlayContext.d.ts +2 -1
  66. package/lib/typescript/contexts/translationContext/TranslationContext.d.ts +7 -2
  67. package/lib/typescript/hooks/__tests__/useTranslatedMessage.test.d.ts +1 -0
  68. package/lib/typescript/hooks/useStreami18n.d.ts +2 -2
  69. package/lib/typescript/hooks/useTranslatedMessage.d.ts +3 -0
  70. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +13 -1
  71. package/lib/typescript/mock-builders/event/messageNew.d.ts +2 -0
  72. package/lib/typescript/mock-builders/event/messageRead.d.ts +8 -0
  73. package/lib/typescript/mock-builders/generator/channel.d.ts +19 -15
  74. package/package.json +3 -3
  75. package/src/components/Attachment/Gallery.tsx +231 -141
  76. package/src/components/ChannelPreview/ChannelPreview.tsx +7 -1
  77. package/src/components/ChannelPreview/__tests__/{ChannelPreview.test.js → ChannelPreview.test.tsx} +79 -41
  78. package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +56 -21
  79. package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +4 -1
  80. package/src/components/Chat/Chat.tsx +6 -3
  81. package/src/components/ImageGallery/ImageGallery.tsx +10 -6
  82. package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +2 -1
  83. package/src/components/Message/Message.tsx +3 -0
  84. package/src/components/Message/MessageSimple/MessageTextContainer.tsx +7 -1
  85. package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx +108 -0
  86. package/src/components/Message/MessageSimple/__tests__/__snapshots__/{MessageTextContainer.test.js.snap → MessageTextContainer.test.tsx.snap} +0 -0
  87. package/src/contexts/messageInputContext/MessageInputContext.tsx +7 -0
  88. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap +4 -0
  89. package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx +2 -1
  90. package/src/contexts/overlayContext/OverlayProvider.tsx +4 -3
  91. package/src/contexts/translationContext/TranslationContext.tsx +10 -1
  92. package/src/hooks/__tests__/useTranslatedMessage.test.tsx +106 -0
  93. package/src/hooks/useStreami18n.ts +2 -2
  94. package/src/hooks/useTranslatedMessage.ts +30 -0
  95. package/src/mock-builders/api/{getOrCreateChannel.js → getOrCreateChannel.ts} +8 -1
  96. package/src/mock-builders/generator/{channel.js → channel.ts} +19 -10
  97. package/src/version.json +1 -1
  98. package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.js +0 -65
@@ -1,10 +1,13 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
3
 
4
+ import type { Attachment } from 'stream-chat';
5
+
4
6
  import { GalleryImage } from './GallaryImage';
5
7
  import { useLoadingImage } from './hooks/useLoadingImage';
6
8
  import { buildGallery } from './utils/buildGallery/buildGallery';
7
9
 
10
+ import type { Thumbnail } from './utils/buildGallery/types';
8
11
  import { getGalleryImageBorderRadius } from './utils/getGalleryImageBorderRadius';
9
12
 
10
13
  import { openUrlSafely } from './utils/openUrlSafely';
@@ -135,26 +138,18 @@ const GalleryWithContext = <
135
138
  VideoThumbnail,
136
139
  } = props;
137
140
 
138
- const { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError } =
139
- useLoadingImage();
140
-
141
141
  const {
142
142
  theme: {
143
- colors: { overlay },
144
143
  messageSimple: {
145
144
  gallery: {
146
145
  galleryContainer,
147
146
  galleryItemColumn,
148
147
  gridHeight,
149
148
  gridWidth,
150
- image,
151
- imageContainer,
152
149
  maxHeight,
153
150
  maxWidth,
154
151
  minHeight,
155
152
  minWidth,
156
- moreImagesContainer,
157
- moreImagesText,
158
153
  },
159
154
  },
160
155
  },
@@ -180,8 +175,6 @@ const GalleryWithContext = <
180
175
  );
181
176
 
182
177
  if (!imagesAndVideos?.length) return null;
183
- const messageText = message?.text;
184
- const messageId = message?.id;
185
178
  const numOfColumns = thumbnailGrid.length;
186
179
 
187
180
  return (
@@ -212,31 +205,7 @@ const GalleryWithContext = <
212
205
  ]}
213
206
  testID={`gallery-${invertedDirections ? 'row' : 'column'}-${colIndex}`}
214
207
  >
215
- {rows.map(({ height, resizeMode, thumb_url, type, url, width }, rowIndex) => {
216
- const openImageViewer = () => {
217
- if (!legacyImageViewerSwipeBehaviour && message) {
218
- // Added if-else to keep the logic readable, instead of DRY.
219
- // if - legacyImageViewerSwipeBehaviour is disabled
220
- // else - legacyImageViewerSwipeBehaviour is enabled
221
- setImages([message]);
222
- setImage({ messageId: message.id, url });
223
- setOverlay('gallery');
224
- } else if (legacyImageViewerSwipeBehaviour) {
225
- setImage({ messageId: message?.id, url });
226
- setOverlay('gallery');
227
- }
228
- };
229
-
230
- const defaultOnPress = () => {
231
- if (type === 'video' && !isVideoPackageAvailable()) {
232
- // This condition is kinda unreachable, since we render videos as file attachment if the video
233
- // library is not installed. But doesn't hurt to have extra safeguard, in case of some customizations.
234
- openUrlSafely(url);
235
- } else {
236
- openImageViewer();
237
- }
238
- };
239
-
208
+ {rows.map((thumbnail, rowIndex) => {
240
209
  const borderRadius = getGalleryImageBorderRadius({
241
210
  alignment,
242
211
  colIndex,
@@ -244,7 +213,7 @@ const GalleryWithContext = <
244
213
  hasThreadReplies,
245
214
  height,
246
215
  invertedDirections,
247
- messageText,
216
+ messageText: message?.text,
248
217
  numOfColumns,
249
218
  numOfRows,
250
219
  rowIndex,
@@ -253,112 +222,35 @@ const GalleryWithContext = <
253
222
  width,
254
223
  });
255
224
 
225
+ if (message === undefined) {
226
+ return null;
227
+ }
228
+
256
229
  return (
257
- <TouchableOpacity
258
- activeOpacity={0.8}
259
- disabled={preventPress}
260
- key={`gallery-item-${messageId}/${colIndex}/${rowIndex}/${imagesAndVideos.length}`}
261
- onLongPress={(event) => {
262
- if (onLongPress) {
263
- onLongPress({
264
- emitter: 'gallery',
265
- event,
266
- });
267
- }
268
- }}
269
- onPress={(event) => {
270
- if (onPress) {
271
- onPress({
272
- defaultHandler: defaultOnPress,
273
- emitter: 'gallery',
274
- event,
275
- });
276
- }
277
- }}
278
- onPressIn={(event) => {
279
- if (onPressIn) {
280
- onPressIn({
281
- defaultHandler: defaultOnPress,
282
- emitter: 'gallery',
283
- event,
284
- });
285
- }
286
- }}
287
- style={[
288
- styles.imageContainer,
289
- {
290
- height,
291
- width,
292
- },
293
- imageContainer,
294
- ]}
295
- testID={`gallery-${
296
- invertedDirections ? 'row' : 'column'
297
- }-${colIndex}-item-${rowIndex}`}
298
- {...additionalTouchableProps}
299
- >
300
- {type === 'video' ? (
301
- <VideoThumbnail
302
- style={[
303
- borderRadius,
304
- image,
305
- {
306
- height: height - 1,
307
- width: width - 1,
308
- },
309
- ]}
310
- thumb_url={thumb_url}
311
- />
312
- ) : (
313
- <View style={styles.imageContainerStyle}>
314
- <MemoizedGalleryImage
315
- onError={(error) => {
316
- console.warn(error);
317
- setLoadingImage(false);
318
- setLoadingImageError(true);
319
- }}
320
- onLoadEnd={() => setLoadingImage(false)}
321
- onLoadStart={() => setLoadingImage(true)}
322
- resizeMode={resizeMode}
323
- style={[
324
- borderRadius,
325
- image,
326
- {
327
- height: height - 1,
328
- width: width - 1,
329
- },
330
- ]}
331
- uri={url}
332
- />
333
- {isLoadingImage && (
334
- <View style={{ position: 'absolute' }}>
335
- <ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
336
- </View>
337
- )}
338
- {isLoadingImageError && (
339
- <View style={{ position: 'absolute' }}>
340
- <ImageLoadingFailedIndicator style={styles.imageLoadingIndicatorStyle} />
341
- </View>
342
- )}
343
- </View>
344
- )}
345
- {colIndex === numOfColumns - 1 &&
346
- rowIndex === numOfRows - 1 &&
347
- imagesAndVideos.length > 4 ? (
348
- <View
349
- style={[
350
- StyleSheet.absoluteFillObject,
351
- styles.moreImagesContainer,
352
- { backgroundColor: overlay },
353
- moreImagesContainer,
354
- ]}
355
- >
356
- <Text style={[styles.moreImagesText, moreImagesText]}>
357
- {`+${imagesAndVideos.length - 4}`}
358
- </Text>
359
- </View>
360
- ) : null}
361
- </TouchableOpacity>
230
+ <GalleryThumbnail
231
+ additionalTouchableProps={additionalTouchableProps}
232
+ borderRadius={borderRadius}
233
+ colIndex={colIndex}
234
+ ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
235
+ ImageLoadingIndicator={ImageLoadingIndicator}
236
+ imagesAndVideos={imagesAndVideos}
237
+ invertedDirections={invertedDirections || false}
238
+ key={rowIndex}
239
+ legacyImageViewerSwipeBehaviour={legacyImageViewerSwipeBehaviour}
240
+ message={message}
241
+ numOfColumns={numOfColumns}
242
+ numOfRows={numOfRows}
243
+ onLongPress={onLongPress}
244
+ onPress={onPress}
245
+ onPressIn={onPressIn}
246
+ preventPress={preventPress}
247
+ rowIndex={rowIndex}
248
+ setImage={setImage}
249
+ setImages={setImages}
250
+ setOverlay={setOverlay}
251
+ thumbnail={thumbnail}
252
+ VideoThumbnail={VideoThumbnail}
253
+ />
362
254
  );
363
255
  })}
364
256
  </View>
@@ -368,6 +260,204 @@ const GalleryWithContext = <
368
260
  );
369
261
  };
370
262
 
263
+ type GalleryThumbnailProps<
264
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
265
+ > = {
266
+ borderRadius: {
267
+ borderBottomLeftRadius: number;
268
+ borderBottomRightRadius: number;
269
+ borderTopLeftRadius: number;
270
+ borderTopRightRadius: number;
271
+ };
272
+ colIndex: number;
273
+ imagesAndVideos: Attachment<StreamChatGenerics>[];
274
+ invertedDirections: boolean;
275
+ message: MessageType<StreamChatGenerics>;
276
+ numOfColumns: number;
277
+ numOfRows: number;
278
+ rowIndex: number;
279
+ thumbnail: Thumbnail;
280
+ } & Pick<
281
+ MessagesContextValue<StreamChatGenerics>,
282
+ | 'additionalTouchableProps'
283
+ | 'legacyImageViewerSwipeBehaviour'
284
+ | 'VideoThumbnail'
285
+ | 'ImageLoadingIndicator'
286
+ | 'ImageLoadingFailedIndicator'
287
+ > &
288
+ Pick<ImageGalleryContextValue<StreamChatGenerics>, 'setImage' | 'setImages'> &
289
+ Pick<
290
+ MessageContextValue<StreamChatGenerics>,
291
+ 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'
292
+ > &
293
+ Pick<OverlayContextValue, 'setOverlay'>;
294
+
295
+ const GalleryThumbnail = <
296
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
297
+ >({
298
+ additionalTouchableProps,
299
+ borderRadius,
300
+ colIndex,
301
+ ImageLoadingFailedIndicator,
302
+ ImageLoadingIndicator,
303
+ imagesAndVideos,
304
+ invertedDirections,
305
+ legacyImageViewerSwipeBehaviour,
306
+ message,
307
+ numOfColumns,
308
+ numOfRows,
309
+ onLongPress,
310
+ onPress,
311
+ onPressIn,
312
+ preventPress,
313
+ rowIndex,
314
+ setImage,
315
+ setImages,
316
+ setOverlay,
317
+ thumbnail,
318
+ VideoThumbnail,
319
+ }: GalleryThumbnailProps<StreamChatGenerics>) => {
320
+ const { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError } =
321
+ useLoadingImage();
322
+
323
+ const {
324
+ theme: {
325
+ colors: { overlay },
326
+ messageSimple: {
327
+ gallery: { image, imageContainer, moreImagesContainer, moreImagesText },
328
+ },
329
+ },
330
+ } = useTheme();
331
+
332
+ const openImageViewer = () => {
333
+ if (!legacyImageViewerSwipeBehaviour && message) {
334
+ // Added if-else to keep the logic readable, instead of DRY.
335
+ // if - legacyImageViewerSwipeBehaviour is disabled
336
+ // else - legacyImageViewerSwipeBehaviour is enabled
337
+ setImages([message]);
338
+ setImage({ messageId: message.id, url: thumbnail.url });
339
+ setOverlay('gallery');
340
+ } else if (legacyImageViewerSwipeBehaviour) {
341
+ setImage({ messageId: message?.id, url: thumbnail.url });
342
+ setOverlay('gallery');
343
+ }
344
+ };
345
+
346
+ const defaultOnPress = () => {
347
+ if (thumbnail.type === 'video' && !isVideoPackageAvailable()) {
348
+ // This condition is kinda unreachable, since we render videos as file attachment if the video
349
+ // library is not installed. But doesn't hurt to have extra safeguard, in case of some customizations.
350
+ openUrlSafely(thumbnail.url);
351
+ } else {
352
+ openImageViewer();
353
+ }
354
+ };
355
+
356
+ return (
357
+ <TouchableOpacity
358
+ activeOpacity={0.8}
359
+ disabled={preventPress}
360
+ key={`gallery-item-${message.id}/${colIndex}/${rowIndex}/${imagesAndVideos.length}`}
361
+ onLongPress={(event) => {
362
+ if (onLongPress) {
363
+ onLongPress({
364
+ emitter: 'gallery',
365
+ event,
366
+ });
367
+ }
368
+ }}
369
+ onPress={(event) => {
370
+ if (onPress) {
371
+ onPress({
372
+ defaultHandler: defaultOnPress,
373
+ emitter: 'gallery',
374
+ event,
375
+ });
376
+ }
377
+ }}
378
+ onPressIn={(event) => {
379
+ if (onPressIn) {
380
+ onPressIn({
381
+ defaultHandler: defaultOnPress,
382
+ emitter: 'gallery',
383
+ event,
384
+ });
385
+ }
386
+ }}
387
+ style={[
388
+ styles.imageContainer,
389
+ {
390
+ height: thumbnail.height,
391
+ width: thumbnail.width,
392
+ },
393
+ imageContainer,
394
+ ]}
395
+ testID={`gallery-${invertedDirections ? 'row' : 'column'}-${colIndex}-item-${rowIndex}`}
396
+ {...additionalTouchableProps}
397
+ >
398
+ {thumbnail.type === 'video' ? (
399
+ <VideoThumbnail
400
+ style={[
401
+ borderRadius,
402
+ image,
403
+ {
404
+ height: thumbnail.height - 1,
405
+ width: thumbnail.width - 1,
406
+ },
407
+ ]}
408
+ thumb_url={thumbnail.thumb_url}
409
+ />
410
+ ) : (
411
+ <View style={styles.imageContainerStyle}>
412
+ <MemoizedGalleryImage
413
+ onError={(error) => {
414
+ console.warn(error);
415
+ setLoadingImage(false);
416
+ setLoadingImageError(true);
417
+ }}
418
+ onLoadEnd={() => setLoadingImage(false)}
419
+ onLoadStart={() => setLoadingImage(true)}
420
+ resizeMode={thumbnail.resizeMode}
421
+ style={[
422
+ borderRadius,
423
+ image,
424
+ {
425
+ height: thumbnail.height - 1,
426
+ width: thumbnail.width - 1,
427
+ },
428
+ ]}
429
+ uri={thumbnail.url}
430
+ />
431
+ {isLoadingImage && (
432
+ <View style={{ position: 'absolute' }}>
433
+ <ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
434
+ </View>
435
+ )}
436
+ {isLoadingImageError && (
437
+ <View style={{ position: 'absolute' }}>
438
+ <ImageLoadingFailedIndicator style={styles.imageLoadingIndicatorStyle} />
439
+ </View>
440
+ )}
441
+ </View>
442
+ )}
443
+ {colIndex === numOfColumns - 1 && rowIndex === numOfRows - 1 && imagesAndVideos.length > 4 ? (
444
+ <View
445
+ style={[
446
+ StyleSheet.absoluteFillObject,
447
+ styles.moreImagesContainer,
448
+ { backgroundColor: overlay },
449
+ moreImagesContainer,
450
+ ]}
451
+ >
452
+ <Text style={[styles.moreImagesText, moreImagesText]}>
453
+ {`+${imagesAndVideos.length - 4}`}
454
+ </Text>
455
+ </View>
456
+ ) : null}
457
+ </TouchableOpacity>
458
+ );
459
+ };
460
+
371
461
  const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(
372
462
  prevProps: GalleryPropsWithContext<StreamChatGenerics>,
373
463
  nextProps: GalleryPropsWithContext<StreamChatGenerics>,
@@ -10,6 +10,7 @@ import {
10
10
  } from '../../contexts/channelsContext/ChannelsContext';
11
11
  import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext';
12
12
 
13
+ import { useTranslatedMessage } from '../../hooks/useTranslatedMessage';
13
14
  import type { DefaultStreamChatGenerics } from '../../types/types';
14
15
 
15
16
  export type ChannelPreviewPropsWithContext<
@@ -38,10 +39,15 @@ const ChannelPreviewWithContext = <
38
39
  | MessageResponse<StreamChatGenerics>
39
40
  | undefined
40
41
  >(channel.state.messages[channel.state.messages.length - 1]);
42
+
43
+ const translatedLastMessage = useTranslatedMessage<StreamChatGenerics>(
44
+ lastMessage || ({} as MessageResponse<StreamChatGenerics>),
45
+ );
46
+
41
47
  const [forceUpdate, setForceUpdate] = useState(0);
42
48
  const [unread, setUnread] = useState(channel.countUnread());
43
49
 
44
- const latestMessagePreview = useLatestMessagePreview(channel, forceUpdate, lastMessage);
50
+ const latestMessagePreview = useLatestMessagePreview(channel, forceUpdate, translatedLastMessage);
45
51
 
46
52
  const channelLastMessage = channel.lastMessage();
47
53
  const channelLastMessageString = `${channelLastMessage?.id}${channelLastMessage?.updated_at}`;
@@ -1,9 +1,14 @@
1
- import React from 'react';
1
+ import React, { ComponentType } from 'react';
2
2
  import { Text } from 'react-native';
3
3
 
4
4
  import { act, render, waitFor } from '@testing-library/react-native';
5
5
 
6
- import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel';
6
+ import type { Channel, StreamChat } from 'stream-chat';
7
+
8
+ import {
9
+ getOrCreateChannelApi,
10
+ GetOrCreateChannelApiParams,
11
+ } from '../../../mock-builders/api/getOrCreateChannel';
7
12
  import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
8
13
  import dispatchMessageNewEvent from '../../../mock-builders/event/messageNew';
9
14
  import dispatchMessageReadEvent from '../../../mock-builders/event/messageRead';
@@ -13,8 +18,23 @@ import { generateUser } from '../../../mock-builders/generator/user';
13
18
  import { getTestClientWithUser } from '../../../mock-builders/mock';
14
19
  import { Chat } from '../../Chat/Chat';
15
20
  import { ChannelPreview } from '../ChannelPreview';
21
+ import type { ChannelPreviewMessengerProps } from '../ChannelPreviewMessenger';
22
+
23
+ import '@testing-library/jest-native/extend-expect';
24
+
25
+ type ChannelPreviewUIComponentProps = {
26
+ channel: {
27
+ id: string;
28
+ };
29
+ latestMessagePreview: {
30
+ messageObject: {
31
+ text: string;
32
+ };
33
+ };
34
+ unread: number;
35
+ };
16
36
 
17
- const ChannelPreviewUIComponent = (props) => (
37
+ const ChannelPreviewUIComponent = (props: ChannelPreviewUIComponentProps) => (
18
38
  <>
19
39
  <Text testID='channel-id'>{props.channel.id}</Text>
20
40
  <Text testID='unread-count'>{props.unread}</Text>
@@ -28,21 +48,27 @@ const ChannelPreviewUIComponent = (props) => (
28
48
 
29
49
  describe('ChannelPreview', () => {
30
50
  const clientUser = generateUser();
31
- let chatClient;
32
- let channel;
33
-
34
- const getComponent = (props = {}) => (
35
- <Chat client={chatClient}>
36
- <ChannelPreview
37
- {...props}
38
- channel={channel}
39
- client={chatClient}
40
- Preview={ChannelPreviewUIComponent}
41
- />
42
- </Chat>
43
- );
44
-
45
- const initializeChannel = async (c) => {
51
+ let chatClient: StreamChat;
52
+ let channel: Channel | null;
53
+
54
+ const TestComponent = (props = {}) => {
55
+ if (channel === null) {
56
+ return null;
57
+ }
58
+
59
+ return (
60
+ <Chat client={chatClient}>
61
+ <ChannelPreview
62
+ {...props}
63
+ channel={channel}
64
+ client={chatClient}
65
+ Preview={ChannelPreviewUIComponent as ComponentType<ChannelPreviewMessengerProps>}
66
+ />
67
+ </Chat>
68
+ );
69
+ };
70
+
71
+ const useInitializeChannel = async (c: GetOrCreateChannelApiParams) => {
46
72
  useMockedApis(chatClient, [getOrCreateChannelApi(c)]);
47
73
 
48
74
  channel = chatClient.channel('messaging');
@@ -58,31 +84,22 @@ describe('ChannelPreview', () => {
58
84
  channel = null;
59
85
  });
60
86
 
61
- it('should render with latest message on channel', async () => {
62
- const message = generateMessage({
63
- user: clientUser,
64
- });
65
- const c = generateChannelResponse({
66
- messages: [message],
67
- });
68
- await initializeChannel(c);
69
- const { queryByText } = render(getComponent());
70
- await waitFor(() => queryByText(message.text));
71
- });
72
-
73
87
  it('should mark channel as read, when message.read event is received for current user', async () => {
74
88
  const c = generateChannelResponse();
75
- await initializeChannel(c);
76
- channel.countUnread = () => 20;
89
+ await useInitializeChannel(c);
90
+
91
+ if (channel !== null) {
92
+ channel.countUnread = () => 20;
93
+ }
77
94
 
78
- const { getByTestId } = render(getComponent());
95
+ const { getByTestId } = render(<TestComponent />);
79
96
 
80
97
  await waitFor(() => getByTestId('channel-id'));
81
98
 
82
99
  expect(getByTestId('unread-count')).toHaveTextContent('20');
83
100
 
84
101
  act(() => {
85
- dispatchMessageReadEvent(chatClient, clientUser, channel);
102
+ dispatchMessageReadEvent(chatClient, clientUser, channel || {});
86
103
  });
87
104
 
88
105
  await waitFor(() => {
@@ -92,9 +109,9 @@ describe('ChannelPreview', () => {
92
109
 
93
110
  it('should update the lastest message on "message.new" event', async () => {
94
111
  const c = generateChannelResponse();
95
- await initializeChannel(c);
112
+ await useInitializeChannel(c);
96
113
 
97
- const { getByTestId } = render(getComponent());
114
+ const { getByTestId } = render(<TestComponent />);
98
115
 
99
116
  await waitFor(() => getByTestId('channel-id'));
100
117
 
@@ -103,7 +120,7 @@ describe('ChannelPreview', () => {
103
120
  });
104
121
 
105
122
  act(() => {
106
- dispatchMessageNewEvent(chatClient, message, channel);
123
+ dispatchMessageNewEvent(chatClient, message, channel || {});
107
124
  });
108
125
 
109
126
  await waitFor(() => {
@@ -113,9 +130,9 @@ describe('ChannelPreview', () => {
113
130
 
114
131
  it('should update the unread count on "message.new" event', async () => {
115
132
  const c = generateChannelResponse();
116
- await initializeChannel(c);
133
+ await useInitializeChannel(c);
117
134
 
118
- const { getByTestId } = render(getComponent());
135
+ const { getByTestId } = render(<TestComponent />);
119
136
 
120
137
  await waitFor(() => getByTestId('channel-id'));
121
138
 
@@ -123,14 +140,35 @@ describe('ChannelPreview', () => {
123
140
  user: clientUser,
124
141
  });
125
142
 
126
- channel.countUnread = () => 10;
143
+ if (channel !== null) {
144
+ channel.countUnread = () => 10;
145
+ }
127
146
 
128
147
  act(() => {
129
- dispatchMessageNewEvent(chatClient, message, channel);
148
+ dispatchMessageNewEvent(chatClient, message, channel || {});
130
149
  });
131
150
 
132
151
  await waitFor(() => {
133
152
  expect(getByTestId('unread-count')).toHaveTextContent('10');
134
153
  });
135
154
  });
155
+
156
+ it('displays messages translated if applicable', async () => {
157
+ chatClient = await getTestClientWithUser({ id: 'mads', language: 'no' });
158
+
159
+ const message = {
160
+ i18n: {
161
+ no_text: 'Hallo verden!',
162
+ },
163
+ text: 'Hello world!',
164
+ };
165
+ const channel = generateChannelResponse({ messages: [message] });
166
+ await useInitializeChannel(channel);
167
+
168
+ const { getByText } = render(<TestComponent />);
169
+
170
+ await waitFor(() => {
171
+ expect(getByText(message.i18n.no_text)).toBeTruthy();
172
+ });
173
+ });
136
174
  });