Skip to content

Commit 19fa45f

Browse files
authored
feat: create a custom hook useSendMultipleFilesMessage (#714)
[UIKIT-4246](https://sendbird.atlassian.net/browse/UIKIT-4246) ### Feat * Create a custom hook `useSendMultipleFilesMessage` * Add some mock util functions for the MessageRequestHandler, Message, and Error instances > Internal: You can import this hook from the `context/hooks` under the Channel and Thread modules. #### How to use ```javascript import { useSendMultipleFilesMessage } from '~/context/hooks/useSendMultipleFilesMessage' const MultipleFilesInput = () => { return ( <input type="file" onChange={(event) => { sendMultipleFilesMessage(event.target.files) }} /> ) } ```
1 parent 2e0db15 commit 19fa45f

File tree

7 files changed

+562
-0
lines changed

7 files changed

+562
-0
lines changed

src/modules/Channel/context/dux/reducers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ export default function reducer(state, action) {
156156
const filteredMessages = state.allMessages.filter((m) => (
157157
m?.reqId !== message?.reqId
158158
));
159+
// [Policy] Pending messages and failed messages
160+
// must always be at the end of the message list
159161
const pendingIndex = filteredMessages.findIndex((msg) => (
160162
msg?.sendingStatus === 'pending' || msg?.sendingStatus === 'failed'
161163
));
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
import { RefObject, createRef } from 'react';
2+
import { GroupChannel } from '@sendbird/chat/groupChannel';
3+
import { UserMessage } from '@sendbird/chat/message';
4+
import { renderHook } from '@testing-library/react';
5+
6+
import {
7+
UseSendMFMDynamicParams,
8+
UseSendMFMStaticParams,
9+
useSendMultipleFilesMessage,
10+
} from '../useSendMultipleFilesMessage';
11+
import { Logger } from '../../../../../lib/SendbirdState';
12+
import {
13+
MockMessageRequestHandlerType,
14+
getMockMessageRequestHandler,
15+
} from '../../../../../utils/testMocks/messageRequestHandler';
16+
import PUBSUB_TOPICS from '../../../../../lib/pubSub/topics';
17+
import { MockMessageStateType, mockSentMessage } from '../../../../../utils/testMocks/message';
18+
import uuidv4 from '../../../../../utils/uuid';
19+
20+
interface UseSendMFMParams extends UseSendMFMDynamicParams, UseSendMFMStaticParams {
21+
messageRequestHandler: MockMessageRequestHandlerType;
22+
}
23+
type GlobalContextType = {
24+
[K in keyof UseSendMFMParams]?: UseSendMFMParams[K];
25+
};
26+
const globalContext: GlobalContextType = {};
27+
const mockFileList = [new File([], 'fileOne'), new File([], 'fileTwo')];
28+
29+
describe('useSendMultipleFilesMessage', () => {
30+
beforeEach(() => {
31+
globalContext.currentChannel = {
32+
url: uuidv4(),
33+
sendMultipleFilesMessage: jest.fn(() => getMockMessageRequestHandler()),
34+
} as unknown as GroupChannel;
35+
globalContext.onBeforeSendMultipleFilesMessage = (params) => params;
36+
globalContext.logger = { info: jest.fn(), warning: jest.fn(), error: jest.fn() };
37+
globalContext.pubSub = { publish: jest.fn() };
38+
globalContext.scrollRef = createRef<HTMLDivElement>();
39+
});
40+
41+
it('should check sending MFM', () => {
42+
const { result } = renderHook(() => (
43+
useSendMultipleFilesMessage({
44+
currentChannel: globalContext.currentChannel as GroupChannel,
45+
onBeforeSendMultipleFilesMessage: globalContext.onBeforeSendMultipleFilesMessage,
46+
}, {
47+
logger: globalContext.logger as Logger,
48+
pubSub: globalContext.pubSub,
49+
scrollRef: globalContext.scrollRef as RefObject<HTMLDivElement>,
50+
})
51+
));
52+
const [sendMultipleFilesMessage] = result.current;
53+
54+
sendMultipleFilesMessage(mockFileList);
55+
56+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
57+
.toHaveBeenCalledWith({
58+
fileInfoList: [
59+
{
60+
file: mockFileList[0],
61+
fileName: mockFileList[0].name,
62+
fileSize: mockFileList[0].size,
63+
mimeType: mockFileList[0].type,
64+
},
65+
{
66+
file: mockFileList[1],
67+
fileName: mockFileList[1].name,
68+
fileSize: mockFileList[1].size,
69+
mimeType: mockFileList[1].type,
70+
},
71+
],
72+
});
73+
expect(globalContext.pubSub?.publish)
74+
.toHaveBeenCalledWith(
75+
PUBSUB_TOPICS.SEND_MESSAGE_START,
76+
{
77+
message: { mockMessageType: MockMessageStateType.PENDING },
78+
channel: {
79+
url: globalContext.currentChannel?.url,
80+
sendMultipleFilesMessage: expect.any(Function),
81+
},
82+
},
83+
);
84+
expect(globalContext.pubSub?.publish)
85+
.not.toHaveBeenCalledWith(
86+
PUBSUB_TOPICS.SEND_MESSAGE_FAILED,
87+
{
88+
message: { mockMessageType: MockMessageStateType.FAILED },
89+
channel: {
90+
url: globalContext.currentChannel?.url,
91+
sendMultipleFilesMessage: expect.any(Function),
92+
},
93+
},
94+
);
95+
expect(globalContext.pubSub?.publish)
96+
.toHaveBeenCalledWith(
97+
PUBSUB_TOPICS.SEND_FILE_MESSAGE,
98+
{
99+
message: { mockMessageType: MockMessageStateType.SUCCEEDED },
100+
channel: {
101+
url: globalContext.currentChannel?.url,
102+
sendMultipleFilesMessage: expect.any(Function),
103+
},
104+
},
105+
);
106+
});
107+
108+
it('should check sending MFM failed', () => {
109+
const { result } = renderHook(() => (
110+
useSendMultipleFilesMessage({
111+
// this mock channel will fail sending MFM -> getMockMessageRequestHandler(false)
112+
currentChannel: {
113+
...globalContext.currentChannel,
114+
sendMultipleFilesMessage: jest.fn(() => getMockMessageRequestHandler(false)),
115+
} as unknown as GroupChannel,
116+
onBeforeSendMultipleFilesMessage: globalContext.onBeforeSendMultipleFilesMessage,
117+
}, {
118+
logger: globalContext.logger as Logger,
119+
pubSub: globalContext.pubSub,
120+
scrollRef: globalContext.scrollRef as RefObject<HTMLDivElement>,
121+
})
122+
));
123+
const [sendMultipleFilesMessage] = result.current;
124+
125+
sendMultipleFilesMessage(mockFileList);
126+
127+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
128+
.not.toHaveBeenCalled();
129+
expect(globalContext.pubSub?.publish)
130+
.not.toHaveBeenCalledWith(
131+
PUBSUB_TOPICS.SEND_MESSAGE_START,
132+
{
133+
message: { mockMessageType: MockMessageStateType.PENDING },
134+
channel: {
135+
url: globalContext.currentChannel?.url,
136+
sendMultipleFilesMessage: expect.any(Function),
137+
},
138+
},
139+
);
140+
expect(globalContext.pubSub?.publish)
141+
.toHaveBeenCalledWith(
142+
PUBSUB_TOPICS.SEND_MESSAGE_FAILED,
143+
{
144+
message: { mockMessageType: MockMessageStateType.FAILED },
145+
channel: {
146+
url: globalContext.currentChannel?.url,
147+
sendMultipleFilesMessage: expect.any(Function),
148+
},
149+
},
150+
);
151+
expect(globalContext.pubSub?.publish)
152+
.not.toHaveBeenCalledWith(
153+
PUBSUB_TOPICS.SEND_FILE_MESSAGE,
154+
{
155+
message: { mockMessageType: MockMessageStateType.SUCCEEDED },
156+
channel: globalContext.currentChannel,
157+
},
158+
);
159+
});
160+
161+
it('should not send message when receiving empty files', () => {
162+
const { result } = renderHook(() => (
163+
useSendMultipleFilesMessage({
164+
currentChannel: globalContext.currentChannel as GroupChannel,
165+
onBeforeSendMultipleFilesMessage: globalContext.onBeforeSendMultipleFilesMessage,
166+
}, {
167+
logger: globalContext.logger as Logger,
168+
pubSub: globalContext.pubSub,
169+
scrollRef: globalContext.scrollRef as RefObject<HTMLDivElement>,
170+
})
171+
));
172+
const [sendMultipleFilesMessage] = result.current;
173+
174+
// receiving an empty array
175+
sendMultipleFilesMessage([]);
176+
177+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
178+
.not.toHaveBeenCalled();
179+
expect(globalContext.pubSub?.publish)
180+
.not.toHaveBeenCalled();
181+
});
182+
183+
it('should not send message when receiving an array of one file', () => {
184+
const { result } = renderHook(() => (
185+
useSendMultipleFilesMessage({
186+
currentChannel: globalContext.currentChannel as GroupChannel,
187+
onBeforeSendMultipleFilesMessage: globalContext.onBeforeSendMultipleFilesMessage,
188+
}, {
189+
logger: globalContext.logger as Logger,
190+
pubSub: globalContext.pubSub,
191+
scrollRef: globalContext.scrollRef as RefObject<HTMLDivElement>,
192+
})
193+
));
194+
const [sendMultipleFilesMessage] = result.current;
195+
196+
// receiving only one file
197+
sendMultipleFilesMessage([mockFileList[0]]);
198+
199+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
200+
.not.toHaveBeenCalled();
201+
expect(globalContext.pubSub?.publish)
202+
.not.toHaveBeenCalled();
203+
});
204+
205+
it('should apply the quoteMessage', () => {
206+
const { result } = renderHook(() => (
207+
useSendMultipleFilesMessage({
208+
currentChannel: globalContext.currentChannel as GroupChannel,
209+
onBeforeSendMultipleFilesMessage: globalContext.onBeforeSendMultipleFilesMessage,
210+
}, {
211+
logger: globalContext.logger as Logger,
212+
pubSub: globalContext.pubSub,
213+
scrollRef: globalContext.scrollRef as RefObject<HTMLDivElement>,
214+
})
215+
));
216+
const [sendMultipleFilesMessage] = result.current;
217+
218+
// send multiple files message with a quote message
219+
sendMultipleFilesMessage(mockFileList, mockSentMessage as unknown as UserMessage);
220+
221+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
222+
.toHaveBeenCalledWith({
223+
fileInfoList: [
224+
{
225+
file: mockFileList[0],
226+
fileName: mockFileList[0].name,
227+
fileSize: mockFileList[0].size,
228+
mimeType: mockFileList[0].type,
229+
},
230+
{
231+
file: mockFileList[1],
232+
fileName: mockFileList[1].name,
233+
fileSize: mockFileList[1].size,
234+
mimeType: mockFileList[1].type,
235+
},
236+
],
237+
isReplyToChannel: true,
238+
parentMessageId: mockSentMessage.messageId,
239+
});
240+
expect(globalContext.pubSub?.publish)
241+
.toHaveBeenCalledWith(
242+
PUBSUB_TOPICS.SEND_MESSAGE_START,
243+
{
244+
message: { mockMessageType: MockMessageStateType.PENDING },
245+
channel: globalContext.currentChannel,
246+
},
247+
);
248+
249+
expect(globalContext.pubSub?.publish)
250+
.not.toHaveBeenCalledWith(
251+
PUBSUB_TOPICS.SEND_MESSAGE_FAILED,
252+
{
253+
message: { mockMessageType: MockMessageStateType.FAILED },
254+
channel: globalContext.currentChannel,
255+
},
256+
);
257+
expect(globalContext.pubSub?.publish)
258+
.toHaveBeenCalledWith(
259+
PUBSUB_TOPICS.SEND_FILE_MESSAGE,
260+
{
261+
message: { mockMessageType: MockMessageStateType.SUCCEEDED },
262+
channel: globalContext.currentChannel,
263+
},
264+
);
265+
});
266+
267+
it('should apply the onBeforeSendMultipleFilesMessage', () => {
268+
const newParamsOptions = {
269+
customType: 'custom-type',
270+
fileInfoList: [new File([], 'newFileOne'), new File([], 'newFileTwo')].map((file) => ({ file })),
271+
};
272+
const { result } = renderHook(() => (
273+
useSendMultipleFilesMessage({
274+
currentChannel: globalContext.currentChannel as GroupChannel,
275+
// modify the message create params before sending a message
276+
onBeforeSendMultipleFilesMessage: (params) => ({
277+
...params,
278+
...newParamsOptions,
279+
}),
280+
}, {
281+
logger: globalContext.logger as Logger,
282+
pubSub: globalContext.pubSub,
283+
scrollRef: globalContext.scrollRef as RefObject<HTMLDivElement>,
284+
})
285+
));
286+
const [sendMultipleFilesMessage] = result.current;
287+
288+
sendMultipleFilesMessage(mockFileList);
289+
290+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
291+
.toHaveBeenCalledWith(newParamsOptions);
292+
expect(globalContext.pubSub?.publish)
293+
.toHaveBeenCalledWith(
294+
PUBSUB_TOPICS.SEND_MESSAGE_START,
295+
{
296+
message: { mockMessageType: MockMessageStateType.PENDING },
297+
channel: globalContext.currentChannel,
298+
},
299+
);
300+
expect(globalContext.pubSub?.publish)
301+
.not.toHaveBeenCalledWith(
302+
PUBSUB_TOPICS.SEND_MESSAGE_FAILED,
303+
{
304+
message: { mockMessageType: MockMessageStateType.FAILED },
305+
channel: globalContext.currentChannel,
306+
},
307+
);
308+
expect(globalContext.pubSub?.publish)
309+
.toHaveBeenCalledWith(
310+
PUBSUB_TOPICS.SEND_FILE_MESSAGE,
311+
{
312+
message: { mockMessageType: MockMessageStateType.SUCCEEDED },
313+
channel: globalContext.currentChannel,
314+
},
315+
);
316+
});
317+
318+
it('should have higher priority with onBeforeSendMultipleFilesMessage rather than quoteMessage', () => {
319+
const newParamsOptions = {
320+
customType: 'custom-type',
321+
fileInfoList: [new File([], 'newFileOne'), new File([], 'newFileTwo')].map((file) => ({ file })),
322+
parentMessageId: 1111,
323+
};
324+
const { result } = renderHook(() => (
325+
useSendMultipleFilesMessage({
326+
currentChannel: globalContext.currentChannel as GroupChannel,
327+
// modify the message create params before sending a message
328+
onBeforeSendMultipleFilesMessage: (params) => ({
329+
...params,
330+
// upsert the properties for the quote message
331+
...newParamsOptions,
332+
}),
333+
}, {
334+
logger: globalContext.logger as Logger,
335+
pubSub: globalContext.pubSub,
336+
scrollRef: globalContext.scrollRef as RefObject<HTMLDivElement>,
337+
})
338+
));
339+
const [sendMultipleFilesMessage] = result.current;
340+
341+
// send multiple files message with a quote message
342+
sendMultipleFilesMessage(mockFileList, mockSentMessage as unknown as UserMessage);
343+
344+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
345+
.toHaveBeenCalledWith({
346+
isReplyToChannel: true,
347+
...newParamsOptions,
348+
});
349+
expect(globalContext.currentChannel?.sendMultipleFilesMessage)
350+
.not.toHaveBeenCalledWith({
351+
customType: newParamsOptions.customType,
352+
fileInfoList: newParamsOptions.fileInfoList,
353+
isReplyToChannel: true,
354+
parentMessageId: mockSentMessage.messageId,
355+
});
356+
expect(globalContext.pubSub?.publish)
357+
.toHaveBeenCalledWith(
358+
PUBSUB_TOPICS.SEND_MESSAGE_START,
359+
{
360+
message: { mockMessageType: MockMessageStateType.PENDING },
361+
channel: globalContext.currentChannel,
362+
},
363+
);
364+
expect(globalContext.pubSub?.publish)
365+
.not.toHaveBeenCalledWith(
366+
PUBSUB_TOPICS.SEND_MESSAGE_FAILED,
367+
{
368+
message: { mockMessageType: MockMessageStateType.FAILED },
369+
channel: globalContext.currentChannel,
370+
},
371+
);
372+
expect(globalContext.pubSub?.publish)
373+
.toHaveBeenCalledWith(
374+
PUBSUB_TOPICS.SEND_FILE_MESSAGE,
375+
{
376+
message: { mockMessageType: MockMessageStateType.SUCCEEDED },
377+
channel: globalContext.currentChannel,
378+
},
379+
);
380+
});
381+
});

0 commit comments

Comments
 (0)