Skip to content

Commit 5b35b02

Browse files
authored
chore: Add tests for SendbirdProvider migration (#1275)
[CLNP-5737](https://sendbird.atlassian.net/browse/CLNP-5737) * Added tests for `Sendbird/index.tsx`, `Sendbird/utils.ts`, `SendbirdProvider.tsx`, `initialState.ts`, and `useSendbird.tsx` ### Before ![image](https://github.com/user-attachments/assets/0bdae22e-f5f5-4880-949c-33ea65d61a29) ### After ![image](https://github.com/user-attachments/assets/b0f6d021-bb80-49cd-b476-cc5a6c155c3e)
1 parent 7590607 commit 5b35b02

File tree

9 files changed

+808
-2
lines changed

9 files changed

+808
-2
lines changed

jest-setup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import './__mocks__/intersectionObserverMock';
2+
import '@testing-library/jest-dom';
23

34
const { JSDOM } = require('jsdom');
45

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { useContext } from 'react';
2+
import { render } from '@testing-library/react';
3+
import { SendbirdContext, createSendbirdContextStore } from '../context/SendbirdContext';
4+
5+
describe('SendbirdContext', () => {
6+
it('should initialize with null by default', () => {
7+
const TestComponent = () => {
8+
const context = useContext(SendbirdContext);
9+
return <div>{context ? 'Not Null' : 'Null'}</div>;
10+
};
11+
12+
const { getByText } = render(<TestComponent />);
13+
expect(getByText('Null')).toBeInTheDocument();
14+
});
15+
16+
it('should provide a valid context to child components', () => {
17+
const mockStore = createSendbirdContextStore();
18+
const TestComponent = () => {
19+
const context = useContext(SendbirdContext);
20+
return <div>{context ? 'Not Null' : 'Null'}</div>;
21+
};
22+
23+
const { getByText } = render(
24+
<SendbirdContext.Provider value={mockStore}>
25+
<TestComponent />
26+
</SendbirdContext.Provider>,
27+
);
28+
29+
expect(getByText('Not Null')).toBeInTheDocument();
30+
});
31+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { SendbirdContextProvider } from '../context/SendbirdProvider';
4+
import useSendbird from '../context/hooks/useSendbird';
5+
6+
const mockState = {
7+
stores: { sdkStore: { initialized: false } },
8+
config: { logger: console, groupChannel: { enableVoiceMessage: false } },
9+
};
10+
const mockActions = { connect: jest.fn(), disconnect: jest.fn() };
11+
12+
jest.mock('../context/hooks/useSendbird', () => ({
13+
__esModule: true,
14+
default: jest.fn(() => ({ state: mockState, actions: mockActions })),
15+
useSendbird: jest.fn(() => ({ state: mockState, actions: mockActions })),
16+
}));
17+
18+
describe('SendbirdProvider', () => {
19+
beforeEach(() => {
20+
// Reset mock functions before each test
21+
jest.clearAllMocks();
22+
23+
// Mock MediaRecorder.isTypeSupported
24+
global.MediaRecorder = {
25+
isTypeSupported: jest.fn((type) => {
26+
const supportedMimeTypes = ['audio/webm', 'audio/wav'];
27+
return supportedMimeTypes.includes(type);
28+
}),
29+
};
30+
31+
// Mock useSendbird return value
32+
useSendbird.mockReturnValue({
33+
state: mockState,
34+
actions: mockActions,
35+
});
36+
});
37+
38+
it('should render child components', () => {
39+
const { getByTestId } = render(
40+
<SendbirdContextProvider appId="mockAppId" userId="mockUserId">
41+
<div data-testid="child">Child Component</div>
42+
</SendbirdContextProvider>,
43+
);
44+
45+
expect(getByTestId('child')).toBeInTheDocument();
46+
});
47+
48+
it('should call connect when mounted', () => {
49+
render(
50+
<SendbirdContextProvider appId="mockAppId" userId="mockUserId">
51+
<div data-testid="child">Child Component</div>
52+
</SendbirdContextProvider>,
53+
);
54+
55+
expect(mockActions.connect).toHaveBeenCalledWith(
56+
expect.objectContaining({
57+
appId: 'mockAppId',
58+
userId: 'mockUserId',
59+
}),
60+
);
61+
});
62+
63+
it('should call disconnect on unmount', () => {
64+
const { unmount } = render(
65+
<SendbirdContextProvider appId="mockAppId" userId="mockUserId">
66+
<div data-testid="child">Child Component</div>
67+
</SendbirdContextProvider>,
68+
);
69+
70+
unmount();
71+
expect(mockActions.disconnect).toHaveBeenCalled();
72+
});
73+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
4+
import { SendbirdProvider, withSendBird } from '../index';
5+
6+
jest.mock('@sendbird/uikit-tools', () => ({
7+
UIKitConfigProvider: jest.fn(({ children }) => <div data-testid="UIKitConfigProvider">{children}</div>),
8+
}));
9+
jest.mock('../context/SendbirdProvider', () => ({
10+
SendbirdContextProvider: jest.fn(({ children }) => <div data-testid="SendbirdContextProvider">{children}</div>),
11+
}));
12+
jest.mock('../context/hooks/useSendbird', () => jest.fn(() => ({
13+
state: { someState: 'testState' },
14+
actions: { someAction: jest.fn() },
15+
})));
16+
jest.mock('../../utils/uikitConfigMapper', () => ({
17+
uikitConfigMapper: jest.fn(() => ({
18+
common: {},
19+
groupChannel: {},
20+
openChannel: {},
21+
})),
22+
}));
23+
jest.mock('../../utils/uikitConfigStorage', () => ({}));
24+
25+
describe('SendbirdProvider/index', () => {
26+
it('renders UIKitConfigProvider with correct localConfigs', () => {
27+
const props = {
28+
replyType: 'threaded',
29+
isMentionEnabled: true,
30+
isReactionEnabled: true,
31+
disableUserProfile: false,
32+
isVoiceMessageEnabled: true,
33+
isTypingIndicatorEnabledOnChannelList: false,
34+
isMessageReceiptStatusEnabledOnChannelList: false,
35+
showSearchIcon: true,
36+
uikitOptions: {},
37+
};
38+
39+
render(<SendbirdProvider {...props} />);
40+
41+
expect(screen.getByTestId('UIKitConfigProvider')).toBeInTheDocument();
42+
expect(screen.getByTestId('SendbirdContextProvider')).toBeInTheDocument();
43+
});
44+
});
45+
46+
describe('withSendbirdContext', () => {
47+
let consoleWarnSpy: jest.SpyInstance;
48+
49+
beforeEach(() => {
50+
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
51+
});
52+
53+
afterEach(() => {
54+
consoleWarnSpy.mockRestore();
55+
});
56+
57+
it('logs a warning if mapStoreToProps is not a function', () => {
58+
const MockComponent = jest.fn(() => <div data-testid="MockComponent" />);
59+
const invalidMapStoreToProps = 'invalidValue';
60+
61+
const WrappedComponent = withSendBird(MockComponent, invalidMapStoreToProps);
62+
63+
render(<WrappedComponent />);
64+
65+
expect(consoleWarnSpy).toHaveBeenCalledWith(
66+
'Second parameter to withSendbirdContext must be a pure function',
67+
);
68+
});
69+
70+
it('renders OriginalComponent with merged props', () => {
71+
const MockComponent = jest.fn((props) => <div data-testid="MockComponent">{props.testProp}</div>);
72+
const mapStoreToProps = (context: any) => ({
73+
mappedProp: context.someState,
74+
});
75+
76+
const WrappedComponent = withSendBird(MockComponent, mapStoreToProps);
77+
78+
render(<WrappedComponent testProp="additionalValue" />);
79+
80+
expect(screen.getByTestId('MockComponent')).toHaveTextContent('additionalValue');
81+
82+
expect(MockComponent).toHaveBeenCalledWith(
83+
expect.objectContaining({
84+
mappedProp: 'testState',
85+
testProp: 'additionalValue',
86+
}),
87+
{},
88+
);
89+
});
90+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { initialState } from '../context/initialState';
2+
3+
describe('initialState', () => {
4+
it('should match the expected structure', () => {
5+
expect(initialState).toMatchObject({
6+
config: expect.any(Object),
7+
stores: expect.any(Object),
8+
utils: expect.any(Object),
9+
eventHandlers: expect.any(Object),
10+
});
11+
});
12+
13+
it('should have default values', () => {
14+
expect(initialState.stores.sdkStore).toEqual({
15+
sdk: {},
16+
initialized: false,
17+
loading: false,
18+
error: undefined,
19+
});
20+
expect(initialState.stores.userStore).toEqual({
21+
user: {},
22+
initialized: false,
23+
loading: false,
24+
});
25+
expect(initialState.stores.appInfoStore).toEqual({
26+
messageTemplatesInfo: undefined,
27+
waitingTemplateKeysMap: {},
28+
});
29+
});
30+
31+
it('should have correct config values', () => {
32+
expect(initialState.config.theme).toBe('light');
33+
expect(initialState.config.replyType).toBe('NONE');
34+
expect(initialState.config.uikitUploadSizeLimit).toBeDefined();
35+
expect(initialState.config.uikitMultipleFilesMessageLimit).toBeDefined();
36+
});
37+
38+
it('should have all eventHandlers initialized', () => {
39+
expect(initialState.eventHandlers.reaction.onPressUserProfile).toBeInstanceOf(Function);
40+
expect(initialState.eventHandlers.connection.onConnected).toBeInstanceOf(Function);
41+
expect(initialState.eventHandlers.connection.onFailed).toBeInstanceOf(Function);
42+
expect(initialState.eventHandlers.modal.onMounted).toBeInstanceOf(Function);
43+
expect(initialState.eventHandlers.message.onSendMessageFailed).toBeInstanceOf(Function);
44+
expect(initialState.eventHandlers.message.onUpdateMessageFailed).toBeInstanceOf(Function);
45+
expect(initialState.eventHandlers.message.onFileUploadFailed).toBeInstanceOf(Function);
46+
});
47+
});

0 commit comments

Comments
 (0)