Upgrading tests, replacing Enzyme with TestRenderer

This commit is contained in:
James Barnsley
2022-09-29 10:38:26 +13:00
parent 129ed32f88
commit 6d6a2b4f24
14 changed files with 7381 additions and 160 deletions

View File

@ -1,28 +0,0 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
],
"env": {
"development": {
"plugins": [
"react-element-info"
]
},
"test": {
"plugins": [
"react-element-info",
"@babel/plugin-transform-runtime",
["polyfill-corejs3", { "method": "usage-global" }]
],
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow'
import { render } from './test-wrapper';
import App from '../src/js/App';
jest.mock('react-redux', () => ({
@ -21,9 +21,7 @@ jest.mock('react-router-dom', () => ({
describe('<App />', () => {
it('should render', () => {
const renderer = new ShallowRenderer();
renderer.render(<App />);
const result = renderer.getRenderOutput();
expect(result.type).toEqual('div');
const result = render(<App />).toJSON();
expect(result).toMatchSnapshot();
});
});

View File

@ -1,70 +1,68 @@
import React from 'react';
import { BrowserRouter } from "react-router-dom";
// Testing-specific
import { shallow, mount, render } from 'enzyme';
// Test subjects
import { render } from '../test-wrapper';
import Dater from '../../src/js/components/Dater';
describe('<Dater />', () => {
jest.mock('react-redux', () => ({
useSelector: jest.fn(),
}));
describe('<Dater />', () => {
it('should render null when given invalid props', () => {
// Invalid type
const dom_1 = shallow(<Dater type="invalid-type" data={100000} />);
expect(dom_1.type()).toEqual(null);
const dom_1 = render(<Dater type="invalid-type" data={100000} />);
expect(dom_1.toJSON()).toEqual(null);
// Missing type
const dom_2 = shallow(<Dater data={100000} />);
expect(dom_2.type()).toEqual(null);
const dom_2 = render(<Dater data={100000} />);
expect(dom_2.toJSON()).toEqual(null);
// Missing data
const dom_4 = shallow(<Dater type="ago" />);
expect(dom_4.type()).toEqual(null);
const dom_4 = render(<Dater type="ago" />);
expect(dom_4.toJSON()).toEqual(null);
});
it('should handle milliseconds', () => {
const dom = shallow(<Dater type="length" data={30000} />);
expect(dom.text()).toEqual('0:30');
const dom = render(<Dater type="length" data={30000} />);
expect(dom.toJSON()).toEqual('0:30');
});
it('should handle date (yyyy-mm-dd)', () => {
const dom = shallow(<Dater type="date" data="2015-10-23" />);
expect(dom.text()).toEqual("23/10/2015");
const dom = render(<Dater type="date" data="2015-10-23" />);
expect(dom.toJSON()).toEqual("23/10/2015");
});
it('should handle date (mm/dd/yyyy)', () => {
const dom = shallow(<Dater type="date" data="10/23/2015" />);
expect(dom.text()).toEqual("23/10/2015");
const dom = render(<Dater type="date" data="10/23/2015" />);
expect(dom.toJSON()).toEqual("23/10/2015");
});
it('should handle ago (days)', () => {
var date = new Date();
date.setDate( date.getDate() - 3 );
const dom = shallow(<Dater type="ago" data={date} />);
expect(dom.text()).toEqual('3 days');
const dom = render(<Dater type="ago" data={date} />);
expect(dom.toJSON().join('')).toEqual('3 days');
});
it('should handle ago (hours)', () => {
var date = new Date();
date.setTime( date.getTime() - 3 * 3600000 );
const dom = shallow(<Dater type="ago" data={date} />);
expect(dom.text()).toEqual('3 hours');
const dom = render(<Dater type="ago" data={date} />);
expect(dom.toJSON().join('')).toEqual('3 hours');
});
it('should handle ago (minutes)', () => {
var date = new Date();
date.setTime( date.getTime() - 3 * 60000 );
const dom = shallow(<Dater type="ago" data={date} />);
expect(dom.text()).toEqual('3 minutes');
const dom = render(<Dater type="ago" data={date} />);
expect(dom.toJSON().join('')).toEqual('3 minutes');
});
it('should handle total time', () => {
var data = [
const data = [
{
duration: 5 * 60000
},
@ -75,7 +73,7 @@ describe('<Dater />', () => {
duration: 5 * 60000
}
];
const dom = shallow(<Dater type="total-time" data={data} />);
expect(dom.text()).toEqual('15 mins');
const dom = render(<Dater type="total-time" data={data} />);
expect(dom.toJSON().join('')).toEqual('15 mins');
});
});

View File

@ -1,15 +1,11 @@
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import TestRenderer from 'react-test-renderer';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { GridItem } from '../../src/js/components/GridItem';
import { render } from '../test-wrapper';
import state from '../state';
jest.mock('react-redux', () => ({
useSelector: jest.fn(),
useDispatch: () => jest.fn(),
useDrag: jest.fn(),
useDispatch: jest.fn(),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@ -27,32 +23,22 @@ describe('<GridItem />', () => {
const { core: { items } } = state;
it('should handle album', () => {
const result = TestRenderer.create(
<BrowserRouter>
<DndProvider backend={HTML5Backend}>
const result = render(
<GridItem item={items['local:album:md5:66fbea3593ba96a15a9d4188bebab50b']} />
</DndProvider>
</BrowserRouter>
).toJSON();
expect(result).toMatchSnapshot();
});
it('should handle artist', () => {
const result = TestRenderer.create(
<BrowserRouter>
<DndProvider backend={HTML5Backend}>
const result = render(
<GridItem item={items['local:artist:md5:4f6e4f979e2c40c5e6ad1735804c29bc']} />
</DndProvider>
</BrowserRouter>
).toJSON();
expect(result).toMatchSnapshot();
});
it('should handle playlist', () => {
const result = TestRenderer.create(
<BrowserRouter>
<DndProvider backend={HTML5Backend}>
const result = render(
<GridItem item={items['m3u:Local%20tester.m3u8']} />
</DndProvider>
</BrowserRouter>
).toJSON();
expect(result).toMatchSnapshot();
});

View File

@ -1,28 +1,17 @@
import React from 'react';
import { BrowserRouter } from "react-router-dom";
// Testing-specific
import { shallow, mount, render } from 'enzyme';
// Test subjects
import { render } from '../test-wrapper';
import Link from '../../src/js/components/Link';
describe('<Link />', () => {
const dom = mount(
<BrowserRouter>
const result = render(
<Link to="test" className="test-classname">Link contents</Link>
</BrowserRouter>
);
).toJSON();
it('should render a valid <a> tag', () => {
const a = dom.find('a');
expect(a.length).toBe(1);
expect(a.find('[href]').length).toBe(1);
expect(a.text()).toEqual('Link contents');
});
it('should handle className prop', () => {
expect(dom.find('a').hasClass('test-classname')).toBe(true);
expect(result.type).toEqual('a');
expect(result.props.href).toEqual('/test');
expect(result.children.join('')).toEqual('Link contents');
expect(result.props.className).toContain('test-classname');
});
});

View File

@ -10,7 +10,7 @@ exports[`<GridItem /> should handle album 1`] = `
className=" "
data-qa-file="Link"
data-qa-node="RouterLink"
href="/album/bG9jYWw6YWxidW06bWQ1OjY2ZmJlYTM1OTNiYTk2YTE1YTlkNDE4OGJlYmFiNTBi/Sirens of the Sea"
href="/album/bG9jYWw6YWxidW06bWQ1OjY2ZmJlYTM1OTNiYTk2YTE1YTlkNDE4OGJlYmFiNTBi/Sirens%20of%20the%20Sea"
onClick={[Function]}
onContextMenu={[Function]}
>
@ -31,8 +31,8 @@ exports[`<GridItem /> should handle album 1`] = `
data-qa-file="Thumbnail"
data-qa-node="div"
style={
Object {
"backgroundImage": "url(\\"/local/17338e740316f18dbb5e3331ac6be6c1-500x500.jpeg\\")",
{
"backgroundImage": "url("/local/17338e740316f18dbb5e3331ac6be6c1-500x500.jpeg")",
}
}
/>
@ -46,8 +46,14 @@ exports[`<GridItem /> should handle album 1`] = `
className="grid__item__name"
data-qa-file="GridItem"
data-qa-node="div"
>
<span
data-qa-file="GridItem"
data-qa-node="span"
title="Sirens of the Sea"
>
Sirens of the Sea
</span>
</div>
<div
className="grid__item__secondary"
@ -125,8 +131,8 @@ exports[`<GridItem /> should handle artist 1`] = `
data-qa-file="Thumbnail"
data-qa-node="div"
style={
Object {
"backgroundImage": "url(\\"https://i.scdn.co/image/4dc7080ef509c36203a131a0eab8dd5e4800d7c2\\")",
{
"backgroundImage": "url("https://i.scdn.co/image/4dc7080ef509c36203a131a0eab8dd5e4800d7c2")",
}
}
/>
@ -140,8 +146,14 @@ exports[`<GridItem /> should handle artist 1`] = `
className="grid__item__name"
data-qa-file="GridItem"
data-qa-node="div"
>
<span
data-qa-file="GridItem"
data-qa-node="span"
title="Above & Beyond"
>
Above & Beyond
</span>
</div>
<div
className="grid__item__secondary"
@ -182,7 +194,7 @@ exports[`<GridItem /> should handle playlist 1`] = `
className=" "
data-qa-file="Link"
data-qa-node="RouterLink"
href="/playlist/bTN1OkxvY2FsJTIwdGVzdGVyLm0zdTg=/Local tester"
href="/playlist/bTN1OkxvY2FsJTIwdGVzdGVyLm0zdTg=/Local%20tester"
onClick={[Function]}
onContextMenu={[Function]}
>
@ -203,8 +215,8 @@ exports[`<GridItem /> should handle playlist 1`] = `
data-qa-file="Thumbnail"
data-qa-node="div"
style={
Object {
"backgroundImage": "url(\\"null\\")",
{
"backgroundImage": "url("null")",
}
}
/>
@ -218,8 +230,14 @@ exports[`<GridItem /> should handle playlist 1`] = `
className="grid__item__name"
data-qa-file="GridItem"
data-qa-node="div"
>
<span
data-qa-file="GridItem"
data-qa-node="span"
title="Local tester"
>
Local tester
</span>
</div>
<div
className="grid__item__secondary"

View File

@ -0,0 +1,13 @@
const transformer = require("jest-transform-yaml").default
const newTransformer = {
...transformer,
process: function (...params) {
return {
code: transformer?.process(...params),
map: null,
}
},
}
module.exports = newTransformer;

View File

@ -1,4 +0,0 @@
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

View File

@ -260,3 +260,4 @@ state.core.items['jest:track:three'] = {
};
export default state;
export { state }

35
__tests__/test-wrapper.js Normal file
View File

@ -0,0 +1,35 @@
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { Provider as ReduxProvider } from 'react-redux/src';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { store } from '../src/js/store';
const customRender = (
element,
{
initialState,
mocks,
...options
} = {},
) => {
return TestRenderer.create(
(
<ReduxProvider store={store}>
<DndProvider backend={HTML5Backend}>
<BrowserRouter basename="/">
<Routes>
<Route path="*" element={element} />
</Routes>
</BrowserRouter>
</DndProvider>
</ReduxProvider>
),
{
...options,
},
);
};
export { customRender as render };

View File

@ -1,15 +1,21 @@
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { Album } from '../../src/js/views/Album';
import * as uiActions from '../../src/js/services/ui/actions';
import * as coreActions from '../../src/js/services/core/actions';
import state from '../state';
import { render } from '../test-wrapper';
import Album from '../../src/js/views/Album';
import { state as mockState } from '../state';
jest.mock('react-dnd', () => ({
...jest.requireActual('react-dnd'),
useDrag: jest.fn(),
useDrop: jest.fn(),
}));
jest.mock('redux-persist', () => ({
...jest.requireActual('redux-persist'),
persistReducer: jest.fn().mockImplementation((config, reducers) => reducers),
}));
jest.mock('react-redux', () => ({
useSelector: jest.fn(),
useDispatch: () => jest.fn(),
connect: () => jest.fn(),
useSelector: jest.fn(() => mockState),
useDispatch: jest.fn(fn => fn()),
connect: jest.fn(fn => fn()),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@ -28,14 +34,8 @@ describe('<Album />', () => {
// Need to rebuild Album to functional component, at which point I'll copy previous
// snapshot testing approach from other project
it.skip('should render accurately', () => {
const result = TestRenderer.create(
<Album
album={album}
uiActions={uiActions}
coreActions={coreActions}
/>
).toJSON();
it('should render accurately', () => {
const result = render(<Album album={album} />).toJSON();
expect(result).toMatchSnapshot();
});
});

View File

@ -1,36 +1,43 @@
module.exports = {
verbose: true,
collectCoverage: true,
testEnvironment: 'jsdom',
testMatch: [
"<rootDir>/__tests__/**/*.test.js?(x)",
],
moduleDirectories: [
"node_modules",
"src",
],
moduleFileExtensions: [
"js",
"jsx",
"yaml",
],
// moduleDirectories: [
// "node_modules",
// "src",
// ],
// moduleFileExtensions: [
// "js",
// "jsx",
// "yaml",
// ],
moduleNameMapper: {
/*"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|yml|yaml)$": "<rootDir>/__mocks__/fileMock.js",*/
"^.+\\.(css|scss|sass|less)$": "identity-obj-proxy",
},
transform: {
"\\.yaml$": "yaml-jest",
"^.+\\.jsx$": "babel-jest",
"^.+\\.js$": "babel-jest",
"^.+\\.ts$": "babel-jest",
"^.+\\.tsx$": "babel-jest",
"^.+\\.(j|t)sx?$": "babel-jest",
"^.+\\.ya?ml$": "<rootDir>/__tests__/jest-yaml-transformer.js",
},
transformIgnorePatterns: [
"node_modules/(?!react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|react-redux)"
],
coverageReporters: [
"lcov",
],
collectCoverageFrom: [
"src/**/*.{js,jsx,ts,tsx}",
],
setupFilesAfterEnv: [
"./__tests__/setup.js"
],
globals: {
window: {
location: {
hostname: 'localhost',
port: 6680,
protocol: 'http',
},
},
},
};

View File

@ -1,6 +1,6 @@
{
"name": "mopidy-iris",
"version": "3.64.1",
"version": "3.65.0",
"description": "Mopidy HTTP interface",
"repository": "https://github.com/jaedb/iris",
"author": "James Barnsley <james@barnsley.nz>",
@ -41,8 +41,8 @@
"devDependencies": {
"@babel/cli": "7.18.6",
"@babel/core": "7.18.6",
"@babel/eslint-parser": "7.18.2",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-transform-modules-commonjs": "7.18.6",
"@babel/plugin-transform-runtime": "7.18.6",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "7.18.6",
@ -50,7 +50,6 @@
"@babel/preset-stage-2": "7.8.3",
"@babel/preset-typescript": "7.18.6",
"@babel/register": "7.18.6",
"@babel/eslint-parser": "7.18.2",
"babel-jest": "28.1.2",
"babel-loader": "8.2.5",
"babel-plugin-add-module-exports": "^1.0.4",
@ -62,8 +61,6 @@
"copy-dir": "^1.3.0",
"core-js": "3.23.3",
"css-loader": "6.7.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"eslint": "8.19.0",
"eslint-config-airbnb": "19.0.4",
"eslint-plugin-import": "2.26.0",
@ -71,9 +68,12 @@
"eslint-plugin-react": "7.30.1",
"eslint-webpack-plugin": "3.2.0",
"expose-loader": "4.0.0",
"fake-indexeddb": "^4.0.0",
"file-loader": "6.2.0",
"fs-copy-file": "2.1.2",
"jest": "28.1.2",
"jest": "29.0.3",
"jest-environment-jsdom": "^29.1.1",
"jest-transform-yaml": "1.0.0",
"js-sha256": "^0.9.0",
"mini-css-extract-plugin": "2.6.1",
"parse5-htmlparser2-tree-adapter": "7.0.0",
@ -89,7 +89,6 @@
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.9.3",
"webpack-strip": "0.1.0",
"yaml-jest": "1.2.0",
"yaml-loader": "0.6.0"
},
"scripts": {

7209
yarn.lock Normal file

File diff suppressed because it is too large Load Diff