Unverified Commit eeb898a0 authored by Andrey Azov's avatar Andrey Azov Committed by GitHub
Browse files

Update tests to use react-testing-library (#428)

Migrate tests for components used on the GenomeBrowser page to react-testing-library.
parent 4ce1f116
Pipeline #120650 passed with stages
in 5 minutes and 34 seconds
......@@ -29,6 +29,11 @@ module.exports = {
}
]
]
},
production: {
plugins: [
['react-remove-properties', { 'properties': [ 'data-test-id' ] }]
]
}
}
};
......@@ -11,7 +11,10 @@ module.exports = {
},
roots: ['<rootDir>/src'],
setupFiles: ['<rootDir>/tests/setup-jest.js'],
setupFilesAfterEnv: ['<rootDir>/tests/setup-enzyme.ts'],
setupFilesAfterEnv: [
'<rootDir>/tests/setup-enzyme.ts',
'<rootDir>/tests/setup-rtl.ts'
],
testEnvironment: 'jsdom',
transform: {
'.+\\.tsx?$': 'babel-jest',
......
......@@ -6053,6 +6053,12 @@
"recast": "^0.14.7"
}
},
"babel-plugin-react-remove-properties": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-remove-properties/-/babel-plugin-react-remove-properties-0.3.0.tgz",
"integrity": "sha512-vbxegtXGyVcUkCvayLzftU95vuvpYFV85pRpeMpohMHeEY46Qe0VNWfkVVcCbaZ12CXHzDFOj0esumATcW83ng==",
"dev": true
},
"babel-plugin-syntax-jsx": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
......@@ -115,6 +115,7 @@
"babel-core": "7.0.0-bridge.0",
"babel-jest": "26.5.2",
"babel-loader": "8.1.0",
"babel-plugin-react-remove-properties": "0.3.0",
"brotli-webpack-plugin": "1.1.0",
"compression-webpack-plugin": "5.0.2",
"connect-history-api-fallback": "1.6.0",
......
......@@ -15,7 +15,7 @@
*/
import React from 'react';
import { render } from 'enzyme';
import { render } from '@testing-library/react';
jest.mock('./BrowserNavBarControls', () => () => (
<div>BrowserNavBarControls</div>
......@@ -27,11 +27,18 @@ import { BrowserNavBar } from './BrowserNavBar';
describe('<BrowserNavBar />', () => {
describe('rendering', () => {
it('correctly interprets the "expanded" prop', () => {
const contractedBar = render(<BrowserNavBar expanded={false} />);
expect(contractedBar.hasClass('browserNavBarExpanded')).toBe(false);
const { container, rerender } = render(
<BrowserNavBar expanded={false} />
);
const browserNavBar = container.firstChild as HTMLDivElement;
expect(browserNavBar.classList.contains('browserNavBarExpanded')).toBe(
false
);
const expandedBar = render(<BrowserNavBar expanded={true} />);
expect(expandedBar.hasClass('browserNavBarExpanded')).toBe(true);
rerender(<BrowserNavBar expanded={true} />);
expect(browserNavBar.classList.contains('browserNavBarExpanded')).toBe(
true
);
});
});
});
......@@ -15,47 +15,49 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import faker from 'faker';
import times from 'lodash/times';
import BrowserNavIcon from './BrowserNavIcon';
import { BrowserNavBarControls } from './BrowserNavBarControls';
import { BrowserNavStates } from '../browserState';
import Overlay from 'src/shared/components/overlay/Overlay';
jest.mock('./BrowserNavIcon', () => () => <div>BrowserNavIcon</div>);
jest.mock('./BrowserNavIcon', () => (props: { enabled: boolean }) => {
const className = props.enabled ? 'browserNavIcon enabled' : 'browserNavIcon';
return <div className={className} />;
});
jest.mock('src/shared/components/overlay/Overlay', () => () => (
<div className="overlay" />
));
const browserNavStates = times(6, faker.random.boolean) as BrowserNavStates;
describe('BrowserNavBarControls', () => {
let wrapper: any;
beforeEach(() => {
wrapper = mount(
it('has an overlay on top when browser nav bar controls are disabled', () => {
const { container } = render(
<BrowserNavBarControls
browserNavStates={browserNavStates}
isDisabled={faker.random.boolean()}
isDisabled={true}
/>
);
});
it('has an overlay on top when browser nav bar controls are disabled', () => {
wrapper.setProps({ isDisabled: true });
expect(wrapper.find(Overlay).length).toBe(1);
expect(container.querySelector('.overlay')).toBeTruthy();
});
it('disables buttons if corresponding actions are not possible', () => {
// browserNavStates are an array of booleans that indicate whether the button
// has already caused maximum corresponding effect, and will have no further effect if pressed
wrapper.setProps({ isDisabled: false });
const { container } = render(
<BrowserNavBarControls
browserNavStates={browserNavStates}
isDisabled={false}
/>
);
const controlButtons = wrapper.find(BrowserNavIcon);
const controlButtons = container.querySelectorAll('.browserNavIcon');
controlButtons.forEach((button: any, index: number) => {
const hasCausedMaximumEffect = browserNavStates[index];
expect(button.prop('enabled')).toBe(!hasCausedMaximumEffect);
controlButtons.forEach((button, index) => {
const isDisabled = browserNavStates[index];
expect(button.classList.contains('enabled')).toBe(!isDisabled);
});
});
});
......@@ -15,20 +15,19 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrowserNavBarMain, BrowserNavBarMainProps } from './BrowserNavBarMain';
import ChromosomeNavigator from 'src/content/app/browser/chromosome-navigator/ChromosomeNavigator';
import BrowserNavBarRegionSwitcher from './BrowserNavBarRegionSwitcher';
import { BreakpointWidth } from 'src/global/globalConfig';
jest.mock(
'src/content/app/browser/chromosome-navigator/ChromosomeNavigator',
() => () => <div>ChromosomeNavigator</div>
() => () => <div className="chromosomeNavigator" />
);
jest.mock('./BrowserNavBarRegionSwitcher', () => () => (
<div>BrowserNavBarRegionSwitcher</div>
<div className="browserNavBarRegionSwitcher" />
));
const props: BrowserNavBarMainProps = {
......@@ -36,46 +35,56 @@ const props: BrowserNavBarMainProps = {
};
describe('BrowserNavBarMain', () => {
let wrapper: any;
beforeEach(() => {
wrapper = mount(<BrowserNavBarMain {...props} />);
});
afterEach(() => {
jest.resetAllMocks();
});
it('does not render chromosome visualization by default for screens smaller than laptops', () => {
expect(wrapper.find(ChromosomeNavigator).length).toBe(0);
const { container } = render(<BrowserNavBarMain {...props} />);
expect(container.querySelector('.chromosomeNavigator')).toBeFalsy();
});
it('renders chromosome visualization by default for laptops or bigger screens', () => {
wrapper.setProps({ viewportWidth: BreakpointWidth.LAPTOP });
const { container } = render(
<BrowserNavBarMain {...props} viewportWidth={BreakpointWidth.LAPTOP} />
);
expect(wrapper.find(ChromosomeNavigator).length).toBe(1);
expect(wrapper.find(BrowserNavBarRegionSwitcher).length).toBe(0);
expect(container.querySelector('.chromosomeNavigator')).toBeTruthy();
expect(container.querySelector('.browserNavBarRegionSwitcher')).toBeFalsy();
});
it('renders RegionSwitcher when user clicks on Change', () => {
wrapper.setProps({ viewportWidth: BreakpointWidth.LAPTOP });
const changeButton = wrapper.find('.contentSwitcher');
changeButton.simulate('click');
expect(wrapper.find(ChromosomeNavigator).length).toBe(0);
expect(wrapper.find(BrowserNavBarRegionSwitcher).length).toBe(1);
const { container } = render(
<BrowserNavBarMain {...props} viewportWidth={BreakpointWidth.LAPTOP} />
);
const changeButton = container.querySelector(
'.contentSwitcher'
) as HTMLSpanElement;
userEvent.click(changeButton);
expect(container.querySelector('.chromosomeNavigator')).toBeFalsy();
expect(
container.querySelector('.browserNavBarRegionSwitcher')
).toBeTruthy();
});
it('renders chromosome visualization when user closes RegionSwitcher', () => {
wrapper.setProps({ viewportWidth: BreakpointWidth.LAPTOP });
const changeButton = wrapper.find('.contentSwitcher');
changeButton.simulate('click');
const closeButton = wrapper.find('.contentSwitcherClose');
closeButton.simulate('click');
expect(wrapper.find(ChromosomeNavigator).length).toBe(1);
expect(wrapper.find(BrowserNavBarRegionSwitcher).length).toBe(0);
const { container } = render(
<BrowserNavBarMain {...props} viewportWidth={BreakpointWidth.LAPTOP} />
);
const changeButton = container.querySelector(
'.contentSwitcher'
) as HTMLSpanElement;
userEvent.click(changeButton);
const closeButton = container.querySelector(
'.contentSwitcherClose'
) as HTMLSpanElement;
userEvent.click(closeButton);
expect(container.querySelector('.chromosomeNavigator')).toBeTruthy();
expect(container.querySelector('.browserNavBarRegionSwitcher')).toBeFalsy();
});
});
......@@ -15,21 +15,19 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import { BrowserNavBarRegionSwitcher } from './BrowserNavBarRegionSwitcher';
import BrowserRegionEditor from '../browser-region-editor/BrowserRegionEditor';
import BrowserRegionField from '../browser-region-field/BrowserRegionField';
import { BreakpointWidth } from 'src/global/globalConfig';
jest.mock(
'src/content/app/browser/browser-region-editor/BrowserRegionEditor',
() => () => <div>BrowserRegionEditor</div>
() => () => <div className="browserRegionEditor" />
);
jest.mock(
'src/content/app/browser/browser-region-field/BrowserRegionField',
() => () => <div>BrowserRegionField</div>
() => () => <div className="browserRegionField" />
);
const props = {
......@@ -39,37 +37,38 @@ const props = {
};
describe('BrowserNavBarRegionSwitcher', () => {
let wrapper: any;
beforeEach(() => {
wrapper = mount(<BrowserNavBarRegionSwitcher {...props} />);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
describe('rendering', () => {
it('renders region field and not region editor on smaller screens', () => {
expect(wrapper.find(BrowserRegionField)).toHaveLength(1);
expect(wrapper.find(BrowserRegionEditor)).toHaveLength(0);
it('renders only region field on smaller screens', () => {
const { container } = render(<BrowserNavBarRegionSwitcher {...props} />);
expect(container.querySelector('.browserRegionField')).toBeTruthy();
expect(container.querySelector('.browserRegionEditor')).toBeFalsy();
});
it('renders both region field and region editor on big desktop screens', () => {
wrapper.setProps({ viewportWidth: BreakpointWidth.BIG_DESKTOP });
const { container } = render(
<BrowserNavBarRegionSwitcher
{...props}
viewportWidth={BreakpointWidth.BIG_DESKTOP}
/>
);
expect(wrapper.find(BrowserRegionEditor)).toHaveLength(1);
expect(wrapper.find(BrowserRegionField)).toHaveLength(1);
expect(container.querySelector('.browserRegionField')).toBeTruthy();
expect(container.querySelector('.browserRegionEditor')).toBeTruthy();
});
});
it('calls cleanup functions on unmount', () => {
const wrapper = mount(<BrowserNavBarRegionSwitcher {...props} />);
const { unmount } = render(<BrowserNavBarRegionSwitcher {...props} />);
expect(props.toggleRegionEditorActive).not.toHaveBeenCalled();
expect(props.toggleRegionFieldActive).not.toHaveBeenCalled();
wrapper.unmount();
unmount();
expect(props.toggleRegionEditorActive).toHaveBeenCalledWith(false);
expect(props.toggleRegionFieldActive).toHaveBeenCalledWith(false);
......
......@@ -15,7 +15,8 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrowserNavIcon } from './BrowserNavIcon';
import browserMessagingService from 'src/content/app/browser/browser-messaging-service';
......@@ -27,12 +28,17 @@ describe('<BrowserNavIcon />', () => {
test('sends navigation message when clicked', () => {
jest.spyOn(browserMessagingService, 'send');
const renderedNavIcon = mount(
const { container } = render(
<BrowserNavIcon browserNavItem={browserNavItem} enabled={true} />
);
const button = container.querySelector('button') as HTMLButtonElement;
renderedNavIcon.find('button').simulate('click');
userEvent.click(button);
expect(browserMessagingService.send).toHaveBeenCalledTimes(1);
expect(browserMessagingService.send).toHaveBeenCalledWith(
'bpane',
browserNavItem.detail
);
(browserMessagingService.send as any).mockRestore();
});
......
......@@ -15,31 +15,36 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { screen, render, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import faker from 'faker';
import {
BrowserRegionEditor,
BrowserRegionEditorProps
} from './BrowserRegionEditor';
import Input from 'src/shared/components/input/Input';
import Select from 'src/shared/components/select/Select';
import Tooltip from 'src/shared/components/tooltip/Tooltip';
import Overlay from 'src/shared/components/overlay/Overlay';
import { createGenomeKaryotype } from 'tests/fixtures/genomes';
import { getCommaSeparatedNumber } from 'src/shared/helpers/formatters/numberFormatter';
import {
getCommaSeparatedNumber,
getNumberWithoutCommas
} from 'src/shared/helpers/formatters/numberFormatter';
import {
createChrLocationValues,
createRegionValidationMessages
} from 'tests/fixtures/browser';
import * as browserHelper from '../browserHelper';
describe('<BrowserRegionEditor />', () => {
afterEach(() => {
jest.resetAllMocks();
});
jest.mock('src/shared/components/select/Select', () => () => (
<div className="select" />
));
jest.mock('src/shared/components/overlay/Overlay', () => () => (
<div className="overlay" />
));
jest.mock('../browserHelper');
describe('<BrowserRegionEditor />', () => {
const initialChrLocation = createChrLocationValues().tupleValue;
const defaultProps: BrowserRegionEditorProps = {
activeGenomeId: faker.lorem.words(),
......@@ -52,64 +57,66 @@ describe('<BrowserRegionEditor />', () => {
toggleRegionEditorActive: jest.fn()
};
let wrapper: any;
beforeEach(() => {
wrapper = mount(<BrowserRegionEditor {...defaultProps} />);
jest.resetAllMocks();
});
describe('rendering', () => {
test('contains Select', () => {
expect(wrapper.find(Select).length).toBe(1);
it('contains Select', () => {
const { container } = render(<BrowserRegionEditor {...defaultProps} />);
expect(container.querySelector('.select')).toBeTruthy();
});
test('contains two Input elements', () => {
expect(wrapper.find(Input).length).toBe(2);
it('contains two input elements', () => {
const { container } = render(<BrowserRegionEditor {...defaultProps} />);
expect(container.querySelectorAll('input').length).toBe(2);
});
test('contains submit and close buttons', () => {
expect(wrapper.find('button[type="submit"]')).toHaveLength(1);
it('contains submit and close buttons', () => {
const { container } = render(<BrowserRegionEditor {...defaultProps} />);
expect(container.querySelector('button[type="submit"]')).toBeTruthy();
});
test('has an overlay on top when disabled', () => {
wrapper.setProps({ isDisabled: true });
expect(wrapper.find(Overlay).length).toBe(1);
it('has an overlay on top when disabled', () => {
const { container } = render(
<BrowserRegionEditor {...defaultProps} isDisabled={true} />
);
expect(container.querySelector('.overlay')).toBeTruthy();
});
});
describe('behaviour', () => {
test('shows form buttons when focussed', () => {
wrapper.find('form').simulate('focus');
expect(wrapper.props().toggleRegionEditorActive).toHaveBeenCalledTimes(1);
it('shows form buttons when focussed', () => {
const { container } = render(<BrowserRegionEditor {...defaultProps} />);
const form = container.querySelector('form') as HTMLFormElement;
fireEvent.focus(form);
expect(defaultProps.toggleRegionEditorActive).toHaveBeenCalledTimes(1);
});
test('validates region input on submit', () => {
it('validates region input on submit', () => {
const [stick] = initialChrLocation;
const locationStartInput = getCommaSeparatedNumber(faker.random.number());
const locationEndInput = getCommaSeparatedNumber(faker.random.number());
const validateRegion = jest.fn();
jest
.spyOn(browserHelper, 'validateRegion')
.mockImplementation(validateRegion);
const { container } = render(<BrowserRegionEditor {...defaultProps} />);
const [firstInput, secondInput] = container.querySelectorAll('input');
const submitButton = container.querySelector(
'button[type="submit"]'
) as HTMLButtonElement;
wrapper
.find(Input)
.first()
.simulate('change', {
target: { value: locationStartInput }
});
userEvent.clear(firstInput);
userEvent.type(firstInput, locationStartInput);
wrapper
.find(Input)
.last()
.simulate('change', { target: { value: locationEndInput } });
userEvent.clear(secondInput);
userEvent.type(secondInput, locationEndInput);
wrapper.find('form').simulate('submit');
userEvent.click(submitButton);
expect(validateRegion).toHaveBeenCalledWith({
expect(browserHelper.validateRegion).toHaveBeenCalledWith({
regionInput: `${stick}:${locationStartInput}-${locationEndInput}`,
genomeId: wrapper.props().activeGenomeId,
genomeId: defaultProps.activeGenomeId,
onSuccess: expect.any(Function),
onError: expect.any(Function)
});
......@@ -124,11 +131,11 @@ describe('<BrowserRegionEditor />', () => {
const locationStartInput = getCommaSeparatedNumber(faker.random.number());
const locationEndInput = getCommaSeparatedNumber(faker.random.number());
const startError = faker.lorem.words();
const endError = faker.lorem.words();
const startError = 'start error message';
const endError = 'end error message';
const mockValidationMessages = createRegionValidationMessages();
test('displays the start error message', () => {
it('displays the start error message', async () => {
const mockErrorMessages = {
...mockValidationMessages.errorMessages,
startError,
......@@ -138,39 +145,33 @@ describe('<BrowserRegionEditor />', () => {
jest
.spyOn(browserHelper, 'validateRegion')
.mockImplementation(
async (params: {
regionInput: string;
genomeId: string | null;
onSuccess: (regionId: string) => void;
onError: (
errorMessages: browserHelper.RegionValidationErrors
) => void;
}) => {
params.onError(mockErrorMessages);
}
async ({
onError
}: {
onError: (arg: typeof mockErrorMessages) => void;
}): Promise<void> => onError(mockErrorMessages)
);
wrapper
.find(Input)
.first()
.simulate('change', { target: { value: locationStartInput } });
const { container } = render(<BrowserRegionEditor {...defaultProps} />);