From 06171dae30de1ed5d831fa3b992aabbe7c4a0979 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Thu, 31 Oct 2024 14:51:32 +0900 Subject: [PATCH 01/12] Migrate CreateChannelProvider --- .../components/CreateChannelUI/index.tsx | 14 ++-- .../components/InviteUsers/index.tsx | 20 ++--- .../components/SelectChannelType.tsx | 12 +-- .../context/CreateChannelProvider.tsx | 80 ++++++++++++++----- .../CreateChannel/context/useCreateChannel.ts | 23 ++++++ 5 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 src/modules/CreateChannel/context/useCreateChannel.ts diff --git a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx index e96258f2b..ba9cdf289 100644 --- a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx +++ b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx @@ -2,10 +2,10 @@ import './create-channel-ui.scss'; import React from 'react'; -import { useCreateChannelContext } from '../../context/CreateChannelProvider'; import InviteUsers from '../InviteUsers'; import SelectChannelType from '../SelectChannelType'; +import { useCreateChannel } from '../../context/useCreateChannel'; export interface CreateChannelUIProps { onCancel?(): void; @@ -16,10 +16,14 @@ const CreateChannel: React.FC = (props: CreateChannelUIPro const { onCancel, renderStepOne } = props; const { - step, - setStep, - userListQuery, - } = useCreateChannelContext(); + state: { + step, + userListQuery, + }, + actions: { + setStep, + }, + } = useCreateChannel(); return ( <> diff --git a/src/modules/CreateChannel/components/InviteUsers/index.tsx b/src/modules/CreateChannel/components/InviteUsers/index.tsx index 2cb75a0b3..23e51b4df 100644 --- a/src/modules/CreateChannel/components/InviteUsers/index.tsx +++ b/src/modules/CreateChannel/components/InviteUsers/index.tsx @@ -4,7 +4,6 @@ import type { GroupChannelCreateParams } from '@sendbird/chat/groupChannel'; import './invite-users.scss'; import { LocalizationContext } from '../../../../lib/LocalizationContext'; -import { useCreateChannelContext } from '../../context/CreateChannelProvider'; import useSendbirdStateContext from '../../../../hooks/useSendbirdStateContext'; import { useMediaQueryContext } from '../../../../lib/MediaQueryContext'; import Modal from '../../../../ui/Modal'; @@ -15,6 +14,7 @@ import UserListItem from '../../../../ui/UserListItem'; import { createDefaultUserListQuery, filterUser, setChannelType } from './utils'; import { noop } from '../../../../utils/utils'; import { UserListQuery } from '../../../../types'; +import { useCreateChannel } from '../../context/useCreateChannel'; export interface InviteUsersProps { onCancel?: () => void; @@ -28,14 +28,16 @@ const InviteUsers: React.FC = ({ userListQuery, }: InviteUsersProps) => { const { - onCreateChannelClick, - onBeforeCreateChannel, - onChannelCreated, - createChannel, - onCreateChannel, - overrideInviteUser, - type, - } = useCreateChannelContext(); + state: { + onCreateChannelClick, + onBeforeCreateChannel, + onChannelCreated, + createChannel, + onCreateChannel, + overrideInviteUser, + type, + }, + } = useCreateChannel(); const globalStore = useSendbirdStateContext(); const userId = globalStore?.config?.userId; diff --git a/src/modules/CreateChannel/components/SelectChannelType.tsx b/src/modules/CreateChannel/components/SelectChannelType.tsx index 9394d7667..1bceb19d3 100644 --- a/src/modules/CreateChannel/components/SelectChannelType.tsx +++ b/src/modules/CreateChannel/components/SelectChannelType.tsx @@ -3,8 +3,6 @@ import React, { useContext } from 'react'; import * as sendbirdSelectors from '../../../lib/selectors'; import useSendbirdStateContext from '../../../hooks/useSendbirdStateContext'; -import { useCreateChannelContext } from '../context/CreateChannelProvider'; - import { LocalizationContext } from '../../../lib/LocalizationContext'; import Label, { LabelColors, LabelTypography } from '../../../ui/Label'; import Icon, { IconTypes, IconColors } from '../../../ui/Icon'; @@ -16,6 +14,7 @@ import { isSuperGroupChannelEnabled, } from '../utils'; import { CHANNEL_TYPE } from '../types'; +import { useCreateChannel } from '../context/useCreateChannel'; export interface SelectChannelTypeProps { onCancel?(): void; @@ -27,11 +26,12 @@ const SelectChannelType: React.FC = (props: SelectChanne const sdk = sendbirdSelectors.getSdk(store); - const createChannelProps = useCreateChannelContext(); const { - setStep, - setType, - } = createChannelProps; + actions: { + setStep, + setType, + }, + } = useCreateChannel(); const { stringSet } = useContext(LocalizationContext); diff --git a/src/modules/CreateChannel/context/CreateChannelProvider.tsx b/src/modules/CreateChannel/context/CreateChannelProvider.tsx index 94bb70028..24f8eea17 100644 --- a/src/modules/CreateChannel/context/CreateChannelProvider.tsx +++ b/src/modules/CreateChannel/context/CreateChannelProvider.tsx @@ -1,16 +1,32 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import { User } from '@sendbird/chat'; import type { GroupChannel, GroupChannelCreateParams, } from '@sendbird/chat/groupChannel'; -import { getCreateGroupChannel } from '../../../lib/selectors'; import useSendbirdStateContext from '../../../hooks/useSendbirdStateContext'; import { CHANNEL_TYPE } from '../types'; import { SendbirdChatType } from '../../../lib/types'; - -const CreateChannelContext = React.createContext(null); +import { createStore } from '../../../utils/storeManager'; +import { useStore } from '../../../hooks/useStore'; + +const CreateChannelContext = React.createContext> | null>(null); + +const initialState = { + sdk: undefined, + createChannel: undefined, + userListQuery: undefined, + onCreateChannelClick: undefined, + onChannelCreated: undefined, + onBeforeCreateChannel: undefined, + step: 0, + setStep: () => {}, + type: CHANNEL_TYPE.GROUP, + setType: () => {}, + onCreateChannel: undefined, + overrideInviteUser: undefined, +}; export interface UserListQuery { hasNext?: boolean; @@ -56,7 +72,7 @@ export interface CreateChannelProviderProps { type CreateChannel = (channelParams: GroupChannelCreateParams) => Promise; -export interface CreateChannelContextInterface { +export interface CreateChannelState { sdk: SendbirdChatType; createChannel: CreateChannel; userListQuery?(): UserListQuery; @@ -76,9 +92,7 @@ export interface CreateChannelContextInterface { onBeforeCreateChannel?(users: Array): GroupChannelCreateParams; step: number, - setStep: React.Dispatch>, type: CHANNEL_TYPE, - setType: React.Dispatch>, /** * @deprecated * Use the onChannelCreated instead @@ -91,9 +105,8 @@ export interface CreateChannelContextInterface { overrideInviteUser?(params: OverrideInviteUserType): void; } -const CreateChannelProvider: React.FC = (props: CreateChannelProviderProps) => { +const CreateChannelManager: React.FC = (props: CreateChannelProviderProps) => { const { - children, onCreateChannelClick, onBeforeCreateChannel, onChannelCreated, @@ -102,32 +115,57 @@ const CreateChannelProvider: React.FC = (props: Crea overrideInviteUser, } = props; + const { updateState } = useCreateChannelStore(); const store = useSendbirdStateContext(); const _userListQuery = userListQuery ?? store?.config?.userListQuery; - const [step, setStep] = useState(0); - const [type, setType] = useState(CHANNEL_TYPE.GROUP); - - return ( - { + updateState({ onCreateChannelClick, onBeforeCreateChannel, onChannelCreated, userListQuery: _userListQuery, - step, - setStep, - type, - setType, onCreateChannel, overrideInviteUser, - }}> + }); + }, [ + onCreateChannelClick, + onBeforeCreateChannel, + onChannelCreated, + userListQuery, + onCreateChannel, + overrideInviteUser, + _userListQuery, + ]); + + return null; +}; +const CreateChannelProvider: React.FC = (props: CreateChannelProviderProps) => { + const { children } = props; + + return ( + + + {children} + + ); +}; + +const createCreateChannelStore = () => createStore(initialState); +const InternalCreateChannelProvider: React.FC> = ({ children }) => { + const storeRef = useRef(createCreateChannelStore()); + + return ( + {children} ); }; +const useCreateChannelStore = () => { + return useStore(CreateChannelContext, state => state, initialState); +}; + const useCreateChannelContext = () => { const context = React.useContext(CreateChannelContext); if (!context) throw new Error('CreateChannelContext not found. Use within the CreateChannel module.'); diff --git a/src/modules/CreateChannel/context/useCreateChannel.ts b/src/modules/CreateChannel/context/useCreateChannel.ts new file mode 100644 index 000000000..e3a8c8f33 --- /dev/null +++ b/src/modules/CreateChannel/context/useCreateChannel.ts @@ -0,0 +1,23 @@ +import { useSyncExternalStore } from 'use-sync-external-store/shim'; +import { useMemo } from 'react'; +import { CreateChannelState, useCreateChannelContext } from './CreateChannelProvider'; +import { CHANNEL_TYPE } from '../types'; + +export const useCreateChannel = () => { + const store = useCreateChannelContext(); + if (!store) throw new Error('useCreateChannel must be used within a CreateChannelProvider'); + + const state: CreateChannelState = useSyncExternalStore(store.subscribe, store.getState); + const actions = useMemo(() => ({ + setStep: (step: number) => store.setState(state => ({ + ...state, + step, + })), + setType: (type: CHANNEL_TYPE) => store.setState(state => ({ + ...state, + type, + })), + }), [store]); + + return { state, actions }; +}; From 0b35de89157c4e8a6ab967c8ccb76367a1d94000 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Fri, 1 Nov 2024 16:27:23 +0900 Subject: [PATCH 02/12] Add test cases --- .../CreateChannelUI.integration.test.tsx | 113 +++++++++++++++++ .../context/CreateChannelProvider.tsx | 2 - .../__tests__/CreateChannelProvider.tsx | 120 ++++++++++++++++++ .../__tests__/useCreateChannel.spec.tsx | 76 +++++++++++ .../CreateChannel/context/useCreateChannel.ts | 3 + src/ui/Modal/index.tsx | 2 +- 6 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx create mode 100644 src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx create mode 100644 src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx diff --git a/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx b/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx new file mode 100644 index 000000000..7dcd88878 --- /dev/null +++ b/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import * as useCreateChannelModule from '../../../context/useCreateChannel'; +import { CHANNEL_TYPE } from '../../../types'; +import { act, render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { LocalizationContext } from '../../../../../lib/LocalizationContext'; +import CreateChannelUI from '../index'; + +jest.mock('../../../../../hooks/useSendbirdStateContext', () => ({ + __esModule: true, + default: jest.fn(() => ({ + stores: { + userStore: { + user: { + userId: ' test-user-id', + }, + }, + sdkStore: { + sdk: { + currentUser: { + userId: 'test-user-id', + }, + createApplicationUserListQuery: () => ({ + next: () => Promise.resolve([{ userId: 'test-user-id' }]), + isLoading: false, + }), + }, + initialized: true, + }, + }, + config: { + logger: console, + userId: 'test-user-id', + groupChannel: { + enableMention: true, + }, + isOnline: true, + }, + })), +})); +jest.mock('../../../context/useCreateChannel'); + +const mockStringSet = { + MODAL__CREATE_CHANNEL__TITLE: 'CREATE_CHANNEL', + MODAL__INVITE_MEMBER__SELECTED: 'USERS_SELECTED', +}; + +const mockLocalizationContext = { + stringSet: mockStringSet, +}; + +const defaultMockState = { + sdk: undefined, + createChannel: undefined, + userListQuery: undefined, + onCreateChannelClick: undefined, + onChannelCreated: undefined, + onBeforeCreateChannel: undefined, + step: 0, + type: CHANNEL_TYPE.GROUP, + onCreateChannel: undefined, + overrideInviteUser: undefined, +}; + +const defaultMockActions = { + setStep: jest.fn(), + setType: jest.fn(), +}; + +describe('CreateChannelUI Integration Tests', () => { + const mockUseMessageSearch = useCreateChannelModule.default as jest.Mock; + + const renderComponent = (mockState = {}, mockActions = {}) => { + mockUseMessageSearch.mockReturnValue({ + state: { ...defaultMockState, ...mockState }, + actions: { ...defaultMockActions, ...mockActions }, + }); + + return render( + + + , + ); + }; + + beforeEach(() => { + jest.clearAllMocks(); + document.body.innerHTML = ` +
+ `; + }); + + it('display initial state correctly', () => { + renderComponent(); + + expect(screen.getByText('CREATE_CHANNEL')).toBeInTheDocument(); + }); + + it('display SelectChannelType when step is 0', () => { + renderComponent({ step: 0 }); + + expect(screen.getByText('CREATE_CHANNEL')).toBeInTheDocument(); + }); + + it('display InviteUsers when step is 1', async () => { + await act(async () => { + renderComponent({ step: 1 }); + }); + + expect(screen.getByText('0 USERS_SELECTED')).toBeInTheDocument(); + }); + +}); diff --git a/src/modules/CreateChannel/context/CreateChannelProvider.tsx b/src/modules/CreateChannel/context/CreateChannelProvider.tsx index 24f8eea17..d975c3aaf 100644 --- a/src/modules/CreateChannel/context/CreateChannelProvider.tsx +++ b/src/modules/CreateChannel/context/CreateChannelProvider.tsx @@ -21,9 +21,7 @@ const initialState = { onChannelCreated: undefined, onBeforeCreateChannel: undefined, step: 0, - setStep: () => {}, type: CHANNEL_TYPE.GROUP, - setType: () => {}, onCreateChannel: undefined, overrideInviteUser: undefined, }; diff --git a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx new file mode 100644 index 000000000..80b60589b --- /dev/null +++ b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { act, waitFor } from '@testing-library/react'; +import { CreateChannelProvider, useCreateChannelContext, UserListQuery } from '../CreateChannelProvider'; +import { CHANNEL_TYPE } from '../../types'; +import { useCreateChannel } from '../useCreateChannel'; +import { renderHook } from '@testing-library/react-hooks'; + +jest.mock('../../../../hooks/useSendbirdStateContext', () => ({ + __esModule: true, + default: jest.fn(() => ({ + stores: { + sdkStore: { + sdk: { + currentUser: { + userId: 'test-user-id', + }, + }, + initialized: true, + }, + }, + config: { logger: console }, + })), +})); + +describe('CreateChannelProvider', () => { + const initialState = { + sdk: undefined, + createChannel: undefined, + userListQuery: undefined, + onCreateChannelClick: undefined, + onChannelCreated: () => {}, + onBeforeCreateChannel: undefined, + step: 0, + type: CHANNEL_TYPE.GROUP, + onCreateChannel: undefined, + overrideInviteUser: undefined, + }; + + it('provide the correct initial state', () => { + const wrapper = ({ children }) => ( + jest.fn()}> + {children} + + ); + + const { result } = renderHook(() => useCreateChannelContext(), { wrapper }); + + expect(result.current.getState()).toMatchObject(initialState); + }); + + it('update state correctly', async () => { + const wrapper = ({ children }) => ( + jest.fn()}> + {children} + + ); + + const { result } = renderHook(() => useCreateChannelContext(), { wrapper }); + expect(result.current.getState().userListQuery).toEqual(undefined); + + const userListQuery = { hasNext: true, next: () => jest.fn(), isLoading: false } as unknown as UserListQuery; + + await act(async () => { + result.current.setState({ userListQuery: () => userListQuery }); + await waitFor(() => { + const newState = result.current.getState(); + expect(newState.userListQuery()).toEqual(userListQuery); + }); + }); + }); + + it('provides correct actions through useCreateChannel hook', () => { + const wrapper = ({ children }) => ( + jest.fn()}> + {children} + + ); + + const { result } = renderHook(() => useCreateChannel(), { wrapper }); + + expect(result.current.actions).toHaveProperty('setStep'); + expect(result.current.actions).toHaveProperty('setType'); + }); + + it('update state correctly when setStep is called', async () => { + const wrapper = ({ children }) => ( + jest.fn()}> + {children} + + ); + + const { result } = renderHook(() => useCreateChannel(), { wrapper }); + await act(async () => { + result.current.actions.setStep(1); + await waitFor(() => { + const updatedState = result.current.state; + expect(updatedState.step).toEqual(1); + }); + }); + }); + + it('update state correctly when setType is called', async () => { + const wrapper = ({ children }) => ( + jest.fn()}> + {children} + + ); + + const { result } = renderHook(() => useCreateChannel(), { wrapper }); + + await act(async () => { + result.current.actions.setType(CHANNEL_TYPE.BROADCAST); + await waitFor(() => { + const updatedState = result.current.state; + expect(updatedState.type).toEqual(CHANNEL_TYPE.BROADCAST); + }); + }); + }); + +}); diff --git a/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx new file mode 100644 index 000000000..392143200 --- /dev/null +++ b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx @@ -0,0 +1,76 @@ +import React from 'react'; + +import { CHANNEL_TYPE } from '../../types'; +import { CreateChannelProvider, useCreateChannelContext } from '../CreateChannelProvider'; +import { renderHook } from '@testing-library/react'; +import { useCreateChannel } from '../useCreateChannel'; + +jest.mock('../../../../hooks/useSendbirdStateContext', () => ({ + __esModule: true, + default: jest.fn(() => ({ + stores: { + sdkStore: { + sdk: { + currentUser: { + userId: 'test-user-id', + }, + }, + initialized: true, + }, + }, + config: { logger: console }, + })), +})); + +jest.mock('../CreateChannelProvider', () => ({ + ...jest.requireActual('../CreateChannelProvider'), + useCreateChannelContext: jest.fn(), +})); + +const initialState = { + sdk: undefined, + createChannel: undefined, + userListQuery: undefined, + onCreateChannelClick: undefined, + onChannelCreated: undefined, + onBeforeCreateChannel: undefined, + step: 0, + type: CHANNEL_TYPE.GROUP, + onCreateChannel: undefined, + overrideInviteUser: undefined, +}; + +const mockStore = { + getState: jest.fn(() => initialState), + setState: jest.fn(), + subscribe: jest.fn(() => jest.fn()), +}; + +const wrapper = ({ children }) => ( + jest.fn()}> + {children} + +); + +describe('useCreateChannel', () => { + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('throws an error if used outside of GroupChannelListProvider', () => { + (useCreateChannelContext as jest.Mock).mockReturnValue(null); + + expect(() => { + renderHook(() => useCreateChannel(), { wrapper }); + }).toThrow(new Error('useCreateChannel must be used within a CreateChannelProvider')); + }); + + it('provide the correct initial state', () => { + (useCreateChannelContext as jest.Mock).mockReturnValue(mockStore); + const { result } = renderHook(() => useCreateChannel(), { wrapper }); + + expect(result.current.state).toEqual(expect.objectContaining(initialState)); + }); + +}); diff --git a/src/modules/CreateChannel/context/useCreateChannel.ts b/src/modules/CreateChannel/context/useCreateChannel.ts index e3a8c8f33..02fc723a7 100644 --- a/src/modules/CreateChannel/context/useCreateChannel.ts +++ b/src/modules/CreateChannel/context/useCreateChannel.ts @@ -13,6 +13,7 @@ export const useCreateChannel = () => { ...state, step, })), + setType: (type: CHANNEL_TYPE) => store.setState(state => ({ ...state, type, @@ -21,3 +22,5 @@ export const useCreateChannel = () => { return { state, actions }; }; + +export default useCreateChannel; diff --git a/src/ui/Modal/index.tsx b/src/ui/Modal/index.tsx index 45482746e..03aeb8ba6 100644 --- a/src/ui/Modal/index.tsx +++ b/src/ui/Modal/index.tsx @@ -12,8 +12,8 @@ import IconButton from '../IconButton'; import Button, { ButtonTypes } from '../Button'; import Icon, { IconTypes, IconColors } from '../Icon'; import Label, { LabelTypography, LabelColors } from '../Label'; -import { useSendbirdStateContext } from '../../lib/Sendbird'; import uuidv4 from '../../utils/uuid'; +import useSendbirdStateContext from '../../hooks/useSendbirdStateContext'; export interface ModalHeaderProps { titleText: string; From 96cd60a39b3210575f471e99b902bd9d7e13c851 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Tue, 5 Nov 2024 17:11:19 +0900 Subject: [PATCH 03/12] Fix test error --- .../context/__tests__/CreateChannelProvider.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx index 80b60589b..f00063f92 100644 --- a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx +++ b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx @@ -28,7 +28,7 @@ describe('CreateChannelProvider', () => { createChannel: undefined, userListQuery: undefined, onCreateChannelClick: undefined, - onChannelCreated: () => {}, + onChannelCreated: expect.any(Function), onBeforeCreateChannel: undefined, step: 0, type: CHANNEL_TYPE.GROUP, @@ -38,19 +38,19 @@ describe('CreateChannelProvider', () => { it('provide the correct initial state', () => { const wrapper = ({ children }) => ( - jest.fn()}> + {children} ); const { result } = renderHook(() => useCreateChannelContext(), { wrapper }); - expect(result.current.getState()).toMatchObject(initialState); + expect(result.current.getState()).toEqual(initialState); }); it('update state correctly', async () => { const wrapper = ({ children }) => ( - jest.fn()}> + {children} ); @@ -71,7 +71,7 @@ describe('CreateChannelProvider', () => { it('provides correct actions through useCreateChannel hook', () => { const wrapper = ({ children }) => ( - jest.fn()}> + {children} ); @@ -84,7 +84,7 @@ describe('CreateChannelProvider', () => { it('update state correctly when setStep is called', async () => { const wrapper = ({ children }) => ( - jest.fn()}> + {children} ); @@ -101,7 +101,7 @@ describe('CreateChannelProvider', () => { it('update state correctly when setType is called', async () => { const wrapper = ({ children }) => ( - jest.fn()}> + {children} ); From b860a4f1437ba003c2281dfdcdaf358cbea91eaf Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Tue, 5 Nov 2024 17:26:21 +0900 Subject: [PATCH 04/12] Fix test error --- .../InviteUsers/__tests__/index.spec.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx b/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx index 7cafa9820..5165ef36b 100644 --- a/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx +++ b/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx @@ -6,14 +6,22 @@ import InviteUsers from '../index'; import { ApplicationUserListQuery } from '@sendbird/chat'; import { SendbirdSdkContext } from '../../../../../lib/SendbirdSdkContext'; import { SendBirdState } from '../../../../../lib/types'; +import { CreateChannelProvider } from '../../../context/CreateChannelProvider'; -jest.mock('../../../context/CreateChannelProvider', () => ({ - useCreateChannelContext: jest.fn(() => ({ - onBeforeCreateChannel: jest.fn(), - onCreateChannel: jest.fn(), - overrideInviteUser: jest.fn(), - createChannel: jest.fn().mockResolvedValue({}), - type: 'group', +jest.mock('../../../../../hooks/useSendbirdStateContext', () => ({ + __esModule: true, + default: jest.fn(() => ({ + stores: { + sdkStore: { + sdk: { + currentUser: { + userId: 'test-user-id', + }, + }, + initialized: true, + }, + }, + config: { logger: console }, })), })); @@ -33,9 +41,11 @@ describe('InviteUsers', () => { ); render( + , + , ); expect(await screen.findByText('Create')).toBeEnabled(); From fd5d01093f41aedf8dc847cde5515f089445106b Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Tue, 5 Nov 2024 17:29:45 +0900 Subject: [PATCH 05/12] Fix lint --- .../components/InviteUsers/__tests__/index.spec.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx b/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx index 5165ef36b..041b134ef 100644 --- a/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx +++ b/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx @@ -42,9 +42,9 @@ describe('InviteUsers', () => { render( - - - , + + + , , ); From 9aa4f4d1355635630b93a8eb1f895b3661ad0ee3 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Tue, 5 Nov 2024 17:32:11 +0900 Subject: [PATCH 06/12] Fix typo --- .../__tests__/CreateChannelUI.integration.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx b/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx index 7dcd88878..1f3ace46f 100644 --- a/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx +++ b/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx @@ -68,10 +68,10 @@ const defaultMockActions = { }; describe('CreateChannelUI Integration Tests', () => { - const mockUseMessageSearch = useCreateChannelModule.default as jest.Mock; + const mockUseCreateChannel = useCreateChannelModule.default as jest.Mock; const renderComponent = (mockState = {}, mockActions = {}) => { - mockUseMessageSearch.mockReturnValue({ + mockUseCreateChannel.mockReturnValue({ state: { ...defaultMockState, ...mockState }, actions: { ...defaultMockActions, ...mockActions }, }); From 7f38509b1b53468aac0c62f7a8b1834126c944fe Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Wed, 6 Nov 2024 16:50:30 +0900 Subject: [PATCH 07/12] Add and fix test --- .../components/CreateChannelUI/index.tsx | 2 +- .../InviteUsers/__tests__/index.spec.tsx | 71 +++++++++++++++---- .../components/InviteUsers/index.tsx | 2 +- .../components/SelectChannelType.tsx | 2 +- ...der.tsx => CreateChannelProvider.spec.tsx} | 8 ++- .../__tests__/useCreateChannel.spec.tsx | 2 +- .../CreateChannel/context/useCreateChannel.ts | 2 +- src/ui/UserListItem/index.tsx | 2 +- 8 files changed, 72 insertions(+), 19 deletions(-) rename src/modules/CreateChannel/context/__tests__/{CreateChannelProvider.tsx => CreateChannelProvider.spec.tsx} (91%) diff --git a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx index ba9cdf289..a9ed7eaed 100644 --- a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx +++ b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx @@ -5,7 +5,7 @@ import React from 'react'; import InviteUsers from '../InviteUsers'; import SelectChannelType from '../SelectChannelType'; -import { useCreateChannel } from '../../context/useCreateChannel'; +import useCreateChannel from '../../context/useCreateChannel'; export interface CreateChannelUIProps { onCancel?(): void; diff --git a/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx b/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx index 041b134ef..c16ff47bf 100644 --- a/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx +++ b/src/modules/CreateChannel/components/InviteUsers/__tests__/index.spec.tsx @@ -1,12 +1,11 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import '@testing-library/jest-dom/matchers'; import InviteUsers from '../index'; import { ApplicationUserListQuery } from '@sendbird/chat'; -import { SendbirdSdkContext } from '../../../../../lib/SendbirdSdkContext'; -import { SendBirdState } from '../../../../../lib/types'; -import { CreateChannelProvider } from '../../../context/CreateChannelProvider'; +import { CHANNEL_TYPE } from '../../../types'; +import * as useCreateChannelModule from '../../../context/useCreateChannel'; +import { LocalizationContext } from '../../../../../lib/LocalizationContext'; jest.mock('../../../../../hooks/useSendbirdStateContext', () => ({ __esModule: true, @@ -24,6 +23,7 @@ jest.mock('../../../../../hooks/useSendbirdStateContext', () => ({ config: { logger: console }, })), })); +jest.mock('../../../context/useCreateChannel'); // Mock createPortal function to render content directly without portal jest.mock('react-dom', () => ({ @@ -31,7 +31,60 @@ jest.mock('react-dom', () => ({ createPortal: (node) => node, })); +const mockStringSet = { + MODAL__CREATE_CHANNEL__TITLE: 'CREATE_CHANNEL', + MODAL__INVITE_MEMBER__SELECTED: 'USERS_SELECTED', + BUTTON__CREATE: 'CREATE', +}; + +const mockLocalizationContext = { + stringSet: mockStringSet, +}; + +const defaultMockState = { + sdk: undefined, + createChannel: undefined, + userListQuery: undefined, + onCreateChannelClick: undefined, + onChannelCreated: undefined, + onBeforeCreateChannel: undefined, + step: 0, + type: CHANNEL_TYPE.GROUP, + onCreateChannel: undefined, + overrideInviteUser: undefined, +}; + +const defaultMockActions = { + setStep: jest.fn(), + setType: jest.fn(), +}; + +const defaultMockInvitUserState = { + user: { userId: 'test-user-id' }, +}; + describe('InviteUsers', () => { + const mockUseCreateChannel = useCreateChannelModule.default as jest.Mock; + + const renderComponent = (mockState = {}, mockActions = {}, mockInviteUsersState = {}) => { + mockUseCreateChannel.mockReturnValue({ + state: { ...defaultMockState, ...mockState }, + actions: { ...defaultMockActions, ...mockActions }, + }); + + const inviteUserProps = { ...defaultMockInvitUserState, ...mockInviteUsersState }; + + return render( + + + , + ); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should enable the modal submit button when there is only the logged-in user is in the user list', async () => { const userListQuery = jest.fn( () => ({ @@ -40,15 +93,9 @@ describe('InviteUsers', () => { } as unknown as ApplicationUserListQuery), ); - render( - - - - , - , - ); + renderComponent({}, {}, { userListQuery }); - expect(await screen.findByText('Create')).toBeEnabled(); + expect(await screen.findByText('CREATE')).toBeEnabled(); }); // TODO: add this case too diff --git a/src/modules/CreateChannel/components/InviteUsers/index.tsx b/src/modules/CreateChannel/components/InviteUsers/index.tsx index 23e51b4df..3059f5e79 100644 --- a/src/modules/CreateChannel/components/InviteUsers/index.tsx +++ b/src/modules/CreateChannel/components/InviteUsers/index.tsx @@ -14,7 +14,7 @@ import UserListItem from '../../../../ui/UserListItem'; import { createDefaultUserListQuery, filterUser, setChannelType } from './utils'; import { noop } from '../../../../utils/utils'; import { UserListQuery } from '../../../../types'; -import { useCreateChannel } from '../../context/useCreateChannel'; +import useCreateChannel from '../../context/useCreateChannel'; export interface InviteUsersProps { onCancel?: () => void; diff --git a/src/modules/CreateChannel/components/SelectChannelType.tsx b/src/modules/CreateChannel/components/SelectChannelType.tsx index 1bceb19d3..fd6fa65dc 100644 --- a/src/modules/CreateChannel/components/SelectChannelType.tsx +++ b/src/modules/CreateChannel/components/SelectChannelType.tsx @@ -14,7 +14,7 @@ import { isSuperGroupChannelEnabled, } from '../utils'; import { CHANNEL_TYPE } from '../types'; -import { useCreateChannel } from '../context/useCreateChannel'; +import useCreateChannel from '../context/useCreateChannel'; export interface SelectChannelTypeProps { onCancel?(): void; diff --git a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx similarity index 91% rename from src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx rename to src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx index f00063f92..5816892a2 100644 --- a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.tsx +++ b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { act, waitFor } from '@testing-library/react'; import { CreateChannelProvider, useCreateChannelContext, UserListQuery } from '../CreateChannelProvider'; import { CHANNEL_TYPE } from '../../types'; -import { useCreateChannel } from '../useCreateChannel'; +import useCreateChannel from '../useCreateChannel'; import { renderHook } from '@testing-library/react-hooks'; jest.mock('../../../../hooks/useSendbirdStateContext', () => ({ @@ -36,6 +36,12 @@ describe('CreateChannelProvider', () => { overrideInviteUser: undefined, }; + it('throw error if useCreateChannelContext() is not used in provider', () => { + const { result } = renderHook(() => useCreateChannelContext()); + + expect(result.error).toEqual(new Error('CreateChannelContext not found. Use within the CreateChannel module.')); + }); + it('provide the correct initial state', () => { const wrapper = ({ children }) => ( diff --git a/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx index 392143200..29f2ffeaf 100644 --- a/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx +++ b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { CHANNEL_TYPE } from '../../types'; import { CreateChannelProvider, useCreateChannelContext } from '../CreateChannelProvider'; import { renderHook } from '@testing-library/react'; -import { useCreateChannel } from '../useCreateChannel'; +import useCreateChannel from '../useCreateChannel'; jest.mock('../../../../hooks/useSendbirdStateContext', () => ({ __esModule: true, diff --git a/src/modules/CreateChannel/context/useCreateChannel.ts b/src/modules/CreateChannel/context/useCreateChannel.ts index 02fc723a7..3ed0259ab 100644 --- a/src/modules/CreateChannel/context/useCreateChannel.ts +++ b/src/modules/CreateChannel/context/useCreateChannel.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { CreateChannelState, useCreateChannelContext } from './CreateChannelProvider'; import { CHANNEL_TYPE } from '../types'; -export const useCreateChannel = () => { +const useCreateChannel = () => { const store = useCreateChannelContext(); if (!store) throw new Error('useCreateChannel must be used within a CreateChannelProvider'); diff --git a/src/ui/UserListItem/index.tsx b/src/ui/UserListItem/index.tsx index f584f35f8..7eb2157f6 100644 --- a/src/ui/UserListItem/index.tsx +++ b/src/ui/UserListItem/index.tsx @@ -3,7 +3,7 @@ import type { User } from '@sendbird/chat'; import type { GroupChannel, Member } from '@sendbird/chat/groupChannel'; import './index.scss'; -import { useSendbirdStateContext } from '../../lib/Sendbird'; +import useSendbirdStateContext from '../../hooks/useSendbirdStateContext'; import { useUserProfileContext } from '../../lib/UserProfileContext'; import { useLocalization } from '../../lib/LocalizationContext'; From 370df398ce7b41613305e97f0a1d7801f54a6cb4 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Wed, 6 Nov 2024 17:20:55 +0900 Subject: [PATCH 08/12] Update useCreateChannelContext method by new convention --- .../context/CreateChannelProvider.tsx | 7 ++-- .../__tests__/CreateChannelProvider.spec.tsx | 33 ++----------------- .../__tests__/useCreateChannel.spec.tsx | 20 ++--------- .../CreateChannel/context/useCreateChannel.ts | 6 ++-- 4 files changed, 13 insertions(+), 53 deletions(-) diff --git a/src/modules/CreateChannel/context/CreateChannelProvider.tsx b/src/modules/CreateChannel/context/CreateChannelProvider.tsx index d975c3aaf..1580a9a7d 100644 --- a/src/modules/CreateChannel/context/CreateChannelProvider.tsx +++ b/src/modules/CreateChannel/context/CreateChannelProvider.tsx @@ -10,6 +10,7 @@ import { CHANNEL_TYPE } from '../types'; import { SendbirdChatType } from '../../../lib/types'; import { createStore } from '../../../utils/storeManager'; import { useStore } from '../../../hooks/useStore'; +import useCreateChannel from './useCreateChannel'; const CreateChannelContext = React.createContext> | null>(null); @@ -165,12 +166,12 @@ const useCreateChannelStore = () => { }; const useCreateChannelContext = () => { - const context = React.useContext(CreateChannelContext); - if (!context) throw new Error('CreateChannelContext not found. Use within the CreateChannel module.'); - return context; + const { state, actions } = useCreateChannel(); + return { ...state, ...actions }; }; export { CreateChannelProvider, + CreateChannelContext, useCreateChannelContext, }; diff --git a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx index 5816892a2..9616311b0 100644 --- a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx +++ b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { act, waitFor } from '@testing-library/react'; -import { CreateChannelProvider, useCreateChannelContext, UserListQuery } from '../CreateChannelProvider'; +import { CreateChannelProvider } from '../CreateChannelProvider'; import { CHANNEL_TYPE } from '../../types'; import useCreateChannel from '../useCreateChannel'; import { renderHook } from '@testing-library/react-hooks'; @@ -36,12 +36,6 @@ describe('CreateChannelProvider', () => { overrideInviteUser: undefined, }; - it('throw error if useCreateChannelContext() is not used in provider', () => { - const { result } = renderHook(() => useCreateChannelContext()); - - expect(result.error).toEqual(new Error('CreateChannelContext not found. Use within the CreateChannel module.')); - }); - it('provide the correct initial state', () => { const wrapper = ({ children }) => ( @@ -49,30 +43,9 @@ describe('CreateChannelProvider', () => { ); - const { result } = renderHook(() => useCreateChannelContext(), { wrapper }); - - expect(result.current.getState()).toEqual(initialState); - }); - - it('update state correctly', async () => { - const wrapper = ({ children }) => ( - - {children} - - ); - - const { result } = renderHook(() => useCreateChannelContext(), { wrapper }); - expect(result.current.getState().userListQuery).toEqual(undefined); - - const userListQuery = { hasNext: true, next: () => jest.fn(), isLoading: false } as unknown as UserListQuery; + const { result } = renderHook(() => useCreateChannel(), { wrapper }); - await act(async () => { - result.current.setState({ userListQuery: () => userListQuery }); - await waitFor(() => { - const newState = result.current.getState(); - expect(newState.userListQuery()).toEqual(userListQuery); - }); - }); + expect(result.current.state).toEqual(initialState); }); it('provides correct actions through useCreateChannel hook', () => { diff --git a/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx index 29f2ffeaf..60675c5ac 100644 --- a/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx +++ b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { CHANNEL_TYPE } from '../../types'; -import { CreateChannelProvider, useCreateChannelContext } from '../CreateChannelProvider'; +import { CreateChannelProvider } from '../CreateChannelProvider'; import { renderHook } from '@testing-library/react'; import useCreateChannel from '../useCreateChannel'; @@ -22,17 +22,12 @@ jest.mock('../../../../hooks/useSendbirdStateContext', () => ({ })), })); -jest.mock('../CreateChannelProvider', () => ({ - ...jest.requireActual('../CreateChannelProvider'), - useCreateChannelContext: jest.fn(), -})); - const initialState = { sdk: undefined, createChannel: undefined, userListQuery: undefined, onCreateChannelClick: undefined, - onChannelCreated: undefined, + onChannelCreated: expect.any(Function), onBeforeCreateChannel: undefined, step: 0, type: CHANNEL_TYPE.GROUP, @@ -40,12 +35,6 @@ const initialState = { overrideInviteUser: undefined, }; -const mockStore = { - getState: jest.fn(() => initialState), - setState: jest.fn(), - subscribe: jest.fn(() => jest.fn()), -}; - const wrapper = ({ children }) => ( jest.fn()}> {children} @@ -59,15 +48,12 @@ describe('useCreateChannel', () => { }); it('throws an error if used outside of GroupChannelListProvider', () => { - (useCreateChannelContext as jest.Mock).mockReturnValue(null); - expect(() => { - renderHook(() => useCreateChannel(), { wrapper }); + renderHook(() => useCreateChannel()); }).toThrow(new Error('useCreateChannel must be used within a CreateChannelProvider')); }); it('provide the correct initial state', () => { - (useCreateChannelContext as jest.Mock).mockReturnValue(mockStore); const { result } = renderHook(() => useCreateChannel(), { wrapper }); expect(result.current.state).toEqual(expect.objectContaining(initialState)); diff --git a/src/modules/CreateChannel/context/useCreateChannel.ts b/src/modules/CreateChannel/context/useCreateChannel.ts index 3ed0259ab..519d6428d 100644 --- a/src/modules/CreateChannel/context/useCreateChannel.ts +++ b/src/modules/CreateChannel/context/useCreateChannel.ts @@ -1,10 +1,10 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { useMemo } from 'react'; -import { CreateChannelState, useCreateChannelContext } from './CreateChannelProvider'; +import { useContext, useMemo } from 'react'; +import { CreateChannelContext, CreateChannelState } from './CreateChannelProvider'; import { CHANNEL_TYPE } from '../types'; const useCreateChannel = () => { - const store = useCreateChannelContext(); + const store = useContext(CreateChannelContext); if (!store) throw new Error('useCreateChannel must be used within a CreateChannelProvider'); const state: CreateChannelState = useSyncExternalStore(store.subscribe, store.getState); From 7ec8a6f67fae473df997eca35d5c1d80b82bff50 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Thu, 28 Nov 2024 13:14:07 +0900 Subject: [PATCH 09/12] Apply PR comment --- .../CreateChannel/components/CreateChannelUI/index.tsx | 6 +++--- .../CreateChannel/components/InviteUsers/index.tsx | 4 +++- .../CreateChannel/context/CreateChannelProvider.tsx | 8 ++------ .../context/__tests__/CreateChannelProvider.spec.tsx | 5 ++--- src/modules/CreateChannel/context/useCreateChannel.ts | 5 +++++ 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx index a9ed7eaed..745d70374 100644 --- a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx +++ b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx @@ -17,7 +17,7 @@ const CreateChannel: React.FC = (props: CreateChannelUIPro const { state: { - step, + pageStep, userListQuery, }, actions: { @@ -28,7 +28,7 @@ const CreateChannel: React.FC = (props: CreateChannelUIPro return ( <> { - step === 0 && ( + pageStep === 0 && ( renderStepOne?.() || ( = (props: CreateChannelUIPro ) } { - step === 1 && ( + pageStep === 1 && ( { diff --git a/src/modules/CreateChannel/components/InviteUsers/index.tsx b/src/modules/CreateChannel/components/InviteUsers/index.tsx index 3059f5e79..04965d726 100644 --- a/src/modules/CreateChannel/components/InviteUsers/index.tsx +++ b/src/modules/CreateChannel/components/InviteUsers/index.tsx @@ -32,11 +32,13 @@ const InviteUsers: React.FC = ({ onCreateChannelClick, onBeforeCreateChannel, onChannelCreated, - createChannel, onCreateChannel, overrideInviteUser, type, }, + actions: { + createChannel, + }, } = useCreateChannel(); const globalStore = useSendbirdStateContext(); diff --git a/src/modules/CreateChannel/context/CreateChannelProvider.tsx b/src/modules/CreateChannel/context/CreateChannelProvider.tsx index 1580a9a7d..f9d229e19 100644 --- a/src/modules/CreateChannel/context/CreateChannelProvider.tsx +++ b/src/modules/CreateChannel/context/CreateChannelProvider.tsx @@ -16,12 +16,11 @@ const CreateChannelContext = React.createContext Promise; - export interface CreateChannelState { sdk: SendbirdChatType; - createChannel: CreateChannel; userListQuery?(): UserListQuery; /** @@ -90,7 +86,7 @@ export interface CreateChannelState { * */ onBeforeCreateChannel?(users: Array): GroupChannelCreateParams; - step: number, + pageStep: number, type: CHANNEL_TYPE, /** * @deprecated diff --git a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx index 9616311b0..e370b3f81 100644 --- a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx +++ b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx @@ -25,12 +25,11 @@ jest.mock('../../../../hooks/useSendbirdStateContext', () => ({ describe('CreateChannelProvider', () => { const initialState = { sdk: undefined, - createChannel: undefined, userListQuery: undefined, onCreateChannelClick: undefined, onChannelCreated: expect.any(Function), onBeforeCreateChannel: undefined, - step: 0, + pageStep: 0, type: CHANNEL_TYPE.GROUP, onCreateChannel: undefined, overrideInviteUser: undefined, @@ -73,7 +72,7 @@ describe('CreateChannelProvider', () => { result.current.actions.setStep(1); await waitFor(() => { const updatedState = result.current.state; - expect(updatedState.step).toEqual(1); + expect(updatedState.pageStep).toEqual(1); }); }); }); diff --git a/src/modules/CreateChannel/context/useCreateChannel.ts b/src/modules/CreateChannel/context/useCreateChannel.ts index 519d6428d..3b634ce80 100644 --- a/src/modules/CreateChannel/context/useCreateChannel.ts +++ b/src/modules/CreateChannel/context/useCreateChannel.ts @@ -2,9 +2,12 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { useContext, useMemo } from 'react'; import { CreateChannelContext, CreateChannelState } from './CreateChannelProvider'; import { CHANNEL_TYPE } from '../types'; +import { getCreateGroupChannel } from '../../../lib/selectors'; +import { useSendbirdStateContext } from '../../../index'; const useCreateChannel = () => { const store = useContext(CreateChannelContext); + const sendbirdStore = useSendbirdStateContext(); if (!store) throw new Error('useCreateChannel must be used within a CreateChannelProvider'); const state: CreateChannelState = useSyncExternalStore(store.subscribe, store.getState); @@ -18,6 +21,8 @@ const useCreateChannel = () => { ...state, type, })), + + createChannel: getCreateGroupChannel(sendbirdStore), }), [store]); return { state, actions }; From c6e6c8d699cf9ae4421ee7e7a596c499b69d7d11 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Thu, 28 Nov 2024 13:32:08 +0900 Subject: [PATCH 10/12] Fix tests --- .../components/CreateChannelUI/index.tsx | 4 ++-- .../CreateChannel/components/SelectChannelType.tsx | 14 +++++++------- .../__tests__/CreateChannelProvider.spec.tsx | 2 +- .../context/__tests__/useCreateChannel.spec.tsx | 3 +-- .../CreateChannel/context/useCreateChannel.ts | 4 ++-- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx index 745d70374..8469efc7f 100644 --- a/src/modules/CreateChannel/components/CreateChannelUI/index.tsx +++ b/src/modules/CreateChannel/components/CreateChannelUI/index.tsx @@ -21,7 +21,7 @@ const CreateChannel: React.FC = (props: CreateChannelUIPro userListQuery, }, actions: { - setStep, + setPageStep, }, } = useCreateChannel(); @@ -41,7 +41,7 @@ const CreateChannel: React.FC = (props: CreateChannelUIPro { - setStep(0); + setPageStep(0); onCancel?.(); }} /> diff --git a/src/modules/CreateChannel/components/SelectChannelType.tsx b/src/modules/CreateChannel/components/SelectChannelType.tsx index fd6fa65dc..9414aa979 100644 --- a/src/modules/CreateChannel/components/SelectChannelType.tsx +++ b/src/modules/CreateChannel/components/SelectChannelType.tsx @@ -28,7 +28,7 @@ const SelectChannelType: React.FC = (props: SelectChanne const { actions: { - setStep, + setPageStep, setType, }, } = useCreateChannel(); @@ -50,13 +50,13 @@ const SelectChannelType: React.FC = (props: SelectChanne className="sendbird-add-channel__rectangle" onClick={() => { setType(CHANNEL_TYPE.GROUP); - setStep(1); + setPageStep(1); }} role="button" tabIndex={0} onKeyDown={() => { setType(CHANNEL_TYPE.GROUP); - setStep(1); + setPageStep(1); }} > = (props: SelectChanne className="sendbird-add-channel__rectangle" onClick={() => { setType(CHANNEL_TYPE.SUPERGROUP); - setStep(1); + setPageStep(1); }} role="button" tabIndex={0} onKeyDown={() => { setType(CHANNEL_TYPE.SUPERGROUP); - setStep(1); + setPageStep(1); }} > = (props: SelectChanne className="sendbird-add-channel__rectangle" onClick={() => { setType(CHANNEL_TYPE.BROADCAST); - setStep(1); + setPageStep(1); }} role="button" tabIndex={0} onKeyDown={() => { setType(CHANNEL_TYPE.BROADCAST); - setStep(1); + setPageStep(1); }} > { const { result } = renderHook(() => useCreateChannel(), { wrapper }); await act(async () => { - result.current.actions.setStep(1); + result.current.actions.setPageStep(1); await waitFor(() => { const updatedState = result.current.state; expect(updatedState.pageStep).toEqual(1); diff --git a/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx index 60675c5ac..4a479958b 100644 --- a/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx +++ b/src/modules/CreateChannel/context/__tests__/useCreateChannel.spec.tsx @@ -24,12 +24,11 @@ jest.mock('../../../../hooks/useSendbirdStateContext', () => ({ const initialState = { sdk: undefined, - createChannel: undefined, userListQuery: undefined, onCreateChannelClick: undefined, onChannelCreated: expect.any(Function), onBeforeCreateChannel: undefined, - step: 0, + pageStep: 0, type: CHANNEL_TYPE.GROUP, onCreateChannel: undefined, overrideInviteUser: undefined, diff --git a/src/modules/CreateChannel/context/useCreateChannel.ts b/src/modules/CreateChannel/context/useCreateChannel.ts index 3b634ce80..b65d42a80 100644 --- a/src/modules/CreateChannel/context/useCreateChannel.ts +++ b/src/modules/CreateChannel/context/useCreateChannel.ts @@ -12,9 +12,9 @@ const useCreateChannel = () => { const state: CreateChannelState = useSyncExternalStore(store.subscribe, store.getState); const actions = useMemo(() => ({ - setStep: (step: number) => store.setState(state => ({ + setPageStep: (pageStep: number) => store.setState(state => ({ ...state, - step, + pageStep, })), setType: (type: CHANNEL_TYPE) => store.setState(state => ({ From 09b20b99e3d1dbcad440106deffbbd89a11c0af5 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Thu, 28 Nov 2024 14:04:19 +0900 Subject: [PATCH 11/12] Fix test --- .../__tests__/CreateChannelUI.integration.test.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx b/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx index 1f3ace46f..37e0dc87a 100644 --- a/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx +++ b/src/modules/CreateChannel/components/CreateChannelUI/__tests__/CreateChannelUI.integration.test.tsx @@ -51,19 +51,18 @@ const mockLocalizationContext = { const defaultMockState = { sdk: undefined, - createChannel: undefined, userListQuery: undefined, onCreateChannelClick: undefined, onChannelCreated: undefined, onBeforeCreateChannel: undefined, - step: 0, + pageStep: 0, type: CHANNEL_TYPE.GROUP, onCreateChannel: undefined, overrideInviteUser: undefined, }; const defaultMockActions = { - setStep: jest.fn(), + setPageStep: jest.fn(), setType: jest.fn(), }; @@ -96,15 +95,15 @@ describe('CreateChannelUI Integration Tests', () => { expect(screen.getByText('CREATE_CHANNEL')).toBeInTheDocument(); }); - it('display SelectChannelType when step is 0', () => { - renderComponent({ step: 0 }); + it('display SelectChannelType when pageStep is 0', () => { + renderComponent({ pageStep: 0 }); expect(screen.getByText('CREATE_CHANNEL')).toBeInTheDocument(); }); - it('display InviteUsers when step is 1', async () => { + it('display InviteUsers when pageStep is 1', async () => { await act(async () => { - renderComponent({ step: 1 }); + renderComponent({ pageStep: 1 }); }); expect(screen.getByText('0 USERS_SELECTED')).toBeInTheDocument(); From 24f23cb81af84b048495491ff0f29505576dafd8 Mon Sep 17 00:00:00 2001 From: "junyoung.lim" Date: Thu, 28 Nov 2024 14:13:07 +0900 Subject: [PATCH 12/12] Fix test --- .../context/__tests__/CreateChannelProvider.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx index 9aef9f3cf..0be826a28 100644 --- a/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx +++ b/src/modules/CreateChannel/context/__tests__/CreateChannelProvider.spec.tsx @@ -56,11 +56,11 @@ describe('CreateChannelProvider', () => { const { result } = renderHook(() => useCreateChannel(), { wrapper }); - expect(result.current.actions).toHaveProperty('setStep'); + expect(result.current.actions).toHaveProperty('setPageStep'); expect(result.current.actions).toHaveProperty('setType'); }); - it('update state correctly when setStep is called', async () => { + it('update state correctly when setPageStep is called', async () => { const wrapper = ({ children }) => ( {children}