Skip to content

Commit 24a678d

Browse files
christopherthielenmergify[bot]
authored andcommitted
test(*): migrate from enzyme to react-testing-library
1 parent ec04ddd commit 24a678d

14 files changed

+544
-846
lines changed

Diff for: jest.setup.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
var Enzyme = require('enzyme');
2-
var Adapter = require('enzyme-adapter-react-16');
1+
// There should be a single listener which simply prints to the
2+
// console. We will wrap that listener in our own listener.
3+
const listeners = window._virtualConsole.listeners('jsdomError');
4+
const originalListener = listeners && listeners[0];
35

4-
Enzyme.configure({ adapter: new Adapter() });
6+
window._virtualConsole.removeAllListeners('jsdomError');
7+
8+
// Add a new listener to swallow JSDOM errors that originate from clicks on anchor tags.
9+
window._virtualConsole.addListener('jsdomError', (error) => {
10+
if (
11+
error.type !== 'not implemented' &&
12+
error.message !== 'Not implemented: navigation (except hash changes)' &&
13+
originalListener
14+
) {
15+
originalListener(error);
16+
}
17+
18+
// swallow error
19+
});

Diff for: package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,12 @@
4747
"prop-types": "^15.6.1"
4848
},
4949
"peerDependencies": {
50-
"react": "^16.8.0"
50+
"react": ">=16.8.0"
5151
},
5252
"devDependencies": {
53+
"@testing-library/jest-dom": "^5.14.1",
54+
"@testing-library/react": "^12.0.0",
5355
"@types/classnames": "^2.3.1",
54-
"@types/enzyme": "^3.10.9",
5556
"@types/jest": "^26.0.24",
5657
"@types/lodash": "^4.14.171",
5758
"@types/prop-types": "15.7.4",
@@ -66,8 +67,6 @@
6667
"canvas": "2.8.0",
6768
"chalk": "^4.0.0",
6869
"cross-env": "^7.0.3",
69-
"enzyme": "^3.4.1",
70-
"enzyme-adapter-react-16": "^1.15.5",
7170
"husky": "^4.3.6",
7271
"jest": "27.0.6",
7372
"prettier": "^2.3.2",
@@ -93,6 +92,7 @@
9392
"transform": {
9493
".(ts|tsx)": "ts-jest"
9594
},
95+
"testEnvironment": "jsdom",
9696
"moduleFileExtensions": [
9797
"js",
9898
"json",

Diff for: src/__tests__/util.tsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import { TransitionOptions, RawParams, StateOrName, TransitionPromise, memoryLocationPlugin } from '@uirouter/core';
3-
import { mount } from 'enzyme';
3+
import { render } from '@testing-library/react';
44
import { act } from 'react-dom/test-utils';
55
import { UIRouterReact } from '../core';
66
import { servicesPlugin, UIRouter } from '../index';
@@ -11,18 +11,19 @@ export const makeTestRouter = (states: ReactStateDeclaration[]) => {
1111
router.plugin(servicesPlugin);
1212
router.plugin(memoryLocationPlugin);
1313
router.locationConfig.html5Mode = () => true;
14-
states.forEach(state => router.stateRegistry.register(state));
14+
states.forEach((state) => router.stateRegistry.register(state));
1515

16-
const mountInRouter: typeof mount = (children, opts) => {
17-
const WrapperComponent = props => {
16+
const mountInRouter: typeof render = (children, opts?) => {
17+
const WrapperComponent = (props) => {
1818
const cloned = React.cloneElement(children, props);
1919
return <UIRouter router={router}>{cloned}</UIRouter>;
2020
};
21-
return mount(<WrapperComponent />, opts);
21+
22+
return render(<WrapperComponent />, opts) as any;
2223
};
2324

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

2829
return { router, routerGo, mountInRouter };

Diff for: src/components/__tests__/UIRouter.test.tsx

+20-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { render } from '@testing-library/react';
12
import * as React from 'react';
23
import * as PropTypes from 'prop-types';
3-
import { mount } from 'enzyme';
44
import { UIRouterContext, UIRouter } from '../UIRouter';
55
import { UIRouterReact } from '../../core';
66
import { memoryLocationPlugin } from '../../index';
@@ -19,7 +19,7 @@ describe('<UIRouter>', () => {
1919
it('throws an error if no plugin or router instance is passed via prop', () => {
2020
muteConsoleErrors();
2121
expect(() =>
22-
mount(
22+
render(
2323
<UIRouter>
2424
<Child />
2525
</UIRouter>
@@ -28,45 +28,49 @@ describe('<UIRouter>', () => {
2828
});
2929

3030
it('creates a router instance', () => {
31-
const wrapper = mount(
31+
const rendered = render(
3232
<UIRouter plugins={[memoryLocationPlugin]} states={[]}>
33-
<UIRouterContext.Consumer>{router => <Child router={router} />}</UIRouterContext.Consumer>
33+
<UIRouterContext.Consumer>
34+
{(router) => <span>{router === undefined ? 'yes' : 'no'}</span>}
35+
</UIRouterContext.Consumer>
3436
</UIRouter>
3537
);
36-
expect(wrapper.find(Child).props().router).toBeDefined();
38+
expect(rendered.asFragment().textContent).toBe('no');
3739
});
3840

3941
it('accepts an instance via prop', () => {
4042
const router = new UIRouterReact();
4143
router.plugin(memoryLocationPlugin);
42-
const wrapper = mount(
44+
const rendered = render(
4345
<UIRouter router={router}>
44-
<UIRouterContext.Consumer>{router => <Child router={router} />}</UIRouterContext.Consumer>
46+
<UIRouterContext.Consumer>
47+
{(instance) => <span>{instance === router ? 'yes' : 'no'}</span>}
48+
</UIRouterContext.Consumer>
4549
</UIRouter>
4650
);
47-
expect(wrapper.find(Child).props().router).toBe(router);
51+
expect(rendered.asFragment().textContent).toBe('yes');
4852
});
4953

5054
it('starts the router', () => {
5155
const router = new UIRouterReact();
5256
router.plugin(memoryLocationPlugin);
53-
spyOn(router, 'start');
54-
mount(
57+
const spy = jest.spyOn(router, 'start');
58+
render(
5559
<UIRouter router={router}>
56-
<UIRouterContext.Consumer>{router => <Child router={router} />}</UIRouterContext.Consumer>
60+
<UIRouterContext.Consumer>{(router) => <Child router={router} />}</UIRouterContext.Consumer>
5761
</UIRouter>
5862
);
59-
expect(router.start).toHaveBeenCalledTimes(1);
63+
expect(spy).toHaveBeenCalledTimes(1);
6064
});
6165

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

66-
mount(
70+
render(
6771
<UIRouter plugins={[memoryLocationPlugin]}>
6872
<UIRouterContext.Consumer>
69-
{_router => {
73+
{(_router) => {
7074
router = _router;
7175
return null;
7276
}}
@@ -80,10 +84,10 @@ describe('<UIRouter>', () => {
8084
it('passes down the correct router instance when passed via props', () => {
8185
const router = new UIRouterReact();
8286
router.plugin(memoryLocationPlugin);
83-
mount(
87+
render(
8488
<UIRouter router={router}>
8589
<UIRouterContext.Consumer>
86-
{_router => {
90+
{(_router) => {
8791
expect(_router).toBe(router);
8892
return null;
8993
}}

Diff for: src/components/__tests__/UISref.test.tsx

+42-34
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { fireEvent } from '@testing-library/react';
12
import * as React from 'react';
23
import { UISref, UIView } from '../../components';
34
import { UISrefActiveContext } from '../UISrefActive';
@@ -21,22 +22,25 @@ const states = [
2122
];
2223

2324
describe('<UISref>', () => {
24-
beforeAll(() => jest.spyOn(React, 'useEffect').mockImplementation(React.useLayoutEffect));
25-
afterAll(() => (React.useEffect as any).mockRestore());
25+
let mockUseEffect: any;
26+
beforeEach(() => (mockUseEffect = jest.spyOn(React, 'useEffect').mockImplementation(React.useLayoutEffect)));
27+
afterEach(() => mockUseEffect.mockRestore());
2628

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

3032
it('renders its child with injected props', async () => {
3133
const wrapper = mountInRouter(
3234
<UISref to="state2">
33-
<a>state2</a>
35+
<a data-testid="anchor">state2</a>
3436
</UISref>
3537
);
3638
await routerGo('state');
37-
const props = wrapper.find('a').props();
38-
expect(typeof props.onClick).toBe('function');
39-
expect(props.href.includes('/state2')).toBe(true);
39+
const goSpy = jest.spyOn(router.stateService, 'go');
40+
const anchor = wrapper.getByTestId('anchor');
41+
expect(anchor.getAttribute('href').includes('/state2')).toBe(true);
42+
anchor.click();
43+
expect(goSpy).toHaveBeenCalledTimes(1);
4044
});
4145

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

53-
const wrapper = mountInRouter(
57+
mountInRouter(
5458
<UISrefActiveContext.Provider value={parentUISrefActiveAddStateFn}>
5559
<UIView />
5660
</UISrefActiveContext.Provider>
5761
);
5862

59-
expect(wrapper.html()).toBe('<a href="/state2" class="">state2</a>');
6063
expect(parentUISrefActiveAddStateFn).toHaveBeenCalled();
6164
await routerGo('state2');
6265
expect(deregisterFn).toHaveBeenCalled();
6366
});
6467

6568
it('triggers a transition to target state', async () => {
6669
const goSpy = jest.spyOn(router.stateService, 'go');
67-
const wrapper = mountInRouter(
70+
const rendered = mountInRouter(
6871
<UISref to="state2">
69-
<a />
72+
<a data-testid="anchor" />
7073
</UISref>
7174
);
7275

73-
wrapper.find('a').simulate('click');
76+
rendered.getByTestId('anchor').click();
7477

7578
expect(goSpy).toHaveBeenCalledTimes(1);
7679
expect(goSpy).toHaveBeenCalledWith('state2', expect.anything(), expect.anything());
@@ -80,12 +83,14 @@ describe('<UISref>', () => {
8083
const log = [];
8184
const goSpy = jest.spyOn(router.stateService, 'go').mockImplementation(() => log.push('go') as any);
8285
const onClickSpy = jest.fn(() => log.push('onClick'));
83-
const wrapper = mountInRouter(
86+
const rendered = mountInRouter(
8487
<UISref to="state2">
85-
<a onClick={onClickSpy}>state2</a>
88+
<a data-testid="anchor" onClick={onClickSpy}>
89+
state2
90+
</a>
8691
</UISref>
8792
);
88-
wrapper.find('a').simulate('click');
93+
rendered.getByTestId('anchor').click();
8994

9095
expect(onClickSpy).toHaveBeenCalled();
9196
expect(goSpy).toHaveBeenCalled();
@@ -95,68 +100,71 @@ describe('<UISref>', () => {
95100
it('calls the child elements onClick function and honors e.preventDefault()', async () => {
96101
const goSpy = jest.spyOn(router.stateService, 'go');
97102
const onClickSpy = jest.fn((e) => e.preventDefault());
98-
const wrapper = mountInRouter(
103+
const rendered = mountInRouter(
99104
<UISref to="state2">
100-
<a onClick={onClickSpy}>state2</a>
105+
<a data-testid="anchor" onClick={onClickSpy}>
106+
state2
107+
</a>
101108
</UISref>
102109
);
103-
wrapper.find('a').simulate('click');
110+
rendered.getByTestId('anchor').click();
104111

105112
expect(onClickSpy).toHaveBeenCalled();
106113
expect(goSpy).not.toHaveBeenCalled();
107114
});
108115

109116
it("doesn't trigger a transition when middle-clicked", async () => {
110117
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
111-
const wrapper = mountInRouter(
118+
const rendered = mountInRouter(
112119
<UISref to="state2">
113-
<a>state2</a>
120+
<a data-testid="anchor">state2</a>
114121
</UISref>
115122
);
116123

117-
const link = wrapper.find('a');
118-
link.simulate('click');
124+
const link = rendered.getByTestId('anchor');
125+
link.click();
119126
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
120127

121-
link.simulate('click', { button: 1 });
128+
fireEvent(link, new MouseEvent('click', { button: 1 }));
122129
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
123130
});
124131

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

133-
const link = wrapper.find('a');
134-
link.simulate('click');
140+
const link = rendered.getByTestId('anchor');
141+
link.click();
135142
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
136143

137-
link.simulate('click', { ctrlKey: true });
144+
fireEvent(link, new MouseEvent('click', { ctrlKey: true }));
138145
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
139146

140-
link.simulate('click', { metaKey: true });
147+
fireEvent(link, new MouseEvent('click', { metaKey: true }));
141148
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
142149

143-
link.simulate('click', { shiftKey: true });
150+
fireEvent(link, new MouseEvent('click', { shiftKey: true }));
144151
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
145152

146-
link.simulate('click', { altKey: true });
153+
fireEvent(link, new MouseEvent('click', { altKey: true }));
147154
expect(stateServiceGoSpy).toHaveBeenCalledTimes(1);
148155
});
149156

150157
it("doesn't trigger a transition when the anchor has a 'target' attribute", async () => {
151158
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
152-
const wrapper = mountInRouter(
159+
const rendered = mountInRouter(
153160
<UISref to="state2">
154-
<a target="_blank">state2</a>
161+
<a data-testid="anchor" target="_blank">
162+
state2
163+
</a>
155164
</UISref>
156165
);
157166

158-
const link = wrapper.find('a');
159-
link.simulate('click');
167+
rendered.getByTestId('anchor').click();
160168
expect(stateServiceGoSpy).not.toHaveBeenCalled();
161169
});
162170
});

0 commit comments

Comments
 (0)