From 89af43df91aa40922110d686e82b7faabbcfbc74 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 3 Oct 2022 11:03:43 -0400 Subject: [PATCH] Add tests for ChatPane component --- app/soapbox/__mocks__/api.ts | 5 + .../chats/components/chat-list-item.tsx | 2 +- .../features/chats/components/chat-list.tsx | 50 ++++----- .../chat-pane/__tests__/chat-pane.test.tsx | 102 ++++++++++++++++++ .../chats/components/chat-pane/blankslate.tsx | 7 +- .../chats/components/chat-pane/chat-pane.tsx | 1 - .../chats/components/chat-search-input.tsx | 1 + app/soapbox/jest/fixtures/chats.json | 62 +++++++++++ 8 files changed, 200 insertions(+), 30 deletions(-) create mode 100644 app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx create mode 100644 app/soapbox/jest/fixtures/chats.json diff --git a/app/soapbox/__mocks__/api.ts b/app/soapbox/__mocks__/api.ts index 060846c94..6e9c75315 100644 --- a/app/soapbox/__mocks__/api.ts +++ b/app/soapbox/__mocks__/api.ts @@ -21,6 +21,11 @@ export const getLinks = (response: AxiosResponse): LinkHeader => { return new LinkHeader(response.headers?.link); }; +export const getNextLink = (response: AxiosResponse) => { + const nextLink = new LinkHeader(response.headers?.link); + return nextLink.refs.find((ref) => ref.uri)?.uri; +}; + export const baseClient = (...params: any[]) => { const axios = api.baseClient(...params); setupMock(axios); diff --git a/app/soapbox/features/chats/components/chat-list-item.tsx b/app/soapbox/features/chats/components/chat-list-item.tsx index 1864cef8b..e9aa81564 100644 --- a/app/soapbox/features/chats/components/chat-list-item.tsx +++ b/app/soapbox/features/chats/components/chat-list-item.tsx @@ -82,7 +82,7 @@ const ChatListItem: React.FC = ({ chat, chatSilence, onC key={chat.id} onClick={() => onClick(chat)} className='group px-2 py-3 w-full flex flex-col rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 focus:shadow-inset-ring' - data-testid='chat' + data-testid='chat-list-item' > diff --git a/app/soapbox/features/chats/components/chat-list.tsx b/app/soapbox/features/chats/components/chat-list.tsx index 6ea676336..abe182f21 100644 --- a/app/soapbox/features/chats/components/chat-list.tsx +++ b/app/soapbox/features/chats/components/chat-list.tsx @@ -4,7 +4,7 @@ import { Virtuoso } from 'react-virtuoso'; import { fetchChats } from 'soapbox/actions/chats'; import PullToRefresh from 'soapbox/components/pull-to-refresh'; -import { Stack } from 'soapbox/components/ui'; +import { Spinner, Stack } from 'soapbox/components/ui'; import PlaceholderChat from 'soapbox/features/placeholder/components/placeholder-chat'; import { useAppDispatch } from 'soapbox/hooks'; import { useChats, useChatSilences } from 'soapbox/queries/chats'; @@ -29,15 +29,13 @@ const ChatList: React.FC = ({ onClickChat, useWindowScroll = false, s const [isNearBottom, setNearBottom] = useState(false); const [isNearTop, setNearTop] = useState(true); - const isEmpty = (!chats || chats.length === 0); - const handleLoadMore = () => { if (hasNextPage && !isFetching) { fetchNextPage(); } }; - const handleRefresh = () => dispatch(fetchChats()) as any; + const handleRefresh = () => dispatch(fetchChats()); const renderEmpty = () => ( @@ -50,29 +48,27 @@ const ChatList: React.FC = ({ onClickChat, useWindowScroll = false, s return (
- {isEmpty ? renderEmpty() : ( - setNearTop(atTop)} - atBottomStateChange={(atBottom) => setNearBottom(atBottom)} - useWindowScroll={useWindowScroll} - data={chats} - endReached={handleLoadMore} - itemContent={(_index, chat) => { - const chatSilence = chatSilences?.find((chatSilence) => String(chatSilence.target_account_id) === chat.account.id); - return ( -
- -
- ); - }} - components={{ - ScrollSeekPlaceholder: () => , - // Footer: () => hasNextPage ? : null, - EmptyPlaceholder: renderEmpty, - }} - /> - )} + setNearTop(atTop)} + atBottomStateChange={(atBottom) => setNearBottom(atBottom)} + useWindowScroll={useWindowScroll} + data={chats} + endReached={handleLoadMore} + itemContent={(_index, chat) => { + const chatSilence = chatSilences?.find((chatSilence) => String(chatSilence.target_account_id) === chat.account.id); + return ( +
+ +
+ ); + }} + components={{ + ScrollSeekPlaceholder: () => , + Footer: () => hasNextPage ? : null, + EmptyPlaceholder: renderEmpty, + }} + />
<> diff --git a/app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx b/app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx new file mode 100644 index 000000000..c3fcf69a7 --- /dev/null +++ b/app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { VirtuosoMockContext } from 'react-virtuoso'; + +import { __stub } from 'soapbox/api'; +import { ChatContext } from 'soapbox/contexts/chat-context'; +import { StatProvider } from 'soapbox/contexts/stat-context'; +import chats from 'soapbox/jest/fixtures/chats.json'; +import { render, rootState, screen, waitFor } from 'soapbox/jest/test-helpers'; +import { normalizeInstance } from 'soapbox/normalizers'; + +import ChatPane from '../chat-pane'; + +const renderComponentWithChatContext = (store = {}) => render( + + + + + + + , + undefined, + store, +); + +const store = rootState + .set('instance', normalizeInstance({ + version: '3.4.1 (compatible; TruthSocial 1.0.0)', + })); + +describe('', () => { + describe('when there are chats', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/pleroma/chats').reply(200, chats, { + link: '; rel=\'prev\'', + }); + }); + }); + + it('renders the chats', async () => { + renderComponentWithChatContext(); + + await waitFor(() => { + expect(screen.getAllByTestId('chat-list-item')).toHaveLength(chats.length); + }); + }); + }); + + describe('when there are no chats', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/pleroma/chats').reply(200, [], { + link: null, + }); + }); + }); + + it('renders the blankslate', async () => { + renderComponentWithChatContext(); + + await waitFor(() => { + expect(screen.getByTestId('chat-pane-blankslate')).toBeInTheDocument(); + }); + }); + }); + + describe('when the software is Truth Social', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/pleroma/chats').reply(200, chats, { + link: '; rel=\'prev\'', + }); + }); + }); + + it('renders the search input', async () => { + renderComponentWithChatContext(store); + + await waitFor(() => { + expect(screen.getByTestId('chat-search-input')).toBeInTheDocument(); + }); + }); + }); + + describe('when the software is not Truth Social', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/pleroma/chats').reply(200, chats, { + link: '; rel=\'prev\'', + }); + }); + }); + + it('does not render the search input', async () => { + renderComponentWithChatContext(); + + await waitFor(() => { + expect(screen.queryAllByTestId('chat-search-input')).toHaveLength(0); + }); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/chats/components/chat-pane/blankslate.tsx b/app/soapbox/features/chats/components/chat-pane/blankslate.tsx index 605ffd384..39670453b 100644 --- a/app/soapbox/features/chats/components/chat-pane/blankslate.tsx +++ b/app/soapbox/features/chats/components/chat-pane/blankslate.tsx @@ -17,7 +17,12 @@ const Blankslate = ({ onSearch }: IBlankslate) => { const intl = useIntl(); return ( - + diff --git a/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx b/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx index fe29641ce..5d4b8ff5a 100644 --- a/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx +++ b/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx @@ -1,4 +1,3 @@ -import sumBy from 'lodash/sumBy'; import React, { useState } from 'react'; import { Stack } from 'soapbox/components/ui'; diff --git a/app/soapbox/features/chats/components/chat-search-input.tsx b/app/soapbox/features/chats/components/chat-search-input.tsx index 557e8a616..e6d616581 100644 --- a/app/soapbox/features/chats/components/chat-search-input.tsx +++ b/app/soapbox/features/chats/components/chat-search-input.tsx @@ -22,6 +22,7 @@ const ChatSearchInput: React.FC = ({ value, onChange, onClear return (