stream-chat-react-native-core 4.8.0 → 4.9.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/lib/commonjs/components/Attachment/GallaryImage.js +41 -0
  2. package/lib/commonjs/components/Attachment/GallaryImage.js.map +1 -0
  3. package/lib/commonjs/components/Attachment/Gallery.js +101 -36
  4. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  5. package/lib/commonjs/components/Attachment/Giphy.js +118 -25
  6. package/lib/commonjs/components/Attachment/Giphy.js.map +1 -1
  7. package/lib/commonjs/components/Attachment/ImageLoadingFailedIndicator.js +105 -0
  8. package/lib/commonjs/components/Attachment/ImageLoadingFailedIndicator.js.map +1 -0
  9. package/lib/commonjs/components/Attachment/ImageLoadingIndicator.js +58 -0
  10. package/lib/commonjs/components/Attachment/ImageLoadingIndicator.js.map +1 -0
  11. package/lib/commonjs/components/Attachment/hooks/useLoadingImage.js +32 -0
  12. package/lib/commonjs/components/Attachment/hooks/useLoadingImage.js.map +1 -0
  13. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteInput.js +3 -2
  14. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  15. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteSuggestionItem.js +1 -1
  16. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteSuggestionItem.js.map +1 -1
  17. package/lib/commonjs/components/Channel/Channel.js +23 -13
  18. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  19. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  20. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  21. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +43 -19
  22. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  23. package/lib/commonjs/components/MessageInput/FileUploadPreview.js +28 -21
  24. package/lib/commonjs/components/MessageInput/FileUploadPreview.js.map +1 -1
  25. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js +16 -17
  26. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js.map +1 -1
  27. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  28. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  29. package/lib/commonjs/components/Reply/Reply.js +7 -2
  30. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  31. package/lib/commonjs/contexts/messagesContext/MessagesContext.js +2 -2
  32. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  33. package/lib/commonjs/contexts/themeContext/utils/theme.js +4 -0
  34. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  35. package/lib/commonjs/mock-builders/api/channelMocks.js +299 -0
  36. package/lib/commonjs/mock-builders/api/channelMocks.js.map +1 -0
  37. package/lib/commonjs/version.json +1 -1
  38. package/lib/module/components/Attachment/GallaryImage.js +41 -0
  39. package/lib/module/components/Attachment/GallaryImage.js.map +1 -0
  40. package/lib/module/components/Attachment/Gallery.js +101 -36
  41. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  42. package/lib/module/components/Attachment/Giphy.js +118 -25
  43. package/lib/module/components/Attachment/Giphy.js.map +1 -1
  44. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js +105 -0
  45. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js.map +1 -0
  46. package/lib/module/components/Attachment/ImageLoadingIndicator.js +58 -0
  47. package/lib/module/components/Attachment/ImageLoadingIndicator.js.map +1 -0
  48. package/lib/module/components/Attachment/hooks/useLoadingImage.js +32 -0
  49. package/lib/module/components/Attachment/hooks/useLoadingImage.js.map +1 -0
  50. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js +3 -2
  51. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  52. package/lib/module/components/AutoCompleteInput/AutoCompleteSuggestionItem.js +1 -1
  53. package/lib/module/components/AutoCompleteInput/AutoCompleteSuggestionItem.js.map +1 -1
  54. package/lib/module/components/Channel/Channel.js +23 -13
  55. package/lib/module/components/Channel/Channel.js.map +1 -1
  56. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  57. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  58. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +43 -19
  59. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  60. package/lib/module/components/MessageInput/FileUploadPreview.js +28 -21
  61. package/lib/module/components/MessageInput/FileUploadPreview.js.map +1 -1
  62. package/lib/module/components/MessageInput/ImageUploadPreview.js +16 -17
  63. package/lib/module/components/MessageInput/ImageUploadPreview.js.map +1 -1
  64. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  65. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  66. package/lib/module/components/Reply/Reply.js +7 -2
  67. package/lib/module/components/Reply/Reply.js.map +1 -1
  68. package/lib/module/contexts/messagesContext/MessagesContext.js +2 -2
  69. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  70. package/lib/module/contexts/themeContext/utils/theme.js +4 -0
  71. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  72. package/lib/module/mock-builders/api/channelMocks.js +299 -0
  73. package/lib/module/mock-builders/api/channelMocks.js.map +1 -0
  74. package/lib/module/version.json +1 -1
  75. package/lib/typescript/components/Attachment/GallaryImage.d.ts +5 -0
  76. package/lib/typescript/components/Attachment/Gallery.d.ts +1 -1
  77. package/lib/typescript/components/Attachment/Giphy.d.ts +1 -1
  78. package/lib/typescript/components/Attachment/ImageLoadingFailedIndicator.d.ts +3 -0
  79. package/lib/typescript/components/Attachment/ImageLoadingIndicator.d.ts +3 -0
  80. package/lib/typescript/components/Attachment/hooks/useLoadingImage.d.ts +7 -0
  81. package/lib/typescript/components/Channel/Channel.d.ts +1 -1
  82. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts +1 -1
  83. package/lib/typescript/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.d.ts +1 -0
  84. package/lib/typescript/components/RTLComponents/WritingDirectionAwareText.d.ts +4 -0
  85. package/lib/typescript/components/Reply/__tests__/Reply.test.d.ts +1 -0
  86. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +10 -0
  87. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +4 -0
  88. package/lib/typescript/mock-builders/api/channelMocks.d.ts +14 -0
  89. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +7 -0
  90. package/lib/typescript/mock-builders/api/useMockedApis.d.ts +1 -0
  91. package/lib/typescript/mock-builders/generator/channel.d.ts +116 -0
  92. package/package.json +3 -2
  93. package/src/components/Attachment/GallaryImage.tsx +22 -0
  94. package/src/components/Attachment/Gallery.tsx +68 -35
  95. package/src/components/Attachment/Giphy.tsx +68 -1
  96. package/src/components/Attachment/ImageLoadingFailedIndicator.tsx +56 -0
  97. package/src/components/Attachment/ImageLoadingIndicator.tsx +31 -0
  98. package/src/components/Attachment/__tests__/Attachment.test.js +9 -3
  99. package/src/components/Attachment/__tests__/Gallery.test.js +36 -1
  100. package/src/components/Attachment/__tests__/Giphy.test.js +110 -90
  101. package/src/components/Attachment/hooks/useLoadingImage.tsx +8 -0
  102. package/src/components/AutoCompleteInput/AutoCompleteInput.tsx +2 -1
  103. package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx +1 -1
  104. package/src/components/Channel/Channel.tsx +8 -0
  105. package/src/components/Channel/hooks/useCreateMessagesContext.ts +4 -0
  106. package/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js +0 -1
  107. package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +145 -0
  108. package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +50 -24
  109. package/src/components/MessageInput/FileUploadPreview.tsx +11 -8
  110. package/src/components/MessageInput/ImageUploadPreview.tsx +4 -5
  111. package/src/components/MessageInput/__tests__/__snapshots__/MessageInput.test.js.snap +1 -0
  112. package/src/components/RTLComponents/WritingDirectionAwareText.tsx +17 -0
  113. package/src/components/Reply/Reply.tsx +19 -3
  114. package/src/components/Reply/__tests__/Reply.test.tsx +48 -0
  115. package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +1 -0
  116. package/src/contexts/messagesContext/MessagesContext.tsx +15 -0
  117. package/src/contexts/themeContext/utils/theme.ts +8 -0
  118. package/src/mock-builders/api/channelMocks.tsx +272 -0
  119. package/src/version.json +1 -1
@@ -4,6 +4,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react-native';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
6
6
  import { MessageProvider } from '../../../contexts/messageContext/MessageContext';
7
+ import { MessagesProvider } from '../../../contexts/messagesContext/MessagesContext';
7
8
  import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
8
9
  import {
9
10
  generateAttachmentAction,
@@ -12,6 +13,9 @@ import {
12
13
  generateImageAttachment,
13
14
  } from '../../../mock-builders/generator/attachment';
14
15
  import { generateMessage } from '../../../mock-builders/generator/message';
16
+
17
+ import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
18
+ import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
15
19
  import { Attachment } from '../Attachment';
16
20
  import { AttachmentActions } from '../AttachmentActions';
17
21
 
@@ -19,9 +23,11 @@ const getAttachmentComponent = (props) => {
19
23
  const message = generateMessage();
20
24
  return (
21
25
  <ThemeProvider>
22
- <MessageProvider value={{ message }}>
23
- <Attachment {...props} />
24
- </MessageProvider>
26
+ <MessagesProvider value={{ ImageLoadingFailedIndicator, ImageLoadingIndicator }}>
27
+ <MessageProvider value={{ message }}>
28
+ <Attachment {...props} />
29
+ </MessageProvider>
30
+ </MessagesProvider>
25
31
  </ThemeProvider>
26
32
  );
27
33
  };
@@ -1,6 +1,11 @@
1
1
  import React from 'react';
2
2
 
3
- import { render, waitFor } from '@testing-library/react-native';
3
+ import {
4
+ fireEvent,
5
+ render,
6
+ waitFor,
7
+ waitForElementToBeRemoved,
8
+ } from '@testing-library/react-native';
4
9
 
5
10
  import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider';
6
11
 
@@ -230,4 +235,34 @@ describe('Gallery', () => {
230
235
  expect(queryAllByTestId('gallery-column-1-item-1').length).toBe(1);
231
236
  });
232
237
  });
238
+
239
+ it('should render an error indicator', async () => {
240
+ const image1 = generateImageAttachment({
241
+ original_height: 300,
242
+ original_width: 600,
243
+ });
244
+
245
+ const component = await getComponent([image1]);
246
+ const { getByA11yLabel, getByAccessibilityHint } = render(component);
247
+
248
+ fireEvent(getByA11yLabel('gallery-image'), 'error');
249
+ expect(getByAccessibilityHint('image-loading-error')).toBeTruthy();
250
+ });
251
+
252
+ it('should render a loading indicator and when successful render the image', async () => {
253
+ const image1 = generateImageAttachment({
254
+ original_height: 300,
255
+ original_width: 600,
256
+ });
257
+
258
+ const component = await getComponent([image1]);
259
+ const { getByA11yLabel, getByAccessibilityHint } = render(component);
260
+
261
+ fireEvent(getByA11yLabel('gallery-image'), 'onLoadStart');
262
+ expect(getByAccessibilityHint('image-loading')).toBeTruthy();
263
+
264
+ fireEvent(getByA11yLabel('gallery-image'), 'onLoadFinish');
265
+ waitForElementToBeRemoved(() => getByAccessibilityHint('image-loading'));
266
+ expect(getByA11yLabel('gallery-image')).toBeTruthy();
267
+ });
233
268
  });
@@ -1,22 +1,28 @@
1
1
  import React from 'react';
2
2
 
3
- import { fireEvent, render, waitFor } from '@testing-library/react-native';
3
+ import {
4
+ cleanup,
5
+ fireEvent,
6
+ render,
7
+ waitFor,
8
+ waitForElementToBeRemoved,
9
+ } from '@testing-library/react-native';
4
10
 
5
11
  import { MessageProvider } from '../../../contexts/messageContext/MessageContext';
12
+ import { MessagesProvider } from '../../../contexts/messagesContext/MessagesContext';
6
13
  import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider';
7
14
 
8
15
  import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
9
16
  import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel';
10
17
  import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
11
- import {
12
- generateGiphyAttachment,
13
- generateImgurAttachment,
14
- } from '../../../mock-builders/generator/attachment';
18
+ import { generateGiphyAttachment } from '../../../mock-builders/generator/attachment';
15
19
  import { generateChannelResponse } from '../../../mock-builders/generator/channel';
16
20
  import { generateMember } from '../../../mock-builders/generator/member';
17
21
  import { generateMessage } from '../../../mock-builders/generator/message';
18
22
  import { generateUser } from '../../../mock-builders/generator/user';
19
23
  import { getTestClientWithUser } from '../../../mock-builders/mock';
24
+ import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
25
+ import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
20
26
  import { Channel } from '../../Channel/Channel';
21
27
  import { Chat } from '../../Chat/Chat';
22
28
  import { MessageList } from '../../MessageList/MessageList';
@@ -27,15 +33,66 @@ describe('Giphy', () => {
27
33
  const message = generateMessage();
28
34
  return (
29
35
  <ThemeProvider>
30
- <MessageProvider value={{ message }}>
31
- <Giphy {...props} />
32
- </MessageProvider>
36
+ <MessagesProvider value={{ ImageLoadingFailedIndicator, ImageLoadingIndicator }}>
37
+ <MessageProvider value={{ message }}>
38
+ <Giphy {...props} />
39
+ </MessageProvider>
40
+ </MessagesProvider>
33
41
  </ThemeProvider>
34
42
  );
35
43
  };
44
+ let chatClient;
45
+ let channel;
46
+ let attachment;
47
+
48
+ const actions = [
49
+ { name: 'image_action', text: 'Send', value: 'send' },
50
+ { name: 'image_action', text: 'Shuffle', value: 'shuffle' },
51
+ {
52
+ name: 'image_action',
53
+ text: 'Cancel',
54
+ value: 'cancel',
55
+ },
56
+ ];
57
+
58
+ const giphy = {
59
+ fixed_height: {
60
+ height: '200',
61
+ url: 'https://media1.giphy.com/media/test/fixed_height.gif',
62
+ width: '375',
63
+ },
64
+ original: {
65
+ height: '256',
66
+ url: 'https://media1.giphy.com/media/test/original.gif',
67
+ width: '480',
68
+ },
69
+ };
70
+ const initChannel = async () => {
71
+ const user1 = generateUser();
72
+ attachment = generateGiphyAttachment();
73
+
74
+ const mockedChannel = generateChannelResponse({
75
+ members: [generateMember({ user: user1 })],
76
+ messages: [
77
+ generateMessage({ user: user1 }),
78
+ generateMessage({ type: 'system', user: undefined }),
79
+ generateMessage({ attachments: [{ ...attachment }], user: user1 }),
80
+ ],
81
+ });
82
+
83
+ chatClient = await getTestClientWithUser({ id: 'testID' });
84
+ useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
85
+ channel = chatClient.channel('messaging', mockedChannel.id);
86
+ await channel.watch();
87
+ };
88
+
89
+ beforeEach(async () => {
90
+ await initChannel();
91
+ });
92
+
93
+ afterEach(cleanup);
36
94
 
37
95
  it('should render Card component for "imgur" type attachment', async () => {
38
- const attachment = generateImgurAttachment();
39
96
  const { getByTestId } = render(getAttachmentComponent({ attachment }));
40
97
 
41
98
  await waitFor(() => {
@@ -44,7 +101,6 @@ describe('Giphy', () => {
44
101
  });
45
102
 
46
103
  it('should render Card component for "giphy" type attachment', async () => {
47
- const attachment = generateGiphyAttachment();
48
104
  const { getByTestId } = render(getAttachmentComponent({ attachment }));
49
105
 
50
106
  await waitFor(() => {
@@ -53,19 +109,7 @@ describe('Giphy', () => {
53
109
  });
54
110
 
55
111
  it('"giphy" attachment size should be customisable', async () => {
56
- const attachment = generateGiphyAttachment();
57
- attachment.giphy = {
58
- fixed_height: {
59
- height: '200',
60
- url: 'https://media1.giphy.com/media/test/fixed_height.gif',
61
- width: '375',
62
- },
63
- original: {
64
- height: '256',
65
- url: 'https://media1.giphy.com/media/test/original.gif',
66
- width: '480',
67
- },
68
- };
112
+ attachment.giphy = giphy;
69
113
  const { getByTestId: getByTestIdFixedHeight } = render(
70
114
  getAttachmentComponent({ attachment, giphyVersion: 'fixed_height' }),
71
115
  );
@@ -94,16 +138,7 @@ describe('Giphy', () => {
94
138
  });
95
139
 
96
140
  it('show render giphy action UI and all the 3 action buttons', async () => {
97
- const attachment = generateGiphyAttachment();
98
- attachment.actions = [
99
- { name: 'image_action', text: 'Send', value: 'send' },
100
- { name: 'image_action', text: 'Shuffle', value: 'shuffle' },
101
- {
102
- name: 'image_action',
103
- text: 'Cancel',
104
- value: 'cancel',
105
- },
106
- ];
141
+ attachment.actions = actions;
107
142
  const { getByTestId } = render(
108
143
  getAttachmentComponent({ attachment, giphyVersion: 'fixed_height' }),
109
144
  );
@@ -117,17 +152,8 @@ describe('Giphy', () => {
117
152
  });
118
153
 
119
154
  it('should trigger the cancel giphy action', async () => {
120
- const attachment = generateGiphyAttachment();
121
155
  const handleAction = jest.fn();
122
- attachment.actions = [
123
- { name: 'image_action', text: 'Send', value: 'send' },
124
- { name: 'image_action', text: 'Shuffle', value: 'shuffle' },
125
- {
126
- name: 'image_action',
127
- text: 'Cancel',
128
- value: 'cancel',
129
- },
130
- ];
156
+ attachment.actions = actions;
131
157
  const { getByTestId } = render(
132
158
  getAttachmentComponent({
133
159
  attachment,
@@ -150,17 +176,8 @@ describe('Giphy', () => {
150
176
  });
151
177
 
152
178
  it('should trigger the shuffle giphy action', async () => {
153
- const attachment = generateGiphyAttachment();
154
179
  const handleAction = jest.fn();
155
- attachment.actions = [
156
- { name: 'image_action', text: 'Send', value: 'send' },
157
- { name: 'image_action', text: 'Shuffle', value: 'shuffle' },
158
- {
159
- name: 'image_action',
160
- text: 'Cancel',
161
- value: 'cancel',
162
- },
163
- ];
180
+ attachment.actions = actions;
164
181
  const { getByTestId } = render(
165
182
  getAttachmentComponent({
166
183
  attachment,
@@ -183,17 +200,8 @@ describe('Giphy', () => {
183
200
  });
184
201
 
185
202
  it('should trigger the send giphy action', async () => {
186
- const attachment = generateGiphyAttachment();
187
203
  const handleAction = jest.fn();
188
- attachment.actions = [
189
- { name: 'image_action', text: 'Send', value: 'send' },
190
- { name: 'image_action', text: 'Shuffle', value: 'shuffle' },
191
- {
192
- name: 'image_action',
193
- text: 'Cancel',
194
- value: 'cancel',
195
- },
196
- ];
204
+ attachment.actions = actions;
197
205
  const { getByTestId } = render(
198
206
  getAttachmentComponent({
199
207
  attachment,
@@ -217,16 +225,7 @@ describe('Giphy', () => {
217
225
 
218
226
  it('giphy attachment UI should render within the message list with actions', async () => {
219
227
  const user1 = generateUser();
220
- const attachment = generateGiphyAttachment();
221
- attachment.actions = [
222
- { name: 'image_action', text: 'Send', value: 'send' },
223
- { name: 'image_action', text: 'Shuffle', value: 'shuffle' },
224
- {
225
- name: 'image_action',
226
- text: 'Cancel',
227
- value: 'cancel',
228
- },
229
- ];
228
+ attachment.actions = actions;
230
229
  const mockedChannel = generateChannelResponse({
231
230
  members: [generateMember({ user: user1 })],
232
231
  messages: [
@@ -260,23 +259,6 @@ describe('Giphy', () => {
260
259
  });
261
260
 
262
261
  it('giphy attachment UI should render within the message list', async () => {
263
- const user1 = generateUser();
264
- const attachment = generateGiphyAttachment();
265
-
266
- const mockedChannel = generateChannelResponse({
267
- members: [generateMember({ user: user1 })],
268
- messages: [
269
- generateMessage({ user: user1 }),
270
- generateMessage({ type: 'system', user: undefined }),
271
- generateMessage({ attachments: [{ ...attachment }], user: user1 }),
272
- ],
273
- });
274
-
275
- const chatClient = await getTestClientWithUser({ id: 'testID' });
276
- useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
277
- const channel = chatClient.channel('messaging', mockedChannel.id);
278
- await channel.watch();
279
-
280
262
  const { queryByTestId } = render(
281
263
  <OverlayProvider>
282
264
  <Chat client={chatClient}>
@@ -291,4 +273,42 @@ describe('Giphy', () => {
291
273
  expect(queryByTestId('giphy-attachment')).toBeTruthy();
292
274
  });
293
275
  });
276
+
277
+ it('should render a error indicator in giphy image', () => {
278
+ const { getByA11yLabel, getByAccessibilityHint } = render(
279
+ <OverlayProvider>
280
+ <Chat client={chatClient}>
281
+ <Channel channel={channel}>
282
+ <MessageList />
283
+ </Channel>
284
+ </Chat>
285
+ </OverlayProvider>,
286
+ );
287
+
288
+ fireEvent(getByA11yLabel('giphy-attachment-image'), 'error');
289
+ expect(getByAccessibilityHint('image-loading-error')).toBeTruthy();
290
+ });
291
+
292
+ it('should render a loading indicator in giphy image and when successful render the image', () => {
293
+ const { getByA11yLabel, getByAccessibilityHint } = render(
294
+ <OverlayProvider>
295
+ <Chat client={chatClient}>
296
+ <Channel channel={channel}>
297
+ <MessageList />
298
+ </Channel>
299
+ </Chat>
300
+ </OverlayProvider>,
301
+ );
302
+
303
+ expect(getByAccessibilityHint('image-loading')).toBeTruthy();
304
+
305
+ fireEvent(getByA11yLabel('giphy-attachment-image'), 'onLoadStart');
306
+
307
+ expect(getByAccessibilityHint('image-loading')).toBeTruthy();
308
+
309
+ fireEvent(getByA11yLabel('giphy-attachment-image'), 'onLoadFinish');
310
+
311
+ waitForElementToBeRemoved(() => getByAccessibilityHint('image-loading'));
312
+ expect(getByA11yLabel('giphy-attachment-image')).toBeTruthy();
313
+ });
294
314
  });
@@ -0,0 +1,8 @@
1
+ import { useState } from 'react';
2
+
3
+ export const useLoadingImage = () => {
4
+ const [isLoadingImage, setLoadingImage] = useState(true);
5
+ const [isLoadingImageError, setLoadingImageError] = useState(false);
6
+
7
+ return { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError };
8
+ };
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
- import { StyleSheet, TextInput, TextInputProps } from 'react-native';
2
+ import { I18nManager, StyleSheet, TextInput, TextInputProps } from 'react-native';
3
3
 
4
4
  import throttle from 'lodash/throttle';
5
5
 
@@ -417,6 +417,7 @@ const AutoCompleteInputWithContext = <
417
417
  {
418
418
  color: black,
419
419
  maxHeight: (textHeight || 17) * numberOfLines,
420
+ textAlign: I18nManager.isRTL ? 'right' : 'left',
420
421
  },
421
422
  inputBox,
422
423
  ]}
@@ -51,7 +51,7 @@ const styles = StyleSheet.create({
51
51
  title: {
52
52
  fontSize: 14,
53
53
  fontWeight: 'bold',
54
- paddingRight: 8,
54
+ paddingHorizontal: 8,
55
55
  },
56
56
  });
57
57
 
@@ -82,6 +82,8 @@ import { FileAttachmentGroup as FileAttachmentGroupDefault } from '../Attachment
82
82
  import { FileIcon as FileIconDefault } from '../Attachment/FileIcon';
83
83
  import { Gallery as GalleryDefault } from '../Attachment/Gallery';
84
84
  import { Giphy as GiphyDefault } from '../Attachment/Giphy';
85
+ import { ImageLoadingFailedIndicator as ImageLoadingFailedIndicatorDefault } from '../Attachment/ImageLoadingFailedIndicator';
86
+ import { ImageLoadingIndicator as ImageLoadingIndicatorDefault } from '../Attachment/ImageLoadingIndicator';
85
87
  import { VideoThumbnail as VideoThumbnailDefault } from '../Attachment/VideoThumbnail';
86
88
  import { AutoCompleteSuggestionHeader as AutoCompleteSuggestionHeaderDefault } from '../AutoCompleteInput/AutoCompleteSuggestionHeader';
87
89
  import { AutoCompleteSuggestionItem as AutoCompleteSuggestionItemDefault } from '../AutoCompleteInput/AutoCompleteSuggestionItem';
@@ -258,6 +260,8 @@ export type ChannelPropsWithContext<
258
260
  | 'InlineUnreadIndicator'
259
261
  | 'isAttachmentEqual'
260
262
  | 'legacyImageViewerSwipeBehaviour'
263
+ | 'ImageLoadingFailedIndicator'
264
+ | 'ImageLoadingIndicator'
261
265
  | 'markdownRules'
262
266
  | 'Message'
263
267
  | 'messageActions'
@@ -470,6 +474,8 @@ const ChannelWithContext = <
470
474
  LoadingIndicator = LoadingIndicatorDefault,
471
475
  loadingMore: loadingMoreProp,
472
476
  loadingMoreRecent: loadingMoreRecentProp,
477
+ ImageLoadingFailedIndicator = ImageLoadingFailedIndicatorDefault,
478
+ ImageLoadingIndicator = ImageLoadingIndicatorDefault,
473
479
  markdownRules,
474
480
  maxMessageLength: maxMessageLengthProp,
475
481
  maxNumberOfFiles = 10,
@@ -1700,6 +1706,8 @@ const ChannelWithContext = <
1700
1706
  handleReaction,
1701
1707
  handleRetry,
1702
1708
  handleThreadReply,
1709
+ ImageLoadingFailedIndicator,
1710
+ ImageLoadingIndicator,
1703
1711
  initialScrollToFirstUnreadMessage,
1704
1712
  InlineDateSeparator,
1705
1713
  InlineUnreadIndicator,
@@ -39,6 +39,8 @@ export const useCreateMessagesContext = <
39
39
  handleReaction,
40
40
  handleRetry,
41
41
  handleThreadReply,
42
+ ImageLoadingFailedIndicator,
43
+ ImageLoadingIndicator,
42
44
  initialScrollToFirstUnreadMessage,
43
45
  InlineDateSeparator,
44
46
  InlineUnreadIndicator,
@@ -126,6 +128,8 @@ export const useCreateMessagesContext = <
126
128
  handleReaction,
127
129
  handleRetry,
128
130
  handleThreadReply,
131
+ ImageLoadingFailedIndicator,
132
+ ImageLoadingIndicator,
129
133
  initialScrollToFirstUnreadMessage,
130
134
  InlineDateSeparator,
131
135
  InlineUnreadIndicator,
@@ -71,7 +71,6 @@ describe('ChannelPreviewMessenger', () => {
71
71
  fireEvent.press(getByTestId('channel-preview-button'));
72
72
 
73
73
  await waitFor(() => {
74
- // eslint-disable-next-line jest/prefer-called-with
75
74
  expect(onSelect).toHaveBeenCalledTimes(1);
76
75
  });
77
76
  });
@@ -0,0 +1,145 @@
1
+ import React, { FC } from 'react';
2
+
3
+ import { renderHook } from '@testing-library/react-hooks/native';
4
+ import { waitFor } from '@testing-library/react-native';
5
+
6
+ import type { DefaultStreamChatGenerics } from 'src/types/types';
7
+
8
+ import type { DefaultGenerics, StreamChat } from 'stream-chat';
9
+
10
+ import { ChatContext, ChatContextValue } from '../../../../contexts/chatContext/ChatContext';
11
+ import {
12
+ CHANNEL,
13
+ CHANNEL_WITH_DELETED_MESSAGES,
14
+ CHANNEL_WITH_EMPTY_MESSAGE,
15
+ CHANNEL_WITH_MENTIONED_USERS,
16
+ CHANNEL_WITH_MESSAGES_ATTACHMENTS,
17
+ CHANNEL_WITH_MESSAGES_COMMAND,
18
+ CHANNEL_WITH_MESSAGES_TEXT,
19
+ CHANNEL_WITH_NO_MESSAGES,
20
+ LATEST_MESSAGE,
21
+ } from '../../../../mock-builders/api/channelMocks';
22
+
23
+ import { generateUser } from '../../../../mock-builders/generator/user';
24
+ import { getTestClientWithUser } from '../../../../mock-builders/mock';
25
+
26
+ import { useLatestMessagePreview } from '../useLatestMessagePreview';
27
+
28
+ describe('useLatestMessagePreview', () => {
29
+ const FORCE_UPDATE = 15;
30
+ const clientUser = generateUser();
31
+ let chatClient: StreamChat<DefaultGenerics> | StreamChat<DefaultStreamChatGenerics>;
32
+
33
+ beforeEach(async () => {
34
+ chatClient = await getTestClientWithUser(clientUser);
35
+ });
36
+
37
+ const ChatProvider: FC<{ children: React.ReactNode }> = ({ children }) => (
38
+ <ChatContext.Provider
39
+ value={
40
+ {
41
+ auto_translation_enabled: true,
42
+ client: chatClient,
43
+ } as unknown as ChatContextValue
44
+ }
45
+ >
46
+ {children}
47
+ </ChatContext.Provider>
48
+ );
49
+
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
+ });
59
+
60
+ it('should return a deleted message preview', async () => {
61
+ const { result } = renderHook(
62
+ () => useLatestMessagePreview(CHANNEL_WITH_DELETED_MESSAGES, FORCE_UPDATE, LATEST_MESSAGE),
63
+ { wrapper: ChatProvider },
64
+ );
65
+ await waitFor(() => {
66
+ expect(result.current.previews).toEqual([{ bold: false, text: 'Message deleted' }]);
67
+ });
68
+ });
69
+
70
+ it('should return an "Nothing yet..." message preview', async () => {
71
+ const { result } = renderHook(
72
+ () => useLatestMessagePreview(CHANNEL_WITH_NO_MESSAGES, FORCE_UPDATE, LATEST_MESSAGE),
73
+ { wrapper: ChatProvider },
74
+ );
75
+ await waitFor(() => {
76
+ expect(result.current.previews).toEqual([{ bold: false, text: 'Nothing yet...' }]);
77
+ });
78
+ });
79
+
80
+ it('should return latest message preview', async () => {
81
+ const { result } = renderHook(
82
+ () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_TEXT, FORCE_UPDATE, LATEST_MESSAGE),
83
+ { wrapper: ChatProvider },
84
+ );
85
+ await waitFor(() => {
86
+ expect(result.current.previews).toEqual([
87
+ { bold: true, text: '@okechukwu: ' },
88
+ { bold: false, text: 'jkbkbiubicbi' },
89
+ ]);
90
+ });
91
+ });
92
+
93
+ it('should return a channel with an empty message preview', async () => {
94
+ const { result } = renderHook(
95
+ () => useLatestMessagePreview(CHANNEL_WITH_EMPTY_MESSAGE, FORCE_UPDATE, LATEST_MESSAGE),
96
+ { wrapper: ChatProvider },
97
+ );
98
+ await waitFor(() => {
99
+ expect(result.current.previews).toEqual([
100
+ { bold: false, text: '' },
101
+ { bold: false, text: 'Empty message...' },
102
+ ]);
103
+ });
104
+ });
105
+
106
+ it('should return a mentioned user (@Max) message preview', async () => {
107
+ const { result } = renderHook(
108
+ () => useLatestMessagePreview(CHANNEL_WITH_MENTIONED_USERS, FORCE_UPDATE, LATEST_MESSAGE),
109
+ { wrapper: ChatProvider },
110
+ );
111
+ await waitFor(() => {
112
+ expect(result.current.previews).toEqual([
113
+ { bold: false, text: '' },
114
+ { bold: false, text: 'Max' },
115
+ ]);
116
+ });
117
+ });
118
+
119
+ it('should return the latest command preview', async () => {
120
+ const { result } = renderHook(
121
+ () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_COMMAND, FORCE_UPDATE, LATEST_MESSAGE),
122
+ { wrapper: ChatProvider },
123
+ );
124
+ await waitFor(() => {
125
+ expect(result.current.previews).toEqual([
126
+ { bold: true, text: '@okechukwu: ' },
127
+ { bold: false, text: '/giphy' },
128
+ ]);
129
+ });
130
+ });
131
+
132
+ it('should return an attachment preview', async () => {
133
+ const { result } = renderHook(
134
+ () =>
135
+ useLatestMessagePreview(CHANNEL_WITH_MESSAGES_ATTACHMENTS, FORCE_UPDATE, LATEST_MESSAGE),
136
+ { wrapper: ChatProvider },
137
+ );
138
+ await waitFor(() => {
139
+ expect(result.current.previews).toEqual([
140
+ { bold: true, text: '@okechukwu: ' },
141
+ { bold: false, text: '🏙 Attachment...' },
142
+ ]);
143
+ });
144
+ });
145
+ });