Skip to content

Commit

Permalink
馃毀(frontend) make tests work with react-query v4 and react 18
Browse files Browse the repository at this point in the history
most of the tests broke due to the changes induced by the new rendering
behavior of react 18 and react-query v4, so we changed the approach
that we had with tests to an approach more based on `waitFor` in order
to have tests the most render-agnostic as possible.
  • Loading branch information
NathanVss committed Nov 8, 2022
1 parent 28d9aae commit a8bdb89
Show file tree
Hide file tree
Showing 49 changed files with 677 additions and 966 deletions.
11 changes: 0 additions & 11 deletions src/frontend/jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
import '@testing-library/jest-dom/extend-expect';
import { Request, Response } from 'node-fetch';

import { setLogger } from 'react-query';
import { noop } from 'utils';

// Mock Request & Reponse objects
global.Request = Request as any;
global.Response = Response as any;
Expand All @@ -25,11 +22,3 @@ RESET_MODULE_EXCEPTIONS.forEach((moduleName) => {
return mockActualRegistry[moduleName];
});
});

/* Prevent log error during tests */
setLogger({
// eslint-disable-next-line no-console
log: console.log,
warn: console.warn,
error: noop,
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fireEvent, getByText, render, screen } from '@testing-library/react';
import { act } from '@testing-library/react-hooks';
import { act, fireEvent, getByText, render, screen } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { Address } from 'types/Joanie';
import * as mockFactories from 'utils/test/factories';
Expand Down
55 changes: 17 additions & 38 deletions src/frontend/js/components/AddressesManagement/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
/**
* Test suite for AddressesManagement component
*/
import { fireEvent, render, screen } from '@testing-library/react';
import { act } from '@testing-library/react-hooks';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import fetchMock from 'fetch-mock';
import { IntlProvider } from 'react-intl';
import { QueryClientProvider } from 'react-query';
import { QueryClientProvider } from '@tanstack/react-query';
import * as mockFactories from 'utils/test/factories';
import { SessionProvider } from 'data/SessionProvider';
import { REACT_QUERY_SETTINGS, RICHIE_USER_TOKEN } from 'settings';
import type * as Joanie from 'types/Joanie';
import createQueryClient from 'utils/react-query/createQueryClient';
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
import AddressesManagement from '.';

jest.mock('utils/context', () => ({
Expand All @@ -28,20 +26,6 @@ jest.mock('utils/indirection/window', () => ({
}));

describe('AddressesManagement', () => {
const initializeUser = () => {
const user = mockFactories.FonzieUserFactory.generate();

sessionStorage.setItem(
REACT_QUERY_SETTINGS.cacheStorage.key,
JSON.stringify(
mockFactories.PersistedClientFactory({
queries: [mockFactories.QueryStateFactory('user', { data: user })],
}),
),
);
sessionStorage.setItem(RICHIE_USER_TOKEN, user.access_token);
};

const handleClose = jest.fn();
const selectAddress = jest.fn();

Expand All @@ -53,16 +37,14 @@ describe('AddressesManagement', () => {
afterEach(() => {
jest.clearAllMocks();
fetchMock.restore();
sessionStorage.clear();
});

it('renders a go back button', async () => {
initializeUser();
fetchMock.get('https://joanie.endpoint/api/addresses/', []);

await act(async () => {
render(
<QueryClientProvider client={createQueryClient({ persistor: true })}>
<QueryClientProvider client={createTestQueryClient({ user: true })}>
<IntlProvider locale="en">
<SessionProvider>
<AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
Expand All @@ -82,15 +64,14 @@ describe('AddressesManagement', () => {
});

it("renders the user's addresses", async () => {
initializeUser();
const addresses = mockFactories.AddressFactory.generate(Math.ceil(Math.random() * 5));
fetchMock.get('https://joanie.endpoint/api/addresses/', addresses);

let container: HTMLElement;

await act(async () => {
({ container } = render(
<QueryClientProvider client={createQueryClient({ persistor: true })}>
<QueryClientProvider client={createTestQueryClient({ user: true })}>
<IntlProvider locale="en">
<SessionProvider>
<AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
Expand All @@ -100,9 +81,11 @@ describe('AddressesManagement', () => {
));
});

// All user's addresses should be displayed
const $addresses = container!.querySelectorAll('.registered-addresses-item');
expect($addresses).toHaveLength(addresses.length);
await waitFor(() => {
// All user's addresses should be displayed
const $addresses = container!.querySelectorAll('.registered-addresses-item');
return expect($addresses).toHaveLength(addresses.length);
});

addresses.forEach((address: Joanie.Address) => {
const $address = screen.getByTestId(`address-${address.id}-title`);
Expand All @@ -122,12 +105,11 @@ describe('AddressesManagement', () => {
});

it('renders a form to create an address', async () => {
initializeUser();
fetchMock.get('https://joanie.endpoint/api/addresses/', []);

await act(async () => {
render(
<QueryClientProvider client={createQueryClient({ persistor: true })}>
<QueryClientProvider client={createTestQueryClient({ user: true })}>
<IntlProvider locale="en">
<SessionProvider>
<AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
Expand Down Expand Up @@ -208,13 +190,12 @@ describe('AddressesManagement', () => {
});

it('renders a form to edit an address when user selects an address to edit', async () => {
initializeUser();
const address = mockFactories.AddressFactory.generate();
fetchMock.get('https://joanie.endpoint/api/addresses/', [address]);

await act(async () => {
render(
<QueryClientProvider client={createQueryClient({ persistor: true })}>
<QueryClientProvider client={createTestQueryClient({ user: true })}>
<IntlProvider locale="en">
<SessionProvider>
<AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
Expand All @@ -231,7 +212,7 @@ describe('AddressesManagement', () => {
screen.getByRole('button', { name: 'Use this address' });

// - Then user selects an address to edit
let $editButton = screen.getByRole('button', {
let $editButton = await screen.findByRole('button', {
name: `Edit "${address.title}" address`,
exact: true,
});
Expand Down Expand Up @@ -349,14 +330,13 @@ describe('AddressesManagement', () => {
});

it('allows user to delete an existing address', async () => {
initializeUser();
const address = mockFactories.AddressFactory.generate();
fetchMock.get('https://joanie.endpoint/api/addresses/', [address]);

let container: HTMLElement;
await act(async () => {
({ container } = render(
<QueryClientProvider client={createQueryClient({ persistor: true })}>
<QueryClientProvider client={createTestQueryClient({ user: true })}>
<IntlProvider locale="en">
<SessionProvider>
<AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
Expand All @@ -371,7 +351,7 @@ describe('AddressesManagement', () => {
.delete(`https://joanie.endpoint/api/addresses/${address.id}/`, {})
.get('https://joanie.endpoint/api/addresses/', [], { overwriteRoutes: true });

const $deleteButton = screen.getByRole('button', {
const $deleteButton = await screen.findByRole('button', {
name: `Delete "${address.title}" address`,
exact: true,
});
Expand All @@ -389,14 +369,13 @@ describe('AddressesManagement', () => {
});

it('allows user to promote an address as main', async () => {
initializeUser();
const [address1, address2] = mockFactories.AddressFactory.generate(2);
address1.is_main = true;
fetchMock.get('https://joanie.endpoint/api/addresses/', [address1, address2]);

await act(async () => {
render(
<QueryClientProvider client={createQueryClient({ persistor: true })}>
<QueryClientProvider client={createTestQueryClient({ user: true })}>
<IntlProvider locale="en">
<SessionProvider>
<AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
Expand Down Expand Up @@ -429,7 +408,7 @@ describe('AddressesManagement', () => {
},
);

const $promoteButton = screen.getByRole('button', {
const $promoteButton = await screen.findByRole('button', {
name: `Define "${address2.title}" address as main`,
exact: true,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import faker from 'faker';
import { act, renderHook } from '@testing-library/react-hooks';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { act, renderHook } from '@testing-library/react';
import { Maybe } from 'types/utils';
import validationSchema from './validationSchema';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
import fetchMock from 'fetch-mock';
import type { PropsWithChildren } from 'react';
import { IntlProvider } from 'react-intl';
import { QueryClientProvider } from 'react-query';
import { QueryClientProvider } from '@tanstack/react-query';
import {
ContextFactory as mockContextFactory,
CourseFactory,
Expand All @@ -14,9 +14,9 @@ import {
import JoanieApiProvider from 'data/JoanieApiProvider';
import { CourseCodeProvider } from 'data/CourseCodeProvider';
import type { Course, CourseRun, Enrollment, OrderLite } from 'types/Joanie';
import createQueryClient from 'utils/react-query/createQueryClient';
import { Deferred } from 'utils/test/deferred';
import { Priority } from 'types';
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
import { CourseRunList, EnrollableCourseRunList, EnrolledCourseRun } from '.';

jest.mock('utils/context', () => ({
Expand Down Expand Up @@ -109,7 +109,7 @@ describe('CourseProductCourseRuns', () => {
const Wrapper = ({ code, children }: PropsWithChildren<{ code: string }>) => (
<IntlProvider locale="en">
<CourseCodeProvider code={code}>
<QueryClientProvider client={createQueryClient()}>
<QueryClientProvider client={createTestQueryClient()}>
<JoanieApiProvider>{children}</JoanieApiProvider>
</QueryClientProvider>
</CourseCodeProvider>
Expand Down Expand Up @@ -299,6 +299,7 @@ describe('CourseProductCourseRuns', () => {
// it should be enabled already to allow early user feedback
expect($button.disabled).toBe(false);

fetchMock.post('https://joanie.test/api/enrollments/', []);
await act(async () => {
// - Select the first course run
const $radio = screen.getByRole('radio', {
Expand All @@ -309,12 +310,11 @@ describe('CourseProductCourseRuns', () => {
fireEvent.click($radio);
fireEvent.click($button);
});

// - As the selected course run is not yet opened for enrollment,
// a message should inform user that he/she cannot enroll now.
// it should be focused so that screen reader users understand better
// the submit button should stay enabled to always allow user feedback on its actions
const error = screen.getByText(
const error = await screen.findByText(
`Enrollment will open on ${dateFormatter.format(new Date(courseRun.enrollment_start))}`,
);
expect(document.activeElement).toEqual(error);
Expand All @@ -326,7 +326,7 @@ describe('CourseProductCourseRuns', () => {
const Wrapper = ({ code, children }: PropsWithChildren<{ code: string }>) => (
<IntlProvider locale="en">
<CourseCodeProvider code={code}>
<QueryClientProvider client={createQueryClient()}>
<QueryClientProvider client={createTestQueryClient()}>
<JoanieApiProvider>{children}</JoanieApiProvider>
</QueryClientProvider>
</CourseCodeProvider>
Expand Down Expand Up @@ -397,7 +397,7 @@ describe('CourseProductCourseRuns', () => {
});

// - While request is pending, an accessible spinner should be displayed
screen.getByRole('status', { name: 'Unrolling...' });
await screen.findByRole('status', { name: 'Unrolling...' });

await act(async () => {
enrollmentDeferred.resolve(200);
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/js/components/CourseProductItem/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getByText, render, screen } from '@testing-library/react';
import fetchMock from 'fetch-mock';
import type { PropsWithChildren } from 'react';
import { IntlProvider } from 'react-intl';
import { QueryClientProvider } from 'react-query';
import { QueryClientProvider } from '@tanstack/react-query';
import {
CertificateProductFactory,
ContextFactory as mockContextFactory,
Expand All @@ -13,7 +13,7 @@ import {
import { CourseCodeProvider } from 'data/CourseCodeProvider';
import JoanieApiProvider from 'data/JoanieApiProvider';
import { CourseRun, Enrollment, OrderLite, Product } from 'types/Joanie';
import createQueryClient from 'utils/react-query/createQueryClient';
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
import CourseProductItem from '.';

jest.mock('utils/context', () => ({
Expand Down Expand Up @@ -74,7 +74,7 @@ describe('CourseProductItem', () => {
<IntlProvider locale="en">
<CourseCodeProvider code={code}>
<JoanieApiProvider>
<QueryClientProvider client={createQueryClient()}>{children}</QueryClientProvider>
<QueryClientProvider client={createTestQueryClient()}>{children}</QueryClientProvider>
</JoanieApiProvider>
</CourseCodeProvider>
</IntlProvider>
Expand Down
12 changes: 7 additions & 5 deletions src/frontend/js/components/CourseProductsList/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { act, render, screen, waitFor } from '@testing-library/react';
import fetchMock from 'fetch-mock';
import type { PropsWithChildren } from 'react';
import { IntlProvider } from 'react-intl';
import { QueryClientProvider } from 'react-query';
import { QueryClientProvider } from '@tanstack/react-query';
import {
ContextFactory as mockContextFactory,
CourseFactory,
Expand All @@ -12,7 +12,7 @@ import { Deferred } from 'utils/test/deferred';
import type { Props as CourseProductItemProps } from 'components/CourseProductItem';
import JoanieApiProvider from 'data/JoanieApiProvider';
import type { Course, CourseProduct, OrderLite } from 'types/Joanie';
import createQueryClient from 'utils/react-query/createQueryClient';
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
import CourseProductsList from '.';

jest.mock('utils/context', () => ({
Expand All @@ -36,7 +36,7 @@ jest.mock('components/CourseProductItem', () => ({
describe('CourseProductsList', () => {
const Wrapper = ({ children }: PropsWithChildren<{}>) => (
<IntlProvider locale="en">
<QueryClientProvider client={createQueryClient()}>
<QueryClientProvider client={createTestQueryClient()}>
<JoanieApiProvider>{children}</JoanieApiProvider>
</QueryClientProvider>
</IntlProvider>
Expand Down Expand Up @@ -89,7 +89,7 @@ describe('CourseProductsList', () => {
});

// - As course does not have products, it should render nothing.
expect(container.children).toHaveLength(0);
await waitFor(() => expect(container.children).toHaveLength(0));
});

it('renders one <CourseProductItem /> per course product', async () => {
Expand Down Expand Up @@ -123,7 +123,9 @@ describe('CourseProductsList', () => {
});

// - It should render one <CourseProductItem /> per product
expect(screen.queryAllByTestId('product-widget')).toHaveLength(course.products.length);
await waitFor(() =>
expect(screen.queryAllByTestId('product-widget')).toHaveLength(course.products.length),
);
// - It should also pass order information if user owns a product
expect(screen.queryAllByTestId('product-widget__price')).toHaveLength(1);
});
Expand Down

0 comments on commit a8bdb89

Please sign in to comment.