From 6b297c3a7e058db1adda42039d3057d0e6fd233f Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 8 Aug 2022 15:53:21 -0400 Subject: [PATCH] Add tests --- .../__tests__/feed-carousel.test.tsx | 69 +++++++++---------- app/soapbox/jest/test-helpers.tsx | 33 ++++++++- .../queries/__tests__/carousels.test.ts | 45 ++++++++++++ app/soapbox/queries/carousels.ts | 14 ++-- 4 files changed, 115 insertions(+), 46 deletions(-) create mode 100644 app/soapbox/queries/__tests__/carousels.test.ts diff --git a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx index 350cf385f..c17ea2c5a 100644 --- a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx +++ b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx @@ -2,8 +2,7 @@ import userEvent from '@testing-library/user-event'; import { Map as ImmutableMap } from 'immutable'; import React from 'react'; -import { __stub } from '../../../api'; -import { render, screen, waitFor } from '../../../jest/test-helpers'; +import { mock, render, screen, waitFor } from '../../../jest/test-helpers'; import FeedCarousel from '../feed-carousel'; jest.mock('../../../hooks/useDimensions', () => ({ @@ -55,63 +54,63 @@ describe('', () => { }; }); - it('should render the Carousel', () => { - store.carousels = { - avatars: [ - { account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' }, - ], - }; + describe('with avatars', () => { + beforeEach(() => { + mock.onGet('/api/v1/truth/carousels/avatars') + .reply(200, [ + { account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '2', acct: 'b', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '3', acct: 'c', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '4', acct: 'd', account_avatar: 'https://example.com/some.jpg' }, + ]); + }); - render(, undefined, store); + it('should render the Carousel', async() => { + render(, undefined, store); - expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(1); + await waitFor(() => { + expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(1); + }); + }); }); describe('with 0 avatars', () => { beforeEach(() => { - store.carousels = { - avatars: [], - }; + mock.onGet('/api/v1/truth/carousels/avatars').reply(200, []); }); - it('renders the error message', () => { + it('renders nothing', async() => { render(, undefined, store); - expect(screen.queryAllByTestId('feed-carousel-error')).toHaveLength(0); + await waitFor(() => { + expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(0); + }); }); }); describe('with a failed request to the API', () => { beforeEach(() => { - store.carousels = { - avatars: [], - error: true, - }; + mock.onGet('/api/v1/truth/carousels/avatars').networkError(); }); - it('renders the error message', () => { + it('renders the error message', async() => { render(, undefined, store); - expect(screen.getByTestId('feed-carousel-error')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('feed-carousel-error')).toBeInTheDocument(); + }); }); }); describe('with multiple pages of avatars', () => { beforeEach(() => { - store.carousels = { - error: false, - avatars: [], - }; - - __stub(mock => { - mock.onGet('/api/v1/truth/carousels/avatars') - .reply(200, [ - { account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' }, - { account_id: '2', acct: 'b', account_avatar: 'https://example.com/some.jpg' }, - { account_id: '3', acct: 'c', account_avatar: 'https://example.com/some.jpg' }, - { account_id: '4', acct: 'd', account_avatar: 'https://example.com/some.jpg' }, - ]); - }); + mock.onGet('/api/v1/truth/carousels/avatars') + .reply(200, [ + { account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '2', acct: 'b', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '3', acct: 'c', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '4', acct: 'd', account_avatar: 'https://example.com/some.jpg' }, + ]); Element.prototype.getBoundingClientRect = jest.fn(() => { return { diff --git a/app/soapbox/jest/test-helpers.tsx b/app/soapbox/jest/test-helpers.tsx index b7223caca..f48bf09f3 100644 --- a/app/soapbox/jest/test-helpers.tsx +++ b/app/soapbox/jest/test-helpers.tsx @@ -1,6 +1,7 @@ import { configureMockStore } from '@jedmao/redux-mock-store'; -import { QueryClientProvider } from '@tanstack/react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, RenderOptions } from '@testing-library/react'; +import MockAdapter from 'axios-mock-adapter'; import { merge } from 'immutable'; import React, { FC, ReactElement } from 'react'; import { IntlProvider } from 'react-intl'; @@ -10,7 +11,7 @@ import { Action, applyMiddleware, createStore } from 'redux'; import thunk from 'redux-thunk'; import '@testing-library/jest-dom'; -import { queryClient } from 'soapbox/queries/client'; +import API from 'soapbox/queries/client'; import NotificationsContainer from '../features/ui/containers/notifications_container'; import { default as rootReducer } from '../reducers'; @@ -28,8 +29,26 @@ const applyActions = (state: any, actions: any, reducer: any) => { return actions.reduce((state: any, action: any) => reducer(state, action), state); }; -const createTestStore = (initialState: any) => createStore(rootReducer, initialState, applyMiddleware(thunk)); +const mock = new MockAdapter(API, { onNoMatch: 'throwException' }); +const queryClient = new QueryClient({ + logger: { + // eslint-disable-next-line no-console + log: console.log, + warn: console.warn, + error: () => { }, + }, + defaultOptions: { + queries: { + retry: false, + }, + }, +}); +beforeEach(() => { + mock.reset(); +}); + +const createTestStore = (initialState: any) => createStore(rootReducer, initialState, applyMiddleware(thunk)); const TestApp: FC = ({ children, storeProps, routerProps = {} }) => { let store: ReturnType; let appState = rootState; @@ -71,6 +90,12 @@ const customRender = ( ...options, }); +const queryWrapper: React.FC = ({ children }) => ( + + {children} + +); + const mockWindowProperty = (property: any, value: any) => { const { [property]: originalProperty } = window; delete window[property]; @@ -97,4 +122,6 @@ export { rootReducer, mockWindowProperty, createTestStore, + mock, + queryWrapper, }; diff --git a/app/soapbox/queries/__tests__/carousels.test.ts b/app/soapbox/queries/__tests__/carousels.test.ts new file mode 100644 index 000000000..61edbce11 --- /dev/null +++ b/app/soapbox/queries/__tests__/carousels.test.ts @@ -0,0 +1,45 @@ +import { renderHook } from '@testing-library/react-hooks'; + +import { mock, queryWrapper, waitFor } from 'soapbox/jest/test-helpers'; + +import useCarouselAvatars from '../carousels'; + +describe('useCarouselAvatars', () => { + describe('with a successul query', () => { + beforeEach(() => { + mock.onGet('/api/v1/truth/carousels/avatars') + .reply(200, [ + { account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '2', acct: 'b', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '3', acct: 'c', account_avatar: 'https://example.com/some.jpg' }, + { account_id: '4', acct: 'd', account_avatar: 'https://example.com/some.jpg' }, + ]); + }); + + it('is successful', async() => { + const { result } = renderHook(() => useCarouselAvatars(), { + wrapper: queryWrapper, + }); + + await waitFor(() => expect(result.current.isFetching).toBe(false)); + + expect(result.current.data?.length).toBe(4); + }); + }); + + describe('with an unsuccessul query', () => { + beforeEach(() => { + mock.onGet('/api/v1/truth/carousels/avatars').networkError(); + }); + + it('is successful', async() => { + const { result } = renderHook(() => useCarouselAvatars(), { + wrapper: queryWrapper, + }); + + await waitFor(() => expect(result.current.isFetching).toBe(false)); + + expect(result.current.error).toBeDefined(); + }); + }); +}); diff --git a/app/soapbox/queries/carousels.ts b/app/soapbox/queries/carousels.ts index 36652e3cb..2989f4a52 100644 --- a/app/soapbox/queries/carousels.ts +++ b/app/soapbox/queries/carousels.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import API from './client'; +import API from 'soapbox/queries/client'; type Avatar = { account_id: string @@ -13,17 +13,15 @@ const getCarouselAvatars = async() => { return data; }; -export default function useCarouselAvatars(): { data: Avatar[], isFetching: boolean, isError: boolean, isSuccess: boolean } { - const { data, isFetching, isError, isSuccess } = useQuery(['carouselAvatars'], getCarouselAvatars, { +export default function useCarouselAvatars() { + const result = useQuery(['carouselAvatars'], getCarouselAvatars, { placeholderData: [], }); - const avatars = data as Avatar[]; + const avatars = result.data; return { - data: avatars, - isFetching, - isError, - isSuccess, + ...result, + data: avatars || [], }; }