Skip to content

Commit 9c9f520

Browse files
authored
Fix migration test failure (#1285)
### Description Fixed the failures of migration tests added by #1279
1 parent 5890933 commit 9c9f520

27 files changed

+202
-177
lines changed

src/lib/Sendbird/context/hooks/useSendbird.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useContext, useMemo, useSyncExternalStore } from 'react';
1+
import { useContext, useMemo } from 'react';
2+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
23
import { SendbirdError, User } from '@sendbird/chat';
34

45
import { SendbirdContext } from '../SendbirdContext';
@@ -11,7 +12,7 @@ export const useSendbird = () => {
1112
const store = useContext(SendbirdContext);
1213
if (!store) throw new Error(NO_CONTEXT_ERROR);
1314

14-
const state = useSyncExternalStore(store.subscribe, store.getState);
15+
const state: SendbirdState = useSyncExternalStore(store.subscribe, store.getState);
1516
const actions = useMemo(() => ({
1617
/* Example: How to set the state basically */
1718
// exampleAction: () => {

src/lib/SendbirdProvider.migration.spec.tsx

Lines changed: 82 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,60 @@
11
/* eslint-disable no-console */
2-
import React from 'react';
2+
import React, { act } from 'react';
33
import { render, renderHook, screen } from '@testing-library/react';
44
import SendbirdProvider, { SendbirdProviderProps } from './Sendbird';
55
import useSendbirdStateContext from './Sendbird/context/hooks/useSendbirdStateContext';
66
import { match } from 'ts-pattern';
77
import { DEFAULT_MULTIPLE_FILES_MESSAGE_LIMIT, DEFAULT_UPLOAD_SIZE_LIMIT } from '../utils/consts';
88

9+
jest.mock('@sendbird/chat', () => {
10+
const mockConnect = jest.fn().mockResolvedValue({
11+
userId: 'test-user-id',
12+
nickname: 'test-nickname',
13+
profileUrl: 'test-profile-url',
14+
});
15+
const mockDisconnect = jest.fn().mockResolvedValue(null);
16+
const mockUpdateCurrentUserInfo = jest.fn().mockResolvedValue(null);
17+
const mockAddExtension = jest.fn().mockReturnThis();
18+
const mockAddSendbirdExtensions = jest.fn().mockReturnThis();
19+
const mockGetMessageTemplatesByToken = jest.fn().mockResolvedValue({
20+
hasMore: false,
21+
token: null,
22+
templates: [],
23+
});
24+
25+
const mockSdk = {
26+
init: jest.fn().mockImplementation(() => mockSdk),
27+
connect: mockConnect,
28+
disconnect: mockDisconnect,
29+
updateCurrentUserInfo: mockUpdateCurrentUserInfo,
30+
addExtension: mockAddExtension,
31+
addSendbirdExtensions: mockAddSendbirdExtensions,
32+
GroupChannel: { createMyGroupChannelListQuery: jest.fn() },
33+
message: {
34+
getMessageTemplatesByToken: mockGetMessageTemplatesByToken,
35+
},
36+
appInfo: {
37+
uploadSizeLimit: 1024 * 1024 * 5,
38+
multipleFilesMessageFileCountLimit: 10,
39+
},
40+
};
41+
42+
return {
43+
__esModule: true,
44+
default: mockSdk,
45+
SendbirdProduct: {
46+
UIKIT_CHAT: 'UIKIT_CHAT',
47+
},
48+
SendbirdPlatform: {
49+
JS: 'JS',
50+
},
51+
DeviceOsPlatform: {
52+
WEB: 'WEB',
53+
MOBILE_WEB: 'MOBILE_WEB',
54+
},
55+
};
56+
});
57+
958
const mockProps: SendbirdProviderProps = {
1059
appId: 'test-app-id',
1160
userId: 'test-user-id',
@@ -39,37 +88,6 @@ const mockProps: SendbirdProviderProps = {
3988
children: <div>Test Child</div>,
4089
};
4190

42-
const mockDisconnect = jest.fn();
43-
const mockConnect = jest.fn();
44-
const mockUpdateCurrentUserInfo = jest.fn();
45-
46-
/**
47-
* Mocking Sendbird SDK
48-
* sdk.connect causes DOMException issue in jest.
49-
* Because it retries many times to connect indexDB.
50-
*/
51-
jest.mock('@sendbird/chat', () => {
52-
return {
53-
__esModule: true,
54-
default: jest.fn().mockImplementation(() => {
55-
return {
56-
connect: mockConnect.mockResolvedValue({
57-
userId: 'test-user-id',
58-
nickname: 'test-nickname',
59-
profileUrl: 'test-profile-url',
60-
}),
61-
disconnect: mockDisconnect.mockResolvedValue(null),
62-
updateCurrentUserInfo: mockUpdateCurrentUserInfo.mockResolvedValue(null),
63-
GroupChannel: { createMyGroupChannelListQuery: jest.fn() },
64-
appInfo: {
65-
uploadSizeLimit: 1024 * 1024 * 5, // 5MB
66-
multipleFilesMessageFileCountLimit: 10,
67-
},
68-
};
69-
}),
70-
};
71-
});
72-
7391
describe('SendbirdProvider Props & Context Interface Validation', () => {
7492
const originalConsoleError = console.error;
7593
let originalFetch;
@@ -95,9 +113,6 @@ describe('SendbirdProvider Props & Context Interface Validation', () => {
95113

96114
beforeEach(() => {
97115
jest.clearAllMocks();
98-
mockConnect.mockClear();
99-
mockDisconnect.mockClear();
100-
mockUpdateCurrentUserInfo.mockClear();
101116

102117
global.MediaRecorder = {
103118
isTypeSupported: jest.fn((type) => {
@@ -119,24 +134,27 @@ describe('SendbirdProvider Props & Context Interface Validation', () => {
119134
});
120135

121136
it('should accept all legacy props without type errors', async () => {
122-
const { rerender } = render(
123-
<SendbirdProvider {...mockProps}>
124-
{mockProps.children}
125-
</SendbirdProvider>,
126-
);
137+
const { rerender } = await act(async () => (
138+
render(
139+
<SendbirdProvider {...mockProps}>
140+
{mockProps.children}
141+
</SendbirdProvider>,
142+
)
143+
));
127144

128-
rerender(
129-
<SendbirdProvider {...mockProps}>
130-
{mockProps.children}
131-
</SendbirdProvider>,
132-
);
145+
await act(async () => (
146+
rerender(
147+
<SendbirdProvider {...mockProps}>
148+
{mockProps.children}
149+
</SendbirdProvider>,
150+
)
151+
));
133152
});
134153

135-
it('should provide all expected keys in context', () => {
154+
it('should provide all expected keys in context', async () => {
136155
const expectedKeys = [
137156
'config',
138157
'stores',
139-
'dispatchers',
140158
'eventHandlers',
141159
'emojiManager',
142160
'utils',
@@ -159,19 +177,21 @@ describe('SendbirdProvider Props & Context Interface Validation', () => {
159177
);
160178
};
161179

162-
render(
163-
<SendbirdProvider appId="test-app-id" userId="test-user-id">
164-
<TestComponent />
165-
</SendbirdProvider>,
166-
);
180+
await act(() => (
181+
render(
182+
<SendbirdProvider appId="test-app-id" userId="test-user-id">
183+
<TestComponent />
184+
</SendbirdProvider>,
185+
)
186+
));
167187

168188
expectedKeys.forEach((key) => {
169189
const element = screen.getByTestId(`context-${key}`);
170190
expect(element).toBeInTheDocument();
171191
});
172192
});
173193

174-
it('should pass all expected values to the config object', () => {
194+
it('should pass all expected values to the config object', async () => {
175195
const mockProps: SendbirdProviderProps = {
176196
appId: 'test-app-id',
177197
userId: 'test-user-id',
@@ -192,7 +212,10 @@ describe('SendbirdProvider Props & Context Interface Validation', () => {
192212
<SendbirdProvider {...mockProps}>{children}</SendbirdProvider>
193213
);
194214

195-
const { result } = renderHook(() => useSendbirdStateContext(), { wrapper });
215+
let result;
216+
await act(async () => {
217+
result = renderHook(() => useSendbirdStateContext(), { wrapper }).result;
218+
});
196219

197220
const config = result.current.config;
198221

@@ -220,14 +243,17 @@ describe('SendbirdProvider Props & Context Interface Validation', () => {
220243
expect(config.markAsDeliveredScheduler).toBeDefined();
221244
});
222245

223-
it('should handle optional and default values correctly', () => {
246+
it('should handle optional and default values correctly', async () => {
224247
const wrapper = ({ children }) => (
225248
<SendbirdProvider {...mockProps} appId="test-app-id" userId="test-user-id">
226249
{children}
227250
</SendbirdProvider>
228251
);
229252

230-
const { result } = renderHook(() => useSendbirdStateContext(), { wrapper });
253+
let result;
254+
await act(async () => {
255+
result = renderHook(() => useSendbirdStateContext(), { wrapper }).result;
256+
});
231257

232258
expect(result.current.config.pubSub).toBeDefined();
233259
expect(result.current.config.logger).toBeDefined();

src/lib/hooks/useMessageTemplateUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export default function useMessageTemplateUtils({
197197
}, [
198198
actions.upsertMessageTemplates,
199199
actions.upsertWaitingTemplateKeys,
200-
sdk.message?.getMessageTemplatesByToken,
200+
sdk?.message?.getMessageTemplatesByToken,
201201
]);
202202
return {
203203
getCachedTemplate,

src/modules/ChannelSettings/__test__/ChannelSettingsProvider.spec.tsx

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,24 @@ const mockLogger = {
1313
error: jest.fn(),
1414
};
1515

16+
const mockStore = {
17+
getState: jest.fn(),
18+
setState: jest.fn(),
19+
subscribe: jest.fn(() => jest.fn()),
20+
};
21+
1622
const initialState = {
1723
channelUrl: 'test-channel',
18-
onCloseClick: undefined,
19-
onLeaveChannel: undefined,
20-
onChannelModified: undefined,
21-
onBeforeUpdateChannel: undefined,
22-
renderUserListItem: undefined,
23-
queries: undefined,
24-
overrideInviteUser: undefined,
2524
channel: null,
2625
loading: false,
2726
invalidChannel: false,
28-
forceUpdateUI: expect.any(Function),
29-
setChannelUpdateId: expect.any(Function),
3027
};
3128

3229
describe('ChannelSettingsProvider', () => {
3330
let wrapper;
3431

3532
beforeEach(() => {
33+
mockStore.getState.mockReturnValue(initialState);
3634
useSendbird.mockReturnValue({
3735
state: {
3836
stores: { sdkStore: { sdk: {}, initialized: true } },
@@ -51,10 +49,13 @@ describe('ChannelSettingsProvider', () => {
5149
jest.clearAllMocks();
5250
});
5351

54-
it('provides the correct initial state', () => {
52+
it('provides the correct initial state and actions', () => {
5553
const { result } = renderHook(() => useChannelSettingsContext(), { wrapper });
5654

57-
expect(result.current.getState()).toEqual(expect.objectContaining(initialState));
55+
expect(result.current.channelUrl).toBe(initialState.channelUrl);
56+
expect(result.current.channel).toBe(initialState.channel);
57+
expect(result.current.loading).toBe(initialState.loading);
58+
expect(result.current.invalidChannel).toBe(initialState.invalidChannel);
5859
});
5960

6061
it('logs a warning if SDK is not initialized', () => {
@@ -66,32 +67,29 @@ describe('ChannelSettingsProvider', () => {
6667
});
6768

6869
renderHook(() => useChannelSettingsContext(), { wrapper });
69-
7070
expect(mockLogger.warning).toHaveBeenCalledWith('ChannelSettings: SDK or GroupChannelModule is not available');
7171
});
7272

73-
it('updates state correctly when setChannelUpdateId is called', async () => {
73+
it('updates channel state correctly', async () => {
7474
const { result } = renderHook(() => useChannelSettingsContext(), { wrapper });
75+
const newChannel = { url: 'new-channel' } as any;
7576

7677
await act(async () => {
77-
result.current.setState({ channelUrl: 'new-channel' });
78-
await waitForStateUpdate();
79-
expect(result.current.getState().channelUrl).toBe('new-channel');
78+
result.current.setChannel(newChannel);
8079
});
80+
81+
expect(result.current.channel).toEqual(newChannel);
8182
});
8283

83-
it('maintains other state values when channel changes', async () => {
84+
it('maintains loading and invalid states', async () => {
8485
const { result } = renderHook(() => useChannelSettingsContext(), { wrapper });
8586

8687
await act(async () => {
87-
result.current.setState({ channel: { name: 'Updated Channel' } });
88-
await waitForStateUpdate();
89-
const updatedState = result.current.getState();
90-
expect(updatedState.channel).toEqual({ name: 'Updated Channel' });
91-
expect(updatedState.loading).toBe(false);
92-
expect(updatedState.invalidChannel).toBe(false);
88+
result.current.setLoading(true);
89+
result.current.setInvalid(true);
9390
});
94-
});
9591

96-
const waitForStateUpdate = () => new Promise(resolve => { setTimeout(resolve, 0); });
92+
expect(result.current.loading).toBe(true);
93+
expect(result.current.invalidChannel).toBe(true);
94+
});
9795
});

0 commit comments

Comments
 (0)