Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CLNP-5045] CreateChannelProvider Migration #1243

Open
wants to merge 9 commits into
base: feat/state-mgmt-migration-1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 mockUseCreateChannel = useCreateChannelModule.default as jest.Mock;

const renderComponent = (mockState = {}, mockActions = {}) => {
mockUseCreateChannel.mockReturnValue({
state: { ...defaultMockState, ...mockState },
actions: { ...defaultMockActions, ...mockActions },
});

return render(
<LocalizationContext.Provider value={mockLocalizationContext as any}>
<CreateChannelUI />
</LocalizationContext.Provider>,
);
};

beforeEach(() => {
jest.clearAllMocks();
document.body.innerHTML = `
<div id='sendbird-modal-root' />
`;
});

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 });
});
Comment on lines +106 to +108
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await act(async () => {
renderComponent({ step: 1 });
});
await act(() => {
renderComponent({ step: 1 });
});

Don't need async here since there's no usage of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If async or act is deleted, the error occurs.

Warning: An update to InviteUsers inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):

The test still pass even when the act is deleted; but I added act and async to prevent the error.


expect(screen.getByText('0 USERS_SELECTED')).toBeInTheDocument();
});

});
14 changes: 9 additions & 5 deletions src/modules/CreateChannel/components/CreateChannelUI/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,10 +16,14 @@ const CreateChannel: React.FC<CreateChannelUIProps> = (props: CreateChannelUIPro
const { onCancel, renderStepOne } = props;

const {
step,
setStep,
userListQuery,
} = useCreateChannelContext();
state: {
step,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단순 궁금증인데 step 이게 어떤걸 의미하는건가요? 코드를 보니 값이 매직넘버인것같아서 의미가 조금 명확해지도록 개선하면 좋을것 같습니다.

Copy link
Collaborator

@HoonBaek HoonBaek Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
image

채널 생성화면에서의 스텝을 말하는 것 같습니다.
(1) 생성할 채널 타입 선택
(2) 초대할 유저 선택

저도 동의합니다!

userListQuery,
},
actions: {
setStep,
},
} = useCreateChannel();

return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,90 @@
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';

jest.mock('../../../context/CreateChannelProvider', () => ({
useCreateChannelContext: jest.fn(() => ({
onBeforeCreateChannel: jest.fn(),
onCreateChannel: jest.fn(),
overrideInviteUser: jest.fn(),
createChannel: jest.fn().mockResolvedValue({}),
type: 'group',
import { CHANNEL_TYPE } from '../../../types';
import * as useCreateChannelModule from '../../../context/useCreateChannel';
import { LocalizationContext } from '../../../../../lib/LocalizationContext';

jest.mock('../../../../../hooks/useSendbirdStateContext', () => ({
__esModule: true,
default: jest.fn(() => ({
stores: {
sdkStore: {
sdk: {
currentUser: {
userId: 'test-user-id',
},
},
initialized: true,
},
},
config: { logger: console },
})),
}));
jest.mock('../../../context/useCreateChannel');

// Mock createPortal function to render content directly without portal
jest.mock('react-dom', () => ({
...jest.requireActual('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(
<LocalizationContext.Provider value={mockLocalizationContext as any}>
<InviteUsers {...inviteUserProps}/>
</LocalizationContext.Provider>,
);
};

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(
() => ({
Expand All @@ -32,13 +93,9 @@ describe('InviteUsers', () => {
} as unknown as ApplicationUserListQuery),
);

render(
<SendbirdSdkContext.Provider value={{} as SendBirdState}>
<InviteUsers userListQuery={userListQuery} />
</SendbirdSdkContext.Provider>,
);
renderComponent({}, {}, { userListQuery });

expect(await screen.findByText('Create')).toBeEnabled();
expect(await screen.findByText('CREATE')).toBeEnabled();
});

// TODO: add this case too
Expand Down
20 changes: 11 additions & 9 deletions src/modules/CreateChannel/components/InviteUsers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -28,14 +28,16 @@ const InviteUsers: React.FC<InviteUsersProps> = ({
userListQuery,
}: InviteUsersProps) => {
const {
onCreateChannelClick,
onBeforeCreateChannel,
onChannelCreated,
createChannel,
onCreateChannel,
overrideInviteUser,
type,
} = useCreateChannelContext();
state: {
onCreateChannelClick,
onBeforeCreateChannel,
onChannelCreated,
createChannel,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createChannel 은 하는 역할이 action 에 들어가는게 좀 더 자연스러울 것 같아요 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 createChannel을 어디에 넣어야하나 조금 고민을 했는데, createChannel이 CreateChannelProvider에서 관리하고 있는 어떠한 state에도 영향을 끼치지 않아서 저희 룰에 따라 state로 넣었습니다. 그럼에도 action에 들어가는 게 더 나을까요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

당장 꺼내서 써야할 때 state vs action 둘중 어디에있을까 추측해야하는 상황이라면 저는 action 에 있을거라 예상하게 될거같아요. 그래서 좀 더 나은 사용성 측면으로는 action 에 들어있는게 자연스러울거같긴합니다!

onCreateChannel,
overrideInviteUser,
type,
},
} = useCreateChannel();

const globalStore = useSendbirdStateContext();
const userId = globalStore?.config?.userId;
Expand Down
12 changes: 6 additions & 6 deletions src/modules/CreateChannel/components/SelectChannelType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -16,6 +14,7 @@ import {
isSuperGroupChannelEnabled,
} from '../utils';
import { CHANNEL_TYPE } from '../types';
import useCreateChannel from '../context/useCreateChannel';

export interface SelectChannelTypeProps {
onCancel?(): void;
Expand All @@ -27,11 +26,12 @@ const SelectChannelType: React.FC<SelectChannelTypeProps> = (props: SelectChanne

const sdk = sendbirdSelectors.getSdk(store);

const createChannelProps = useCreateChannelContext();
const {
setStep,
setType,
} = createChannelProps;
actions: {
setStep,
setType,
},
} = useCreateChannel();

const { stringSet } = useContext(LocalizationContext);

Expand Down
Loading