diff --git a/jest-setup.js b/jest-setup.js
index 2379beb83..4e791c2ad 100644
--- a/jest-setup.js
+++ b/jest-setup.js
@@ -1,4 +1,5 @@
import './__mocks__/intersectionObserverMock';
+import '@testing-library/jest-dom';
const { JSDOM } = require('jsdom');
diff --git a/src/lib/Sendbird/__tests__/SendbirdContext.spec.tsx b/src/lib/Sendbird/__tests__/SendbirdContext.spec.tsx
new file mode 100644
index 000000000..58b8ad2c5
--- /dev/null
+++ b/src/lib/Sendbird/__tests__/SendbirdContext.spec.tsx
@@ -0,0 +1,31 @@
+import React, { useContext } from 'react';
+import { render } from '@testing-library/react';
+import { SendbirdContext, createSendbirdContextStore } from '../context/SendbirdContext';
+
+describe('SendbirdContext', () => {
+ it('should initialize with null by default', () => {
+ const TestComponent = () => {
+ const context = useContext(SendbirdContext);
+ return
{context ? 'Not Null' : 'Null'}
;
+ };
+
+ const { getByText } = render();
+ expect(getByText('Null')).toBeInTheDocument();
+ });
+
+ it('should provide a valid context to child components', () => {
+ const mockStore = createSendbirdContextStore();
+ const TestComponent = () => {
+ const context = useContext(SendbirdContext);
+ return {context ? 'Not Null' : 'Null'}
;
+ };
+
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText('Not Null')).toBeInTheDocument();
+ });
+});
diff --git a/src/lib/Sendbird/__tests__/SendbirdProvider.spec.tsx b/src/lib/Sendbird/__tests__/SendbirdProvider.spec.tsx
new file mode 100644
index 000000000..fbe631e48
--- /dev/null
+++ b/src/lib/Sendbird/__tests__/SendbirdProvider.spec.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { SendbirdContextProvider } from '../context/SendbirdProvider';
+import useSendbird from '../context/hooks/useSendbird';
+
+const mockState = {
+ stores: { sdkStore: { initialized: false } },
+ config: { logger: console, groupChannel: { enableVoiceMessage: false } },
+};
+const mockActions = { connect: jest.fn(), disconnect: jest.fn() };
+
+jest.mock('../context/hooks/useSendbird', () => ({
+ __esModule: true,
+ default: jest.fn(() => ({ state: mockState, actions: mockActions })),
+ useSendbird: jest.fn(() => ({ state: mockState, actions: mockActions })),
+}));
+
+describe('SendbirdProvider', () => {
+ beforeEach(() => {
+ // Reset mock functions before each test
+ jest.clearAllMocks();
+
+ // Mock MediaRecorder.isTypeSupported
+ global.MediaRecorder = {
+ isTypeSupported: jest.fn((type) => {
+ const supportedMimeTypes = ['audio/webm', 'audio/wav'];
+ return supportedMimeTypes.includes(type);
+ }),
+ };
+
+ // Mock useSendbird return value
+ useSendbird.mockReturnValue({
+ state: mockState,
+ actions: mockActions,
+ });
+ });
+
+ it('should render child components', () => {
+ const { getByTestId } = render(
+
+ Child Component
+ ,
+ );
+
+ expect(getByTestId('child')).toBeInTheDocument();
+ });
+
+ it('should call connect when mounted', () => {
+ render(
+
+ Child Component
+ ,
+ );
+
+ expect(mockActions.connect).toHaveBeenCalledWith(
+ expect.objectContaining({
+ appId: 'mockAppId',
+ userId: 'mockUserId',
+ }),
+ );
+ });
+
+ it('should call disconnect on unmount', () => {
+ const { unmount } = render(
+
+ Child Component
+ ,
+ );
+
+ unmount();
+ expect(mockActions.disconnect).toHaveBeenCalled();
+ });
+});
diff --git a/src/lib/Sendbird/__tests__/index.spec.tsx b/src/lib/Sendbird/__tests__/index.spec.tsx
new file mode 100644
index 000000000..9c76d3bdc
--- /dev/null
+++ b/src/lib/Sendbird/__tests__/index.spec.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+
+import { SendbirdProvider, withSendBird } from '../index';
+
+jest.mock('@sendbird/uikit-tools', () => ({
+ UIKitConfigProvider: jest.fn(({ children }) => {children}
),
+}));
+jest.mock('../context/SendbirdProvider', () => ({
+ SendbirdContextProvider: jest.fn(({ children }) => {children}
),
+}));
+jest.mock('../context/hooks/useSendbird', () => jest.fn(() => ({
+ state: { someState: 'testState' },
+ actions: { someAction: jest.fn() },
+})));
+jest.mock('../../utils/uikitConfigMapper', () => ({
+ uikitConfigMapper: jest.fn(() => ({
+ common: {},
+ groupChannel: {},
+ openChannel: {},
+ })),
+}));
+jest.mock('../../utils/uikitConfigStorage', () => ({}));
+
+describe('SendbirdProvider/index', () => {
+ it('renders UIKitConfigProvider with correct localConfigs', () => {
+ const props = {
+ replyType: 'threaded',
+ isMentionEnabled: true,
+ isReactionEnabled: true,
+ disableUserProfile: false,
+ isVoiceMessageEnabled: true,
+ isTypingIndicatorEnabledOnChannelList: false,
+ isMessageReceiptStatusEnabledOnChannelList: false,
+ showSearchIcon: true,
+ uikitOptions: {},
+ };
+
+ render();
+
+ expect(screen.getByTestId('UIKitConfigProvider')).toBeInTheDocument();
+ expect(screen.getByTestId('SendbirdContextProvider')).toBeInTheDocument();
+ });
+});
+
+describe('withSendbirdContext', () => {
+ let consoleWarnSpy: jest.SpyInstance;
+
+ beforeEach(() => {
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('logs a warning if mapStoreToProps is not a function', () => {
+ const MockComponent = jest.fn(() => );
+ const invalidMapStoreToProps = 'invalidValue';
+
+ const WrappedComponent = withSendBird(MockComponent, invalidMapStoreToProps);
+
+ render();
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ 'Second parameter to withSendbirdContext must be a pure function',
+ );
+ });
+
+ it('renders OriginalComponent with merged props', () => {
+ const MockComponent = jest.fn((props) => {props.testProp}
);
+ const mapStoreToProps = (context: any) => ({
+ mappedProp: context.someState,
+ });
+
+ const WrappedComponent = withSendBird(MockComponent, mapStoreToProps);
+
+ render();
+
+ expect(screen.getByTestId('MockComponent')).toHaveTextContent('additionalValue');
+
+ expect(MockComponent).toHaveBeenCalledWith(
+ expect.objectContaining({
+ mappedProp: 'testState',
+ testProp: 'additionalValue',
+ }),
+ {},
+ );
+ });
+});
diff --git a/src/lib/Sendbird/__tests__/initialState.spec.ts b/src/lib/Sendbird/__tests__/initialState.spec.ts
new file mode 100644
index 000000000..a99e49195
--- /dev/null
+++ b/src/lib/Sendbird/__tests__/initialState.spec.ts
@@ -0,0 +1,47 @@
+import { initialState } from '../context/initialState';
+
+describe('initialState', () => {
+ it('should match the expected structure', () => {
+ expect(initialState).toMatchObject({
+ config: expect.any(Object),
+ stores: expect.any(Object),
+ utils: expect.any(Object),
+ eventHandlers: expect.any(Object),
+ });
+ });
+
+ it('should have default values', () => {
+ expect(initialState.stores.sdkStore).toEqual({
+ sdk: {},
+ initialized: false,
+ loading: false,
+ error: undefined,
+ });
+ expect(initialState.stores.userStore).toEqual({
+ user: {},
+ initialized: false,
+ loading: false,
+ });
+ expect(initialState.stores.appInfoStore).toEqual({
+ messageTemplatesInfo: undefined,
+ waitingTemplateKeysMap: {},
+ });
+ });
+
+ it('should have correct config values', () => {
+ expect(initialState.config.theme).toBe('light');
+ expect(initialState.config.replyType).toBe('NONE');
+ expect(initialState.config.uikitUploadSizeLimit).toBeDefined();
+ expect(initialState.config.uikitMultipleFilesMessageLimit).toBeDefined();
+ });
+
+ it('should have all eventHandlers initialized', () => {
+ expect(initialState.eventHandlers.reaction.onPressUserProfile).toBeInstanceOf(Function);
+ expect(initialState.eventHandlers.connection.onConnected).toBeInstanceOf(Function);
+ expect(initialState.eventHandlers.connection.onFailed).toBeInstanceOf(Function);
+ expect(initialState.eventHandlers.modal.onMounted).toBeInstanceOf(Function);
+ expect(initialState.eventHandlers.message.onSendMessageFailed).toBeInstanceOf(Function);
+ expect(initialState.eventHandlers.message.onUpdateMessageFailed).toBeInstanceOf(Function);
+ expect(initialState.eventHandlers.message.onFileUploadFailed).toBeInstanceOf(Function);
+ });
+});
diff --git a/src/lib/Sendbird/__tests__/useSendbird.spec.tsx b/src/lib/Sendbird/__tests__/useSendbird.spec.tsx
new file mode 100644
index 000000000..ddcb777fd
--- /dev/null
+++ b/src/lib/Sendbird/__tests__/useSendbird.spec.tsx
@@ -0,0 +1,410 @@
+import React from 'react';
+import { renderHook, act } from '@testing-library/react-hooks';
+import useSendbird from '../context/hooks/useSendbird';
+import { SendbirdContext, createSendbirdContextStore } from '../context/SendbirdContext';
+
+jest.mock('../utils', () => {
+ const actualUtils = jest.requireActual('../utils');
+ return {
+ ...actualUtils,
+ initSDK: jest.fn(() => ({
+ connect: jest.fn().mockResolvedValue({ userId: 'mockUserId' }),
+ updateCurrentUserInfo: jest.fn().mockResolvedValue({}),
+ })),
+ setupSDK: jest.fn(),
+ };
+});
+
+describe('useSendbird', () => {
+ let mockStore;
+ const mockLogger = { error: jest.fn(), info: jest.fn() };
+
+ beforeEach(() => {
+ mockStore = createSendbirdContextStore();
+ });
+
+ const wrapper = ({ children }) => (
+ {children}
+ );
+
+ describe('General behavior', () => {
+ it('should throw an error if used outside SendbirdProvider', () => {
+ const { result } = renderHook(() => useSendbird());
+ expect(result.error).toBeDefined();
+ expect(result.error.message).toBe('No sendbird state value available. Make sure you are rendering `` at the top of your app.');
+ });
+
+ it('should return state and actions when used within SendbirdProvider', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+ expect(result.current.state).toBeDefined();
+ expect(result.current.actions).toBeDefined();
+ });
+ });
+
+ describe('SDK actions', () => {
+ it('should update state when initSdk is called', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ act(() => {
+ result.current.actions.initSdk('mockSdk');
+ });
+
+ expect(mockStore.getState().stores.sdkStore.sdk).toBe('mockSdk');
+ expect(mockStore.getState().stores.sdkStore.initialized).toBe(true);
+ });
+
+ it('should reset SDK state when resetSdk is called', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ act(() => {
+ result.current.actions.initSdk('mockSdk');
+ });
+
+ act(() => {
+ result.current.actions.resetSdk();
+ });
+
+ const sdkStore = mockStore.getState().stores.sdkStore;
+ expect(sdkStore.sdk).toBeNull();
+ expect(sdkStore.initialized).toBe(false);
+ expect(sdkStore.loading).toBe(false);
+ });
+
+ it('should set SDK loading state correctly', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ act(() => {
+ result.current.actions.setSdkLoading(true);
+ });
+
+ expect(mockStore.getState().stores.sdkStore.loading).toBe(true);
+
+ act(() => {
+ result.current.actions.setSdkLoading(false);
+ });
+
+ expect(mockStore.getState().stores.sdkStore.loading).toBe(false);
+ });
+
+ it('should handle SDK errors correctly', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ act(() => {
+ result.current.actions.sdkError();
+ });
+
+ const sdkStore = mockStore.getState().stores.sdkStore;
+ expect(sdkStore.error).toBe(true);
+ expect(sdkStore.loading).toBe(false);
+ expect(sdkStore.initialized).toBe(false);
+ });
+ });
+
+ describe('User actions', () => {
+ it('should update user state when initUser is called', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ const mockUser = { id: 'mockUserId', name: 'mockUserName' };
+ act(() => {
+ result.current.actions.initUser(mockUser);
+ });
+
+ const userStore = mockStore.getState().stores.userStore;
+ expect(userStore.user).toEqual(mockUser);
+ expect(userStore.initialized).toBe(true);
+ });
+
+ it('should reset user state when resetUser is called', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ const mockUser = { id: 'mockUserId', name: 'mockUserName' };
+ act(() => {
+ result.current.actions.initUser(mockUser);
+ });
+
+ act(() => {
+ result.current.actions.resetUser();
+ });
+
+ const userStore = mockStore.getState().stores.userStore;
+ expect(userStore.user).toBeNull();
+ expect(userStore.initialized).toBe(false);
+ });
+
+ it('should update user info when updateUserInfo is called', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ const initialUser = { id: 'mockUserId', name: 'oldName' };
+ const updatedUser = { id: 'mockUserId', name: 'newName' };
+
+ act(() => {
+ result.current.actions.initUser(initialUser);
+ });
+
+ act(() => {
+ result.current.actions.updateUserInfo(updatedUser);
+ });
+
+ const userStore = mockStore.getState().stores.userStore;
+ expect(userStore.user).toEqual(updatedUser);
+ });
+ });
+
+ describe('AppInfo actions', () => {
+ it('should initialize message templates info with initMessageTemplateInfo', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ const mockPayload = { templatesMap: { key1: 'template1', key2: 'template2' } };
+
+ act(() => {
+ result.current.actions.initMessageTemplateInfo({ payload: mockPayload });
+ });
+
+ const appInfoStore = mockStore.getState().stores.appInfoStore;
+ expect(appInfoStore.messageTemplatesInfo).toEqual(mockPayload);
+ expect(appInfoStore.waitingTemplateKeysMap).toEqual({});
+ });
+
+ it('should upsert message templates with upsertMessageTemplates', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ act(() => {
+ mockStore.setState((state) => ({
+ ...state,
+ stores: {
+ ...state.stores,
+ appInfoStore: {
+ ...state.stores.appInfoStore,
+ messageTemplatesInfo: { templatesMap: {} },
+ waitingTemplateKeysMap: { key1: {}, key2: {} },
+ },
+ },
+ }));
+ });
+
+ act(() => {
+ result.current.actions.upsertMessageTemplates({
+ payload: [
+ { key: 'key1', template: 'templateContent1' },
+ { key: 'key2', template: 'templateContent2' },
+ ],
+ });
+ });
+
+ const appInfoStore = mockStore.getState().stores.appInfoStore;
+ expect(appInfoStore.messageTemplatesInfo.templatesMap).toEqual({
+ key1: 'templateContent1',
+ key2: 'templateContent2',
+ });
+ expect(appInfoStore.waitingTemplateKeysMap).toEqual({});
+ });
+
+ it('should upsert waiting template keys with upsertWaitingTemplateKeys', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ const mockPayload = {
+ keys: ['key1', 'key2'],
+ requestedAt: Date.now(),
+ };
+
+ act(() => {
+ result.current.actions.upsertWaitingTemplateKeys({ payload: mockPayload });
+ });
+
+ const appInfoStore = mockStore.getState().stores.appInfoStore;
+ expect(appInfoStore.waitingTemplateKeysMap.key1).toEqual({
+ erroredMessageIds: [],
+ requestedAt: mockPayload.requestedAt,
+ });
+ expect(appInfoStore.waitingTemplateKeysMap.key2).toEqual({
+ erroredMessageIds: [],
+ requestedAt: mockPayload.requestedAt,
+ });
+ });
+
+ it('should mark error waiting template keys with markErrorWaitingTemplateKeys', () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ act(() => {
+ mockStore.setState((state) => ({
+ ...state,
+ stores: {
+ ...state.stores,
+ appInfoStore: {
+ ...state.stores.appInfoStore,
+ waitingTemplateKeysMap: {
+ key1: { erroredMessageIds: [] },
+ key2: { erroredMessageIds: ['existingErrorId'] },
+ },
+ },
+ },
+ }));
+ });
+
+ act(() => {
+ result.current.actions.markErrorWaitingTemplateKeys({
+ payload: {
+ keys: ['key1', 'key2'],
+ messageId: 'newErrorId',
+ },
+ });
+ });
+
+ const appInfoStore = mockStore.getState().stores.appInfoStore;
+ expect(appInfoStore.waitingTemplateKeysMap.key1.erroredMessageIds).toContain('newErrorId');
+ expect(appInfoStore.waitingTemplateKeysMap.key2.erroredMessageIds).toContain('newErrorId');
+ expect(appInfoStore.waitingTemplateKeysMap.key2.erroredMessageIds).toContain('existingErrorId');
+ });
+
+ });
+
+ describe('Connection actions', () => {
+ it('should connect and initialize SDK correctly', async () => {
+ const mockStore = createSendbirdContextStore();
+ const wrapper = ({ children }) => (
+ {children}
+ );
+
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ const mockActions = result.current.actions;
+
+ await act(async () => {
+ await mockActions.connect({
+ logger: mockLogger,
+ userId: 'mockUserId',
+ appId: 'mockAppId',
+ accessToken: 'mockAccessToken',
+ nickname: 'mockNickname',
+ profileUrl: 'mockProfileUrl',
+ isMobile: false,
+ sdkInitParams: {},
+ customApiHost: '',
+ customWebSocketHost: '',
+ customExtensionParams: {},
+ eventHandlers: {
+ connection: {
+ onConnected: jest.fn(),
+ onFailed: jest.fn(),
+ },
+ },
+ initializeMessageTemplatesInfo: jest.fn(),
+ initDashboardConfigs: jest.fn(),
+ configureSession: jest.fn(),
+ });
+ });
+
+ const sdkStore = mockStore.getState().stores.sdkStore;
+ const userStore = mockStore.getState().stores.userStore;
+
+ expect(sdkStore.initialized).toBe(true);
+ expect(sdkStore.sdk).toBeDefined();
+ expect(userStore.user).toEqual({ userId: 'mockUserId' });
+ });
+
+ it('should disconnect and reset SDK correctly', async () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ act(() => {
+ result.current.actions.initSdk('mockSdk');
+ });
+
+ await act(async () => {
+ await result.current.actions.disconnect({ logger: mockLogger });
+ });
+
+ const sdkStore = mockStore.getState().stores.sdkStore;
+ const userStore = mockStore.getState().stores.userStore;
+
+ expect(sdkStore.sdk).toBeNull();
+ expect(userStore.user).toBeNull();
+ });
+
+ it('should trigger onConnected event handler after successful connection', async () => {
+ const mockOnConnected = jest.fn();
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ await act(async () => {
+ await result.current.actions.connect({
+ logger: mockLogger,
+ userId: 'mockUserId',
+ appId: 'mockAppId',
+ accessToken: 'mockAccessToken',
+ eventHandlers: {
+ connection: {
+ onConnected: mockOnConnected,
+ },
+ },
+ });
+ });
+
+ expect(mockOnConnected).toHaveBeenCalledWith({ userId: 'mockUserId' });
+ });
+
+ it('should call initSDK and setupSDK with correct parameters during connect', async () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+ const mockInitSDK = jest.requireMock('../utils').initSDK;
+ const mockSetupSDK = jest.requireMock('../utils').setupSDK;
+
+ await act(async () => {
+ await result.current.actions.connect({
+ logger: mockLogger,
+ userId: 'mockUserId',
+ appId: 'mockAppId',
+ accessToken: 'mockAccessToken',
+ sdkInitParams: {},
+ });
+ });
+
+ expect(mockInitSDK).toHaveBeenCalledWith({
+ appId: 'mockAppId',
+ customApiHost: undefined,
+ customWebSocketHost: undefined,
+ sdkInitParams: {},
+ });
+
+ expect(mockSetupSDK).toHaveBeenCalled();
+ });
+
+ it('should handle connection failure and trigger onFailed event handler', async () => {
+ const { result } = renderHook(() => useSendbird(), { wrapper });
+
+ const mockOnFailed = jest.fn();
+ const mockLogger = { error: jest.fn(), info: jest.fn() };
+
+ const mockSdk = {
+ connect: jest.fn(() => {
+ throw new Error('Mock connection error');
+ }),
+ };
+ jest.requireMock('../utils').initSDK.mockReturnValue(mockSdk);
+
+ await act(async () => {
+ await result.current.actions.connect({
+ logger: mockLogger,
+ userId: 'mockUserId',
+ appId: 'mockAppId',
+ accessToken: 'mockAccessToken',
+ eventHandlers: {
+ connection: {
+ onFailed: mockOnFailed,
+ },
+ },
+ });
+ });
+
+ const sdkStore = mockStore.getState().stores.sdkStore;
+ const userStore = mockStore.getState().stores.userStore;
+
+ expect(sdkStore.sdk).toBeNull();
+ expect(userStore.user).toBeNull();
+
+ expect(mockLogger.error).toHaveBeenCalledWith(
+ 'SendbirdProvider | useSendbird/connect failed',
+ expect.any(Error),
+ );
+
+ expect(mockOnFailed).toHaveBeenCalledWith(expect.any(Error));
+ });
+ });
+});
diff --git a/src/lib/Sendbird/__tests__/utils.spec.ts b/src/lib/Sendbird/__tests__/utils.spec.ts
new file mode 100644
index 000000000..1b0ee5ec8
--- /dev/null
+++ b/src/lib/Sendbird/__tests__/utils.spec.ts
@@ -0,0 +1,154 @@
+import SendbirdChat from '@sendbird/chat';
+
+import type { SendbirdState, SdkStore, UserStore, AppInfoStore, SendbirdStateConfig } from '../types';
+import { updateAppInfoStore, updateSdkStore, updateUserStore, initSDK, setupSDK } from '../utils';
+
+jest.mock('@sendbird/chat', () => ({
+ init: jest.fn(),
+ GroupChannelModule: jest.fn(),
+ OpenChannelModule: jest.fn(),
+ DeviceOsPlatform: {
+ MOBILE_WEB: 'mobile_web',
+ WEB: 'web',
+ },
+ SendbirdPlatform: {
+ JS: 'js',
+ },
+ SendbirdProduct: {
+ UIKIT_CHAT: 'uikit_chat',
+ },
+}));
+
+describe('State Update Functions', () => {
+ const initialState: SendbirdState = {
+ config: {
+ appId: 'testAppId',
+ } as SendbirdStateConfig,
+ stores: {
+ appInfoStore: {
+ waitingTemplateKeysMap: {},
+ messageTemplatesInfo: undefined,
+ },
+ sdkStore: {
+ error: false,
+ initialized: false,
+ loading: false,
+ sdk: {} as any,
+ },
+ userStore: {
+ initialized: false,
+ loading: false,
+ user: {} as any,
+ },
+ },
+ eventHandlers: undefined,
+ emojiManager: {} as any,
+ utils: {} as any,
+ };
+
+ test('updateAppInfoStore merges payload with existing appInfoStore', () => {
+ const payload: Partial = { messageTemplatesInfo: { templateKey: 'templateValue' } };
+ const updatedState = updateAppInfoStore(initialState, payload);
+
+ expect(updatedState.stores.appInfoStore).toEqual({
+ waitingTemplateKeysMap: {},
+ messageTemplatesInfo: { templateKey: 'templateValue' },
+ });
+ });
+
+ test('updateSdkStore merges payload with existing sdkStore', () => {
+ const payload: Partial = { initialized: true, error: true };
+ const updatedState = updateSdkStore(initialState, payload);
+
+ expect(updatedState.stores.sdkStore).toEqual({
+ error: true,
+ initialized: true,
+ loading: false,
+ sdk: {} as any,
+ });
+ });
+
+ test('updateUserStore merges payload with existing userStore', () => {
+ const payload: Partial = { initialized: true, loading: true };
+ const updatedState = updateUserStore(initialState, payload);
+
+ expect(updatedState.stores.userStore).toEqual({
+ initialized: true,
+ loading: true,
+ user: {} as any,
+ });
+ });
+});
+
+describe('initSDK', () => {
+ it('initializes SendbirdChat with required parameters', () => {
+ const params = { appId: 'testAppId' };
+ initSDK(params);
+
+ expect(SendbirdChat.init).toHaveBeenCalledWith(
+ expect.objectContaining({
+ appId: 'testAppId',
+ modules: expect.any(Array),
+ localCacheEnabled: true,
+ }),
+ );
+ });
+
+ it('includes customApiHost and customWebSocketHost if provided', () => {
+ const params = {
+ appId: 'testAppId',
+ customApiHost: 'https://custom.api',
+ customWebSocketHost: 'wss://custom.websocket',
+ };
+ initSDK(params);
+
+ expect(SendbirdChat.init).toHaveBeenCalledWith(
+ expect.objectContaining({
+ customApiHost: 'https://custom.api',
+ customWebSocketHost: 'wss://custom.websocket',
+ }),
+ );
+ });
+});
+
+const mockSdk = {
+ addExtension: jest.fn(),
+ addSendbirdExtensions: jest.fn(),
+ setSessionHandler: jest.fn(),
+};
+const mockLogger = {
+ info: jest.fn(),
+};
+
+describe('setupSDK', () => {
+ it('sets up SDK with extensions and session handler', () => {
+ const params = {
+ logger: mockLogger,
+ sessionHandler: { onSessionExpired: jest.fn() },
+ isMobile: false,
+ customExtensionParams: { customKey: 'customValue' },
+ };
+
+ setupSDK(mockSdk, params);
+
+ expect(mockLogger.info).toHaveBeenCalledWith(
+ 'SendbirdProvider | useConnect/setupConnection/setVersion',
+ expect.any(Object),
+ );
+ expect(mockSdk.addExtension).toHaveBeenCalledWith('sb_uikit', expect.any(String));
+ expect(mockSdk.addSendbirdExtensions).toHaveBeenCalledWith(
+ expect.any(Array),
+ expect.any(Object),
+ { customKey: 'customValue' },
+ );
+ expect(mockSdk.setSessionHandler).toHaveBeenCalledWith(params.sessionHandler);
+ });
+
+ it('does not set session handler if not provided', () => {
+ const params = { logger: mockLogger };
+
+ setupSDK(mockSdk, params);
+
+ expect(mockSdk.setSessionHandler).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/lib/Sendbird/context/hooks/useSendbird.tsx b/src/lib/Sendbird/context/hooks/useSendbird.tsx
index 908242fe3..34844183a 100644
--- a/src/lib/Sendbird/context/hooks/useSendbird.tsx
+++ b/src/lib/Sendbird/context/hooks/useSendbird.tsx
@@ -224,7 +224,7 @@ export const useSendbird = () => {
actions.resetUser();
logger.info?.('SendbirdProvider | useSendbird/disconnect completed');
},
- }), [store]);
+ }), [store, state.stores.appInfoStore]);
return { state, actions };
};
diff --git a/src/lib/Sendbird/index.tsx b/src/lib/Sendbird/index.tsx
index ef8d1fe57..f071aa83e 100644
--- a/src/lib/Sendbird/index.tsx
+++ b/src/lib/Sendbird/index.tsx
@@ -57,7 +57,7 @@ const withSendbirdContext = (OriginalComponent: any, mapStoreToProps: (props: an
const ContextAwareComponent = (props) => {
const { state, actions } = useSendbird();
const context = { ...state, ...actions };
- if (mapStoreToProps && typeof mapStoreToProps !== 'function') {
+ if (!mapStoreToProps || typeof mapStoreToProps !== 'function') {
// eslint-disable-next-line no-console
console.warn('Second parameter to withSendbirdContext must be a pure function');
}