Skip to content

Commit

Permalink
test(*): migrate from enzyme to react-testing-library
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen authored and mergify[bot] committed Jul 27, 2021
1 parent ec04ddd commit 24a678d
Show file tree
Hide file tree
Showing 14 changed files with 544 additions and 846 deletions.
21 changes: 18 additions & 3 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
var Enzyme = require('enzyme');
var Adapter = require('enzyme-adapter-react-16');
// There should be a single listener which simply prints to the
// console. We will wrap that listener in our own listener.
const listeners = window._virtualConsole.listeners('jsdomError');
const originalListener = listeners && listeners[0];

Enzyme.configure({ adapter: new Adapter() });
window._virtualConsole.removeAllListeners('jsdomError');

// Add a new listener to swallow JSDOM errors that originate from clicks on anchor tags.
window._virtualConsole.addListener('jsdomError', (error) => {
if (
error.type !== 'not implemented' &&
error.message !== 'Not implemented: navigation (except hash changes)' &&
originalListener
) {
originalListener(error);
}

// swallow error
});
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@
"prop-types": "^15.6.1"
},
"peerDependencies": {
"react": "^16.8.0"
"react": ">=16.8.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/classnames": "^2.3.1",
"@types/enzyme": "^3.10.9",
"@types/jest": "^26.0.24",
"@types/lodash": "^4.14.171",
"@types/prop-types": "15.7.4",
Expand All @@ -66,8 +67,6 @@
"canvas": "2.8.0",
"chalk": "^4.0.0",
"cross-env": "^7.0.3",
"enzyme": "^3.4.1",
"enzyme-adapter-react-16": "^1.15.5",
"husky": "^4.3.6",
"jest": "27.0.6",
"prettier": "^2.3.2",
Expand All @@ -93,6 +92,7 @@
"transform": {
".(ts|tsx)": "ts-jest"
},
"testEnvironment": "jsdom",
"moduleFileExtensions": [
"js",
"json",
Expand Down
15 changes: 8 additions & 7 deletions src/__tests__/util.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { TransitionOptions, RawParams, StateOrName, TransitionPromise, memoryLocationPlugin } from '@uirouter/core';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { UIRouterReact } from '../core';
import { servicesPlugin, UIRouter } from '../index';
Expand All @@ -11,18 +11,19 @@ export const makeTestRouter = (states: ReactStateDeclaration[]) => {
router.plugin(servicesPlugin);
router.plugin(memoryLocationPlugin);
router.locationConfig.html5Mode = () => true;
states.forEach(state => router.stateRegistry.register(state));
states.forEach((state) => router.stateRegistry.register(state));

const mountInRouter: typeof mount = (children, opts) => {
const WrapperComponent = props => {
const mountInRouter: typeof render = (children, opts?) => {
const WrapperComponent = (props) => {
const cloned = React.cloneElement(children, props);
return <UIRouter router={router}>{cloned}</UIRouter>;
};
return mount(<WrapperComponent />, opts);

return render(<WrapperComponent />, opts) as any;
};

const routerGo = function(to: StateOrName, params?: RawParams, options?: TransitionOptions) {
return (act(() => router.stateService.go(to, params, options)) as any) as TransitionPromise;
const routerGo = function (to: StateOrName, params?: RawParams, options?: TransitionOptions): TransitionPromise {
return act(() => router.stateService.go(to, params, options) as any) as any;
};

return { router, routerGo, mountInRouter };
Expand Down
36 changes: 20 additions & 16 deletions src/components/__tests__/UIRouter.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render } from '@testing-library/react';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { mount } from 'enzyme';
import { UIRouterContext, UIRouter } from '../UIRouter';
import { UIRouterReact } from '../../core';
import { memoryLocationPlugin } from '../../index';
Expand All @@ -19,7 +19,7 @@ describe('<UIRouter>', () => {
it('throws an error if no plugin or router instance is passed via prop', () => {
muteConsoleErrors();
expect(() =>
mount(
render(
<UIRouter>
<Child />
</UIRouter>
Expand All @@ -28,45 +28,49 @@ describe('<UIRouter>', () => {
});

it('creates a router instance', () => {
const wrapper = mount(
const rendered = render(
<UIRouter plugins={[memoryLocationPlugin]} states={[]}>
<UIRouterContext.Consumer>{router => <Child router={router} />}</UIRouterContext.Consumer>
<UIRouterContext.Consumer>
{(router) => <span>{router === undefined ? 'yes' : 'no'}</span>}
</UIRouterContext.Consumer>
</UIRouter>
);
expect(wrapper.find(Child).props().router).toBeDefined();
expect(rendered.asFragment().textContent).toBe('no');
});

it('accepts an instance via prop', () => {
const router = new UIRouterReact();
router.plugin(memoryLocationPlugin);
const wrapper = mount(
const rendered = render(
<UIRouter router={router}>
<UIRouterContext.Consumer>{router => <Child router={router} />}</UIRouterContext.Consumer>
<UIRouterContext.Consumer>
{(instance) => <span>{instance === router ? 'yes' : 'no'}</span>}
</UIRouterContext.Consumer>
</UIRouter>
);
expect(wrapper.find(Child).props().router).toBe(router);
expect(rendered.asFragment().textContent).toBe('yes');
});

it('starts the router', () => {
const router = new UIRouterReact();
router.plugin(memoryLocationPlugin);
spyOn(router, 'start');
mount(
const spy = jest.spyOn(router, 'start');
render(
<UIRouter router={router}>
<UIRouterContext.Consumer>{router => <Child router={router} />}</UIRouterContext.Consumer>
<UIRouterContext.Consumer>{(router) => <Child router={router} />}</UIRouterContext.Consumer>
</UIRouter>
);
expect(router.start).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(1);
});

describe('<UIRouterCosumer>', () => {
it('passes down the router instance', () => {
let router;

mount(
render(
<UIRouter plugins={[memoryLocationPlugin]}>
<UIRouterContext.Consumer>
{_router => {
{(_router) => {
router = _router;
return null;
}}
Expand All @@ -80,10 +84,10 @@ describe('<UIRouter>', () => {
it('passes down the correct router instance when passed via props', () => {
const router = new UIRouterReact();
router.plugin(memoryLocationPlugin);
mount(
render(
<UIRouter router={router}>
<UIRouterContext.Consumer>
{_router => {
{(_router) => {
expect(_router).toBe(router);
return null;
}}
Expand Down
76 changes: 42 additions & 34 deletions src/components/__tests__/UISref.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { fireEvent } from '@testing-library/react';
import * as React from 'react';
import { UISref, UIView } from '../../components';
import { UISrefActiveContext } from '../UISrefActive';
Expand All @@ -21,22 +22,25 @@ const states = [
];

describe('<UISref>', () => {
beforeAll(() => jest.spyOn(React, 'useEffect').mockImplementation(React.useLayoutEffect));
afterAll(() => (React.useEffect as any).mockRestore());
let mockUseEffect: any;
beforeEach(() => (mockUseEffect = jest.spyOn(React, 'useEffect').mockImplementation(React.useLayoutEffect)));
afterEach(() => mockUseEffect.mockRestore());

let { router, routerGo, mountInRouter } = makeTestRouter([]);
beforeEach(() => ({ router, routerGo, mountInRouter } = makeTestRouter(states)));

it('renders its child with injected props', async () => {
const wrapper = mountInRouter(
<UISref to="state2">
<a>state2</a>
<a data-testid="anchor">state2</a>
</UISref>
);
await routerGo('state');
const props = wrapper.find('a').props();
expect(typeof props.onClick).toBe('function');
expect(props.href.includes('/state2')).toBe(true);
const goSpy = jest.spyOn(router.stateService, 'go');
const anchor = wrapper.getByTestId('anchor');
expect(anchor.getAttribute('href').includes('/state2')).toBe(true);
anchor.click();
expect(goSpy).toHaveBeenCalledTimes(1);
});

it('throws if state name is not a string', () => {
Expand All @@ -50,27 +54,26 @@ describe('<UISref>', () => {
const deregisterFn = jest.fn();
const parentUISrefActiveAddStateFn = jest.fn(() => deregisterFn);

const wrapper = mountInRouter(
mountInRouter(
<UISrefActiveContext.Provider value={parentUISrefActiveAddStateFn}>
<UIView />
</UISrefActiveContext.Provider>
);

expect(wrapper.html()).toBe('<a href="/state2" class="">state2</a>');
expect(parentUISrefActiveAddStateFn).toHaveBeenCalled();
await routerGo('state2');
expect(deregisterFn).toHaveBeenCalled();
});

it('triggers a transition to target state', async () => {
const goSpy = jest.spyOn(router.stateService, 'go');
const wrapper = mountInRouter(
const rendered = mountInRouter(
<UISref to="state2">
<a />
<a data-testid="anchor" />
</UISref>
);

wrapper.find('a').simulate('click');
rendered.getByTestId('anchor').click();

expect(goSpy).toHaveBeenCalledTimes(1);
expect(goSpy).toHaveBeenCalledWith('state2', expect.anything(), expect.anything());
Expand All @@ -80,12 +83,14 @@ describe('<UISref>', () => {
const log = [];
const goSpy = jest.spyOn(router.stateService, 'go').mockImplementation(() => log.push('go') as any);
const onClickSpy = jest.fn(() => log.push('onClick'));
const wrapper = mountInRouter(
const rendered = mountInRouter(
<UISref to="state2">
<a onClick={onClickSpy}>state2</a>
<a data-testid="anchor" onClick={onClickSpy}>
state2
</a>
</UISref>
);
wrapper.find('a').simulate('click');
rendered.getByTestId('anchor').click();

expect(onClickSpy).toHaveBeenCalled();
expect(goSpy).toHaveBeenCalled();
Expand All @@ -95,68 +100,71 @@ describe('<UISref>', () => {
it('calls the child elements onClick function and honors e.preventDefault()', async () => {
const goSpy = jest.spyOn(router.stateService, 'go');
const onClickSpy = jest.fn((e) => e.preventDefault());
const wrapper = mountInRouter(
const rendered = mountInRouter(
<UISref to="state2">
<a onClick={onClickSpy}>state2</a>
<a data-testid="anchor" onClick={onClickSpy}>
state2
</a>
</UISref>
);
wrapper.find('a').simulate('click');
rendered.getByTestId('anchor').click();

expect(onClickSpy).toHaveBeenCalled();
expect(goSpy).not.toHaveBeenCalled();
});

it("doesn't trigger a transition when middle-clicked", async () => {
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
const wrapper = mountInRouter(
const rendered = mountInRouter(
<UISref to="state2">
<a>state2</a>
<a data-testid="anchor">state2</a>
</UISref>
);

const link = wrapper.find('a');
link.simulate('click');
const link = rendered.getByTestId('anchor');
link.click();
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);

link.simulate('click', { button: 1 });
fireEvent(link, new MouseEvent('click', { button: 1 }));
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
});

it("doesn't trigger a transition when ctrl/meta/shift/alt+clicked", async () => {
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
const wrapper = mountInRouter(
const rendered = mountInRouter(
<UISref to="state2">
<a>state2</a>
<a data-testid="anchor">state2</a>
</UISref>
);

const link = wrapper.find('a');
link.simulate('click');
const link = rendered.getByTestId('anchor');
link.click();
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);

link.simulate('click', { ctrlKey: true });
fireEvent(link, new MouseEvent('click', { ctrlKey: true }));
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);

link.simulate('click', { metaKey: true });
fireEvent(link, new MouseEvent('click', { metaKey: true }));
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);

link.simulate('click', { shiftKey: true });
fireEvent(link, new MouseEvent('click', { shiftKey: true }));
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);

link.simulate('click', { altKey: true });
fireEvent(link, new MouseEvent('click', { altKey: true }));
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
});

it("doesn't trigger a transition when the anchor has a 'target' attribute", async () => {
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
const wrapper = mountInRouter(
const rendered = mountInRouter(
<UISref to="state2">
<a target="_blank">state2</a>
<a data-testid="anchor" target="_blank">
state2
</a>
</UISref>
);

const link = wrapper.find('a');
link.simulate('click');
rendered.getByTestId('anchor').click();
expect(stateServiceGoSpy).not.toHaveBeenCalled();
});
});
Loading

0 comments on commit 24a678d

Please sign in to comment.