Skip to content

Commit 7f4737e

Browse files
committed
Add unit tests
1 parent 74702f6 commit 7f4737e

File tree

7 files changed

+711
-58
lines changed

7 files changed

+711
-58
lines changed

src/modules/Channel/context/ChannelProvider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import useUpdateMessageCallback from './hooks/useUpdateMessageCallback';
4343
import useResendMessageCallback from './hooks/useResendMessageCallback';
4444
import useSendMessageCallback from './hooks/useSendMessageCallback';
4545
import useSendFileMessageCallback from './hooks/useSendFileMessageCallback';
46-
import useToggleReactionCallback from '../../GroupChannel/context/hooks/useToggleReactionCallback';
46+
import useToggleReactionCallback from './hooks/useToggleReactionCallback';
4747
import useScrollToMessage from './hooks/useScrollToMessage';
4848
import useSendVoiceMessageCallback from './hooks/useSendVoiceMessageCallback';
4949
import { getCaseResolvedReplyType, getCaseResolvedThreadReplySelectType } from '../../../lib/utils/resolvedReplyType';

src/modules/Channel/context/hooks/useToggleReactionCallback.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,38 @@ import { GroupChannel } from '@sendbird/chat/groupChannel';
33
import { LoggerInterface } from '../../../../lib/Logger';
44
import { BaseMessage } from '@sendbird/chat/message';
55

6-
type UseToggleReactionCallbackOptions = {
7-
currentGroupChannel: GroupChannel | null;
8-
};
9-
type UseToggleReactionCallbackParams = {
10-
logger: LoggerInterface;
11-
};
6+
const LOG_PRESET = 'useToggleReactionCallback:';
7+
128
export default function useToggleReactionCallback(
13-
{ currentGroupChannel }: UseToggleReactionCallbackOptions,
14-
{ logger }: UseToggleReactionCallbackParams,
9+
currentChannel: GroupChannel | null,
10+
logger?: LoggerInterface,
1511
) {
1612
return useCallback(
1713
(message: BaseMessage, key: string, isReacted: boolean) => {
14+
if (!currentChannel) {
15+
logger?.warning(`${LOG_PRESET} currentChannel doesn't exist`, currentChannel);
16+
return;
17+
}
1818
if (isReacted) {
19-
currentGroupChannel
20-
?.deleteReaction(message, key)
19+
currentChannel
20+
.deleteReaction(message, key)
2121
.then((res) => {
22-
logger.info('Delete reaction success', res);
22+
logger?.info(`${LOG_PRESET} Delete reaction success`, res);
2323
})
2424
.catch((err) => {
25-
logger.warning('Delete reaction failed', err);
25+
logger?.warning(`${LOG_PRESET} Delete reaction failed`, err);
2626
});
2727
} else {
28-
currentGroupChannel
29-
?.addReaction(message, key)
28+
currentChannel
29+
.addReaction(message, key)
3030
.then((res) => {
31-
logger.info('Add reaction success', res);
31+
logger?.info(`${LOG_PRESET} Add reaction success`, res);
3232
})
3333
.catch((err) => {
34-
logger.warning('Add reaction failed', err);
34+
logger?.warning(`${LOG_PRESET} Add reaction failed`, err);
3535
});
3636
}
3737
},
38-
[currentGroupChannel],
38+
[currentChannel],
3939
);
4040
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import '@testing-library/jest-dom/extend-expect';
4+
import { GroupChannelUIView } from '../components/GroupChannelUI/GroupChannelUIView';
5+
import useSendbirdStateContext from '../../../hooks/useSendbirdStateContext';
6+
7+
jest.mock('../../../hooks/useSendbirdStateContext');
8+
9+
const mockUseSendbirdStateContext = useSendbirdStateContext as jest.Mock;
10+
11+
describe('GroupChannelUIView Integration Tests', () => {
12+
const defaultProps = {
13+
channelUrl: 'test-channel',
14+
isInvalid: false,
15+
renderChannelHeader: jest.fn(() => <div>Channel Header</div>),
16+
renderMessageList: jest.fn(() => <div>Message List</div>),
17+
renderMessageInput: jest.fn(() => <div>Message Input</div>),
18+
};
19+
20+
beforeEach(() => {
21+
mockUseSendbirdStateContext.mockImplementation(() => ({
22+
stores: {
23+
sdkStore: { error: null },
24+
},
25+
config: {
26+
logger: { info: jest.fn() },
27+
isOnline: true,
28+
groupChannel: {
29+
enableTypingIndicator: true,
30+
typingIndicatorTypes: new Set(['text']),
31+
},
32+
},
33+
}));
34+
});
35+
36+
it('renders basic channel components correctly', () => {
37+
render(<GroupChannelUIView {...defaultProps} />);
38+
39+
expect(screen.getByText('Channel Header')).toBeInTheDocument();
40+
expect(screen.getByText('Message List')).toBeInTheDocument();
41+
expect(screen.getByText('Message Input')).toBeInTheDocument();
42+
});
43+
44+
it('renders loading placeholder when isLoading is true', () => {
45+
render(<GroupChannelUIView {...defaultProps} isLoading={true} />);
46+
// Placeholder is a just loading spinner in this case
47+
expect(screen.getByRole('button')).toHaveClass('sendbird-icon-spinner');
48+
});
49+
50+
it('renders invalid placeholder when channelUrl is missing', () => {
51+
render(<GroupChannelUIView {...defaultProps} channelUrl="" />);
52+
expect(screen.getByText('No channels')).toBeInTheDocument();
53+
});
54+
55+
it('renders error placeholder when isInvalid is true', () => {
56+
render(<GroupChannelUIView {...defaultProps} isInvalid={true} />);
57+
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
58+
});
59+
60+
it('renders SDK error placeholder when SDK has error', () => {
61+
mockUseSendbirdStateContext.mockImplementation(() => ({
62+
stores: {
63+
sdkStore: { error: new Error('SDK Error') },
64+
},
65+
config: {
66+
logger: { info: jest.fn() },
67+
isOnline: true,
68+
},
69+
}));
70+
71+
render(<GroupChannelUIView {...defaultProps} />);
72+
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
73+
expect(screen.getByText('Retry')).toBeInTheDocument();
74+
});
75+
76+
it('renders custom placeholders when provided', () => {
77+
const renderPlaceholderLoader = () => <div>Custom Loader</div>;
78+
const renderPlaceholderInvalid = () => <div>Custom Invalid</div>;
79+
80+
const { rerender } = render(
81+
<GroupChannelUIView
82+
{...defaultProps}
83+
isLoading={true}
84+
renderPlaceholderLoader={renderPlaceholderLoader}
85+
/>,
86+
);
87+
expect(screen.getByText('Custom Loader')).toBeInTheDocument();
88+
89+
rerender(
90+
<GroupChannelUIView
91+
{...defaultProps}
92+
isInvalid={true}
93+
renderPlaceholderInvalid={renderPlaceholderInvalid}
94+
/>,
95+
);
96+
expect(screen.getByText('Custom Invalid')).toBeInTheDocument();
97+
});
98+
});
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import React from 'react';
2+
import { waitFor, act } from '@testing-library/react';
3+
import { renderHook } from '@testing-library/react-hooks';
4+
import { GroupChannelProvider, useGroupChannelContext } from '../GroupChannelProvider';
5+
import { useGroupChannel } from '../hooks/useGroupChannel';
6+
7+
const mockLogger = { warning: jest.fn() };
8+
const mockChannel = {
9+
url: 'test-channel',
10+
members: [{ userId: '1', nickname: 'user1' }],
11+
};
12+
13+
const mockGetChannel = jest.fn().mockResolvedValue(mockChannel);
14+
const mockMessageCollection = {
15+
dispose: jest.fn(),
16+
setMessageCollectionHandler: jest.fn(),
17+
initialize: jest.fn().mockResolvedValue(null),
18+
loadPrevious: jest.fn(),
19+
loadNext: jest.fn(),
20+
};
21+
jest.mock('../../../../hooks/useSendbirdStateContext', () => ({
22+
__esModule: true,
23+
default: jest.fn(() => ({
24+
stores: {
25+
sdkStore: {
26+
sdk: {
27+
groupChannel: {
28+
getChannel: mockGetChannel,
29+
addGroupChannelHandler: jest.fn(),
30+
removeGroupChannelHandler: jest.fn(),
31+
},
32+
createMessageCollection: jest.fn().mockReturnValue(mockMessageCollection),
33+
},
34+
initialized: true,
35+
},
36+
},
37+
config: {
38+
logger: mockLogger,
39+
markAsReadScheduler: {
40+
push: jest.fn(),
41+
},
42+
groupChannel: {
43+
replyType: 'NONE',
44+
threadReplySelectType: 'PARENT',
45+
},
46+
groupChannelSettings: {
47+
enableMessageSearch: true,
48+
},
49+
isOnline: true,
50+
pubSub: {
51+
subscribe: () => ({ remove: jest.fn() }),
52+
},
53+
},
54+
})),
55+
}));
56+
57+
describe('GroupChannelProvider', () => {
58+
it('provides the correct initial state', () => {
59+
const wrapper = ({ children }) => (
60+
<GroupChannelProvider channelUrl="test-channel">
61+
{children}
62+
</GroupChannelProvider>
63+
);
64+
65+
const { result } = renderHook(() => useGroupChannelContext(), { wrapper });
66+
67+
expect(result.current.channelUrl).toBe('test-channel');
68+
expect(result.current.currentChannel).toBe(null);
69+
expect(result.current.isScrollBottomReached).toBe(true);
70+
});
71+
72+
it('updates state correctly when channel is fetched', async () => {
73+
const wrapper = ({ children }) => (
74+
<GroupChannelProvider channelUrl="test-channel">
75+
{children}
76+
</GroupChannelProvider>
77+
);
78+
79+
const { result } = renderHook(() => useGroupChannel(), { wrapper });
80+
81+
act(async () => {
82+
await waitFor(() => {
83+
expect(result.current.state.currentChannel).toBeTruthy();
84+
expect(result.current.state.currentChannel?.url).toBe('test-channel');
85+
});
86+
});
87+
});
88+
89+
it('handles channel error correctly', async () => {
90+
const mockError = new Error('Channel fetch failed');
91+
jest.spyOn(console, 'error').mockImplementation(() => {});
92+
93+
jest.mock('../../../../hooks/useSendbirdStateContext', () => ({
94+
default: () => ({
95+
stores: {
96+
sdkStore: {
97+
sdk: {
98+
groupChannel: {
99+
getChannel: jest.fn().mockRejectedValue(mockError),
100+
},
101+
},
102+
initialized: true,
103+
},
104+
},
105+
config: { logger: console },
106+
}),
107+
}));
108+
109+
const wrapper = ({ children }) => (
110+
<GroupChannelProvider channelUrl="error-channel">
111+
{children}
112+
</GroupChannelProvider>
113+
);
114+
115+
const { result } = renderHook(() => useGroupChannel(), { wrapper });
116+
117+
act(async () => {
118+
await waitFor(() => {
119+
expect(result.current.state.fetchChannelError).toBeNull();
120+
expect(result.current.state.currentChannel).toBeNull();
121+
});
122+
});
123+
});
124+
125+
it('correctly handles scroll to bottom', async () => {
126+
const wrapper = ({ children }) => (
127+
<GroupChannelProvider channelUrl="test-channel">
128+
{children}
129+
</GroupChannelProvider>
130+
);
131+
132+
const { result } = renderHook(() => useGroupChannel(), { wrapper });
133+
134+
act(async () => {
135+
result.current.actions.scrollToBottom();
136+
await waitFor(() => {
137+
expect(result.current.state.isScrollBottomReached).toBe(true);
138+
});
139+
});
140+
});
141+
});

0 commit comments

Comments
 (0)