import React from 'react';
import { Text } from 'react-native';
import { act, render, waitFor } from '@testing-library/react-native';
import type { Channel, StreamChat } from 'stream-chat';
import { ChannelsProvider } from '../../../contexts/channelsContext/ChannelsContext';
import type { ChannelsContextValue } from '../../../contexts/channelsContext/ChannelsContext';
import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext';
import {
getOrCreateChannelApi,
GetOrCreateChannelApiParams,
} from '../../../mock-builders/api/getOrCreateChannel';
import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
import dispatchMessageNewEvent from '../../../mock-builders/event/messageNew';
import dispatchNotificationMarkRead from '../../../mock-builders/event/notificationMarkRead';
import dispatchNotificationMarkUnread from '../../../mock-builders/event/notificationMarkUnread';
import { generateChannel, generateChannelResponse } from '../../../mock-builders/generator/channel';
import { generateMessage } from '../../../mock-builders/generator/message';
import { generateUser } from '../../../mock-builders/generator/user';
import { getTestClientWithUser } from '../../../mock-builders/mock';
import { Chat } from '../../Chat/Chat';
import { ChannelPreview } from '../ChannelPreview';
import '@testing-library/jest-native/extend-expect';
import { LastMessageType } from '../hooks/useChannelPreviewData';
type ChannelPreviewUIComponentProps = {
channel: {
id: string;
};
lastMessage: LastMessageType;
unread: number;
muted: boolean;
};
const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() });
const mockChannelSwipableWrapper = jest.fn(({ children }: React.PropsWithChildren) => (
{children}
));
jest.mock('../ChannelSwipableWrapper', () => ({
ChannelSwipableWrapper: (...args: unknown[]) => mockChannelSwipableWrapper(...args),
}));
const ChannelPreviewUIComponent = (props: ChannelPreviewUIComponentProps) => {
return (
<>
{props.channel.id}
{props.unread}
{props.lastMessage?.text}
>
);
};
const initChannelFromData = async (
chatClient: StreamChat,
overrides: Record = {},
) => {
const mockedChannel = generateChannelResponse(overrides);
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
const channel = chatClient.channel('messaging', mockedChannel.channel.id);
await channel.watch();
channel.countUnread = jest.fn().mockReturnValue(0);
channel.initialized = true;
channel.lastMessage = jest.fn().mockReturnValue(generateMessage());
channel.muteStatus = jest.fn().mockReturnValue({ muted: false });
channel.state.messages = [generateMessage()];
return channel;
};
describe('ChannelPreview', () => {
const clientUser = generateUser();
let chatClient: StreamChat;
let channel: Channel | null;
const TestComponent = (props = {}) => {
if (channel === null) {
return null;
}
return (
);
};
const generateChannelWrapper = (overrides: Record) =>
generateChannel({
countUnread: jest.fn().mockReturnValue(0),
initialized: true,
lastMessage: jest.fn().mockReturnValue(generateMessage()),
muteStatus: jest.fn().mockReturnValue({ muted: false }),
...overrides,
});
const useInitializeChannel = async (c: GetOrCreateChannelApiParams) => {
useMockedApis(chatClient, [getOrCreateChannelApi(c)]);
channel = chatClient.channel('messaging');
await channel.watch();
};
beforeEach(async () => {
chatClient = await getTestClientWithUser(clientUser);
});
afterEach(() => {
channel = null;
});
describe('notification.mark_read event', () => {
it("should not update the unread count if the event's cid does not match the channel's cid", async () => {
channel = await initChannelFromData(chatClient);
channel.countUnread = jest.fn().mockReturnValue(10);
channel.on = channelOnMock;
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('10');
});
act(() => {
dispatchNotificationMarkRead(chatClient, { cid: 'channel-id' });
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('10');
});
});
it('should update the unread count to 0', async () => {
const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() });
const countUnreadMock = jest.fn();
countUnreadMock.mockReturnValue(10);
channel = await initChannelFromData(chatClient);
channel.countUnread = countUnreadMock;
channel.on = channelOnMock;
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('10');
});
countUnreadMock.mockReturnValue(0);
act(() => {
dispatchNotificationMarkRead(chatClient, channel || {});
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
});
});
describe('notification.mark_unread event', () => {
it("should not update the unread count if the event's cid is undefined", async () => {
const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() });
channel = await initChannelFromData(chatClient);
channel.on = channelOnMock;
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
act(() => {
dispatchNotificationMarkUnread(
chatClient,
{},
{
unread_channels: 2,
unread_messages: 5,
},
);
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
});
it("should not update the unread count if the event's cid does not match the channel's cid", async () => {
const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() });
channel = await initChannelFromData(chatClient);
channel.on = channelOnMock;
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
act(() => {
dispatchNotificationMarkUnread(
chatClient,
{ cid: 'channel-id' },
{
unread_channels: 2,
unread_messages: 5,
},
);
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
});
it("should not update the unread count if the event's user id does not match the client's user id", async () => {
const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() });
channel = await initChannelFromData(chatClient);
channel.on = channelOnMock;
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
act(() => {
dispatchNotificationMarkUnread(
chatClient,
{ cid: channel?.cid },
{
unread_channels: 2,
unread_messages: 5,
user: { id: 'random-id' },
},
);
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
});
it("should update the unread count if the event's user id matches the client's user id", async () => {
const c = generateChannelResponse();
await useInitializeChannel(c);
const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() });
const testChannel = generateChannelWrapper({
...channel,
on: channelOnMock,
});
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
act(() => {
dispatchNotificationMarkUnread(
chatClient,
{ cid: testChannel?.cid },
{
unread_channels: 2,
unread_messages: 5,
user: { id: clientUser.id },
},
);
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('5');
});
});
});
it('should update the unread count to 0 if the channel is muted', async () => {
const someOtherUser = generateUser({ id: 'not-me' });
const c = generateChannelResponse();
await useInitializeChannel(c);
channel.muteStatus = jest.fn().mockReturnValue({ muted: true });
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
for (let i = 0; i < 10; i++) {
const message = generateMessage({
user: someOtherUser,
});
act(() => {
dispatchMessageNewEvent(chatClient, message, channel || {});
});
}
await waitFor(() => getByTestId('channel-id'));
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('0');
});
act(() => {
dispatchNotificationMarkUnread(
chatClient,
{ cid: channel?.cid },
{
unread_channels: 2,
unread_messages: 5,
user: { id: clientUser.id },
},
);
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('5');
});
});
it('should update the latest message on "message.new" event', async () => {
const c = generateChannelResponse();
await useInitializeChannel(c);
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
const message = generateMessage({
user: clientUser,
});
act(() => {
dispatchMessageNewEvent(chatClient, message, channel || {});
});
await waitFor(() => {
expect(getByTestId('latest-message')).toHaveTextContent(message.text);
});
});
it('should update the unread count on "message.new" event', async () => {
const c = generateChannelResponse();
await useInitializeChannel(c);
const { getByTestId } = render();
await waitFor(() => getByTestId('channel-id'));
const message = generateMessage({
user: clientUser,
});
if (channel !== null) {
channel.countUnread = () => 10;
}
act(() => {
dispatchMessageNewEvent(chatClient, message, channel || {});
});
await waitFor(() => {
expect(getByTestId('unread-count')).toHaveTextContent('10');
});
});
it('displays messages translated if applicable', async () => {
chatClient = await getTestClientWithUser({ id: 'mads', language: 'no' });
const message = {
i18n: {
no_text: 'Hallo verden!',
},
text: 'Hello world!',
};
const channel = generateChannelResponse({ messages: [message] });
await useInitializeChannel(channel);
const { getByText } = render();
await waitFor(() => {
expect(getByText(message.i18n.no_text)).toBeTruthy();
});
});
describe('swipeActionsEnabled', () => {
const ChannelDetailsBottomSheetOverride = () => null;
const SwipePreview = ({ lastMessage, muted, unread }: ChannelPreviewUIComponentProps) => (
<>
{`${muted}`}
{`${unread}`}
{lastMessage?.text ?? ''}
>
);
const SwipeTestComponent = ({
channelDetailsBottomSheet,
swipeActionsEnabled,
}: {
channelDetailsBottomSheet?: React.ComponentType;
swipeActionsEnabled: boolean;
}) => {
if (channel === null) {
return null;
}
return (
);
};
beforeEach(async () => {
mockChannelSwipableWrapper.mockClear();
channel = await initChannelFromData(chatClient);
});
it('does not render ChannelSwipableWrapper when swipeActionsEnabled is false', async () => {
const { getByTestId, queryByTestId } = render(
,
);
await waitFor(() => expect(getByTestId('preview-unread')).toHaveTextContent('0'));
expect(queryByTestId('swipe-wrapper')).toBeNull();
expect(mockChannelSwipableWrapper).not.toHaveBeenCalled();
});
it('renders ChannelSwipableWrapper when swipeActionsEnabled is true', async () => {
const { getByTestId } = render();
await waitFor(() => expect(getByTestId('swipe-wrapper')).toBeTruthy());
expect(mockChannelSwipableWrapper).toHaveBeenCalled();
});
it('makes ChannelDetailsBottomSheet override available via WithComponents', async () => {
render(
,
);
// ChannelDetailsBottomSheet is now read from useComponentsContext() by
// ChannelSwipableWrapper rather than passed as a prop from ChannelPreview.
// Since ChannelSwipableWrapper is mocked, we verify the override is
// provided via WithComponents (set up in SwipeTestComponent).
await waitFor(() => expect(mockChannelSwipableWrapper).toHaveBeenCalled());
});
});
});