Skip to content

Commit

Permalink
Add basic test cases for the new ThreadProvider (#1274)
Browse files Browse the repository at this point in the history
### Changelog
This PR will add the test cases for the new `ThreadProvider` and its
related hooks.
#### Before
<img width="1163" alt="스크린샷 2024-12-09 오전 11 29 53"
src="https://github.com/user-attachments/assets/40ffc29e-0774-4ac7-973d-15af55bc88fd">

#### After
<img width="1199" alt="스크린샷 2024-12-09 오전 11 28 39"
src="https://github.com/user-attachments/assets/ea3366c0-2f11-4e1f-af6f-a423d006ab42">

---------

Co-authored-by: Irene Ryu <[email protected]>
  • Loading branch information
git-babel and AhyoungRyu authored Dec 9, 2024
1 parent 5b35b02 commit b9e7602
Show file tree
Hide file tree
Showing 22 changed files with 3,817 additions and 204 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,392 @@
import * as useThreadModule from '../../../context/useThread';
import { ChannelStateTypes, ParentMessageStateTypes, ThreadListStateTypes } from '../../../types';
import { EmojiContainer } from '@sendbird/chat';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { LocalizationContext } from '../../../../../lib/LocalizationContext';
import ThreadUI from '../index';
import React from 'react';
import '@testing-library/jest-dom/extend-expect';

const mockSendUserMessage = jest.fn();

const mockChannel = {
url: 'test-channel',
members: [{ userId: 'test-user-id', nickname: 'user1' }],
updateUserMessage: jest.fn().mockImplementation(async (message) => mockNewMessage(message)),
sendUserMessage: mockSendUserMessage,
isGroupChannel: jest.fn().mockImplementation(() => true),
};

const mockNewMessage = (message) => ({
messageId: 42,
message: message ?? 'new message',
});

const mockMessage = {
messageId: 1,
message: 'first message',
};

const mockGetMessage = jest.fn().mockResolvedValue(mockMessage);
const mockGetChannel = jest.fn().mockResolvedValue(mockChannel);

const mockState = {
stores: {
sdkStore: {
sdk: {
getMessage: mockGetMessage,
groupChannel: {
getChannel: mockGetChannel,
},
},
initialized: true,
},
userStore: { user: { userId: 'test-user-id' } },
},
config: {
logger: console,
isOnline: true,
pubSub: {
publish: jest.fn(),
},
groupChannel: {
enableMention: true,
enableReactions: true,
replyType: 'THREAD',
},
},
};

jest.mock('../../../../../lib/Sendbird/context/hooks/useSendbird', () => ({
__esModule: true,
default: jest.fn(() => ({ state: mockState })),
useSendbird: jest.fn(() => ({ state: mockState })),
}));

jest.mock('../../../context/useThread');

const mockStringSet = {
DATE_FORMAT__MESSAGE_CREATED_AT: 'p',
};

const mockLocalizationContext = {
stringSet: mockStringSet,
};

const defaultMockState = {
channelUrl: '',
message: null,
onHeaderActionClick: undefined,
onMoveToParentMessage: undefined,
onBeforeSendUserMessage: undefined,
onBeforeSendFileMessage: undefined,
onBeforeSendVoiceMessage: undefined,
onBeforeSendMultipleFilesMessage: undefined,
onBeforeDownloadFileMessage: undefined,
isMultipleFilesMessageEnabled: undefined,
filterEmojiCategoryIds: undefined,
currentChannel: undefined,
allThreadMessages: [
{
messageId: 2,
message: 'threaded message 1',
isUserMessage: () => true,
},
],
localThreadMessages: [],
parentMessage: null,
channelState: ChannelStateTypes.INITIALIZED,
parentMessageState: ParentMessageStateTypes.INITIALIZED,
threadListState: ThreadListStateTypes.INITIALIZED,
hasMorePrev: false,
hasMoreNext: false,
emojiContainer: {} as EmojiContainer,
isMuted: false,
isChannelFrozen: false,
currentUserId: '',
typingMembers: [],
nicknamesMap: null,
};

const defaultMockActions = {
fetchPrevThreads: jest.fn((callback) => {
callback();
}),
fetchNextThreads: jest.fn((callback) => {
callback();
}),
};

describe('CreateChannelUI Integration Tests', () => {
const mockUseThread = useThreadModule.default as jest.Mock;

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

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

beforeEach(() => {
jest.clearAllMocks();
});

it('display initial state correctly', async () => {
await act(async () => {
renderComponent(
{
parentMessage: {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
},
},
);
});

expect(screen.getByText('parent message')).toBeInTheDocument();
expect(screen.getByText('threaded message 1')).toBeInTheDocument();
});

it('fetchPrevThread is correctly called when scroll is top', async () => {
let container;
const parentMessage = {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
getThreadedMessagesByTimestamp: () => ({
parentMessage,
threadedMessages: [
{ messageId: 3, message: 'threaded message -1', isUserMessage: () => true },
{ messageId: 4, message: 'threaded message 0', isUserMessage: () => true },
],
}),
};

await act(async () => {
const result = renderComponent(
{
parentMessage,
hasMorePrev: true,
},
);

container = result.container;
});

const scrollContainer = container.getElementsByClassName('sendbird-thread-ui--scroll')[0];
fireEvent.scroll(scrollContainer, { target: { scrollY: -1 } });

await waitFor(() => {
expect(defaultMockActions.fetchPrevThreads).toBeCalledTimes(1);
});
});

it('fetchNextThreads is correctly called when scroll is bottom', async () => {
let container;
const parentMessage = {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
getThreadedMessagesByTimestamp: () => ({
parentMessage,
threadedMessages: [
{ messageId: 3, message: 'threaded message -1', isUserMessage: () => true },
{ messageId: 4, message: 'threaded message 0', isUserMessage: () => true },
],
}),
};

await act(async () => {
const result = renderComponent(
{
parentMessage,
hasMoreNext: true,
},
);

container = result.container;
});

const scrollContainer = container.getElementsByClassName('sendbird-thread-ui--scroll')[0];
fireEvent.scroll(scrollContainer, { target: { scrollY: scrollContainer.scrollHeight + 1 } });

await waitFor(() => {
expect(defaultMockActions.fetchNextThreads).toBeCalledTimes(1);
});
});

it('show proper placeholder when ParentMessageStateTypes is NIL', async () => {
let container;
const parentMessage = {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
};

await act(async () => {
const result = renderComponent(
{
parentMessage,
parentMessageState: ParentMessageStateTypes.NIL,
},
);

container = result.container;
});

await waitFor(() => {
const placeholder = container.getElementsByClassName('placeholder-nil')[0];
expect(placeholder).not.toBe(undefined);
});

});

it('show proper placeholder when ParentMessageStateTypes is LOADING', async () => {
let container;
const parentMessage = {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
};

await act(async () => {
const result = renderComponent(
{
parentMessage,
parentMessageState: ParentMessageStateTypes.LOADING,
},
);

container = result.container;
});

await waitFor(() => {
const placeholder = container.getElementsByClassName('placeholder-loading')[0];
expect(placeholder).not.toBe(undefined);
});

});

it('show proper placeholder when ParentMessageStateTypes is INVALID', async () => {
let container;
const parentMessage = {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
};

await act(async () => {
const result = renderComponent(
{
parentMessage,
parentMessageState: ParentMessageStateTypes.INVALID,
},
);

container = result.container;
});

await waitFor(() => {
const placeholder = container.getElementsByClassName('placeholder-invalid')[0];
expect(placeholder).not.toBe(undefined);
});
});

it('show proper placeholder when ThreadListState is LOADING', async () => {
let container;
const parentMessage = {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
};

await act(async () => {
const result = renderComponent(
{
parentMessage,
threadListState: ThreadListStateTypes.LOADING,
},
);

container = result.container;
});

await waitFor(() => {
const placeholder = container.getElementsByClassName('placeholder-loading')[0];
expect(placeholder).not.toBe(undefined);
});
});

it('show proper placeholder when ThreadListState is INVALID', async () => {
let container;
const parentMessage = {
messageId: 1,
message: 'parent message',
isUserMessage: () => true,
isTextMessage: true,
createdAt: 0,
sender: {
userId: 'test-user-id',
},
};

await act(async () => {
const result = renderComponent(
{
parentMessage,
threadListState: ThreadListStateTypes.INVALID,
},
);

container = result.container;
});

await waitFor(() => {
const placeholder = container.getElementsByClassName('placeholder-invalid')[0];
expect(placeholder).not.toBe(undefined);
});
});

});
Loading

0 comments on commit b9e7602

Please sign in to comment.