From 1406fe8b6548ea5d516927d3e22553e6bf60e9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Fri, 7 Jul 2017 00:27:40 +0200 Subject: [PATCH] Tests: Setup Jest as an alternative test runner (#1382) --- .eslintrc.json | 14 ++++-- .gitignore | 1 + CONTRIBUTING.md | 2 +- blocks/api/test/paste.js | 11 ++-- blocks/test/full-content.js | 5 ++ components/form-token-field/test/index.js | 15 +----- editor/test/selectors.js | 2 +- package.json | 58 +++++++++++++++++++--- test/pgejs-transform.js | 16 ++++++ bootstrap-test.js => test/setup-globals.js | 27 +--------- test/setup-test-framework.js | 17 +++++++ test/setup-wp-aliases.js | 18 +++++++ test/style-mock.js | 1 + webpack.config.js | 31 ------------ 14 files changed, 129 insertions(+), 89 deletions(-) create mode 100644 test/pgejs-transform.js rename bootstrap-test.js => test/setup-globals.js (62%) create mode 100644 test/setup-test-framework.js create mode 100644 test/setup-wp-aliases.js create mode 100644 test/style-mock.js diff --git a/.eslintrc.json b/.eslintrc.json index 69be6d90c0d78..126c086a2e554 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,14 +4,16 @@ "extends": [ "wordpress", "plugin:react/recommended", - "plugin:jsx-a11y/recommended" - ], + "plugin:jsx-a11y/recommended", + "plugin:jest/recommended" + ], "env": { "browser": false, "es6": true, "node": true, - "mocha": true - }, + "mocha": true, + "jest/globals": true + }, "parserOptions": { "sourceType": "module", "ecmaFeatures": { @@ -26,7 +28,8 @@ }, "plugins": [ "react", - "jsx-a11y" + "jsx-a11y", + "jest" ], "settings": { "react": { @@ -46,6 +49,7 @@ "eol-last": "error", "func-call-spacing": "error", "indent": [ "error", "tab", { "SwitchCase": 1 } ], + "jest/valid-expect": "off", "jsx-quotes": "error", "key-spacing": "error", "keyword-spacing": "error", diff --git a/.gitignore b/.gitignore index 50b75a90309fc..d7d26aa3e3104 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules build +coverage gutenberg.pot .vscode *.log diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7aa0112686ead..5c35641668489 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ Gutenberg contains both PHP and JavaScript code, and encourages testing and code ### JavaScript Testing -Tests for JavaScript use [Mocha](https://mochajs.org/) as the test runner and [Chai BDD](http://chaijs.com/api/bdd/) as an assertion library (with a [small variation](https://github.com/prodatakey/dirty-chai) on assertion properties). If needed, you can also use [Sinon](http://sinonjs.org/) for mocking and [Enzyme](https://github.com/airbnb/enzyme) for React component testing. +Tests for JavaScript use [Jest](http://facebook.github.io/jest//) as the test runner. If needed, you can also use [Sinon](http://sinonjs.org/) for mocking and [Enzyme](https://github.com/airbnb/enzyme) for React component testing. Assuming you've followed the instructions above to install Node and project dependencies, tests can be run from the command-line with NPM: diff --git a/blocks/api/test/paste.js b/blocks/api/test/paste.js index 66c00553951b5..8892a8ffc3d2f 100644 --- a/blocks/api/test/paste.js +++ b/blocks/api/test/paste.js @@ -1,10 +1,11 @@ -import { describe, it } from 'mocha'; +/** + * External dependencies + */ import { equal } from 'assert'; -import { JSDOM } from 'jsdom'; - -const { window } = new JSDOM(); -const { document } = window; +/** + * Internal dependencies + */ import { normaliseToBlockLevelNodes } from '../paste'; describe( 'normaliseToBlockLevelNodes', () => { diff --git a/blocks/test/full-content.js b/blocks/test/full-content.js index 5d1948b7e158e..d3ff5b552ec60 100644 --- a/blocks/test/full-content.js +++ b/blocks/test/full-content.js @@ -89,6 +89,11 @@ function normalizeParsedBlocks( blocks ) { } describe( 'full post content fixture', () => { + before( () => { + // Registers all blocks + require( 'blocks' ); + } ); + fileBasenames.forEach( f => { it( f, () => { const content = readFixtureFile( f + '.html' ); diff --git a/components/form-token-field/test/index.js b/components/form-token-field/test/index.js index 88517e684392f..7c818d6b44231 100644 --- a/components/form-token-field/test/index.js +++ b/components/form-token-field/test/index.js @@ -31,11 +31,9 @@ const charCodes = { comma: 44, }; -describe( 'FormTokenField', function() { - if ( ! process.env.RUN_SLOW_TESTS ) { - return; - } +const maybeDescribe = process.env.RUN_SLOW_TESTS ? describe : describe.skip; +maybeDescribe( 'FormTokenField', function() { let wrapper, tokenFieldNode, textInputNode; function setText( text ) { @@ -181,9 +179,6 @@ describe( 'FormTokenField', function() { } ); it( 'should manage the selected suggestion based on both keyboard and mouse events', test( function() { - // We need a high timeout here to accomodate Travis CI - this.timeout( 10000 ); - setText( 'th' ); expect( getSuggestionsText() ).to.deep.equal( fixtures.matchingSuggestions.th ); expect( getSelectedSuggestion() ).to.equal( null ); @@ -397,12 +392,6 @@ describe( 'FormTokenField', function() { expect( textInputNode.prop( 'value' ) ).to.equal( ' quux' ); } ); - it( 'should skip empty tokens at the beginning of a paste', function() { - setText( ', ,\t \t ,,baz, quux' ); - expect( wrapper.state( 'tokens' ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ] ); - expect( textInputNode.prop( 'value' ) ).to.equal( ' quux' ); - } ); - it( 'should skip empty tokens in the middle of a paste', function() { setText( 'baz, ,\t \t ,,quux' ); expect( wrapper.state( 'tokens' ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ] ); diff --git a/editor/test/selectors.js b/editor/test/selectors.js index a92f4bfadf17b..796ad2b6772e4 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -681,7 +681,7 @@ describe( 'selectors', () => { expect( getMultiSelectedBlockUids( state ) ).to.eql( [] ); } ); - it( 'should return empty if there is no multi selection', () => { + it( 'should return selected block uids if there is multi selection', () => { const state = { editor: { blockOrder: [ 5, 4, 3, 2, 1 ], diff --git a/package.json b/package.json index a0ac4b25f9deb..fde91ce6b0bfc 100644 --- a/package.json +++ b/package.json @@ -59,14 +59,14 @@ "enzyme": "^2.8.2", "eslint": "^3.17.1", "eslint-config-wordpress": "^1.1.0", + "eslint-plugin-jest": "~20.0.3", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.3", "expose-loader": "^0.7.3", "extract-text-webpack-plugin": "^2.1.0", "gettext-parser": "^1.2.2", - "glob": "^7.1.1", - "jsdom": "^10.1.0", - "mocha": "^3.2.0", + "jest": "~20.0.4", + "jest-junit-reporter": "~1.1.0", "node-sass": "^4.5.0", "pegjs": "^0.10.0", "pegjs-loader": "^0.5.1", @@ -82,11 +82,9 @@ "sinon-test": "^1.0.2", "style-loader": "^0.14.1", "tinymce": "^4.5.6", - "webpack": "^2.2.1", - "webpack-node-externals": "^1.5.4" + "webpack": "^2.2.1" }, "scripts": { - "test-unit": "cross-env NODE_ENV=test webpack && mocha build --require bootstrap-test.js", "build": "cross-env BABEL_ENV=default NODE_ENV=production webpack", "gettext-strings": "cross-env BABEL_ENV=gettext webpack", "lint": "eslint . .storybook", @@ -95,6 +93,52 @@ "ci": "concurrently \"npm run build\" \"npm test\"", "package-plugin": "./bin/build-plugin-zip.sh", "storybook": "start-storybook -p 6006", - "build-storybook": "build-storybook" + "build-storybook": "build-storybook", + "test-unit": "jest", + "test-unit:coverage": "jest --coverage", + "test-unit:watch": "jest --watch" + }, + "jest": { + "collectCoverageFrom": [ + "**/blocks/**/*.js", + "**/components/**/*.js", + "**/date/**/*.js", + "**/editor/**/*.js", + "**/element/**/*.js", + "**/i18n/**/*.js", + "**/utils/**/*.js" + ], + "coveragePathIgnorePatterns": [ + "/components/clipboard-button/index.js", + "/components/notice/index.js", + "/components/notice/list.js", + "/components/form-toggle/index.js", + "/components/form-token-field/token.js" + ], + "moduleNameMapper": { + "\\.scss$": "/test/style-mock.js" + }, + "modulePaths": [ + "" + ], + "setupFiles": [ + "/test/setup-globals.js", + "/test/setup-wp-aliases.js" + ], + "setupTestFrameworkScriptFile": "/test/setup-test-framework.js", + "testMatch": [ + "**/blocks/**/test/*.js", + "**/components/**/test/*.js", + "**/date/**/test/*.js", + "**/editor/**/test/*.js", + "**/element/**/test/*.js", + "**/i18n/**/test/*.js", + "**/utils/**/test/*.js" ], + "timers": "fake", + "transform": { + "^.+\\.jsx?$": "babel-jest", + "\\.pegjs$": "/test/pgejs-transform.js" + }, + "verbose": true } } diff --git a/test/pgejs-transform.js b/test/pgejs-transform.js new file mode 100644 index 0000000000000..b5f84d20aba00 --- /dev/null +++ b/test/pgejs-transform.js @@ -0,0 +1,16 @@ +const pegjs = require( 'pegjs' ); + +module.exports = { + process( src ) { + // Description of PEG.js options: https://github.com/pegjs/pegjs#javascript-api + const pegOptions = { + output: 'source', + cache: false, + optimize: 'speed', + trace: false, + }; + const methodName = ( typeof pegjs.generate === 'function' ) ? 'generate' : 'buildParser'; + + return `module.exports = ${ pegjs[ methodName ]( src, pegOptions ) };`; + }, +}; diff --git a/bootstrap-test.js b/test/setup-globals.js similarity index 62% rename from bootstrap-test.js rename to test/setup-globals.js index a29de5fc8b947..434a668cc097e 100644 --- a/bootstrap-test.js +++ b/test/setup-globals.js @@ -1,29 +1,3 @@ -// Chai plugins -require( 'chai' ) - .use( require( 'dirty-chai' ) ) - .use( require( 'sinon-chai' ) ); - -// Sinon plugins -const sinon = require( 'sinon' ); -const sinonTest = require( 'sinon-test' ); -sinon.test = sinonTest.configureTest( sinon ); -sinon.testCase = sinonTest.configureTestCase( sinon ); - -// Fake DOM -const { JSDOM } = require( 'jsdom' ); -const dom = new JSDOM( '', { - features: { - FetchExternalResources: false, - ProcessExternalResources: false, - SkipExternalResources: true, - }, -} ); - -global.window = dom.window; -global.document = dom.window.document; -global.navigator = dom.window.navigator; -global.requestAnimationFrame = window.setTimeout; - // These are necessary to load TinyMCE successfully global.URL = window.URL; global.window.tinyMCEPreInit = { @@ -60,6 +34,7 @@ global.window._wpDateSettings = { string: 'America/New_York', }, }; + global.wp = global.wp || {}; global.wp.a11y = { speak: () => {}, diff --git a/test/setup-test-framework.js b/test/setup-test-framework.js new file mode 100644 index 0000000000000..df9c3f0942ad0 --- /dev/null +++ b/test/setup-test-framework.js @@ -0,0 +1,17 @@ +require( 'core-js/modules/es7.object.values' ); + +// Chai plugins +require( 'chai' ) + .use( require( 'dirty-chai' ) ) + .use( require( 'sinon-chai' ) ); + +// Sinon plugins +const sinon = require( 'sinon' ); +const sinonTest = require( 'sinon-test' ); +sinon.test = sinonTest.configureTest( sinon ); +sinon.testCase = sinonTest.configureTestCase( sinon ); + +// Make sure we can share test helpers between Mocha and Jest +global.after = global.afterAll; +global.before = global.beforeAll; +global.context = global.describe; diff --git a/test/setup-wp-aliases.js b/test/setup-wp-aliases.js new file mode 100644 index 0000000000000..3a25617c50383 --- /dev/null +++ b/test/setup-wp-aliases.js @@ -0,0 +1,18 @@ +const lazySetupAlias = object => + name => + Object.defineProperty( object, name, { + get: () => require( name ), + } ); +const lazySetupWpAlias = lazySetupAlias( global.wp ); + +const entryPointNames = [ + 'element', + 'i18n', + 'components', + 'utils', + 'blocks', + 'date', + 'editor', +]; + +entryPointNames.forEach( lazySetupWpAlias ); diff --git a/test/style-mock.js b/test/style-mock.js new file mode 100644 index 0000000000000..f053ebf7976e3 --- /dev/null +++ b/test/style-mock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/webpack.config.js b/webpack.config.js index 2648adc0bd248..c1940ee209dab 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,6 @@ /** * External dependencies */ - -const glob = require( 'glob' ); const webpack = require( 'webpack' ); const ExtractTextPlugin = require( 'extract-text-webpack-plugin' ); @@ -149,35 +147,6 @@ switch ( process.env.NODE_ENV ) { config.plugins.push( new webpack.optimize.UglifyJsPlugin() ); break; - case 'test': - config.target = 'node'; - config.node = { - __dirname: true, - }; - config.module.rules = [ - ...entryPointNames.map( ( entry ) => ( { - test: require.resolve( './' + entry + '/index.js' ), - use: 'expose-loader?wp.' + entry, - } ) ), - ...config.module.rules, - ]; - const testFiles = glob.sync( - './{' + Object.keys( config.entry ).sort() + '}/**/test/*.js' - ); - config.entry = [ - ...entryPointNames.map( - entryPointName => './' + entryPointName + '/index.js' - ), - ...testFiles.filter( f => /full-content\.js$/.test( f ) ), - ...testFiles.filter( f => ! /full-content\.js$/.test( f ) ), - ]; - config.externals = [ require( 'webpack-node-externals' )() ]; - config.output = { - filename: 'build/test.js', - path: __dirname, - }; - break; - default: config.devtool = 'source-map'; }