Skip to content

Commit 60f4963

Browse files
committed
refactor: replace enzyme with testing library
1 parent ed1bfa6 commit 60f4963

10 files changed

+126
-138
lines changed

website/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,9 @@ If a community libdef is not available, you can try writing your own and placing
267267

268268
### Testing and Linting
269269

270-
We use [Jest][jest] with [Enzyme][enzyme] to test our code and React components, [TypeScript][ts] for typechecking, [Stylelint][stylelint] and [ESLint][eslint] using [Airbnb config][eslint-airbnb] and [Prettier][prettier] for linting and formatting.
270+
We use [Jest][jest] with [Enzyme][enzyme] and [Testing Library][testing-library] to test our code and React components, [TypeScript][ts] for typechecking, [Stylelint][stylelint] and [ESLint][eslint] using [Airbnb config][eslint-airbnb] and [Prettier][prettier] for linting and formatting.
271+
272+
**Note: The majority of React tests are written with Enzyme. For new unit tests, please try to use [Testing Library][testing-library] instead!**
271273

272274
```sh
273275
# Run all tests once with code coverage
@@ -404,6 +406,7 @@ Components should keep their styles and tests in the same directory with the sam
404406
[bootstrap]: https://getbootstrap.com/
405407
[jest]: https://facebook.github.io/jest/
406408
[enzyme]: http://airbnb.io/enzyme/
409+
[testing-library]: https://testing-library.com/docs/react-testing-library/intro/
407410
[ts]: https://www.typescriptlang.org/
408411
[eslint]: https://eslint.org/
409412
[svgr]: https://github.com/smooth-code/svgr

website/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
4141
"@svgr/webpack": "8.1.0",
4242
"@testing-library/dom": "10.4.0",
43-
"@testing-library/jest-dom": "6.6.3",
44-
"@testing-library/react": "16.1.0",
43+
"@testing-library/jest-dom": "^6.6.3",
44+
"@testing-library/react": "^16.3.0",
4545
"@testing-library/user-event": "14.5.2",
4646
"@types/body-scroll-lock": "2.6.2",
4747
"@types/enzyme": "3.10.18",
Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { mount } from 'enzyme';
1+
import { render, screen } from '@testing-library/react';
22
import { defaultLectureOption } from 'test-utils/optimiser';
33
import OptimiserButton, { OptimiserButtonProps } from './OptimiserButton';
44

5-
import styles from './OptimiserButton.scss';
6-
75
describe('OptimiserButton', () => {
86
it('should be enabled when there are lesson options', () => {
97
const props: OptimiserButtonProps = {
@@ -12,10 +10,8 @@ describe('OptimiserButton', () => {
1210
freeDayConflicts: [],
1311
onClick: jest.fn(),
1412
};
15-
const wrapper = mount(<OptimiserButton {...props} />);
16-
const button = wrapper.find(`.${styles.optimizeButton}`);
17-
expect(button.exists()).toBe(true);
18-
expect(button.prop('disabled')).toBeFalsy();
13+
render(<OptimiserButton {...props} />);
14+
expect(screen.getByRole('button')).toBeEnabled();
1915
});
2016

2117
it('should be disabled when there are no lesson options', () => {
@@ -25,10 +21,8 @@ describe('OptimiserButton', () => {
2521
freeDayConflicts: [],
2622
onClick: jest.fn(),
2723
};
28-
const wrapper = mount(<OptimiserButton {...props} />);
29-
const button = wrapper.find(`.${styles.optimizeButton}`);
30-
expect(button.exists()).toBe(true);
31-
expect(button.prop('disabled')).toBe(true);
24+
render(<OptimiserButton {...props} />);
25+
expect(screen.getByRole('button')).toBeDisabled();
3226
});
3327

3428
it('should be disabled when optimising', () => {
@@ -38,10 +32,8 @@ describe('OptimiserButton', () => {
3832
freeDayConflicts: [],
3933
onClick: jest.fn(),
4034
};
41-
const wrapper = mount(<OptimiserButton {...props} />);
42-
const button = wrapper.find(`.${styles.optimizeButton}`);
43-
expect(button.exists()).toBe(true);
44-
expect(button.prop('disabled')).toBe(true);
35+
render(<OptimiserButton {...props} />);
36+
expect(screen.getByRole('button')).toBeDisabled();
4537
});
4638

4739
it('should be disabled when there are free day conflicts', () => {
@@ -58,10 +50,8 @@ describe('OptimiserButton', () => {
5850
],
5951
onClick: jest.fn(),
6052
};
61-
const wrapper = mount(<OptimiserButton {...props} />);
62-
const button = wrapper.find(`.${styles.optimizeButton}`);
63-
expect(button.exists()).toBe(true);
64-
expect(button.prop('disabled')).toBe(true);
53+
render(<OptimiserButton {...props} />);
54+
expect(screen.getByRole('button')).toBeDisabled();
6555
});
6656

6757
it('should show "Searching and optimising..." when optimising', () => {
@@ -71,10 +61,8 @@ describe('OptimiserButton', () => {
7161
freeDayConflicts: [],
7262
onClick: jest.fn(),
7363
};
74-
const wrapper = mount(<OptimiserButton {...props} />);
75-
const button = wrapper.find(`.${styles.optimizeButton}`);
76-
expect(button.exists()).toBe(true);
77-
expect(button.text().includes('Searching and optimising...')).toBe(true);
64+
render(<OptimiserButton {...props} />);
65+
expect(screen.getByRole('button')).toHaveTextContent('Searching and optimising...');
7866
});
7967

8068
it('should show "Optimise Timetable" when not optimising', () => {
@@ -84,9 +72,7 @@ describe('OptimiserButton', () => {
8472
freeDayConflicts: [],
8573
onClick: jest.fn(),
8674
};
87-
const wrapper = mount(<OptimiserButton {...props} />);
88-
const button = wrapper.find(`.${styles.optimizeButton}`);
89-
expect(button.exists()).toBe(true);
90-
expect(button.text().includes('Optimise Timetable')).toBe(true);
75+
render(<OptimiserButton {...props} />);
76+
expect(screen.getByRole('button')).toHaveTextContent('Optimise Timetable');
9177
});
9278
});
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mount } from 'enzyme';
1+
import { render } from '@testing-library/react';
22
import { defaultTutorialOption } from 'test-utils/optimiser';
33
import OptimiserFreeDayConflicts from './OptimiserFreeDayConflicts';
44

@@ -12,13 +12,13 @@ describe('OptimiserFreeDayConflicts', () => {
1212
days: ['Monday', 'Tuesday'],
1313
},
1414
];
15-
const wrapper = mount(<OptimiserFreeDayConflicts freeDayConflicts={freeDayConflicts} />);
16-
expect(wrapper.text().includes('Free Day Conflicts')).toBe(true);
17-
expect(wrapper.text().includes(defaultTutorialOption.displayText)).toBe(true);
15+
const { container } = render(<OptimiserFreeDayConflicts freeDayConflicts={freeDayConflicts} />);
16+
expect(container).toHaveTextContent('Free Day Conflicts');
17+
expect(container).toHaveTextContent(defaultTutorialOption.displayText);
1818
});
1919

2020
it('should not render when there are no conflicts', () => {
21-
const wrapper = mount(<OptimiserFreeDayConflicts freeDayConflicts={[]} />);
22-
expect(wrapper.isEmptyRender()).toBe(true);
21+
const { container } = render(<OptimiserFreeDayConflicts freeDayConflicts={[]} />);
22+
expect(container).toBeEmptyDOMElement();
2323
});
2424
});
Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
import { render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
13
import useOptimiserForm from 'views/hooks/useOptimiserForm';
2-
import { mount } from 'enzyme';
34
import OptimiserFreeDaySelect from './OptimiserFreeDaySelect';
45

5-
import styles from './OptimiserFreeDaySelect.scss';
6-
76
jest.mock('./OptimiserFormTooltip', () => ({
87
__esModule: true,
98
default: () => <div />,
@@ -22,32 +21,34 @@ describe('OptimiserLessonOptionSelect', () => {
2221
};
2322

2423
it('should not show saturday', () => {
25-
const wrapper = mount(<Helper hasSaturday={false} />);
26-
expect(wrapper.text().includes('Monday')).toBe(true);
27-
expect(wrapper.text().includes('Tuesday')).toBe(true);
28-
expect(wrapper.text().includes('Wednesday')).toBe(true);
29-
expect(wrapper.text().includes('Thursday')).toBe(true);
30-
expect(wrapper.text().includes('Friday')).toBe(true);
31-
expect(wrapper.text().includes('Saturday')).toBe(false);
24+
const { container } = render(<Helper hasSaturday={false} />);
25+
expect(container).toHaveTextContent('Monday');
26+
expect(container).toHaveTextContent('Tuesday');
27+
expect(container).toHaveTextContent('Wednesday');
28+
expect(container).toHaveTextContent('Thursday');
29+
expect(container).toHaveTextContent('Friday');
30+
expect(container).not.toHaveTextContent('Saturday');
3231
});
3332

3433
it('should show saturday', () => {
35-
const wrapper = mount(<Helper hasSaturday />);
36-
expect(wrapper.text().includes('Monday')).toBe(true);
37-
expect(wrapper.text().includes('Tuesday')).toBe(true);
38-
expect(wrapper.text().includes('Wednesday')).toBe(true);
39-
expect(wrapper.text().includes('Thursday')).toBe(true);
40-
expect(wrapper.text().includes('Friday')).toBe(true);
41-
expect(wrapper.text().includes('Saturday')).toBe(true);
34+
const { container } = render(<Helper hasSaturday />);
35+
expect(container).toHaveTextContent('Monday');
36+
expect(container).toHaveTextContent('Tuesday');
37+
expect(container).toHaveTextContent('Wednesday');
38+
expect(container).toHaveTextContent('Thursday');
39+
expect(container).toHaveTextContent('Friday');
40+
expect(container).toHaveTextContent('Saturday');
4241
});
4342

44-
it('should toggle the selected day', () => {
45-
const wrapper = mount(<Helper hasSaturday={false} />);
46-
const monday = wrapper.find(`.${styles.freeDaysButton}`).at(0);
47-
expect(monday.hasClass('active')).toBe(false);
48-
monday.simulate('click');
49-
expect(wrapper.find(`.${styles.freeDaysButton}`).at(0).hasClass('active')).toBe(true);
50-
monday.simulate('click');
51-
expect(wrapper.find(`.${styles.freeDaysButton}`).at(0).hasClass('active')).toBe(false);
43+
it('should toggle the selected day', async () => {
44+
render(<Helper hasSaturday={false} />);
45+
const monday = screen.getByText('Monday');
46+
expect(monday).not.toHaveClass('active');
47+
48+
await userEvent.click(screen.getByText('Monday'));
49+
expect(screen.getByText('Monday')).toHaveClass('active');
50+
51+
await userEvent.click(screen.getByText('Monday'));
52+
expect(screen.getByText('Monday')).not.toHaveClass('active');
5253
});
5354
});
Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { defaultLectureOption, defaultTutorialOption } from 'test-utils/optimiser';
14
import { LessonOption } from 'types/optimiser';
25
import useOptimiserForm from 'views/hooks/useOptimiserForm';
3-
import { mount } from 'enzyme';
4-
import { defaultLectureOption, defaultTutorialOption } from 'test-utils/optimiser';
56
import OptimiserLessonOptionSelect from './OptimiserLessonOptionSelect';
67

78
import styles from './OptimiserLessonOptionSelect.scss';
@@ -27,35 +28,32 @@ describe('OptimiserLessonOptionSelect', () => {
2728
};
2829

2930
it('should show a warning when there are no lesson options', () => {
30-
const wrapper = mount(<Helper lessonOptions={[]} />);
31-
expect(wrapper.text().includes('No Lessons Found')).toBe(true);
32-
expect(wrapper.find(`.${styles.lessonButtons}`).exists()).toBe(false);
31+
render(<Helper lessonOptions={[]} />);
32+
expect(screen.getByRole('alert')).toHaveTextContent('No Lessons Found');
3333
});
3434

3535
it('should show all lesson options', () => {
3636
const lessonOptions = [defaultLectureOption, defaultTutorialOption];
37-
const wrapper = mount(<Helper lessonOptions={lessonOptions} />);
38-
expect(wrapper.text().includes('No Lessons Found')).toBe(false);
39-
expect(wrapper.find(`.${styles.lessonButtons}`).exists()).toBe(true);
40-
expect(wrapper.find(`.${styles.lessonButton}`)).toHaveLength(2);
41-
expect(wrapper.text().includes(defaultLectureOption.displayText)).toBe(true);
42-
expect(wrapper.text().includes(defaultTutorialOption.displayText)).toBe(true);
37+
const { container } = render(<Helper lessonOptions={lessonOptions} />);
38+
expect(container).not.toHaveTextContent('No Lessons Found');
39+
expect(container).toHaveTextContent(defaultLectureOption.displayText);
40+
expect(container).toHaveTextContent(defaultTutorialOption.displayText);
4341
});
4442

45-
it('should toggle lesson option', () => {
43+
it('should toggle lesson option', async () => {
4644
const lessonOptions = [defaultLectureOption];
47-
const wrapper = mount(<Helper lessonOptions={lessonOptions} />);
48-
const lectureButton = wrapper.find(`.${styles.lessonButton}`);
49-
expect(lectureButton).toHaveLength(1);
50-
expect(wrapper.find(`.${styles.selected}`).exists()).toBe(false);
51-
expect(wrapper.find(`.${styles.unselected}`).exists()).toBe(true);
52-
53-
lectureButton.simulate('click');
54-
expect(wrapper.find(`.${styles.selected}`).exists()).toBe(true);
55-
expect(wrapper.find(`.${styles.unselected}`).exists()).toBe(false);
56-
57-
lectureButton.simulate('click');
58-
expect(wrapper.find(`.${styles.selected}`).exists()).toBe(false);
59-
expect(wrapper.find(`.${styles.unselected}`).exists()).toBe(true);
45+
render(<Helper lessonOptions={lessonOptions} />);
46+
47+
const lectureButton = screen.getByRole('button');
48+
expect(lectureButton).not.toHaveClass(styles.selected);
49+
expect(lectureButton).toHaveClass(styles.unselected);
50+
51+
await userEvent.click(lectureButton);
52+
expect(lectureButton).toHaveClass(styles.selected);
53+
expect(lectureButton).not.toHaveClass(styles.unselected);
54+
55+
await userEvent.click(lectureButton);
56+
expect(lectureButton).not.toHaveClass(styles.selected);
57+
expect(lectureButton).toHaveClass(styles.unselected);
6058
});
6159
});
Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { mount } from 'enzyme';
1+
import { render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
23
import useOptimiserForm from 'views/hooks/useOptimiserForm';
34
import {
45
OptimiserLessonTimeRangeSelect,
@@ -13,19 +14,18 @@ jest.mock('./OptimiserFormTooltip', () => ({
1314
}));
1415

1516
describe('OptimiserTimeRangeSelect', () => {
16-
it('should call setTime when valuue is changed', () => {
17+
it('should call setTime when valuue is changed', async () => {
1718
const setTime = jest.fn();
1819
const props: TimeRangeSelectProps = {
1920
currentValue: '0800',
2021
timeValues: ['0800', '0830', '0900'],
2122
setTime,
2223
};
23-
const wrapper = mount(<OptimiserTimeRangeSelect {...props} />);
24-
const dropdown = wrapper.find('select');
25-
expect(dropdown.exists()).toBe(true);
26-
expect(dropdown.props().value).toEqual('0800');
27-
dropdown.simulate('change', { target: { value: '0830' } });
28-
expect(setTime).toHaveBeenCalledTimes(1);
24+
render(<OptimiserTimeRangeSelect {...props} />);
25+
const select = screen.getByTestId('optimiserTimeRangeSelect');
26+
expect(select).toHaveValue('0800');
27+
expect(select).toHaveDisplayValue('08:00');
28+
await userEvent.selectOptions(select, '08:30');
2929
expect(setTime).toHaveBeenCalledWith('0830');
3030
});
3131
});
@@ -36,21 +36,21 @@ describe('OptimiserLessonTimeRangeSelect', () => {
3636
return <OptimiserLessonTimeRangeSelect optimiserFormFields={optimiserFormFields} />;
3737
};
3838

39-
it('should update the lesson time range', () => {
40-
const wrapper = mount(<Helper />);
41-
const dropdowns = wrapper.find('select');
42-
expect(dropdowns).toHaveLength(2);
39+
it('should update the lesson time range', async () => {
40+
render(<Helper />);
41+
const selects = screen.getAllByTestId('optimiserTimeRangeSelect');
42+
expect(selects).toHaveLength(2);
4343

44-
expect(dropdowns.at(0).props().value).toEqual('0800');
45-
expect(dropdowns.at(1).props().value).toEqual('1900');
44+
expect(selects.at(0)).toHaveValue('0800');
45+
expect(selects.at(1)).toHaveValue('1900');
4646

47-
dropdowns.at(0).simulate('change', { target: { value: '0830' } });
48-
expect(wrapper.find('select').at(0).props().value).toEqual('0830');
49-
expect(wrapper.find('select').at(1).props().value).toEqual('1900');
47+
await userEvent.selectOptions(selects.at(0)!, '0830');
48+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(0)).toHaveValue('0830');
49+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(1)).toHaveValue('1900');
5050

51-
dropdowns.at(1).simulate('change', { target: { value: '1200' } });
52-
expect(wrapper.find('select').at(0).props().value).toEqual('0830');
53-
expect(wrapper.find('select').at(1).props().value).toEqual('1200');
51+
await userEvent.selectOptions(selects.at(1)!, '1200');
52+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(0)).toHaveValue('0830');
53+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(1)).toHaveValue('1200');
5454
});
5555
});
5656

@@ -60,20 +60,20 @@ describe('OptimiserLunchTimeRangeSelect', () => {
6060
return <OptimiserLunchTimeRangeSelect optimiserFormFields={optimiserFormFields} />;
6161
};
6262

63-
it('should update the lunch time range', () => {
64-
const wrapper = mount(<Helper />);
65-
const dropdowns = wrapper.find('select');
66-
expect(dropdowns).toHaveLength(2);
63+
it('should update the lunch time range', async () => {
64+
render(<Helper />);
65+
const selects = screen.getAllByTestId('optimiserTimeRangeSelect');
66+
expect(selects).toHaveLength(2);
6767

68-
expect(dropdowns.at(0).props().value).toEqual('1200');
69-
expect(dropdowns.at(1).props().value).toEqual('1400');
68+
expect(selects.at(0)).toHaveValue('1200');
69+
expect(selects.at(1)).toHaveValue('1400');
7070

71-
dropdowns.at(0).simulate('change', { target: { value: '1100' } });
72-
expect(wrapper.find('select').at(0).props().value).toEqual('1100');
73-
expect(wrapper.find('select').at(1).props().value).toEqual('1400');
71+
await userEvent.selectOptions(selects.at(0)!, '1100');
72+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(0)).toHaveValue('1100');
73+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(1)).toHaveValue('1400');
7474

75-
dropdowns.at(1).simulate('change', { target: { value: '1700' } });
76-
expect(wrapper.find('select').at(0).props().value).toEqual('1100');
77-
expect(wrapper.find('select').at(1).props().value).toEqual('1700');
75+
await userEvent.selectOptions(selects.at(1)!, '1700');
76+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(0)).toHaveValue('1100');
77+
expect(screen.getAllByTestId('optimiserTimeRangeSelect').at(1)).toHaveValue('1700');
7878
});
7979
});

website/src/views/optimiser/OptimiserForm/OptimiserTimeRangeSelect.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const OptimiserTimeRangeSelect: React.FC<TimeRangeSelectProps> = ({
1818
setTime,
1919
}) => (
2020
<select
21+
data-testid="optimiserTimeRangeSelect"
2122
className={styles.optimiserDropdown}
2223
value={currentValue}
2324
onChange={(e) => setTime(e.target.value)}

0 commit comments

Comments
 (0)