stream-chat-react-native-core 4.9.0-beta.2 → 4.9.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 -190
  2. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  3. package/lib/commonjs/components/Attachment/VideoThumbnail.js +11 -5
  4. package/lib/commonjs/components/Attachment/VideoThumbnail.js.map +1 -1
  5. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  6. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  7. package/lib/commonjs/components/Message/Message.js +7 -6
  8. package/lib/commonjs/components/Message/Message.js.map +1 -1
  9. package/lib/commonjs/components/MessageInput/FileUploadPreview.js +24 -18
  10. package/lib/commonjs/components/MessageInput/FileUploadPreview.js.map +1 -1
  11. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js +2 -2
  12. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js.map +1 -1
  13. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  14. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  15. package/lib/commonjs/components/Reply/Reply.js +7 -2
  16. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  17. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +20 -27
  18. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  19. package/lib/commonjs/mock-builders/generator/attachment.js +3 -2
  20. package/lib/commonjs/mock-builders/generator/attachment.js.map +1 -1
  21. package/lib/commonjs/utils/getUrlOfImageAttachment.js +1 -1
  22. package/lib/commonjs/utils/getUrlOfImageAttachment.js.map +1 -1
  23. package/lib/commonjs/version.json +1 -1
  24. package/lib/module/components/Attachment/Gallery.js +246 -190
  25. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  26. package/lib/module/components/Attachment/VideoThumbnail.js +11 -5
  27. package/lib/module/components/Attachment/VideoThumbnail.js.map +1 -1
  28. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  29. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  30. package/lib/module/components/Message/Message.js +7 -6
  31. package/lib/module/components/Message/Message.js.map +1 -1
  32. package/lib/module/components/MessageInput/FileUploadPreview.js +24 -18
  33. package/lib/module/components/MessageInput/FileUploadPreview.js.map +1 -1
  34. package/lib/module/components/MessageInput/ImageUploadPreview.js +2 -2
  35. package/lib/module/components/MessageInput/ImageUploadPreview.js.map +1 -1
  36. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  37. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  38. package/lib/module/components/Reply/Reply.js +7 -2
  39. package/lib/module/components/Reply/Reply.js.map +1 -1
  40. package/lib/module/contexts/messageInputContext/MessageInputContext.js +20 -27
  41. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  42. package/lib/module/mock-builders/generator/attachment.js +3 -2
  43. package/lib/module/mock-builders/generator/attachment.js.map +1 -1
  44. package/lib/module/utils/getUrlOfImageAttachment.js +1 -1
  45. package/lib/module/utils/getUrlOfImageAttachment.js.map +1 -1
  46. package/lib/module/version.json +1 -1
  47. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts +3 -1
  48. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts +1 -0
  49. package/lib/typescript/components/RTLComponents/WritingDirectionAwareText.d.ts +4 -0
  50. package/lib/typescript/components/Reply/__tests__/Reply.test.d.ts +1 -0
  51. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +1 -0
  52. package/lib/typescript/contexts/messageInputContext/__tests__/MessageInputContext.test.d.ts +1 -0
  53. package/lib/typescript/contexts/messageInputContext/__tests__/isValidMessage.test.d.ts +1 -0
  54. package/lib/typescript/contexts/messageInputContext/__tests__/pickFile.test.d.ts +1 -0
  55. package/lib/typescript/contexts/messageInputContext/__tests__/removeFile.test.d.ts +1 -0
  56. package/lib/typescript/contexts/messageInputContext/__tests__/removeImage.test.d.ts +1 -0
  57. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessage.test.d.ts +1 -0
  58. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessageAsync.test.d.ts +1 -0
  59. package/lib/typescript/contexts/messageInputContext/__tests__/updateMessage.test.d.ts +1 -0
  60. package/lib/typescript/contexts/messageInputContext/__tests__/uploadFile.test.d.ts +1 -0
  61. package/lib/typescript/contexts/messageInputContext/__tests__/uploadImage.test.d.ts +1 -0
  62. package/lib/typescript/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.d.ts +1 -0
  63. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +7 -0
  64. package/lib/typescript/mock-builders/api/useMockedApis.d.ts +1 -0
  65. package/lib/typescript/mock-builders/generator/attachment.d.ts +10 -0
  66. package/lib/typescript/mock-builders/generator/channel.d.ts +116 -0
  67. package/lib/typescript/mock-builders/generator/message.d.ts +38 -0
  68. package/lib/typescript/mock-builders/generator/user.d.ts +9 -9
  69. package/package.json +7 -5
  70. package/src/components/Attachment/Gallery.tsx +231 -140
  71. package/src/components/Attachment/VideoThumbnail.tsx +12 -5
  72. package/src/components/Attachment/__tests__/Attachment.test.js +12 -0
  73. package/src/components/Attachment/__tests__/Gallery.test.js +23 -1
  74. package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +1 -0
  75. package/src/components/Attachment/utils/buildGallery/types.ts +1 -0
  76. package/src/components/Message/Message.tsx +5 -1
  77. package/src/components/MessageInput/FileUploadPreview.tsx +7 -5
  78. package/src/components/MessageInput/ImageUploadPreview.tsx +2 -2
  79. package/src/components/RTLComponents/WritingDirectionAwareText.tsx +17 -0
  80. package/src/components/Reply/Reply.tsx +19 -3
  81. package/src/components/Reply/__tests__/Reply.test.tsx +48 -0
  82. package/src/contexts/messageInputContext/MessageInputContext.tsx +3 -4
  83. package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx +208 -0
  84. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap +109 -0
  85. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessageAsync.test.tsx.snap +33 -0
  86. package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx +71 -0
  87. package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +67 -0
  88. package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx +82 -0
  89. package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx +80 -0
  90. package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +286 -0
  91. package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx +124 -0
  92. package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx +92 -0
  93. package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx +76 -0
  94. package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx +55 -0
  95. package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx +62 -0
  96. package/src/mock-builders/generator/attachment.js +4 -2
  97. package/src/utils/getUrlOfImageAttachment.ts +1 -1
  98. package/src/version.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { StyleSheet, View, ViewProps } from 'react-native';
2
+ import { ImageBackground, StyleSheet, View, ViewProps } from 'react-native';
3
3
 
4
4
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
5
5
  import { Play } from '../../icons';
@@ -24,7 +24,9 @@ const styles = StyleSheet.create({
24
24
  },
25
25
  });
26
26
 
27
- export type VideoThumbnailProps = ViewProps;
27
+ export type VideoThumbnailProps = ViewProps & {
28
+ thumb_url?: string;
29
+ };
28
30
 
29
31
  export const VideoThumbnail: React.FC<VideoThumbnailProps> = (props) => {
30
32
  const {
@@ -34,12 +36,17 @@ export const VideoThumbnail: React.FC<VideoThumbnailProps> = (props) => {
34
36
  },
35
37
  },
36
38
  } = useTheme();
37
- const { style, ...rest } = props;
39
+ const { style, thumb_url, ...rest } = props;
38
40
  return (
39
- <View {...rest} style={[styles.container, container, style]}>
41
+ <ImageBackground
42
+ accessibilityLabel='video-thumbnail'
43
+ source={{ uri: thumb_url }}
44
+ {...rest}
45
+ style={[styles.container, container, style]}
46
+ >
40
47
  <View style={[styles.roundedView, roundedView]}>
41
48
  <Play height={24} pathFill={'#000'} width={24} />
42
49
  </View>
43
- </View>
50
+ </ImageBackground>
44
51
  );
45
52
  };
@@ -11,8 +11,10 @@ import {
11
11
  generateAudioAttachment,
12
12
  generateFileAttachment,
13
13
  generateImageAttachment,
14
+ generateVideoAttachment,
14
15
  } from '../../../mock-builders/generator/attachment';
15
16
  import { generateMessage } from '../../../mock-builders/generator/message';
17
+ import * as NativeUtils from '../../../native';
16
18
 
17
19
  import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
18
20
  import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
@@ -47,6 +49,16 @@ describe('Attachment', () => {
47
49
  });
48
50
  });
49
51
 
52
+ it('should render File component for "video" type attachment', async () => {
53
+ jest.spyOn(NativeUtils, 'isVideoPackageAvailable').mockImplementation(jest.fn(() => false));
54
+ const attachment = generateVideoAttachment();
55
+ const { getByTestId } = render(getAttachmentComponent({ attachment }));
56
+
57
+ await waitFor(() => {
58
+ expect(getByTestId('file-attachment')).toBeTruthy();
59
+ });
60
+ });
61
+
50
62
  it('should render File component for "file" type attachment', async () => {
51
63
  const attachment = generateFileAttachment();
52
64
  const { getByTestId } = render(getAttachmentComponent({ attachment }));
@@ -11,12 +11,16 @@ import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvide
11
11
 
12
12
  import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel';
13
13
  import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
14
- import { generateImageAttachment } from '../../../mock-builders/generator/attachment';
14
+ import {
15
+ generateImageAttachment,
16
+ generateVideoAttachment,
17
+ } from '../../../mock-builders/generator/attachment';
15
18
  import { generateChannelResponse } from '../../../mock-builders/generator/channel';
16
19
  import { generateMember } from '../../../mock-builders/generator/member';
17
20
  import { generateMessage } from '../../../mock-builders/generator/message';
18
21
  import { generateUser } from '../../../mock-builders/generator/user';
19
22
  import { getTestClientWithUser } from '../../../mock-builders/mock';
23
+ import * as NativeUtils from '../../../native';
20
24
  import { Channel } from '../../Channel/Channel';
21
25
  import { Chat } from '../../Chat/Chat';
22
26
  import { MessageList } from '../../MessageList/MessageList';
@@ -75,6 +79,24 @@ describe('Gallery', () => {
75
79
  });
76
80
  });
77
81
 
82
+ it('should render one image and one video attachment', async () => {
83
+ jest.spyOn(NativeUtils, 'isVideoPackageAvailable').mockImplementation(jest.fn(() => true));
84
+ const attachment1 = generateImageAttachment({
85
+ original_height: 600,
86
+ original_width: 400,
87
+ });
88
+ const attachment2 = generateVideoAttachment();
89
+ const component = await getComponent([attachment1, attachment2]);
90
+ const { getAllByA11yLabel, queryAllByTestId } = render(component);
91
+
92
+ await waitFor(() => {
93
+ expect(queryAllByTestId('gallery-row-0').length).toBe(1);
94
+
95
+ expect(queryAllByTestId('gallery-row-0-item-0').length).toBe(1);
96
+ expect(getAllByA11yLabel('video-thumbnail').length).toBe(1);
97
+ });
98
+ });
99
+
78
100
  it('should render portrait and landscape image in two rows', async () => {
79
101
  const attachment1 = generateImageAttachment({
80
102
  original_height: 600,
@@ -36,6 +36,7 @@ export function buildThumbnail<
36
36
  resizeMode: resizeMode
37
37
  ? resizeMode
38
38
  : ((image.original_height && image.original_width ? 'contain' : 'cover') as ImageResizeMode),
39
+ thumb_url: image.thumb_url,
39
40
  type: image.type,
40
41
  url: shouldResize
41
42
  ? getResizedImageUrl({
@@ -6,6 +6,7 @@ export type Thumbnail = {
6
6
  url: string;
7
7
  width: number;
8
8
  id?: string;
9
+ thumb_url?: string;
9
10
  type?: string;
10
11
  };
11
12
 
@@ -333,7 +333,11 @@ const MessageWithContext = <
333
333
  acc.files.push(cur);
334
334
  acc.other = []; // remove other attachments if a file exists
335
335
  } else if (cur.type === 'video' && !cur.og_scrape_url && isVideoPackageAvailable()) {
336
- acc.videos.push({ image_url: cur.asset_url, type: 'video' });
336
+ acc.videos.push({
337
+ image_url: cur.asset_url,
338
+ thumb_url: cur.thumb_url,
339
+ type: 'video',
340
+ });
337
341
  acc.other = [];
338
342
  } else if (cur.type === 'video' && !cur.og_scrape_url) {
339
343
  acc.files.push(cur);
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
- import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
2
+ import { FlatList, I18nManager, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
3
 
4
4
  import { UploadProgressIndicator } from './UploadProgressIndicator';
5
5
 
@@ -19,6 +19,7 @@ import { Warning } from '../../icons/Warning';
19
19
  import type { DefaultStreamChatGenerics } from '../../types/types';
20
20
  import { getIndicatorTypeForFileState, ProgressIndicatorTypes } from '../../utils/utils';
21
21
  import { getFileSizeDisplayText } from '../Attachment/FileAttachment';
22
+ import { WritingDirectionAwareText } from '../RTLComponents/WritingDirectionAwareText';
22
23
 
23
24
  const FILE_PREVIEW_HEIGHT = 60;
24
25
  const WARNING_ICON_SIZE = 16;
@@ -71,7 +72,7 @@ const styles = StyleSheet.create({
71
72
  },
72
73
  warningIconStyle: {
73
74
  borderRadius: 24,
74
- marginTop: 3,
75
+ marginTop: 2,
75
76
  },
76
77
  });
77
78
 
@@ -105,9 +106,9 @@ const UnsupportedFileTypeOrFileSizeIndicator = ({
105
106
  </Text>
106
107
  </View>
107
108
  ) : (
108
- <Text style={[styles.fileSizeText, { color: grey }, fileSizeText]}>
109
- {item.file.duration ? item.file.duration : getFileSizeDisplayText(item.file.size)}
110
- </Text>
109
+ <WritingDirectionAwareText style={[styles.fileSizeText, { color: grey }, fileSizeText]}>
110
+ {item.file.duration || getFileSizeDisplayText(item.file.size)}
111
+ </WritingDirectionAwareText>
111
112
  );
112
113
  };
113
114
 
@@ -188,6 +189,7 @@ const FileUploadPreviewWithContext = <
188
189
  24 - // 24 = close icon size
189
190
  24, // 24 = internal padding
190
191
  },
192
+ I18nManager.isRTL ? { writingDirection: 'rtl' } : { writingDirection: 'ltr' },
191
193
  filenameText,
192
194
  ]}
193
195
  >
@@ -27,7 +27,7 @@ const styles = StyleSheet.create({
27
27
  },
28
28
  fileSizeText: {
29
29
  fontSize: 12,
30
- paddingLeft: 10,
30
+ paddingHorizontal: 10,
31
31
  },
32
32
  flatList: { paddingBottom: 12 },
33
33
  iconContainer: {
@@ -54,7 +54,7 @@ const styles = StyleSheet.create({
54
54
  },
55
55
  warningIconStyle: {
56
56
  borderRadius: 24,
57
- marginTop: 4,
57
+ marginTop: 6,
58
58
  },
59
59
  warningText: {
60
60
  alignItems: 'center',
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { I18nManager, StyleSheet, Text, ViewProps } from 'react-native';
3
+
4
+ const styles = StyleSheet.create({
5
+ defaultStyle: { writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' },
6
+ });
7
+
8
+ export type WritingDirectionAwareTextProps = ViewProps;
9
+
10
+ export const WritingDirectionAwareText: React.FC<WritingDirectionAwareTextProps> = (props) => {
11
+ const { children, style, ...rest } = props;
12
+ return (
13
+ <Text {...rest} style={[style, styles.defaultStyle]}>
14
+ {children}
15
+ </Text>
16
+ );
17
+ };
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import React, { useContext, useState } from 'react';
2
2
  import { Image, ImageStyle, StyleSheet, View, ViewStyle } from 'react-native';
3
3
 
4
4
  import merge from 'lodash/merge';
@@ -7,8 +7,8 @@ import type { Attachment } from 'stream-chat';
7
7
 
8
8
  import { useMessageContext } from '../../contexts/messageContext/MessageContext';
9
9
  import {
10
+ MessageInputContext,
10
11
  MessageInputContextValue,
11
- useMessageInputContext,
12
12
  } from '../../contexts/messageInputContext/MessageInputContext';
13
13
  import {
14
14
  MessagesContextValue,
@@ -267,6 +267,22 @@ const ReplyWithContext = <
267
267
  );
268
268
  };
269
269
 
270
+ /**
271
+ * When a reply is rendered in a MessageSimple, it does
272
+ * not have a MessageInputContext. As this is deliberate,
273
+ * this function exists to avoid the error thrown when
274
+ * using a context outside of its provider.
275
+ * */
276
+ const useMessageInputContextIfAvailable = <
277
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
278
+ >() => {
279
+ const contextValue = useContext(
280
+ MessageInputContext,
281
+ ) as unknown as MessageInputContextValue<StreamChatGenerics>;
282
+
283
+ return contextValue;
284
+ };
285
+
270
286
  const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(
271
287
  prevProps: ReplyPropsWithContext<StreamChatGenerics>,
272
288
  nextProps: ReplyPropsWithContext<StreamChatGenerics>,
@@ -307,7 +323,7 @@ export const Reply = <
307
323
  const { FileAttachmentIcon = FileIconDefault, MessageAvatar = MessageAvatarDefault } =
308
324
  useMessagesContext<StreamChatGenerics>();
309
325
 
310
- const { editing, quotedMessage } = useMessageInputContext<StreamChatGenerics>();
326
+ const { editing, quotedMessage } = useMessageInputContextIfAvailable<StreamChatGenerics>();
311
327
 
312
328
  const quotedEditingMessage = (
313
329
  typeof editing !== 'boolean' ? editing?.quoted_message || false : false
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+
3
+ import { render, waitFor } from '@testing-library/react-native';
4
+
5
+ import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider';
6
+ import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel';
7
+ import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
8
+ import { generateChannelResponse } from '../../../mock-builders/generator/channel';
9
+ import { getTestClientWithUser } from '../../../mock-builders/mock';
10
+ import { Channel } from '../../Channel/Channel';
11
+ import { Chat } from '../../Chat/Chat';
12
+ import { Reply } from '../Reply';
13
+
14
+ describe('<Reply/>', () => {
15
+ it('can be rendered outside of a MessageInputProvider', async () => {
16
+ const oldEnvironment = process.env;
17
+ process.env.NODE_ENV = 'not_test';
18
+
19
+ const chatClient = await getTestClientWithUser({ id: 'neil' });
20
+
21
+ const mockedChannel = generateChannelResponse();
22
+ useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
23
+ const channel = chatClient.channel('messaging', 'some-chat');
24
+ await channel.watch();
25
+
26
+ const TestComponent = () => (
27
+ <OverlayProvider>
28
+ <Chat client={chatClient}>
29
+ <Channel channel={channel} client={chatClient}>
30
+ <Reply />
31
+ </Channel>
32
+ </Chat>
33
+ </OverlayProvider>
34
+ );
35
+
36
+ try {
37
+ const { toJSON } = render(<TestComponent />);
38
+
39
+ await waitFor(() => {
40
+ expect(toJSON()).not.toBeNull();
41
+ });
42
+ } catch (error: unknown) {
43
+ throw new Error(`Error thrown while rendering Reply: ${error}`);
44
+ }
45
+
46
+ process.env = oldEnvironment;
47
+ });
48
+ });
@@ -62,6 +62,7 @@ export type FileUpload = {
62
62
  file: File;
63
63
  id: string;
64
64
  state: FileStateValue;
65
+ thumb_url?: string;
65
66
  url?: string;
66
67
  };
67
68
 
@@ -710,6 +711,7 @@ export const MessageInputProvider = <
710
711
  duration: file.file.duration,
711
712
  file_size: file.file.size,
712
713
  mime_type: file.file.type,
714
+ thumb_url: file.thumb_url,
713
715
  title: file.file.name,
714
716
  type: 'video',
715
717
  } as Attachment<StreamChatGenerics>);
@@ -897,9 +899,6 @@ export const MessageInputProvider = <
897
899
  };
898
900
 
899
901
  const uploadFile = async ({ newFile }: { newFile: FileUpload }) => {
900
- if (!newFile) {
901
- return;
902
- }
903
902
  const { file, id } = newFile;
904
903
 
905
904
  setFileUploads(getUploadSetStateAction(id, FileState.UPLOADING));
@@ -911,7 +910,7 @@ export const MessageInputProvider = <
911
910
  } else if (channel && file.uri) {
912
911
  response = await channel.sendFile(file.uri, file.name, file.type);
913
912
  }
914
- const extraData: Partial<FileUpload> = { url: response.file };
913
+ const extraData: Partial<FileUpload> = { thumb_url: response.thumb_url, url: response.file };
915
914
  setFileUploads(getUploadSetStateAction(id, FileState.UPLOADED, extraData));
916
915
  } catch (error: unknown) {
917
916
  handleFileOrImageUploadError(error, false, id);
@@ -0,0 +1,208 @@
1
+ import React, { PropsWithChildren } from 'react';
2
+
3
+ import type { TextInput } from 'react-native';
4
+
5
+ import { act, renderHook } from '@testing-library/react-hooks';
6
+
7
+ import type { AppSettingsAPIResponse, StreamChat } from 'stream-chat';
8
+
9
+ import { ChatContextValue, ChatProvider } from '../../../contexts/chatContext/ChatContext';
10
+
11
+ import {
12
+ generateFileAttachment,
13
+ generateImageAttachment,
14
+ } from '../../../mock-builders/generator/attachment';
15
+
16
+ import { generateMessage } from '../../../mock-builders/generator/message';
17
+ import { generateUser } from '../../../mock-builders/generator/user';
18
+
19
+ import * as NativeUtils from '../../../native';
20
+ import type { DefaultStreamChatGenerics } from '../../../types/types';
21
+ import { FileState } from '../../../utils/utils';
22
+ import {
23
+ InputMessageInputContextValue,
24
+ MessageInputContextValue,
25
+ MessageInputProvider,
26
+ useMessageInputContext,
27
+ } from '../MessageInputContext';
28
+
29
+ type WrapperType<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> =
30
+ Partial<InputMessageInputContextValue<StreamChatGenerics>>;
31
+
32
+ afterEach(jest.clearAllMocks);
33
+
34
+ describe('MessageInputContext', () => {
35
+ const Wrapper = <
36
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
37
+ >({
38
+ children,
39
+ ...rest
40
+ }: PropsWithChildren<WrapperType<StreamChatGenerics>>) => (
41
+ <ChatProvider
42
+ value={
43
+ {
44
+ appSettings: {
45
+ app: {
46
+ file_upload_config: {
47
+ blocked_file_extensions: ['.mp3'],
48
+ blocked_mime_types: ['video/mp4'],
49
+ },
50
+ image_upload_config: {
51
+ blocked_file_extensions: ['.png'],
52
+ blocked_mime_types: ['image/png'],
53
+ },
54
+ },
55
+ } as unknown as AppSettingsAPIResponse<StreamChatGenerics>,
56
+ client: {
57
+ updateMessage: jest.fn().mockResolvedValue({ message: generateMessage() }),
58
+ } as unknown as StreamChat<StreamChatGenerics>,
59
+ } as ChatContextValue<StreamChatGenerics>
60
+ }
61
+ >
62
+ <MessageInputProvider
63
+ value={
64
+ {
65
+ ...rest,
66
+ } as MessageInputContextValue<StreamChatGenerics>
67
+ }
68
+ >
69
+ {children}
70
+ </MessageInputProvider>
71
+ </ChatProvider>
72
+ );
73
+
74
+ it('appendText works', () => {
75
+ const { result } = renderHook(() => useMessageInputContext(), {
76
+ initialProps: {
77
+ editing: true,
78
+ hasImagePicker: true,
79
+ },
80
+ wrapper: Wrapper,
81
+ });
82
+
83
+ act(() => {
84
+ result.current.setFileUploads([]);
85
+ result.current.setImageUploads([]);
86
+ result.current.setMentionedUsers([]);
87
+ result.current.setText('dummy');
88
+ result.current.appendText('text');
89
+ });
90
+
91
+ expect(result.current.text).toBe('dummytext');
92
+ });
93
+
94
+ it('uploadNewImage with blocked image extensions to be not supported', () => {
95
+ const { result } = renderHook(() => useMessageInputContext(), {
96
+ initialProps: {
97
+ editing: true,
98
+ maxNumberOfFiles: 2,
99
+ },
100
+ wrapper: Wrapper,
101
+ });
102
+
103
+ act(() => {
104
+ result.current.uploadNewImage(
105
+ generateImageAttachment({
106
+ uri: 'https://www.bastiaanmulder.nl/wp-content/uploads/2013/11/dummy-image-square.png',
107
+ }),
108
+ );
109
+ });
110
+
111
+ expect(result.current.imageUploads[0].state).toBe(FileState.NOT_SUPPORTED);
112
+ });
113
+
114
+ it('onSelectItem works', () => {
115
+ const mentioned_user = generateUser();
116
+ const initialUsers = [mentioned_user.id];
117
+
118
+ const { result } = renderHook(() => useMessageInputContext(), {
119
+ initialProps: {
120
+ editing: true,
121
+ hasImagePicker: true,
122
+ },
123
+ wrapper: Wrapper,
124
+ });
125
+
126
+ act(() => {
127
+ result.current.setFileUploads([]);
128
+ result.current.setImageUploads([]);
129
+ result.current.setMentionedUsers(initialUsers);
130
+ result.current.onSelectItem(mentioned_user);
131
+ });
132
+
133
+ expect(result.current.mentionedUsers.length).toBe(2);
134
+ });
135
+
136
+ it('setInputBoxRef works', () => {
137
+ const setInputRefMock = jest.fn();
138
+ const inputRef = React.createRef<TextInput | null>();
139
+ const { result } = renderHook(() => useMessageInputContext(), {
140
+ initialProps: {
141
+ editing: true,
142
+ setInputRef: setInputRefMock,
143
+ },
144
+ wrapper: Wrapper,
145
+ });
146
+
147
+ act(() => {
148
+ result.current.setInputBoxRef(inputRef.current);
149
+ });
150
+
151
+ expect(setInputRefMock).toHaveBeenCalled();
152
+ });
153
+
154
+ it('openCommandsPicker works', () => {
155
+ const { result } = renderHook(() => useMessageInputContext(), {
156
+ initialProps: {
157
+ editing: true,
158
+ hasImagePicker: true,
159
+ },
160
+ wrapper: Wrapper,
161
+ });
162
+
163
+ act(() => {
164
+ result.current.openCommandsPicker();
165
+ });
166
+
167
+ expect(result.current.text).toBe('/');
168
+ });
169
+
170
+ it('openMentionPicker works', () => {
171
+ const { result } = renderHook(() => useMessageInputContext(), {
172
+ initialProps: {
173
+ editing: true,
174
+ hasImagePicker: true,
175
+ },
176
+ wrapper: Wrapper,
177
+ });
178
+
179
+ act(() => {
180
+ result.current.openMentionsPicker();
181
+ });
182
+
183
+ expect(result.current.text).toBe('@');
184
+ });
185
+
186
+ it('openAttachmentPicker works', async () => {
187
+ jest.spyOn(NativeUtils, 'pickDocument').mockImplementation(
188
+ jest.fn().mockResolvedValue({
189
+ cancelled: false,
190
+ docs: [generateFileAttachment(), generateImageAttachment()],
191
+ }),
192
+ );
193
+ const { result } = renderHook(() => useMessageInputContext(), {
194
+ initialProps: {
195
+ editing: true,
196
+ hasFilePicker: true,
197
+ hasImagePicker: false,
198
+ },
199
+ wrapper: Wrapper,
200
+ });
201
+
202
+ act(() => {
203
+ result.current.openAttachmentPicker();
204
+ });
205
+
206
+ expect(await result.current.pickFile()).toBe(undefined);
207
+ });
208
+ });
@@ -0,0 +1,109 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`MessageInputContext's sendMessage exit sendMessage when edit message is not boolean image upload status is uploaded successfully 1`] = `
4
+ Object {
5
+ "attachments": Array [
6
+ Object {
7
+ "fallback": undefined,
8
+ "image_url": undefined,
9
+ "original_height": undefined,
10
+ "original_width": undefined,
11
+ "type": "image",
12
+ },
13
+ ],
14
+ "created_at": "Sat Jul 02 2022 23:55:13 GMT+0530 (India Standard Time)",
15
+ "html": "<p>regular</p>",
16
+ "id": "7a85f744-cc89-4f82-a1d4-5456432cc8bf",
17
+ "mentioned_users": Array [
18
+ "dummy1",
19
+ "dummy2",
20
+ ],
21
+ "quoted_message": undefined,
22
+ "text": "",
23
+ "type": "regular",
24
+ "updated_at": "Sat Jul 02 2022 23:55:13 GMT+0530 (India Standard Time)",
25
+ "user": Object {
26
+ "banned": false,
27
+ "created_at": "2020-04-27T13:39:49.331742Z",
28
+ "id": "5d6f6322-567e-4e1e-af90-97ef1ed5cc23",
29
+ "image": "fc86ddcb-bac4-400c-9afd-b0c0a1c0cd33",
30
+ "name": "50cbdd0e-ca7e-4478-9e2c-be0f1ac6a995",
31
+ "online": false,
32
+ "role": "user",
33
+ "updated_at": "2020-04-27T13:39:49.332087Z",
34
+ },
35
+ }
36
+ `;
37
+
38
+ exports[`MessageInputContext's sendMessage exit sendMessage when file upload status is uploaded successfully 1`] = `
39
+ Object {
40
+ "attachments": Array [
41
+ Object {
42
+ "asset_url": undefined,
43
+ "file_size": undefined,
44
+ "mime_type": "file",
45
+ "title": "dummy.pdf",
46
+ "type": "file",
47
+ },
48
+ Object {
49
+ "asset_url": undefined,
50
+ "duration": undefined,
51
+ "file_size": undefined,
52
+ "mime_type": "video/mp4",
53
+ "thumb_url": undefined,
54
+ "title": "dummy.pdf",
55
+ "type": "video",
56
+ },
57
+ Object {
58
+ "asset_url": undefined,
59
+ "duration": undefined,
60
+ "file_size": undefined,
61
+ "mime_type": "audio/mp3",
62
+ "title": "dummy.pdf",
63
+ "type": "audio",
64
+ },
65
+ Object {
66
+ "fallback": "dummy.pdf",
67
+ "image_url": undefined,
68
+ "type": "image",
69
+ },
70
+ ],
71
+ "mentioned_users": Array [
72
+ "dummy1",
73
+ "dummy2",
74
+ ],
75
+ "parent_id": undefined,
76
+ "quoted_message_id": undefined,
77
+ "show_in_channel": undefined,
78
+ "text": "",
79
+ }
80
+ `;
81
+
82
+ exports[`MessageInputContext's sendMessage exit sendMessage when image upload status is uploaded successfully 1`] = `
83
+ Object {
84
+ "attachments": Array [
85
+ Object {
86
+ "fallback": undefined,
87
+ "image_url": undefined,
88
+ "original_height": undefined,
89
+ "original_width": undefined,
90
+ "type": "image",
91
+ },
92
+ Object {
93
+ "fallback": undefined,
94
+ "image_url": undefined,
95
+ "original_height": undefined,
96
+ "original_width": undefined,
97
+ "type": "image",
98
+ },
99
+ ],
100
+ "mentioned_users": Array [
101
+ "dummy1",
102
+ "dummy2",
103
+ ],
104
+ "parent_id": undefined,
105
+ "quoted_message_id": undefined,
106
+ "show_in_channel": undefined,
107
+ "text": "",
108
+ }
109
+ `;