Skip to content

Commit 2a92ef3

Browse files
git-babelAhyoungRyu
authored andcommitted
refactor: Add unit tests for new GroupChannelListProvider (#1233)
This PR contains the unit tests for the recent refactoring of `GroupChannelListProvider`. * add test about the new provider and its state management logic itself. * add the integration test with `GroupChannelListUI` component. --------- Co-authored-by: Irene Ryu <[email protected]>
1 parent dd4d887 commit 2a92ef3

File tree

6 files changed

+410
-31
lines changed

6 files changed

+410
-31
lines changed

src/lib/UserProfileContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useContext } from 'react';
22
import type { GroupChannel } from '@sendbird/chat/groupChannel';
33
import type { RenderUserProfileProps } from '../types';
4-
import { useSendbirdStateContext } from './Sendbird';
4+
import useSendbirdStateContext from '../hooks/useSendbirdStateContext';
55

66
interface UserProfileContextInterface {
77
isOpenChannel: boolean;
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { fireEvent, render, screen } from '@testing-library/react';
2+
import GroupChannelListUI from '../index';
3+
import '@testing-library/jest-dom/extend-expect';
4+
import React from 'react';
5+
import { useGroupChannelList as useGroupChannelListModule } from '../../../context/useGroupChannelList';
6+
import { LocalizationContext } from '../../../../../lib/LocalizationContext';
7+
8+
jest.mock('../../../../../hooks/useSendbirdStateContext', () => ({
9+
__esModule: true,
10+
default: jest.fn(() => ({
11+
stores: {
12+
userStore: {
13+
user: {
14+
userId: ' test-user-id',
15+
},
16+
},
17+
sdkStore: {
18+
sdk: {
19+
currentUser: {
20+
userId: 'test-user-id',
21+
},
22+
},
23+
initialized: true,
24+
},
25+
},
26+
config: {
27+
logger: console,
28+
userId: 'test-user-id',
29+
groupChannel: {
30+
enableMention: true,
31+
},
32+
isOnline: true,
33+
},
34+
})),
35+
}));
36+
jest.mock('../../../context/useGroupChannelList');
37+
38+
const mockStringSet = {
39+
PLACE_HOLDER__NO_CHANNEL: 'No channels',
40+
TYPING_INDICATOR__IS_TYPING: 'is typing...',
41+
TYPING_INDICATOR__AND: 'and',
42+
TYPING_INDICATOR__ARE_TYPING: 'are typing...',
43+
TYPING_INDICATOR__MULTIPLE_TYPING: 'Several people are typing...',
44+
};
45+
46+
const defaultMockState = {
47+
className: '',
48+
selectedChannelUrl: '',
49+
disableAutoSelect: false,
50+
allowProfileEdit: false,
51+
isTypingIndicatorEnabled: false,
52+
isMessageReceiptStatusEnabled: false,
53+
onChannelSelect: undefined,
54+
onChannelCreated: undefined,
55+
onThemeChange: undefined,
56+
onCreateChannelClick: undefined,
57+
onBeforeCreateChannel: undefined,
58+
onUserProfileUpdated: undefined,
59+
typingChannelUrls: [],
60+
refreshing: false,
61+
initialized: false,
62+
groupChannels: [],
63+
refresh: null,
64+
loadMore: null,
65+
};
66+
67+
describe('GroupChannelListUI Integration Tests', () => {
68+
69+
const renderComponent = (mockState = {}) => {
70+
const mockUseGroupChannelList = useGroupChannelListModule as jest.Mock;
71+
72+
mockUseGroupChannelList.mockReturnValue({
73+
state: { ...defaultMockState, ...mockState },
74+
});
75+
76+
return render(
77+
<LocalizationContext.Provider value={{ stringSet: mockStringSet } as any}>
78+
<GroupChannelListUI />,
79+
</LocalizationContext.Provider>,
80+
);
81+
};
82+
83+
beforeEach(() => {
84+
jest.clearAllMocks();
85+
});
86+
87+
it('display loader if not initialized', () => {
88+
const { container } = renderComponent();
89+
90+
expect(container.getElementsByClassName('sendbird-loader')[0]).toBeInTheDocument();
91+
});
92+
93+
it('display results when available', () => {
94+
renderComponent({
95+
groupChannels: [
96+
{ name: 'test-group-channel-1' },
97+
{ name: 'test-group-channel-2' },
98+
],
99+
initialized: true,
100+
});
101+
102+
expect(screen.getByText('test-group-channel-1')).toBeInTheDocument();
103+
expect(screen.getByText('test-group-channel-2')).toBeInTheDocument();
104+
});
105+
106+
it('handle no result', () => {
107+
renderComponent({
108+
groupChannels: [],
109+
initialized: true,
110+
});
111+
112+
expect(screen.getByText(mockStringSet.PLACE_HOLDER__NO_CHANNEL)).toBeInTheDocument();
113+
});
114+
115+
it('handle selectedChannelUrl', () => {
116+
const { container } = renderComponent({
117+
groupChannels: [
118+
{ name: 'test-group-channel-1', url: 'test-group-channel-url-1' },
119+
{ name: 'test-group-channel-2', url: 'test-group-channel-url-2' },
120+
],
121+
selectedChannelUrl: 'test-group-channel-url-2',
122+
initialized: true,
123+
});
124+
125+
const selected = container.getElementsByClassName('sendbird-channel-preview--active')[0];
126+
127+
expect(selected).toBeInTheDocument();
128+
expect(selected.getAttribute('tabindex')).toEqual('1');
129+
});
130+
131+
it('handle disableAutoSelect', () => {
132+
const { container } = renderComponent({
133+
groupChannels: [
134+
{ name: 'test-group-channel-1', url: 'test-group-channel-url-1' },
135+
{ name: 'test-group-channel-2', url: 'test-group-channel-url-2' },
136+
],
137+
disableAutoSelect: true,
138+
initialized: true,
139+
});
140+
141+
const selected = container.getElementsByClassName('sendbird-channel-preview--active')[0];
142+
143+
expect(selected).toBeUndefined();
144+
});
145+
146+
it('handle allowProfileEdit', () => {
147+
const { container } = renderComponent({
148+
allowProfileEdit: true,
149+
initialized: true,
150+
});
151+
152+
expect(container.getElementsByClassName('sendbird-channel-header--allow-edit')[0]).toBeInTheDocument();
153+
});
154+
155+
it('handle isTypingIndicatorEnabled', () => {
156+
renderComponent({
157+
groupChannels: [
158+
{ name: 'test-group-channel-1', url: 'test-group-channel-url-1', getTypingUsers: () => ['test-user-1', 'test-user-2'] },
159+
{ name: 'test-group-channel-2', url: 'test-group-channel-url-2', getTypingUsers: () => ['test-user-2'] },
160+
],
161+
typingChannelUrls: ['test-group-channel-url-1', 'test-group-channel-url-2'],
162+
selectedChannelUrl: 'test-group-channel-url-1',
163+
isTypingIndicatorEnabled: true,
164+
initialized: true,
165+
});
166+
167+
expect(screen.getByText(mockStringSet.TYPING_INDICATOR__IS_TYPING, { exact: false })).toBeInTheDocument();
168+
expect(screen.getByText(mockStringSet.TYPING_INDICATOR__ARE_TYPING, { exact: false })).toBeInTheDocument();
169+
});
170+
171+
it('handle onChannelSelect', () => {
172+
const onChannelSelect = jest.fn();
173+
174+
renderComponent({
175+
groupChannels: [
176+
{ name: 'test-group-channel-1', url: 'test-group-channel-url-1' },
177+
{ name: 'test-group-channel-2', url: 'test-group-channel-url-2' },
178+
],
179+
initialized: true,
180+
onChannelSelect,
181+
});
182+
183+
const channel = screen.getByText('test-group-channel-1');
184+
fireEvent.click(channel);
185+
186+
expect(onChannelSelect).toHaveBeenCalledTimes(1);
187+
});
188+
189+
});

src/modules/GroupChannelList/context/GroupChannelListProvider.tsx

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect, useRef, useState } from 'react';
1+
import React, { useContext, useEffect, useRef } from 'react';
22

33
import type { User } from '@sendbird/chat';
44
import type { GroupChannel, GroupChannelCreateParams, GroupChannelFilterParams } from '@sendbird/chat/groupChannel';
@@ -11,11 +11,11 @@ import { UserProfileProvider } from '../../../lib/UserProfileContext';
1111
import type { UserProfileProviderProps } from '../../../lib/UserProfileContext';
1212
import { useMarkAsDeliveredScheduler } from '../../../lib/hooks/useMarkAsDeliveredScheduler';
1313
import useOnlineStatus from '../../../lib/hooks/useOnlineStatus';
14-
import { noop } from '../../../utils/utils';
1514
import type { SdkStore } from '../../../lib/types';
1615
import { PartialRequired } from '../../../utils/typeHelpers/partialRequired';
1716
import { createStore } from '../../../utils/storeManager';
1817
import { useStore } from '../../../hooks/useStore';
18+
import { noop } from '../../../utils/utils';
1919

2020
type OnCreateChannelClickParams = { users: Array<string>; onClose: () => void; channelType: CHANNEL_TYPE };
2121
type ChannelListDataSource = ReturnType<typeof useGroupChannelList>;
@@ -66,8 +66,8 @@ const initialState: GroupChannelListState = {
6666
allowProfileEdit: false,
6767
isTypingIndicatorEnabled: false,
6868
isMessageReceiptStatusEnabled: false,
69-
onChannelSelect: noop,
70-
onChannelCreated: noop,
69+
onChannelSelect: expect.any(Function),
70+
onChannelCreated: expect.any(Function),
7171
onThemeChange: noop,
7272
onCreateChannelClick: noop,
7373
onBeforeCreateChannel: null,
@@ -87,27 +87,26 @@ const useGroupChannelListStore = () => {
8787
return useStore(GroupChannelListContext, state => state, initialState);
8888
};
8989

90-
export const GroupChannelListManager: React.FC<GroupChannelListProviderProps> = (props: GroupChannelListProviderProps) => {
91-
const {
92-
className = '',
93-
selectedChannelUrl,
90+
export const GroupChannelListManager: React.FC<GroupChannelListProviderProps> = ({
91+
className = '',
92+
selectedChannelUrl = '',
9493

95-
disableAutoSelect = false,
96-
allowProfileEdit,
97-
isTypingIndicatorEnabled,
98-
isMessageReceiptStatusEnabled,
94+
disableAutoSelect = false,
95+
allowProfileEdit,
96+
isTypingIndicatorEnabled,
97+
isMessageReceiptStatusEnabled,
9998

100-
channelListQueryParams,
101-
onThemeChange,
102-
onChannelSelect = noop,
103-
onChannelCreated = noop,
104-
onCreateChannelClick,
105-
onBeforeCreateChannel,
106-
onUserProfileUpdated,
107-
} = props;
99+
channelListQueryParams,
100+
onThemeChange,
101+
onChannelSelect,
102+
onChannelCreated,
103+
onCreateChannelClick,
104+
onBeforeCreateChannel,
105+
onUserProfileUpdated,
106+
}: GroupChannelListProviderProps) => {
108107

109108
const { config, stores } = useSendbirdStateContext();
110-
const { updateState } = useGroupChannelListStore();
109+
const { state, updateState } = useGroupChannelListStore();
111110
const { sdkStore } = stores;
112111

113112
const sdk = sdkStore.sdk;
@@ -135,21 +134,25 @@ export const GroupChannelListManager: React.FC<GroupChannelListProviderProps> =
135134

136135
// Recreates the GroupChannelCollection when `channelListQueryParams` change
137136
useEffect(() => {
138-
refresh();
137+
refresh?.();
139138
}, [
140139
Object.keys(channelListQueryParams ?? {}).sort()
141140
.map((key: string) => `${key}=${encodeURIComponent(JSON.stringify(channelListQueryParams[key]))}`)
142141
.join('&'),
143142
]);
144143

145-
const [typingChannelUrls, setTypingChannelUrls] = useState<string[]>([]);
144+
const { typingChannelUrls } = state;
146145
useGroupChannelHandler(sdk, {
147146
onTypingStatusUpdated: (channel) => {
148147
const channelList = typingChannelUrls.filter((channelUrl) => channelUrl !== channel.url);
149148
if (channel.getTypingUsers()?.length > 0) {
150-
setTypingChannelUrls(channelList.concat(channel.url));
149+
updateState({
150+
typingChannelUrls: (channelList.concat(channel.url)),
151+
});
151152
} else {
152-
setTypingChannelUrls(channelList);
153+
updateState({
154+
typingChannelUrls: (channelList),
155+
});
153156
}
154157
},
155158
});
@@ -191,7 +194,7 @@ export const GroupChannelListManager: React.FC<GroupChannelListProviderProps> =
191194
typingChannelUrls,
192195
refreshing,
193196
initialized,
194-
groupChannels,
197+
groupChannels.map(channel => channel.url).join(),
195198
refresh,
196199
loadMore,
197200
]);
@@ -200,7 +203,7 @@ export const GroupChannelListManager: React.FC<GroupChannelListProviderProps> =
200203
};
201204

202205
const createGroupChannelListStore = () => createStore(initialState);
203-
const InternalGroupChannelListStoreProvider = ({ children }) => {
206+
const InternalGroupChannelListProvider = ({ children }) => {
204207
const storeRef = useRef(createGroupChannelListStore());
205208
return (
206209
<GroupChannelListContext.Provider value={storeRef.current}>
@@ -213,12 +216,12 @@ export const GroupChannelListProvider = (props: GroupChannelListProviderProps) =
213216
const { children, className } = props;
214217

215218
return (
216-
<InternalGroupChannelListStoreProvider>
219+
<InternalGroupChannelListProvider>
217220
<GroupChannelListManager {...props} />
218221
<UserProfileProvider {...props}>
219222
<div className={`sendbird-channel-list ${className}`}>{children}</div>
220223
</UserProfileProvider>
221-
</InternalGroupChannelListStoreProvider>
224+
</InternalGroupChannelListProvider>
222225
);
223226
};
224227

0 commit comments

Comments
 (0)