Add tests
This commit is contained in:
parent
a68aeb8464
commit
81bfc06990
|
@ -216,7 +216,12 @@ class DropdownMenu extends React.PureComponent<IDropdownMenu, IDropdownMenuState
|
|||
// It should not be transformed when mounting because the resulting
|
||||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : undefined }} ref={this.setRef}>
|
||||
<div
|
||||
className={`dropdown-menu ${placement}`}
|
||||
style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : undefined }}
|
||||
ref={this.setRef}
|
||||
data-testid='dropdown-menu'
|
||||
>
|
||||
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
||||
<ul>
|
||||
{items.map((option, i) => this.renderItem(option, i))}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { ChatContext } from 'soapbox/contexts/chat-context';
|
||||
import { IAccount } from 'soapbox/queries/accounts';
|
||||
|
||||
import { __stub } from '../../../../api';
|
||||
import { queryClient, render, rootState, screen, waitFor } from '../../../../jest/test-helpers';
|
||||
import { IChat, IChatMessage } from '../../../../queries/chats';
|
||||
import ChatMessageList from '../chat-message-list';
|
||||
|
||||
const chat: IChat = {
|
||||
id: '14',
|
||||
unread: 5,
|
||||
created_by_account: '2',
|
||||
account: {
|
||||
id: '1',
|
||||
avatar: 'url',
|
||||
acct: 'username',
|
||||
} as IAccount,
|
||||
last_message: null,
|
||||
accepted: true,
|
||||
} as IChat;
|
||||
|
||||
const chatMessages: IChatMessage[] = [
|
||||
{
|
||||
account_id: '1',
|
||||
chat_id: '14',
|
||||
content: 'this is the first chat',
|
||||
created_at: new Date('2022-09-09T16:02:26.186Z'),
|
||||
id: '1',
|
||||
unread: false,
|
||||
pending: false,
|
||||
},
|
||||
{
|
||||
account_id: '2',
|
||||
chat_id: '14',
|
||||
content: 'this is the second chat',
|
||||
created_at: new Date('2022-09-09T16:04:26.186Z'),
|
||||
id: '2',
|
||||
unread: true,
|
||||
pending: false,
|
||||
},
|
||||
];
|
||||
|
||||
// Mock scrollIntoView function.
|
||||
window.HTMLElement.prototype.scrollIntoView = function() { };
|
||||
Object.assign(navigator, {
|
||||
clipboard: {
|
||||
writeText: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const store = rootState.set('me', '1');
|
||||
|
||||
const renderComponentWithChatContext = () => render(
|
||||
<ChatContext.Provider value={{ chat }}>
|
||||
<ChatMessageList chat={chat} />
|
||||
</ChatContext.Provider>,
|
||||
undefined,
|
||||
store,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
describe('<ChatMessageList />', () => {
|
||||
describe('when the query is loading', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet(`/api/v1/pleroma/chats/${chat.id}/messages`).reply(200, chatMessages, {
|
||||
link: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the skeleton loader', async() => {
|
||||
renderComponentWithChatContext();
|
||||
|
||||
expect(screen.queryAllByTestId('placeholder-chat-message')).toHaveLength(5);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chat-message-list-intro')).toBeInTheDocument();
|
||||
expect(screen.queryAllByTestId('placeholder-chat-message')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the query is finished loading', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet(`/api/v1/pleroma/chats/${chat.id}/messages`).reply(200, chatMessages, {
|
||||
link: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the intro', async() => {
|
||||
renderComponentWithChatContext();
|
||||
|
||||
expect(screen.queryAllByTestId('chat-message-list-intro')).toHaveLength(0);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chat-message-list-intro')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the messages', async() => {
|
||||
renderComponentWithChatContext();
|
||||
|
||||
expect(screen.queryAllByTestId('chat-message')).toHaveLength(0);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByTestId('chat-message')).toHaveLength(chatMessages.length);
|
||||
expect(screen.queryAllByTestId('chat-message')[0]).toHaveTextContent(chatMessages[0].content);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the correct menu options depending on the owner of the message', async() => {
|
||||
renderComponentWithChatContext();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByTestId('chat-message-menu')).toHaveLength(2);
|
||||
});
|
||||
|
||||
await userEvent.click(screen.queryAllByTestId('chat-message-menu')[0].querySelector('button') as any);
|
||||
expect(screen.getByTestId('dropdown-menu')).toHaveTextContent('Delete');
|
||||
expect(screen.getByTestId('dropdown-menu')).toHaveTextContent('Copy');
|
||||
|
||||
await userEvent.click(screen.queryAllByTestId('chat-message-menu')[1].querySelector('button') as any);
|
||||
expect(screen.getByTestId('dropdown-menu')).not.toHaveTextContent('Delete');
|
||||
expect(screen.getByTestId('dropdown-menu')).toHaveTextContent('Copy');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
import ChatPaneHeader from '../chat-pane-header';
|
||||
|
||||
describe('<ChatPaneHeader />', () => {
|
||||
it('handles the onToggle prop', async() => {
|
||||
const mockFn = jest.fn();
|
||||
render(<ChatPaneHeader title='title' onToggle={mockFn} isOpen />);
|
||||
|
||||
await userEvent.click(screen.getByTestId('icon-button'));
|
||||
expect(mockFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('the "title" prop', () => {
|
||||
describe('when it is a string', () => {
|
||||
it('renders the title', () => {
|
||||
const title = 'Messages';
|
||||
render(<ChatPaneHeader title={title} onToggle={jest.fn()} isOpen />);
|
||||
|
||||
expect(screen.getByTestId('title')).toHaveTextContent(title);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it is a node', () => {
|
||||
it('renders the title', () => {
|
||||
const title = (
|
||||
<div><p>hello world</p></div>
|
||||
);
|
||||
render(<ChatPaneHeader title={title} onToggle={jest.fn()} isOpen />);
|
||||
|
||||
expect(screen.getByTestId('title')).toHaveTextContent('hello world');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('the "unreadCount" prop', () => {
|
||||
describe('when present', () => {
|
||||
it('renders the unread count', () => {
|
||||
const count = 14;
|
||||
render(<ChatPaneHeader title='title' onToggle={jest.fn()} isOpen unreadCount={count} />);
|
||||
|
||||
expect(screen.getByTestId('unread-count')).toHaveTextContent(String(count));
|
||||
});
|
||||
});
|
||||
|
||||
describe('when 0', () => {
|
||||
it('does not render the unread count', () => {
|
||||
const count = 0;
|
||||
render(<ChatPaneHeader title='title' onToggle={jest.fn()} isOpen unreadCount={count} />);
|
||||
|
||||
expect(screen.queryAllByTestId('unread-count')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when unprovided', () => {
|
||||
it('does not render the unread count', () => {
|
||||
render(<ChatPaneHeader title='title' onToggle={jest.fn()} isOpen />);
|
||||
|
||||
expect(screen.queryAllByTestId('unread-count')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('secondaryAction prop', () => {
|
||||
it('handles the secondaryAction callback', async() => {
|
||||
const mockFn = jest.fn();
|
||||
render(
|
||||
<ChatPaneHeader
|
||||
title='title'
|
||||
onToggle={jest.fn()}
|
||||
isOpen
|
||||
secondaryAction={mockFn}
|
||||
secondaryActionIcon='icon.svg'
|
||||
/>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.queryAllByTestId('icon-button')[0]);
|
||||
expect(mockFn).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { ChatProvider } from 'soapbox/contexts/chat-context';
|
||||
|
||||
import { render, screen, waitFor } from '../../../../jest/test-helpers';
|
||||
import ChatSearch from '../chat-search';
|
||||
|
||||
const renderComponent = () => render(
|
||||
<ChatProvider>
|
||||
<ChatSearch />
|
||||
</ChatProvider>,
|
||||
);
|
||||
|
||||
describe('<ChatSearch />', () => {
|
||||
it('renders correctly', () => {
|
||||
renderComponent();
|
||||
|
||||
expect(screen.getByTestId('pane-header')).toHaveTextContent('Messages');
|
||||
});
|
||||
|
||||
describe('when the pane is closed', () => {
|
||||
it('does not render the search input', () => {
|
||||
renderComponent();
|
||||
expect(screen.queryAllByTestId('search')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the pane is open', () => {
|
||||
beforeEach(async() => {
|
||||
renderComponent();
|
||||
await userEvent.click(screen.getByTestId('icon-button'));
|
||||
});
|
||||
|
||||
it('renders the search input', () => {
|
||||
expect(screen.getByTestId('search')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('when searching', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/accounts/search').reply(200, [{
|
||||
id: '1',
|
||||
avatar: 'url',
|
||||
verified: false,
|
||||
display_name: 'steve',
|
||||
acct: 'sjobs',
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders accounts', async() => {
|
||||
renderComponent();
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.type(screen.getByTestId('search'), 'ste');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByTestId('account')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
import React from 'react';
|
||||
|
||||
import { IChat } from 'soapbox/queries/chats';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
import Chat from '../chat';
|
||||
|
||||
const chat: any = {
|
||||
id: '1',
|
||||
unread: 5,
|
||||
created_by_account: '2',
|
||||
last_message: {
|
||||
account_id: '2',
|
||||
chat_id: '1',
|
||||
content: 'hello world',
|
||||
created_at: '2022-09-09T16:02:26.186Z',
|
||||
discarded_at: null,
|
||||
id: '12332423234',
|
||||
unread: true,
|
||||
},
|
||||
created_at: new Date('2022-09-09T16:02:26.186Z'),
|
||||
updated_at: new Date('2022-09-09T16:02:26.186Z'),
|
||||
accepted: true,
|
||||
discarded_at: null,
|
||||
account: {
|
||||
acct: 'username',
|
||||
display_name: 'johnnie',
|
||||
},
|
||||
};
|
||||
|
||||
describe('<Chat />', () => {
|
||||
it('renders correctly', () => {
|
||||
render(<Chat chat={chat as IChat} onClick={jest.fn()} />);
|
||||
|
||||
expect(screen.getByTestId('chat')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('chat')).toHaveTextContent(chat.account.display_name);
|
||||
});
|
||||
|
||||
describe('last message content', () => {
|
||||
it('renders the last message', () => {
|
||||
render(<Chat chat={chat as IChat} onClick={jest.fn()} />);
|
||||
|
||||
expect(screen.getByTestId('chat-last-message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render the last message', () => {
|
||||
const changedChat = { ...chat, last_message: null };
|
||||
render(<Chat chat={changedChat as IChat} onClick={jest.fn()} />);
|
||||
|
||||
expect(screen.queryAllByTestId('chat-last-message')).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('unread', () => {
|
||||
it('renders the unread dot', () => {
|
||||
render(<Chat chat={chat as IChat} onClick={jest.fn()} />);
|
||||
|
||||
expect(screen.getByTestId('chat-unread-indicator')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render the unread dot', () => {
|
||||
const changedChat = { ...chat, last_message: { ...chat.last_message, unread: false } };
|
||||
render(<Chat chat={changedChat as IChat} onClick={jest.fn()} />);
|
||||
|
||||
expect(screen.queryAllByTestId('chat-unread-indicator')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -37,6 +37,7 @@ const ChatMessageListIntro = () => {
|
|||
|
||||
return (
|
||||
<Stack
|
||||
data-testid='chat-message-list-intro'
|
||||
justifyContent='center'
|
||||
alignItems='center'
|
||||
space={4}
|
||||
|
|
|
@ -3,7 +3,7 @@ import classNames from 'clsx';
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import escape from 'lodash/escape';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { useState, useEffect, useRef, useLayoutEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
|
@ -81,7 +81,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, autosize }) => {
|
|||
const formattedChatMessages = chatMessages || [];
|
||||
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat.account, 'blocked_by']));
|
||||
const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat.account.id, 'blocked_by']));
|
||||
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
const messagesEnd = useRef<HTMLDivElement>(null);
|
||||
|
@ -240,7 +240,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, autosize }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div key={chatMessage.id} className='group'>
|
||||
<div key={chatMessage.id} className='group' data-testid='chat-message'>
|
||||
<Stack
|
||||
space={1}
|
||||
className={classNames({
|
||||
|
@ -293,7 +293,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, autosize }) => {
|
|||
>
|
||||
{maybeRenderMedia(chatMessage)}
|
||||
<Text size='sm' theme='inherit' dangerouslySetInnerHTML={{ __html: parseContent(chatMessage) }} />
|
||||
<div className='chat-message__menu'>
|
||||
<div className='chat-message__menu' data-testid='chat-message-menu'>
|
||||
<DropdownMenuContainer
|
||||
items={menu}
|
||||
src={require('@tabler/icons/dots.svg')}
|
||||
|
@ -395,7 +395,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, autosize }) => {
|
|||
return (
|
||||
<Stack alignItems='center' justifyContent='center' className='h-full flex-grow'>
|
||||
<Stack alignItems='center' space={2}>
|
||||
<Avatar src={chat.account.avatar_static} size={75} />
|
||||
<Avatar src={chat.account.avatar} size={75} />
|
||||
<Text align='center'>
|
||||
<>
|
||||
<Text tag='span'>You are blocked by</Text>
|
||||
|
|
|
@ -21,6 +21,7 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
|
|||
secondaryActionIcon,
|
||||
title,
|
||||
unreadCount,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const ButtonComp = isToggleable ? 'button' : 'div';
|
||||
|
@ -30,9 +31,10 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<HStack alignItems='center' justifyContent='between' className='rounded-t-xl h-16 py-3 px-4'>
|
||||
<HStack {...rest} alignItems='center' justifyContent='between' className='rounded-t-xl h-16 py-3 px-4'>
|
||||
<ButtonComp
|
||||
className='flex-grow flex items-center flex-row space-x-1 h-16'
|
||||
data-testid='title'
|
||||
{...buttonProps}
|
||||
>
|
||||
{typeof title === 'string' ? (
|
||||
|
@ -43,7 +45,7 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
|
|||
|
||||
{(typeof unreadCount !== 'undefined' && unreadCount > 0) && (
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Text weight='semibold'>
|
||||
<Text weight='semibold' data-testid='unread-count'>
|
||||
({unreadCount})
|
||||
</Text>
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ const ChatSearch = () => {
|
|||
return (
|
||||
<Pane isOpen={isOpen} index={0} main>
|
||||
<ChatPaneHeader
|
||||
data-testid='pane-header'
|
||||
title={
|
||||
<HStack alignItems='center' space={2}>
|
||||
<button onClick={() => setSearching(false)}>
|
||||
|
@ -71,6 +72,7 @@ const ChatSearch = () => {
|
|||
<Stack space={4} className='flex-grow h-full'>
|
||||
<div className='px-4'>
|
||||
<Input
|
||||
data-testid='search'
|
||||
type='text'
|
||||
autoFocus
|
||||
placeholder='Type a name'
|
||||
|
@ -100,6 +102,7 @@ const ChatSearch = () => {
|
|||
handleClickOnSearchResult.mutate(account.id);
|
||||
clearValue();
|
||||
}}
|
||||
data-testid='account'
|
||||
>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Avatar src={account.avatar} size={40} />
|
||||
|
|
|
@ -18,6 +18,7 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
|
|||
type='button'
|
||||
onClick={() => onClick(chat)}
|
||||
className='px-4 py-2 w-full flex flex-col hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
data-testid='chat'
|
||||
>
|
||||
<HStack alignItems='center' justifyContent='between' space={2} className='w-full'>
|
||||
<HStack alignItems='center' space={2}>
|
||||
|
@ -30,9 +31,16 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
|
|||
</div>
|
||||
|
||||
{chat.last_message?.content && (
|
||||
<Text align='left' size='sm' weight='medium' theme='muted' truncate className='max-w-[200px]'>
|
||||
{chat.last_message?.content}
|
||||
</Text>
|
||||
<Text
|
||||
align='left'
|
||||
size='sm'
|
||||
weight='medium'
|
||||
theme='muted'
|
||||
truncate
|
||||
className='max-w-[200px]'
|
||||
data-testid='chat-last-message'
|
||||
dangerouslySetInnerHTML={{ __html: chat.last_message?.content }}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</HStack>
|
||||
|
@ -40,7 +48,10 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
|
|||
{chat.last_message && (
|
||||
<HStack alignItems='center' space={2}>
|
||||
{chat.last_message.unread && (
|
||||
<div className='w-2 h-2 rounded-full bg-secondary-500' />
|
||||
<div
|
||||
className='w-2 h-2 rounded-full bg-secondary-500'
|
||||
data-testid='chat-unread-indicator'
|
||||
/>
|
||||
)}
|
||||
|
||||
<RelativeTimestamp timestamp={chat.last_message.created_at} size='sm' />
|
||||
|
|
|
@ -13,6 +13,7 @@ const PlaceholderChatMessage = ({ isMyMessage = false }: { isMyMessage?: boolean
|
|||
|
||||
return (
|
||||
<Stack
|
||||
data-testid='placeholder-chat-message'
|
||||
space={1}
|
||||
className={classNames({
|
||||
'max-w-[85%] animate-pulse': true,
|
||||
|
|
Loading…
Reference in New Issue