Cleanup Jest config (#6654)

General cleanup after the [Jest 24 PR](#6278). 

This also includes `jsdom@14` via https://www.npmjs.com/package/jest-environment-jsdom-fourteen. Since we have a node >= 8.10 requirement, we are able to use the latest version of `jsdom` which includes additional implementations of browser APIs such as `MutationObserver` (which we had an issue filed for over at #6617).

/cc @SimenB. Is there more you recommend we do to cleanup our [Jest config](b0cbf2caa1/packages/react-scripts/scripts/utils/createJestConfig.js) for TypeScript?

Co-authored-by: Ian Sutherland <ian@iansutherland.ca>
This commit is contained in:
Ian Schmitz
2019-04-04 10:00:00 -07:00
committed by Ian Sutherland
parent e630238d0d
commit 76fea02277
15 changed files with 165 additions and 149 deletions

View File

@@ -25,7 +25,7 @@
"get-port": "^4.2.0",
"globby": "^9.1.0",
"husky": "^1.3.1",
"jest": "^24.5.0",
"jest": "24.7.1",
"lerna": "2.9.1",
"lerna-changelog": "~0.8.2",
"lint-staged": "^8.0.4",

View File

@@ -15,8 +15,8 @@
"@babel/core": "^7.1.0"
},
"devDependencies": {
"babel-plugin-tester": "^5.5.1",
"jest": "^24.5.0"
"babel-plugin-tester": "^6.0.1",
"jest": "24.7.1"
},
"scripts": {
"test": "jest"

View File

@@ -16,6 +16,6 @@
"index.js"
],
"devDependencies": {
"jest": "24.5.0"
"jest": "24.7.1"
}
}

View File

@@ -74,7 +74,7 @@
},
"devDependencies": {
"cross-env": "^5.2.0",
"jest": "^24.5.0"
"jest": "24.7.1"
},
"scripts": {
"test": "cross-env FORCE_COLOR=true jest"

View File

@@ -33,9 +33,8 @@
"@babel/code-frame": "7.0.0",
"@babel/core": "7.3.4",
"anser": "1.4.8",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.1",
"babel-jest": "24.5.0",
"babel-jest": "24.7.1",
"babel-loader": "8.0.5",
"babel-preset-react-app": "^7.0.2",
"chalk": "^2.4.2",
@@ -49,7 +48,7 @@
"eslint-plugin-react": "7.12.4",
"flow-bin": "^0.63.1",
"html-entities": "1.2.1",
"jest": "24.5.0",
"jest": "24.7.1",
"jest-fetch-mock": "2.1.1",
"object-assign": "4.1.1",
"promise": "8.0.2",

View File

@@ -1,7 +1,7 @@
{
"dependencies": {
"bootstrap": "4.1.1",
"jest": "24.5.0",
"jest": "24.7.1",
"node-sass": "4.8.3",
"normalize.css": "7.0.0",
"prop-types": "15.5.6",

View File

@@ -8,7 +8,7 @@ Tests are automatically run by the CI tools.
In order to run them locally, without having to manually install and configure everything, the `yarn e2e:docker` CLI command can be used.
This is a simple script that runs a **Docker** container, where the node version, git branch to clone, test suite, and whether to run it with `yarn` or `npm` can be chosen.
Simply run `yarn e2e:docker -- --help` to get additional info.
Simply run `yarn e2e:docker --help` to get additional info.
If you need guidance installing **Docker**, you should follow their [official docs](https://docs.docker.com/engine/installation/).

View File

@@ -9,8 +9,15 @@ import initDOM from './initDOM';
describe('Integration', () => {
describe('Environment variables', () => {
let doc;
afterEach(() => {
doc && doc.defaultView.close();
doc = undefined;
});
it('file env variables', async () => {
const doc = await initDOM('file-env-variables');
doc = await initDOM('file-env-variables');
expect(
doc.getElementById('feature-file-env-original-1').textContent
@@ -34,18 +41,16 @@ describe('Integration', () => {
'x-from-development-env'
);
}
doc.defaultView.close();
});
it('NODE_PATH', async () => {
const doc = await initDOM('node-path');
doc = await initDOM('node-path');
expect(doc.getElementById('feature-node-path').childElementCount).toBe(4);
doc.defaultView.close();
});
it('PUBLIC_URL', async () => {
const doc = await initDOM('public-url');
doc = await initDOM('public-url');
const prefix =
process.env.NODE_ENV === 'development'
@@ -57,20 +62,18 @@ describe('Integration', () => {
expect(
doc.querySelector('head link[rel="shortcut icon"]').getAttribute('href')
).toBe(`${prefix}/favicon.ico`);
doc.defaultView.close();
});
it('shell env variables', async () => {
const doc = await initDOM('shell-env-variables');
doc = await initDOM('shell-env-variables');
expect(
doc.getElementById('feature-shell-env-variables').textContent
).toBe('fromtheshell.');
doc.defaultView.close();
});
it('expand .env variables', async () => {
const doc = await initDOM('expand-env-variables');
doc = await initDOM('expand-env-variables');
expect(doc.getElementById('feature-expand-env-1').textContent).toBe(
'basic'
@@ -84,7 +87,6 @@ describe('Integration', () => {
expect(
doc.getElementById('feature-expand-env-existing').textContent
).toBe('fromtheshell');
doc.defaultView.close();
});
});
});

View File

@@ -6,46 +6,31 @@
*/
const fs = require('fs');
const http = require('http');
const jsdom = require('jsdom/lib/old-api.js');
const { JSDOM, ResourceLoader } = require('jsdom');
const path = require('path');
const url = require('url');
let getMarkup;
export let resourceLoader;
if (process.env.E2E_FILE) {
const file = path.isAbsolute(process.env.E2E_FILE)
const file =
process.env.E2E_FILE &&
(path.isAbsolute(process.env.E2E_FILE)
? process.env.E2E_FILE
: path.join(process.cwd(), process.env.E2E_FILE);
const markup = fs.readFileSync(file, 'utf8');
getMarkup = () => markup;
: path.join(process.cwd(), process.env.E2E_FILE));
export const fetchFile = url => {
const pathPrefix = process.env.PUBLIC_URL.replace(/^https?:\/\/[^/]+\/?/, '');
return fs.readFileSync(
path.join(path.dirname(file), url.pathname.replace(pathPrefix, '')),
'utf8'
);
};
resourceLoader = (resource, callback) =>
callback(
null,
fs.readFileSync(
path.join(
path.dirname(file),
resource.url.pathname.replace(pathPrefix, '')
),
'utf8'
)
);
} else if (process.env.E2E_URL) {
getMarkup = () =>
new Promise(resolve => {
http.get(process.env.E2E_URL, res => {
let rawData = '';
res.on('data', chunk => (rawData += chunk));
res.on('end', () => resolve(rawData));
});
});
const fileResourceLoader = new class FileResourceLoader extends ResourceLoader {
fetch(href, options) {
return Promise.resolve(fetchFile(url.parse(href)));
}
}();
resourceLoader = (resource, callback) => resource.defaultFetch(callback);
} else {
if (!process.env.E2E_FILE && !process.env.E2E_URL) {
it.only('can run jsdom (at least one of "E2E_FILE" or "E2E_URL" environment variables must be provided)', () => {
expect(
new Error("This isn't the error you are looking for.")
@@ -54,18 +39,46 @@ if (process.env.E2E_FILE) {
}
export default feature =>
new Promise(async resolve => {
const markup = await getMarkup();
const host = process.env.E2E_URL || 'http://www.example.org/spa:3000';
const doc = jsdom.jsdom(markup, {
created: (_, win) =>
win.addEventListener('ReactFeatureDidMount', () => resolve(doc), true),
deferClose: true,
pretendToBeVisual: true,
resourceLoader,
url: `${host}#${feature}`,
virtualConsole: jsdom.createVirtualConsole().sendTo(console),
});
new Promise(async (resolve, reject) => {
try {
const host = process.env.E2E_URL || 'http://www.example.org/spa:3000';
const url = `${host}#${feature}`;
doc.close();
let window;
if (process.env.E2E_FILE) {
window = (await JSDOM.fromFile(file, {
pretendToBeVisual: true,
resources: fileResourceLoader,
runScripts: 'dangerously',
url,
})).window;
} else {
window = (await JSDOM.fromURL(url, {
pretendToBeVisual: true,
resources: 'usable',
runScripts: 'dangerously',
})).window;
}
const { document } = window;
document.addEventListener(
'ReactFeatureDidMount',
() => resolve(document),
{ capture: true, once: true }
);
document.addEventListener(
'ReactFeatureError',
() => {
// Cleanup jsdom instance since we don't need it anymore
window.close();
reject(`Error loading feature: ${feature}`);
},
{ capture: true, once: true }
);
} catch (e) {
reject(e);
}
});

View File

@@ -9,137 +9,129 @@ import initDOM from './initDOM';
describe('Integration', () => {
describe('Language syntax', () => {
let doc;
afterEach(() => {
doc && doc.defaultView.close();
doc = undefined;
});
it('array destructuring', async () => {
const doc = await initDOM('array-destructuring');
doc = await initDOM('array-destructuring');
expect(
doc.getElementById('feature-array-destructuring').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('array spread', async () => {
const doc = await initDOM('array-spread');
doc = await initDOM('array-spread');
expect(doc.getElementById('feature-array-spread').childElementCount).toBe(
4
);
doc.defaultView.close();
});
it('async/await', async () => {
const doc = await initDOM('async-await');
doc = await initDOM('async-await');
expect(doc.getElementById('feature-async-await').childElementCount).toBe(
4
);
doc.defaultView.close();
});
it('class properties', async () => {
const doc = await initDOM('class-properties');
doc = await initDOM('class-properties');
expect(
doc.getElementById('feature-class-properties').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('computed properties', async () => {
const doc = await initDOM('computed-properties');
doc = await initDOM('computed-properties');
expect(
doc.getElementById('feature-computed-properties').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('custom interpolation', async () => {
const doc = await initDOM('custom-interpolation');
doc = await initDOM('custom-interpolation');
expect(
doc.getElementById('feature-custom-interpolation').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('default parameters', async () => {
const doc = await initDOM('default-parameters');
doc = await initDOM('default-parameters');
expect(
doc.getElementById('feature-default-parameters').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('destructuring and await', async () => {
const doc = await initDOM('destructuring-and-await');
doc = await initDOM('destructuring-and-await');
expect(
doc.getElementById('feature-destructuring-and-await').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('generators', async () => {
const doc = await initDOM('generators');
doc = await initDOM('generators');
expect(doc.getElementById('feature-generators').childElementCount).toBe(
4
);
doc.defaultView.close();
});
it('object destructuring', async () => {
const doc = await initDOM('object-destructuring');
doc = await initDOM('object-destructuring');
expect(
doc.getElementById('feature-object-destructuring').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('object spread', async () => {
const doc = await initDOM('object-spread');
doc = await initDOM('object-spread');
expect(
doc.getElementById('feature-object-spread').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('promises', async () => {
const doc = await initDOM('promises');
doc = await initDOM('promises');
expect(doc.getElementById('feature-promises').childElementCount).toBe(4);
doc.defaultView.close();
});
it('rest + default', async () => {
const doc = await initDOM('rest-and-default');
doc = await initDOM('rest-and-default');
expect(
doc.getElementById('feature-rest-and-default').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('rest parameters', async () => {
const doc = await initDOM('rest-parameters');
doc = await initDOM('rest-parameters');
expect(
doc.getElementById('feature-rest-parameters').childElementCount
).toBe(4);
doc.defaultView.close();
});
it('template interpolation', async () => {
const doc = await initDOM('template-interpolation');
doc = await initDOM('template-interpolation');
expect(
doc.getElementById('feature-template-interpolation').childElementCount
).toBe(4);
doc.defaultView.close();
});
});
});

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import initDOM, { resourceLoader } from './initDOM';
import initDOM, { fetchFile } from './initDOM';
import url from 'url';
const matchCSS = (doc, regexes) => {
@@ -17,11 +17,11 @@ const matchCSS = (doc, regexes) => {
href = elem.href;
}
}
resourceLoader({ url: url.parse(href) }, (_, textContent) => {
for (const regex of regexes) {
expect(textContent).toMatch(regex);
}
});
const textContent = fetchFile(url.parse(href));
for (const regex of regexes) {
expect(textContent).toMatch(regex);
}
} else {
for (let i = 0; i < regexes.length; ++i) {
expect(
@@ -33,118 +33,111 @@ const matchCSS = (doc, regexes) => {
describe('Integration', () => {
describe('Webpack plugins', () => {
let doc;
afterEach(() => {
doc && doc.defaultView.close();
doc = undefined;
});
it('css inclusion', async () => {
const doc = await initDOM('css-inclusion');
doc = await initDOM('css-inclusion');
matchCSS(doc, [
/html\{/,
/#feature-css-inclusion\{background:.+;color:.+}/,
]);
doc.defaultView.close();
});
it('css modules inclusion', async () => {
const doc = await initDOM('css-modules-inclusion');
doc = await initDOM('css-modules-inclusion');
matchCSS(doc, [
/.+style_cssModulesInclusion__.+\{background:.+;color:.+}/,
/.+assets_cssModulesIndexInclusion__.+\{background:.+;color:.+}/,
]);
doc.defaultView.close();
});
it('scss inclusion', async () => {
const doc = await initDOM('scss-inclusion');
doc = await initDOM('scss-inclusion');
matchCSS(doc, [/#feature-scss-inclusion\{background:.+;color:.+}/]);
doc.defaultView.close();
});
it('scss modules inclusion', async () => {
const doc = await initDOM('scss-modules-inclusion');
doc = await initDOM('scss-modules-inclusion');
matchCSS(doc, [
/.+scss-styles_scssModulesInclusion.+\{background:.+;color:.+}/,
/.+assets_scssModulesIndexInclusion.+\{background:.+;color:.+}/,
]);
doc.defaultView.close();
});
it('sass inclusion', async () => {
const doc = await initDOM('sass-inclusion');
doc = await initDOM('sass-inclusion');
matchCSS(doc, [/#feature-sass-inclusion\{background:.+;color:.+}/]);
doc.defaultView.close();
});
it('sass modules inclusion', async () => {
const doc = await initDOM('sass-modules-inclusion');
doc = await initDOM('sass-modules-inclusion');
matchCSS(doc, [
/.+sass-styles_sassModulesInclusion.+\{background:.+;color:.+}/,
/.+assets_sassModulesIndexInclusion.+\{background:.+;color:.+}/,
]);
doc.defaultView.close();
});
it('image inclusion', async () => {
const doc = await initDOM('image-inclusion');
doc = await initDOM('image-inclusion');
expect(doc.getElementById('feature-image-inclusion').src).toMatch(
/^data:image\/jpeg;base64.+==$/
);
doc.defaultView.close();
});
it('no ext inclusion', async () => {
const doc = await initDOM('no-ext-inclusion');
doc = await initDOM('no-ext-inclusion');
expect(doc.getElementById('feature-no-ext-inclusion').href).toMatch(
/\/static\/media\/aFileWithoutExt\.[a-f0-9]{8}\.bin$/
);
doc.defaultView.close();
});
it('json inclusion', async () => {
const doc = await initDOM('json-inclusion');
doc = await initDOM('json-inclusion');
expect(doc.getElementById('feature-json-inclusion').textContent).toBe(
'This is an abstract.'
);
doc.defaultView.close();
});
it('linked modules', async () => {
const doc = await initDOM('linked-modules');
doc = await initDOM('linked-modules');
expect(doc.getElementById('feature-linked-modules').textContent).toBe(
'2.0.0'
);
doc.defaultView.close();
});
it('svg inclusion', async () => {
const doc = await initDOM('svg-inclusion');
doc = await initDOM('svg-inclusion');
expect(doc.getElementById('feature-svg-inclusion').src).toMatch(
/\/static\/media\/logo\..+\.svg$/
);
doc.defaultView.close();
});
it('svg component', async () => {
const doc = await initDOM('svg-component');
doc = await initDOM('svg-component');
expect(doc.getElementById('feature-svg-component').textContent).toBe('');
doc.defaultView.close();
});
it('svg in css', async () => {
const doc = await initDOM('svg-in-css');
doc = await initDOM('svg-in-css');
matchCSS(doc, [/\/static\/media\/logo\..+\.svg/]);
doc.defaultView.close();
});
it('unknown ext inclusion', async () => {
const doc = await initDOM('unknown-ext-inclusion');
doc = await initDOM('unknown-ext-inclusion');
expect(doc.getElementById('feature-unknown-ext-inclusion').href).toMatch(
/\/static\/media\/aFileWithExt\.[a-f0-9]{8}\.unknown$/
);
doc.defaultView.close();
});
});
});

View File

@@ -10,11 +10,17 @@ import PropTypes from 'prop-types';
class BuiltEmitter extends Component {
static propTypes = {
feature: PropTypes.func.isRequired,
error: PropTypes.string,
feature: PropTypes.func,
};
componentDidMount() {
const { feature } = this.props;
const { error, feature } = this.props;
if (error) {
this.handleError(error);
return;
}
// Class components must call this.props.onReady when they're ready for the test.
// We will assume functional components are ready immediately after mounting.
@@ -23,6 +29,10 @@ class BuiltEmitter extends Component {
}
}
handleError(error) {
document.dispatchEvent(new Event('ReactFeatureError'));
}
handleReady() {
document.dispatchEvent(new Event('ReactFeatureDidMount'));
}
@@ -34,9 +44,10 @@ class BuiltEmitter extends Component {
} = this;
return (
<div>
{createElement(feature, {
onReady: handleReady,
})}
{feature &&
createElement(feature, {
onReady: handleReady,
})}
</div>
);
}
@@ -52,7 +63,13 @@ class App extends Component {
}
componentDidMount() {
const feature = window.location.hash.slice(1);
const url = window.location.href;
// const feature = window.location.hash.slice(1);
// This works around an issue of a duplicate hash in the href
// Ex: http://localhost:3001/#array-destructuring#array-destructuring
// This seems like a jsdom bug as the URL in initDom.js appears to be correct
const feature = url.slice(url.lastIndexOf("#") + 1);
switch (feature) {
case 'array-destructuring':
import('./features/syntax/ArrayDestructuring').then(f =>
@@ -223,7 +240,7 @@ class App extends Component {
);
break;
default:
throw new Error(`Missing feature "${feature}"`);
this.setState({ error: `Missing feature "${feature}"` });
}
}
@@ -232,9 +249,9 @@ class App extends Component {
}
render() {
const { feature } = this.state;
if (feature !== null) {
return <BuiltEmitter feature={feature} />;
const { error, feature } = this.state;
if (error || feature) {
return <BuiltEmitter error={error} feature={feature} />;
}
return null;
}

View File

@@ -28,9 +28,8 @@
"@svgr/webpack": "4.1.0",
"@typescript-eslint/eslint-plugin": "1.4.1",
"@typescript-eslint/parser": "1.4.1",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.1",
"babel-jest": "24.5.0",
"babel-jest": "24.7.1",
"babel-loader": "8.0.5",
"babel-plugin-named-asset-import": "^0.3.1",
"babel-preset-react-app": "^7.0.2",
@@ -50,9 +49,10 @@
"fs-extra": "7.0.1",
"html-webpack-plugin": "4.0.0-beta.5",
"identity-obj-proxy": "3.0.0",
"jest": "24.5.0",
"jest-resolve": "24.5.0",
"jest-watch-typeahead": "^0.2.1",
"jest": "24.7.1",
"jest-environment-jsdom-fourteen": "0.1.0",
"jest-resolve": "24.7.1",
"jest-watch-typeahead": "0.3.0",
"mini-css-extract-plugin": "0.5.0",
"optimize-css-assets-webpack-plugin": "5.0.1",
"pnp-webpack-plugin": "1.2.1",

View File

@@ -35,8 +35,7 @@ module.exports = (resolve, rootDir, isEjecting) => {
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}',
],
testEnvironment: 'jsdom',
testURL: 'http://localhost',
testEnvironment: 'jest-environment-jsdom-fourteen',
transform: {
'^.+\\.(js|jsx|ts|tsx)$': isEjecting
? '<rootDir>/node_modules/babel-jest'
@@ -58,8 +57,8 @@ module.exports = (resolve, rootDir, isEjecting) => {
ext => !ext.includes('mjs')
),
watchPlugins: [
require.resolve('jest-watch-typeahead/filename'),
require.resolve('jest-watch-typeahead/testname'),
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
};
if (rootDir) {

View File

@@ -114,6 +114,7 @@ docker run \
--env NPM_CONFIG_PREFIX=/home/node/.npm \
--env NPM_CONFIG_QUIET=true \
--tty \
--rm \
--user node \
--volume ${PWD}/..:/var/create-react-app \
--workdir /home/node \