Skip to content

Commit b9e7602

Browse files
Add basic test cases for the new ThreadProvider (#1274)
### Changelog This PR will add the test cases for the new `ThreadProvider` and its related hooks. #### Before <img width="1163" alt="스크린샷 2024-12-09 오전 11 29 53" src="https://github.com/user-attachments/assets/40ffc29e-0774-4ac7-973d-15af55bc88fd"> #### After <img width="1199" alt="스크린샷 2024-12-09 오전 11 28 39" src="https://github.com/user-attachments/assets/ea3366c0-2f11-4e1f-af6f-a423d006ab42"> --------- Co-authored-by: Irene Ryu <[email protected]>
1 parent 5b35b02 commit b9e7602

22 files changed

+3817
-204
lines changed
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
import * as useThreadModule from '../../../context/useThread';
2+
import { ChannelStateTypes, ParentMessageStateTypes, ThreadListStateTypes } from '../../../types';
3+
import { EmojiContainer } from '@sendbird/chat';
4+
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
5+
import { LocalizationContext } from '../../../../../lib/LocalizationContext';
6+
import ThreadUI from '../index';
7+
import React from 'react';
8+
import '@testing-library/jest-dom/extend-expect';
9+
10+
const mockSendUserMessage = jest.fn();
11+
12+
const mockChannel = {
13+
url: 'test-channel',
14+
members: [{ userId: 'test-user-id', nickname: 'user1' }],
15+
updateUserMessage: jest.fn().mockImplementation(async (message) => mockNewMessage(message)),
16+
sendUserMessage: mockSendUserMessage,
17+
isGroupChannel: jest.fn().mockImplementation(() => true),
18+
};
19+
20+
const mockNewMessage = (message) => ({
21+
messageId: 42,
22+
message: message ?? 'new message',
23+
});
24+
25+
const mockMessage = {
26+
messageId: 1,
27+
message: 'first message',
28+
};
29+
30+
const mockGetMessage = jest.fn().mockResolvedValue(mockMessage);
31+
const mockGetChannel = jest.fn().mockResolvedValue(mockChannel);
32+
33+
const mockState = {
34+
stores: {
35+
sdkStore: {
36+
sdk: {
37+
getMessage: mockGetMessage,
38+
groupChannel: {
39+
getChannel: mockGetChannel,
40+
},
41+
},
42+
initialized: true,
43+
},
44+
userStore: { user: { userId: 'test-user-id' } },
45+
},
46+
config: {
47+
logger: console,
48+
isOnline: true,
49+
pubSub: {
50+
publish: jest.fn(),
51+
},
52+
groupChannel: {
53+
enableMention: true,
54+
enableReactions: true,
55+
replyType: 'THREAD',
56+
},
57+
},
58+
};
59+
60+
jest.mock('../../../../../lib/Sendbird/context/hooks/useSendbird', () => ({
61+
__esModule: true,
62+
default: jest.fn(() => ({ state: mockState })),
63+
useSendbird: jest.fn(() => ({ state: mockState })),
64+
}));
65+
66+
jest.mock('../../../context/useThread');
67+
68+
const mockStringSet = {
69+
DATE_FORMAT__MESSAGE_CREATED_AT: 'p',
70+
};
71+
72+
const mockLocalizationContext = {
73+
stringSet: mockStringSet,
74+
};
75+
76+
const defaultMockState = {
77+
channelUrl: '',
78+
message: null,
79+
onHeaderActionClick: undefined,
80+
onMoveToParentMessage: undefined,
81+
onBeforeSendUserMessage: undefined,
82+
onBeforeSendFileMessage: undefined,
83+
onBeforeSendVoiceMessage: undefined,
84+
onBeforeSendMultipleFilesMessage: undefined,
85+
onBeforeDownloadFileMessage: undefined,
86+
isMultipleFilesMessageEnabled: undefined,
87+
filterEmojiCategoryIds: undefined,
88+
currentChannel: undefined,
89+
allThreadMessages: [
90+
{
91+
messageId: 2,
92+
message: 'threaded message 1',
93+
isUserMessage: () => true,
94+
},
95+
],
96+
localThreadMessages: [],
97+
parentMessage: null,
98+
channelState: ChannelStateTypes.INITIALIZED,
99+
parentMessageState: ParentMessageStateTypes.INITIALIZED,
100+
threadListState: ThreadListStateTypes.INITIALIZED,
101+
hasMorePrev: false,
102+
hasMoreNext: false,
103+
emojiContainer: {} as EmojiContainer,
104+
isMuted: false,
105+
isChannelFrozen: false,
106+
currentUserId: '',
107+
typingMembers: [],
108+
nicknamesMap: null,
109+
};
110+
111+
const defaultMockActions = {
112+
fetchPrevThreads: jest.fn((callback) => {
113+
callback();
114+
}),
115+
fetchNextThreads: jest.fn((callback) => {
116+
callback();
117+
}),
118+
};
119+
120+
describe('CreateChannelUI Integration Tests', () => {
121+
const mockUseThread = useThreadModule.default as jest.Mock;
122+
123+
const renderComponent = (mockState = {}, mockActions = {}) => {
124+
mockUseThread.mockReturnValue({
125+
state: { ...defaultMockState, ...mockState },
126+
actions: { ...defaultMockActions, ...mockActions },
127+
});
128+
129+
return render(
130+
<LocalizationContext.Provider value={mockLocalizationContext as any}>
131+
<ThreadUI/>
132+
</LocalizationContext.Provider>,
133+
);
134+
};
135+
136+
beforeEach(() => {
137+
jest.clearAllMocks();
138+
});
139+
140+
it('display initial state correctly', async () => {
141+
await act(async () => {
142+
renderComponent(
143+
{
144+
parentMessage: {
145+
messageId: 1,
146+
message: 'parent message',
147+
isUserMessage: () => true,
148+
isTextMessage: true,
149+
createdAt: 0,
150+
sender: {
151+
userId: 'test-user-id',
152+
},
153+
},
154+
},
155+
);
156+
});
157+
158+
expect(screen.getByText('parent message')).toBeInTheDocument();
159+
expect(screen.getByText('threaded message 1')).toBeInTheDocument();
160+
});
161+
162+
it('fetchPrevThread is correctly called when scroll is top', async () => {
163+
let container;
164+
const parentMessage = {
165+
messageId: 1,
166+
message: 'parent message',
167+
isUserMessage: () => true,
168+
isTextMessage: true,
169+
createdAt: 0,
170+
sender: {
171+
userId: 'test-user-id',
172+
},
173+
getThreadedMessagesByTimestamp: () => ({
174+
parentMessage,
175+
threadedMessages: [
176+
{ messageId: 3, message: 'threaded message -1', isUserMessage: () => true },
177+
{ messageId: 4, message: 'threaded message 0', isUserMessage: () => true },
178+
],
179+
}),
180+
};
181+
182+
await act(async () => {
183+
const result = renderComponent(
184+
{
185+
parentMessage,
186+
hasMorePrev: true,
187+
},
188+
);
189+
190+
container = result.container;
191+
});
192+
193+
const scrollContainer = container.getElementsByClassName('sendbird-thread-ui--scroll')[0];
194+
fireEvent.scroll(scrollContainer, { target: { scrollY: -1 } });
195+
196+
await waitFor(() => {
197+
expect(defaultMockActions.fetchPrevThreads).toBeCalledTimes(1);
198+
});
199+
});
200+
201+
it('fetchNextThreads is correctly called when scroll is bottom', async () => {
202+
let container;
203+
const parentMessage = {
204+
messageId: 1,
205+
message: 'parent message',
206+
isUserMessage: () => true,
207+
isTextMessage: true,
208+
createdAt: 0,
209+
sender: {
210+
userId: 'test-user-id',
211+
},
212+
getThreadedMessagesByTimestamp: () => ({
213+
parentMessage,
214+
threadedMessages: [
215+
{ messageId: 3, message: 'threaded message -1', isUserMessage: () => true },
216+
{ messageId: 4, message: 'threaded message 0', isUserMessage: () => true },
217+
],
218+
}),
219+
};
220+
221+
await act(async () => {
222+
const result = renderComponent(
223+
{
224+
parentMessage,
225+
hasMoreNext: true,
226+
},
227+
);
228+
229+
container = result.container;
230+
});
231+
232+
const scrollContainer = container.getElementsByClassName('sendbird-thread-ui--scroll')[0];
233+
fireEvent.scroll(scrollContainer, { target: { scrollY: scrollContainer.scrollHeight + 1 } });
234+
235+
await waitFor(() => {
236+
expect(defaultMockActions.fetchNextThreads).toBeCalledTimes(1);
237+
});
238+
});
239+
240+
it('show proper placeholder when ParentMessageStateTypes is NIL', async () => {
241+
let container;
242+
const parentMessage = {
243+
messageId: 1,
244+
message: 'parent message',
245+
isUserMessage: () => true,
246+
isTextMessage: true,
247+
createdAt: 0,
248+
sender: {
249+
userId: 'test-user-id',
250+
},
251+
};
252+
253+
await act(async () => {
254+
const result = renderComponent(
255+
{
256+
parentMessage,
257+
parentMessageState: ParentMessageStateTypes.NIL,
258+
},
259+
);
260+
261+
container = result.container;
262+
});
263+
264+
await waitFor(() => {
265+
const placeholder = container.getElementsByClassName('placeholder-nil')[0];
266+
expect(placeholder).not.toBe(undefined);
267+
});
268+
269+
});
270+
271+
it('show proper placeholder when ParentMessageStateTypes is LOADING', async () => {
272+
let container;
273+
const parentMessage = {
274+
messageId: 1,
275+
message: 'parent message',
276+
isUserMessage: () => true,
277+
isTextMessage: true,
278+
createdAt: 0,
279+
sender: {
280+
userId: 'test-user-id',
281+
},
282+
};
283+
284+
await act(async () => {
285+
const result = renderComponent(
286+
{
287+
parentMessage,
288+
parentMessageState: ParentMessageStateTypes.LOADING,
289+
},
290+
);
291+
292+
container = result.container;
293+
});
294+
295+
await waitFor(() => {
296+
const placeholder = container.getElementsByClassName('placeholder-loading')[0];
297+
expect(placeholder).not.toBe(undefined);
298+
});
299+
300+
});
301+
302+
it('show proper placeholder when ParentMessageStateTypes is INVALID', async () => {
303+
let container;
304+
const parentMessage = {
305+
messageId: 1,
306+
message: 'parent message',
307+
isUserMessage: () => true,
308+
isTextMessage: true,
309+
createdAt: 0,
310+
sender: {
311+
userId: 'test-user-id',
312+
},
313+
};
314+
315+
await act(async () => {
316+
const result = renderComponent(
317+
{
318+
parentMessage,
319+
parentMessageState: ParentMessageStateTypes.INVALID,
320+
},
321+
);
322+
323+
container = result.container;
324+
});
325+
326+
await waitFor(() => {
327+
const placeholder = container.getElementsByClassName('placeholder-invalid')[0];
328+
expect(placeholder).not.toBe(undefined);
329+
});
330+
});
331+
332+
it('show proper placeholder when ThreadListState is LOADING', async () => {
333+
let container;
334+
const parentMessage = {
335+
messageId: 1,
336+
message: 'parent message',
337+
isUserMessage: () => true,
338+
isTextMessage: true,
339+
createdAt: 0,
340+
sender: {
341+
userId: 'test-user-id',
342+
},
343+
};
344+
345+
await act(async () => {
346+
const result = renderComponent(
347+
{
348+
parentMessage,
349+
threadListState: ThreadListStateTypes.LOADING,
350+
},
351+
);
352+
353+
container = result.container;
354+
});
355+
356+
await waitFor(() => {
357+
const placeholder = container.getElementsByClassName('placeholder-loading')[0];
358+
expect(placeholder).not.toBe(undefined);
359+
});
360+
});
361+
362+
it('show proper placeholder when ThreadListState is INVALID', async () => {
363+
let container;
364+
const parentMessage = {
365+
messageId: 1,
366+
message: 'parent message',
367+
isUserMessage: () => true,
368+
isTextMessage: true,
369+
createdAt: 0,
370+
sender: {
371+
userId: 'test-user-id',
372+
},
373+
};
374+
375+
await act(async () => {
376+
const result = renderComponent(
377+
{
378+
parentMessage,
379+
threadListState: ThreadListStateTypes.INVALID,
380+
},
381+
);
382+
383+
container = result.container;
384+
});
385+
386+
await waitFor(() => {
387+
const placeholder = container.getElementsByClassName('placeholder-invalid')[0];
388+
expect(placeholder).not.toBe(undefined);
389+
});
390+
});
391+
392+
});

0 commit comments

Comments
 (0)