stream-chat-react-native-core 4.9.0-beta.3 → 4.10.0-beta.1

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 (150) 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/ChannelPreview/ChannelPreview.js +7 -3
  8. package/lib/commonjs/components/ChannelPreview/ChannelPreview.js.map +1 -1
  9. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +2 -1
  10. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  11. package/lib/commonjs/components/Chat/Chat.js +9 -5
  12. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  13. package/lib/commonjs/components/ImageGallery/ImageGallery.js +25 -22
  14. package/lib/commonjs/components/ImageGallery/ImageGallery.js.map +1 -1
  15. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js +15 -15
  16. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  17. package/lib/commonjs/components/Message/Message.js +13 -8
  18. package/lib/commonjs/components/Message/Message.js.map +1 -1
  19. package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js +7 -4
  20. package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  21. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +34 -35
  22. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  23. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  24. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  25. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js +15 -13
  26. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js.map +1 -1
  27. package/lib/commonjs/contexts/translationContext/TranslationContext.js +8 -4
  28. package/lib/commonjs/contexts/translationContext/TranslationContext.js.map +1 -1
  29. package/lib/commonjs/hooks/useStreami18n.js.map +1 -1
  30. package/lib/commonjs/hooks/useTranslatedMessage.js +34 -0
  31. package/lib/commonjs/hooks/useTranslatedMessage.js.map +1 -0
  32. package/lib/commonjs/mock-builders/api/getOrCreateChannel.js.map +1 -1
  33. package/lib/commonjs/mock-builders/generator/attachment.js +3 -2
  34. package/lib/commonjs/mock-builders/generator/attachment.js.map +1 -1
  35. package/lib/commonjs/mock-builders/generator/channel.js +8 -4
  36. package/lib/commonjs/mock-builders/generator/channel.js.map +1 -1
  37. package/lib/commonjs/utils/getUrlOfImageAttachment.js +1 -1
  38. package/lib/commonjs/utils/getUrlOfImageAttachment.js.map +1 -1
  39. package/lib/commonjs/version.json +1 -1
  40. package/lib/module/components/Attachment/Gallery.js +246 -190
  41. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  42. package/lib/module/components/Attachment/VideoThumbnail.js +11 -5
  43. package/lib/module/components/Attachment/VideoThumbnail.js.map +1 -1
  44. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  45. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  46. package/lib/module/components/ChannelPreview/ChannelPreview.js +7 -3
  47. package/lib/module/components/ChannelPreview/ChannelPreview.js.map +1 -1
  48. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +2 -1
  49. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  50. package/lib/module/components/Chat/Chat.js +9 -5
  51. package/lib/module/components/Chat/Chat.js.map +1 -1
  52. package/lib/module/components/ImageGallery/ImageGallery.js +25 -22
  53. package/lib/module/components/ImageGallery/ImageGallery.js.map +1 -1
  54. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js +15 -15
  55. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  56. package/lib/module/components/Message/Message.js +13 -8
  57. package/lib/module/components/Message/Message.js.map +1 -1
  58. package/lib/module/components/Message/MessageSimple/MessageTextContainer.js +7 -4
  59. package/lib/module/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  60. package/lib/module/contexts/messageInputContext/MessageInputContext.js +34 -35
  61. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  62. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  63. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  64. package/lib/module/contexts/overlayContext/OverlayProvider.js +15 -13
  65. package/lib/module/contexts/overlayContext/OverlayProvider.js.map +1 -1
  66. package/lib/module/contexts/translationContext/TranslationContext.js +8 -4
  67. package/lib/module/contexts/translationContext/TranslationContext.js.map +1 -1
  68. package/lib/module/hooks/useStreami18n.js.map +1 -1
  69. package/lib/module/hooks/useTranslatedMessage.js +34 -0
  70. package/lib/module/hooks/useTranslatedMessage.js.map +1 -0
  71. package/lib/module/mock-builders/api/getOrCreateChannel.js.map +1 -1
  72. package/lib/module/mock-builders/generator/attachment.js +3 -2
  73. package/lib/module/mock-builders/generator/attachment.js.map +1 -1
  74. package/lib/module/mock-builders/generator/channel.js +8 -4
  75. package/lib/module/mock-builders/generator/channel.js.map +1 -1
  76. package/lib/module/utils/getUrlOfImageAttachment.js +1 -1
  77. package/lib/module/utils/getUrlOfImageAttachment.js.map +1 -1
  78. package/lib/module/version.json +1 -1
  79. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts +3 -1
  80. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts +1 -0
  81. package/lib/typescript/components/ChannelPreview/__tests__/ChannelPreview.test.d.ts +1 -0
  82. package/lib/typescript/components/Message/MessageSimple/__tests__/MessageTextContainer.test.d.ts +1 -0
  83. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +1 -0
  84. package/lib/typescript/contexts/messageInputContext/__tests__/MessageInputContext.test.d.ts +1 -0
  85. package/lib/typescript/contexts/messageInputContext/__tests__/isValidMessage.test.d.ts +1 -0
  86. package/lib/typescript/contexts/messageInputContext/__tests__/pickFile.test.d.ts +1 -0
  87. package/lib/typescript/contexts/messageInputContext/__tests__/removeFile.test.d.ts +1 -0
  88. package/lib/typescript/contexts/messageInputContext/__tests__/removeImage.test.d.ts +1 -0
  89. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessage.test.d.ts +1 -0
  90. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessageAsync.test.d.ts +1 -0
  91. package/lib/typescript/contexts/messageInputContext/__tests__/updateMessage.test.d.ts +1 -0
  92. package/lib/typescript/contexts/messageInputContext/__tests__/uploadFile.test.d.ts +1 -0
  93. package/lib/typescript/contexts/messageInputContext/__tests__/uploadImage.test.d.ts +1 -0
  94. package/lib/typescript/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.d.ts +1 -0
  95. package/lib/typescript/contexts/messageOverlayContext/MessageOverlayContext.d.ts +2 -1
  96. package/lib/typescript/contexts/translationContext/TranslationContext.d.ts +7 -2
  97. package/lib/typescript/hooks/__tests__/useTranslatedMessage.test.d.ts +1 -0
  98. package/lib/typescript/hooks/useStreami18n.d.ts +2 -2
  99. package/lib/typescript/hooks/useTranslatedMessage.d.ts +3 -0
  100. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +13 -1
  101. package/lib/typescript/mock-builders/event/messageNew.d.ts +2 -0
  102. package/lib/typescript/mock-builders/event/messageRead.d.ts +8 -0
  103. package/lib/typescript/mock-builders/generator/attachment.d.ts +10 -0
  104. package/lib/typescript/mock-builders/generator/channel.d.ts +27 -23
  105. package/lib/typescript/mock-builders/generator/message.d.ts +38 -0
  106. package/lib/typescript/mock-builders/generator/user.d.ts +9 -9
  107. package/package.json +7 -6
  108. package/src/components/Attachment/Gallery.tsx +231 -140
  109. package/src/components/Attachment/VideoThumbnail.tsx +12 -5
  110. package/src/components/Attachment/__tests__/Attachment.test.js +12 -0
  111. package/src/components/Attachment/__tests__/Gallery.test.js +23 -1
  112. package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +1 -0
  113. package/src/components/Attachment/utils/buildGallery/types.ts +1 -0
  114. package/src/components/ChannelPreview/ChannelPreview.tsx +7 -1
  115. package/src/components/ChannelPreview/__tests__/{ChannelPreview.test.js → ChannelPreview.test.tsx} +79 -41
  116. package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +56 -21
  117. package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +4 -1
  118. package/src/components/Chat/Chat.tsx +6 -3
  119. package/src/components/ImageGallery/ImageGallery.tsx +10 -6
  120. package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +2 -1
  121. package/src/components/Message/Message.tsx +8 -1
  122. package/src/components/Message/MessageSimple/MessageTextContainer.tsx +7 -1
  123. package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx +108 -0
  124. package/src/components/Message/MessageSimple/__tests__/__snapshots__/{MessageTextContainer.test.js.snap → MessageTextContainer.test.tsx.snap} +0 -0
  125. package/src/contexts/messageInputContext/MessageInputContext.tsx +10 -4
  126. package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx +208 -0
  127. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap +113 -0
  128. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessageAsync.test.tsx.snap +33 -0
  129. package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx +71 -0
  130. package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +67 -0
  131. package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx +82 -0
  132. package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx +80 -0
  133. package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +286 -0
  134. package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx +124 -0
  135. package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx +92 -0
  136. package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx +76 -0
  137. package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx +55 -0
  138. package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx +62 -0
  139. package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx +2 -1
  140. package/src/contexts/overlayContext/OverlayProvider.tsx +4 -3
  141. package/src/contexts/translationContext/TranslationContext.tsx +10 -1
  142. package/src/hooks/__tests__/useTranslatedMessage.test.tsx +106 -0
  143. package/src/hooks/useStreami18n.ts +2 -2
  144. package/src/hooks/useTranslatedMessage.ts +30 -0
  145. package/src/mock-builders/api/{getOrCreateChannel.js → getOrCreateChannel.ts} +8 -1
  146. package/src/mock-builders/generator/attachment.js +4 -2
  147. package/src/mock-builders/generator/{channel.js → channel.ts} +19 -10
  148. package/src/utils/getUrlOfImageAttachment.ts +1 -1
  149. package/src/version.json +1 -1
  150. package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.js +0 -65
@@ -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
  });
@@ -5,11 +5,10 @@ import { waitFor } from '@testing-library/react-native';
5
5
 
6
6
  import type { DefaultStreamChatGenerics } from 'src/types/types';
7
7
 
8
- import type { DefaultGenerics, StreamChat } from 'stream-chat';
8
+ import type { DefaultGenerics, MessageResponse, StreamChat } from 'stream-chat';
9
9
 
10
10
  import { ChatContext, ChatContextValue } from '../../../../contexts/chatContext/ChatContext';
11
11
  import {
12
- CHANNEL,
13
12
  CHANNEL_WITH_DELETED_MESSAGES,
14
13
  CHANNEL_WITH_EMPTY_MESSAGE,
15
14
  CHANNEL_WITH_MENTIONED_USERS,
@@ -47,19 +46,11 @@ describe('useLatestMessagePreview', () => {
47
46
  </ChatContext.Provider>
48
47
  );
49
48
 
50
- it('should return a channel latest message', async () => {
51
- const { result } = renderHook(
52
- () => useLatestMessagePreview(CHANNEL, FORCE_UPDATE, LATEST_MESSAGE),
53
- { wrapper: ChatProvider },
54
- );
55
- await waitFor(() => {
56
- expect(result.current.previews).toBeTruthy();
57
- });
58
- });
49
+ it('should return a deleted message preview if the latest message is deleted', async () => {
50
+ const latestMessage = { type: 'deleted' } as unknown as MessageResponse;
59
51
 
60
- it('should return a deleted message preview', async () => {
61
52
  const { result } = renderHook(
62
- () => useLatestMessagePreview(CHANNEL_WITH_DELETED_MESSAGES, FORCE_UPDATE, LATEST_MESSAGE),
53
+ () => useLatestMessagePreview(CHANNEL_WITH_DELETED_MESSAGES, FORCE_UPDATE, latestMessage),
63
54
  { wrapper: ChatProvider },
64
55
  );
65
56
  await waitFor(() => {
@@ -67,9 +58,11 @@ describe('useLatestMessagePreview', () => {
67
58
  });
68
59
  });
69
60
 
70
- it('should return an "Nothing yet..." message preview', async () => {
61
+ it('should return an "Nothing yet..." message preview if channel has no messages', async () => {
62
+ const latestMessage = undefined;
63
+
71
64
  const { result } = renderHook(
72
- () => useLatestMessagePreview(CHANNEL_WITH_NO_MESSAGES, FORCE_UPDATE, LATEST_MESSAGE),
65
+ () => useLatestMessagePreview(CHANNEL_WITH_NO_MESSAGES, FORCE_UPDATE, latestMessage),
73
66
  { wrapper: ChatProvider },
74
67
  );
75
68
  await waitFor(() => {
@@ -77,11 +70,12 @@ describe('useLatestMessagePreview', () => {
77
70
  });
78
71
  });
79
72
 
80
- it('should return latest message preview', async () => {
73
+ it('should use latestMessage if provided', async () => {
81
74
  const { result } = renderHook(
82
75
  () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_TEXT, FORCE_UPDATE, LATEST_MESSAGE),
83
76
  { wrapper: ChatProvider },
84
77
  );
78
+
85
79
  await waitFor(() => {
86
80
  expect(result.current.previews).toEqual([
87
81
  { bold: true, text: '@okechukwu: ' },
@@ -91,10 +85,13 @@ describe('useLatestMessagePreview', () => {
91
85
  });
92
86
 
93
87
  it('should return a channel with an empty message preview', async () => {
88
+ const latestMessage = {} as unknown as MessageResponse;
89
+
94
90
  const { result } = renderHook(
95
- () => useLatestMessagePreview(CHANNEL_WITH_EMPTY_MESSAGE, FORCE_UPDATE, LATEST_MESSAGE),
91
+ () => useLatestMessagePreview(CHANNEL_WITH_EMPTY_MESSAGE, FORCE_UPDATE, latestMessage),
96
92
  { wrapper: ChatProvider },
97
93
  );
94
+
98
95
  await waitFor(() => {
99
96
  expect(result.current.previews).toEqual([
100
97
  { bold: false, text: '' },
@@ -104,8 +101,16 @@ describe('useLatestMessagePreview', () => {
104
101
  });
105
102
 
106
103
  it('should return a mentioned user (@Max) message preview', async () => {
104
+ const latestMessage = {
105
+ mentioned_users: [{ id: 'Max', name: 'Max' }],
106
+ text: 'Max',
107
+ user: {
108
+ id: 'okechukwu',
109
+ },
110
+ } as unknown as MessageResponse;
111
+
107
112
  const { result } = renderHook(
108
- () => useLatestMessagePreview(CHANNEL_WITH_MENTIONED_USERS, FORCE_UPDATE, LATEST_MESSAGE),
113
+ () => useLatestMessagePreview(CHANNEL_WITH_MENTIONED_USERS, FORCE_UPDATE, latestMessage),
109
114
  { wrapper: ChatProvider },
110
115
  );
111
116
  await waitFor(() => {
@@ -117,8 +122,15 @@ describe('useLatestMessagePreview', () => {
117
122
  });
118
123
 
119
124
  it('should return the latest command preview', async () => {
125
+ const latestMessage = {
126
+ command: 'giphy',
127
+ user: {
128
+ id: 'okechukwu',
129
+ },
130
+ } as unknown as MessageResponse;
131
+
120
132
  const { result } = renderHook(
121
- () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_COMMAND, FORCE_UPDATE, LATEST_MESSAGE),
133
+ () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_COMMAND, FORCE_UPDATE, latestMessage),
122
134
  { wrapper: ChatProvider },
123
135
  );
124
136
  await waitFor(() => {
@@ -130,11 +142,18 @@ describe('useLatestMessagePreview', () => {
130
142
  });
131
143
 
132
144
  it('should return an attachment preview', async () => {
145
+ const latestMessage = {
146
+ attachments: ['arbitrary value'],
147
+ user: {
148
+ id: 'okechukwu',
149
+ },
150
+ } as unknown as MessageResponse;
151
+
133
152
  const { result } = renderHook(
134
- () =>
135
- useLatestMessagePreview(CHANNEL_WITH_MESSAGES_ATTACHMENTS, FORCE_UPDATE, LATEST_MESSAGE),
153
+ () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_ATTACHMENTS, FORCE_UPDATE, latestMessage),
136
154
  { wrapper: ChatProvider },
137
155
  );
156
+
138
157
  await waitFor(() => {
139
158
  expect(result.current.previews).toEqual([
140
159
  { bold: true, text: '@okechukwu: ' },
@@ -142,4 +161,20 @@ describe('useLatestMessagePreview', () => {
142
161
  ]);
143
162
  });
144
163
  });
164
+
165
+ it('should default to messages from the channel state if latestMessage is undefined', async () => {
166
+ const latestMessage = undefined;
167
+
168
+ const { result } = renderHook(
169
+ () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_TEXT, FORCE_UPDATE, latestMessage),
170
+ { wrapper: ChatProvider },
171
+ );
172
+
173
+ await waitFor(() => {
174
+ expect(result.current.previews).toEqual([
175
+ { bold: true, text: '@okechukwu: ' },
176
+ { bold: false, text: 'jkbkbiubicbi' },
177
+ ]);
178
+ });
179
+ });
145
180
  });
@@ -207,7 +207,10 @@ const getLatestMessagePreview = <
207
207
  status: MessageReadStatus.NOT_SENT_BY_CURRENT_USER,
208
208
  };
209
209
  }
210
- const message = lastMessage || messages.length ? messages[messages.length - 1] : undefined;
210
+
211
+ const channelStateLastMessage = messages.length ? messages[messages.length - 1] : undefined;
212
+
213
+ const message = lastMessage !== undefined ? lastMessage : channelStateLastMessage;
211
214
 
212
215
  return {
213
216
  created_at: getLatestMessageDisplayDate(message, tDateTimeParser),
@@ -15,8 +15,9 @@ import { useOverlayContext } from '../../contexts/overlayContext/OverlayContext'
15
15
  import { DeepPartial, ThemeProvider } from '../../contexts/themeContext/ThemeContext';
16
16
  import type { Theme } from '../../contexts/themeContext/utils/theme';
17
17
  import {
18
- TranslationContextValue,
18
+ DEFAULT_USER_LANGUAGE,
19
19
  TranslationProvider,
20
+ TranslatorFunctions,
20
21
  } from '../../contexts/translationContext/TranslationContext';
21
22
  import { useStreami18n } from '../../hooks/useStreami18n';
22
23
  import init from '../../init';
@@ -129,7 +130,7 @@ const ChatWithContext = <
129
130
  const { children, client, closeConnectionOnBackground = true, i18nInstance, style } = props;
130
131
 
131
132
  const [channel, setChannel] = useState<Channel<StreamChatGenerics>>();
132
- const [translators, setTranslators] = useState<TranslationContextValue>({
133
+ const [translators, setTranslators] = useState<TranslatorFunctions>({
133
134
  t: (key: string) => key,
134
135
  tDateTimeParser: (input?: string | number | Date) => Dayjs(input),
135
136
  });
@@ -178,7 +179,9 @@ const ChatWithContext = <
178
179
 
179
180
  return (
180
181
  <ChatProvider<StreamChatGenerics> value={chatContext}>
181
- <TranslationProvider value={translators}>
182
+ <TranslationProvider
183
+ value={{ ...translators, userLanguage: client.user?.language || DEFAULT_USER_LANGUAGE }}
184
+ >
182
185
  <ThemeProvider style={style}>{children}</ThemeProvider>
183
186
  </TranslationProvider>
184
187
  </ChatProvider>
@@ -273,21 +273,25 @@ export const ImageGallery = <
273
273
 
274
274
  const attachmentPhotos = attachmentImages.map((a) => {
275
275
  const imageUrl = getUrlOfImageAttachment(a) as string;
276
+ const giphyURL = a.giphy?.[giphyVersion]?.url || a.thumb_url || a.image_url;
276
277
 
277
278
  return {
278
279
  channelId: cur.cid,
279
280
  created_at: cur.created_at,
280
281
  id: `photoId-${cur.id}-${imageUrl}`,
281
282
  messageId: cur.id,
282
- mime_type: a.mime_type,
283
+ mime_type: a.type === 'giphy' ? 'image/gif' : a.mime_type,
283
284
  original_height: a.original_height,
284
285
  original_width: a.original_width,
285
286
  type: a.type,
286
- uri: getResizedImageUrl({
287
- height: screenHeight,
288
- url: imageUrl,
289
- width: screenWidth,
290
- }),
287
+ uri:
288
+ a.type === 'giphy'
289
+ ? giphyURL
290
+ : getResizedImageUrl({
291
+ height: screenHeight,
292
+ url: imageUrl,
293
+ width: screenWidth,
294
+ }),
291
295
  user: cur.user,
292
296
  user_id: cur.user_id,
293
297
  };
@@ -171,7 +171,8 @@ export const ImageGalleryFooterWithContext = <
171
171
  }-${selectedIndex}.${extension}`,
172
172
  fromUrl: photo.uri,
173
173
  });
174
- await shareImage({ type: photo.mime_type, url: localFile });
174
+ // `image/jpeg` is added for the case where the mime_type isn't available for a file/image
175
+ await shareImage({ type: photo.mime_type ?? 'image/jpeg', url: localFile });
175
176
  await deleteFile({ uri: localFile });
176
177
  } catch (error) {
177
178
  console.log(error);
@@ -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);
@@ -499,6 +503,8 @@ const MessageWithContext = <
499
503
  updateMessage,
500
504
  });
501
505
 
506
+ const { userLanguage } = useTranslationContext();
507
+
502
508
  const showMessageOverlay = async (messageReactions = false, error = errorOrFailed) => {
503
509
  await dismissKeyboard();
504
510
 
@@ -548,6 +554,7 @@ const MessageWithContext = <
548
554
  ownCapabilities,
549
555
  supportedReactions,
550
556
  threadList,
557
+ userLanguage,
551
558
  videos: attachments.videos,
552
559
  });
553
560
 
@@ -14,7 +14,9 @@ import {
14
14
  import { useTheme } from '../../../contexts/themeContext/ThemeContext';
15
15
 
16
16
  import type { MarkdownStyle, Theme } from '../../../contexts/themeContext/utils/theme';
17
+ import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage';
17
18
  import type { DefaultStreamChatGenerics } from '../../../types/types';
19
+ import type { MessageType } from '../../MessageList/hooks/useMessageList';
18
20
 
19
21
  const styles = StyleSheet.create({
20
22
  textContainer: { maxWidth: 250, paddingHorizontal: 16 },
@@ -75,6 +77,10 @@ const MessageTextContainerWithContext = <
75
77
  },
76
78
  } = theme;
77
79
 
80
+ const translatedMessage = useTranslatedMessage<StreamChatGenerics>(
81
+ message,
82
+ ) as MessageType<StreamChatGenerics>;
83
+
78
84
  if (!message.text) return null;
79
85
 
80
86
  const markdownStyles = { ...markdown, ...markdownStylesProp };
@@ -94,7 +100,7 @@ const MessageTextContainerWithContext = <
94
100
  ...markdownStyles,
95
101
  ...(onlyEmojis ? onlyEmojiMarkdown : {}),
96
102
  },
97
- message,
103
+ message: translatedMessage,
98
104
  messageOverlay,
99
105
  messageTextNumberOfLines,
100
106
  onLongPress,
@@ -0,0 +1,108 @@
1
+ import React from 'react';
2
+ import { Text } from 'react-native';
3
+
4
+ import { cleanup, render, waitFor } from '@testing-library/react-native';
5
+
6
+ import { OverlayProvider } from '../../../../contexts/overlayContext/OverlayProvider';
7
+ import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext';
8
+ import { defaultTheme } from '../../../../contexts/themeContext/utils/theme';
9
+ import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel';
10
+ import { useMockedApis } from '../../../../mock-builders/api/useMockedApis';
11
+ import { generateChannelResponse } from '../../../../mock-builders/generator/channel';
12
+ import {
13
+ generateMessage,
14
+ generateStaticMessage,
15
+ } from '../../../../mock-builders/generator/message';
16
+ import { generateStaticUser } from '../../../../mock-builders/generator/user';
17
+ import { getTestClientWithUser } from '../../../../mock-builders/mock';
18
+ import { Channel } from '../../../Channel/Channel';
19
+ import { Chat } from '../../../Chat/Chat';
20
+ import type { MessageType } from '../../../MessageList/hooks/useMessageList';
21
+ import { MessageList } from '../../../MessageList/MessageList';
22
+ import { MessageTextContainer } from '../MessageTextContainer';
23
+
24
+ afterEach(cleanup);
25
+
26
+ describe('MessageTextContainer', () => {
27
+ it('should render message text container', async () => {
28
+ const staticUser = generateStaticUser(1);
29
+ const message = generateMessage({
30
+ user: { ...staticUser, image: undefined },
31
+ });
32
+ const { getByTestId, getByText, rerender, toJSON } = render(
33
+ <ThemeProvider style={defaultTheme}>
34
+ <MessageTextContainer message={message as unknown as MessageType} />
35
+ </ThemeProvider>,
36
+ );
37
+
38
+ await waitFor(() => {
39
+ expect(getByTestId('message-text-container')).toBeTruthy();
40
+ expect(getByText(message.text)).toBeTruthy();
41
+ });
42
+
43
+ rerender(
44
+ <ThemeProvider style={defaultTheme}>
45
+ <MessageTextContainer
46
+ message={message as unknown as MessageType}
47
+ MessageText={({ message }) => <Text testID='message-text'>{message?.text}</Text>}
48
+ />
49
+ </ThemeProvider>,
50
+ );
51
+
52
+ await waitFor(() => {
53
+ expect(getByTestId('message-text-container')).toBeTruthy();
54
+ expect(getByTestId('message-text')).toBeTruthy();
55
+ expect(getByText(message.text)).toBeTruthy();
56
+ });
57
+
58
+ const staticMessage = generateStaticMessage('Hello World', {
59
+ user: staticUser,
60
+ });
61
+
62
+ rerender(
63
+ <ThemeProvider style={defaultTheme}>
64
+ <MessageTextContainer message={staticMessage as unknown as MessageType} />
65
+ </ThemeProvider>,
66
+ );
67
+
68
+ await waitFor(() => {
69
+ expect(toJSON()).toMatchSnapshot();
70
+ });
71
+ });
72
+
73
+ it('should display a translated message if applicable', async () => {
74
+ const chatClient = await getTestClientWithUser({ id: 'mads', language: 'no' });
75
+
76
+ const message = {
77
+ i18n: {
78
+ no_text: 'Hallo verden!',
79
+ },
80
+ text: 'Hello world!',
81
+ };
82
+
83
+ const mockedChannel = generateChannelResponse({
84
+ id: 'chans',
85
+ messages: [message],
86
+ });
87
+
88
+ useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
89
+ const channel = chatClient.channel('messaging', 'chans');
90
+ await channel.watch();
91
+
92
+ const TestComponent = () => (
93
+ <OverlayProvider>
94
+ <Chat client={chatClient}>
95
+ <Channel channel={channel}>
96
+ <MessageList />
97
+ </Channel>
98
+ </Chat>
99
+ </OverlayProvider>
100
+ );
101
+
102
+ const { getByText } = render(<TestComponent />);
103
+
104
+ await waitFor(() => {
105
+ expect(getByText(message.i18n.no_text)).toBeTruthy();
106
+ });
107
+ });
108
+ });
@@ -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
 
@@ -668,10 +669,14 @@ export const MessageInputProvider = <
668
669
  }
669
670
  }
670
671
 
672
+ // To get the mime type of the image from the file name and send it as an response for an image
673
+ const mime_type: string | boolean = lookup(image.file.filename as string);
674
+
671
675
  if (image.state === FileState.UPLOADED || image.state === FileState.FINISHED) {
672
676
  attachments.push({
673
677
  fallback: image.file.name,
674
678
  image_url: image.url,
679
+ mime_type,
675
680
  original_height: image.height,
676
681
  original_width: image.width,
677
682
  type: 'image',
@@ -688,11 +693,14 @@ export const MessageInputProvider = <
688
693
  sending.current = false;
689
694
  return;
690
695
  }
696
+ const mime_type: string | boolean = lookup(file.file.name as string);
697
+
691
698
  if (file.state === FileState.UPLOADED || file.state === FileState.FINISHED) {
692
699
  if (file.file.type?.startsWith('image/')) {
693
700
  attachments.push({
694
701
  fallback: file.file.name,
695
702
  image_url: file.url,
703
+ mime_type,
696
704
  type: 'image',
697
705
  } as Attachment<StreamChatGenerics>);
698
706
  } else if (file.file.type?.startsWith('audio/')) {
@@ -710,6 +718,7 @@ export const MessageInputProvider = <
710
718
  duration: file.file.duration,
711
719
  file_size: file.file.size,
712
720
  mime_type: file.file.type,
721
+ thumb_url: file.thumb_url,
713
722
  title: file.file.name,
714
723
  type: 'video',
715
724
  } as Attachment<StreamChatGenerics>);
@@ -897,9 +906,6 @@ export const MessageInputProvider = <
897
906
  };
898
907
 
899
908
  const uploadFile = async ({ newFile }: { newFile: FileUpload }) => {
900
- if (!newFile) {
901
- return;
902
- }
903
909
  const { file, id } = newFile;
904
910
 
905
911
  setFileUploads(getUploadSetStateAction(id, FileState.UPLOADING));
@@ -911,7 +917,7 @@ export const MessageInputProvider = <
911
917
  } else if (channel && file.uri) {
912
918
  response = await channel.sendFile(file.uri, file.name, file.type);
913
919
  }
914
- const extraData: Partial<FileUpload> = { url: response.file };
920
+ const extraData: Partial<FileUpload> = { thumb_url: response.thumb_url, url: response.file };
915
921
  setFileUploads(getUploadSetStateAction(id, FileState.UPLOADED, extraData));
916
922
  } catch (error: unknown) {
917
923
  handleFileOrImageUploadError(error, false, id);