Skip to content

Commit 8acfdf4

Browse files
authored
[CLNP-5043] Add unit tests for improved MessageSearch module (#1228)
https://sendbird.atlassian.net/browse/CLNP-5043 Two things are handled based on what I mentioned in https://sendbird.atlassian.net/wiki/spaces/UIKitreact/pages/2511765635/UIKit+React+new+State+Management+Method+Proposal#4.1-Unit-Test - [x] Added unit tests for `useMessageSearch` hook and new `MessageSearchProvider` - [x] Added integration tests for `MessageSearchUI` component So the MessageSearch module test coverage has been changed **from** File --------------------------------------------------| % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s <img width="814" alt="Screenshot 2024-10-08 at 2 36 55 PM" src="https://github.com/user-attachments/assets/c0fef6fe-0fc1-4f37-b74f-0486d70352a7"> **to** <img width="941" alt="after" src="https://github.com/user-attachments/assets/7fc19fb8-810c-4256-8230-3884d11e109a"> note that it used to be like 0%, but now the test coverage of the newly added files is almost 100%; green 🟩. ### Checklist Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If unsure, ask the members. This is a reminder of what we look for before merging your code. - [x] **All tests pass locally with my changes** - [x] **I have added tests that prove my fix is effective or that my feature works** - [ ] **Public components / utils / props are appropriately exported** - [ ] I have added necessary documentation (if appropriate)
1 parent 4030eda commit 8acfdf4

File tree

11 files changed

+605
-134
lines changed

11 files changed

+605
-134
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"@svgr/rollup": "^8.1.0",
101101
"@testing-library/jest-dom": "^5.16.5",
102102
"@testing-library/react": "^13.4.0",
103+
"@testing-library/react-hooks": "^8.0.1",
103104
"@testing-library/user-event": "^14.4.3",
104105
"@typescript-eslint/eslint-plugin": "^6.17.0",
105106
"@typescript-eslint/parser": "^6.17.0",
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
3+
import '@testing-library/jest-dom/extend-expect';
4+
import MessageSearchUI from '../components/MessageSearchUI';
5+
import { LocalizationContext } from '../../../lib/LocalizationContext';
6+
import * as useMessageSearchModule from '../context/hooks/useMessageSearch';
7+
8+
jest.mock('../context/hooks/useMessageSearch');
9+
10+
const mockStringSet = {
11+
SEARCH_IN: 'Search in',
12+
SEARCH_PLACEHOLDER: 'Search',
13+
SEARCHING: 'Searching...',
14+
NO_SEARCHED_MESSAGE: 'No results found',
15+
NO_TITLE: 'No title',
16+
PLACE_HOLDER__RETRY_TO_CONNECT: 'Retry',
17+
};
18+
19+
const mockLocalizationContext = {
20+
stringSet: mockStringSet,
21+
};
22+
23+
const defaultMockState = {
24+
isQueryInvalid: false,
25+
searchString: '',
26+
requestString: '',
27+
currentChannel: null,
28+
loading: false,
29+
scrollRef: { current: null },
30+
hasMoreResult: false,
31+
onScroll: jest.fn(),
32+
allMessages: [],
33+
onResultClick: jest.fn(),
34+
selectedMessageId: null,
35+
};
36+
37+
const defaultMockActions = {
38+
setSelectedMessageId: jest.fn(),
39+
handleRetryToConnect: jest.fn(),
40+
};
41+
42+
describe('MessageSearchUI Integration Tests', () => {
43+
const mockUseMessageSearch = useMessageSearchModule.default as jest.Mock;
44+
45+
const renderComponent = (mockState = {}, mockActions = {}) => {
46+
mockUseMessageSearch.mockReturnValue({
47+
state: { ...defaultMockState, ...mockState },
48+
actions: { ...defaultMockActions, ...mockActions },
49+
});
50+
51+
return render(
52+
<LocalizationContext.Provider value={mockLocalizationContext as any}>
53+
<MessageSearchUI />
54+
</LocalizationContext.Provider>,
55+
);
56+
};
57+
58+
beforeEach(() => {
59+
jest.clearAllMocks();
60+
});
61+
62+
it('renders initial state correctly', () => {
63+
renderComponent();
64+
expect(screen.getByText('Search in')).toBeInTheDocument();
65+
});
66+
67+
it('displays loading state when search is in progress', () => {
68+
renderComponent({ loading: true, searchString: 'test query', requestString: 'test query' });
69+
expect(screen.getByText(mockStringSet.SEARCHING)).toBeInTheDocument();
70+
});
71+
72+
it('displays search results when available', () => {
73+
renderComponent({
74+
allMessages: [
75+
{ messageId: 1, message: 'Message 1', sender: { nickname: 'Sender 1' } },
76+
{ messageId: 2, message: 'Message 2', sender: { nickname: 'Sender 2' } },
77+
],
78+
searchString: 'test query',
79+
});
80+
expect(screen.getByText('Message 1')).toBeInTheDocument();
81+
expect(screen.getByText('Message 2')).toBeInTheDocument();
82+
});
83+
84+
it('handles no results state', () => {
85+
renderComponent({ allMessages: [], searchString: 'no results query', requestString: 'no results query' });
86+
expect(screen.getByText(mockStringSet.NO_SEARCHED_MESSAGE)).toBeInTheDocument();
87+
});
88+
89+
it('handles error state and retry', async () => {
90+
const handleRetryToConnect = jest.fn();
91+
renderComponent(
92+
{ isQueryInvalid: true, searchString: 'error query', requestString: 'error query' },
93+
{ handleRetryToConnect },
94+
);
95+
expect(screen.getByText(mockStringSet.PLACE_HOLDER__RETRY_TO_CONNECT)).toBeInTheDocument();
96+
97+
const retryButton = screen.getByText('Retry');
98+
fireEvent.click(retryButton);
99+
100+
expect(handleRetryToConnect).toHaveBeenCalled();
101+
});
102+
103+
it('triggers loading more messages when scrolled near bottom', async () => {
104+
const onScroll = jest.fn();
105+
const loadMoreMessages = jest.fn();
106+
const { container } = renderComponent({
107+
allMessages: [{ messageId: 1, message: 'Message 1' }],
108+
hasMoreResult: true,
109+
onScroll,
110+
});
111+
112+
const scrollContainer = container.firstChild as Element;
113+
114+
// define scroll container properties
115+
Object.defineProperty(scrollContainer, 'scrollHeight', { configurable: true, value: 300 });
116+
Object.defineProperty(scrollContainer, 'clientHeight', { configurable: true, value: 500 });
117+
Object.defineProperty(scrollContainer, 'scrollTop', { configurable: true, value: 450 });
118+
119+
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
120+
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
121+
122+
if (scrollTop + clientHeight >= scrollHeight - 1) {
123+
loadMoreMessages();
124+
}
125+
};
126+
127+
fireEvent.scroll(scrollContainer);
128+
handleScroll({ currentTarget: scrollContainer } as React.UIEvent<HTMLDivElement>);
129+
130+
expect(loadMoreMessages).toHaveBeenCalled();
131+
});
132+
133+
it('handles message click', () => {
134+
const setSelectedMessageId = jest.fn();
135+
const onResultClick = jest.fn();
136+
renderComponent(
137+
{
138+
allMessages: [{ messageId: 1, message: 'Message 1', sender: { nickname: 'Sender 1' } }],
139+
searchString: 'Message 1',
140+
onResultClick,
141+
},
142+
{ setSelectedMessageId },
143+
);
144+
145+
const message = screen.getByText('Message 1');
146+
fireEvent.click(message);
147+
148+
expect(setSelectedMessageId).toHaveBeenCalledWith(1);
149+
expect(onResultClick).toHaveBeenCalled();
150+
});
151+
});

src/modules/MessageSearch/components/MessageSearchUI/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const MessageSearchUI: React.FC<MessageSearchUIProps> = ({
3535
}: MessageSearchUIProps) => {
3636
const {
3737
state: {
38-
isInvalid,
38+
isQueryInvalid,
3939
searchString,
4040
requestString,
4141
currentChannel,
@@ -83,7 +83,7 @@ export const MessageSearchUI: React.FC<MessageSearchUIProps> = ({
8383
return stringSet.NO_TITLE;
8484
};
8585

86-
if (isInvalid && searchString && requestString) {
86+
if (isQueryInvalid && searchString && requestString) {
8787
return renderPlaceHolderError?.() || (
8888
<div className="sendbird-message-search">
8989
<PlaceHolder

src/modules/MessageSearch/context/MessageSearchProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface MessageSearchState extends MessageSearchProviderProps {
2828
channelUrl: string;
2929
allMessages: ClientSentMessages[];
3030
loading: boolean;
31-
isInvalid: boolean;
31+
isQueryInvalid: boolean;
3232
initialized: boolean;
3333
currentChannel: GroupChannel | null;
3434
currentMessageSearchQuery: MessageSearchQuery | null;
@@ -45,7 +45,7 @@ const initialState: MessageSearchState = {
4545
channelUrl: '',
4646
allMessages: [],
4747
loading: false,
48-
isInvalid: false,
48+
isQueryInvalid: false,
4949
initialized: false,
5050
currentChannel: null,
5151
currentMessageSearchQuery: null,

0 commit comments

Comments
 (0)