diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 054e0b0b0b62..934ee2da7a53 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,7 +3,7 @@ // for the documentation about the extensions.json format "recommendations": [ "dbaeumer.vscode-eslint", - "adamvoss.yaml", + "redhat.vscode-yaml", "flowtype.flow-for-vscode", "esbenp.prettier-vscode", "Orta.vscode-jest" diff --git a/CHANGELOG.md b/CHANGELOG.md index d374d3581a5e..e362eb52cc72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ ## master +### Features + +- `[babel-jest]` Add support for `babel.config.js` added in Babel 7.0.0 ([#6911](https://github.com/facebook/jest/pull/6911)) +- `[jest-cli]` Add Support for `globalSetup` and `globalTeardown` in projects ([#6865](https://github.com/facebook/jest/pull/6865)) + ### Fixes +- `[expect]` Fix variadic custom asymmetric matchers ([#6898](https://github.com/facebook/jest/pull/6898)) - `[jest-cli]` Fix incorrect `testEnvironmentOptions` warning ([#6852](https://github.com/facebook/jest/pull/6852)) - `[jest-each`] Prevent done callback being supplied to describe ([#6843](https://github.com/facebook/jest/pull/6843)) - `[jest-config`] Better error message for a case when a preset module was found, but no `jest-preset.js` or `jest-preset.json` at the root ([#6863](https://github.com/facebook/jest/pull/6863)) @@ -10,6 +16,8 @@ - `[docs]` Add custom toMatchSnapshot matcher docs ([#6837](https://github.com/facebook/jest/pull/6837)) - `[docs]` Improve the documentation regarding preset configuration ([#6864](https://github.com/facebook/jest/issues/6864)) +- `[docs]` Clarify usage of `--projects` CLI option ([#6872](https://github.com/facebook/jest/pull/6872)) +- `[docs]` Correct `failure-change` notification mode ([#6878](https://github.com/facebook/jest/pull/6878)) ## 23.5.0 diff --git a/README.md b/README.md index dedd4a9899ff..0a15324a470d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ CircleCI Build Status Travis Build Status Windows Build Status + Codecov badge npm version SemVer Blazing Fast diff --git a/TestUtils.js b/TestUtils.js index 41215da10eb8..b9c9417d8c60 100644 --- a/TestUtils.js +++ b/TestUtils.js @@ -79,6 +79,8 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = { errorOnDeprecated: false, filter: null, forceCoverageMatch: [], + globalSetup: null, + globalTeardown: null, globals: {}, haste: { providesModuleNodeModules: [], diff --git a/docs/CLI.md b/docs/CLI.md index 2ed79bea9d79..44e584f87d19 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -213,9 +213,9 @@ Alias: `-o`. Attempts to identify which tests to run based on which files have c Allows the test suite to pass when no files are found. -### `--projects ... ` +### `--projects ... ` -Run tests from one or more projects. +Run tests from one or more projects, found in the specified paths; also takes path globs. This option is the CLI equivalent of the [`projects`](configuration#projects-arraystring--projectconfig) configuration option. Note that if configuration files are found in the specified paths, _all_ projects specified within those configuration files will be run. ### `--reporters` diff --git a/docs/Configuration.md b/docs/Configuration.md index 07adbcb4a016..278921c06723 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -404,7 +404,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string] diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index fc60a8775b06..77236126a489 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -31,38 +31,41 @@ The argument to `expect` should be the value that your code produces, and any ar ### `expect.extend(matchers)` -You can use `expect.extend` to add your own matchers to Jest. For example, let's say that you're testing a number theory library and you're frequently asserting that numbers are divisible by other numbers. You could abstract that into a `toBeDivisibleBy` matcher: +You can use `expect.extend` to add your own matchers to Jest. For example, let's say that you're testing a number utility library and you're frequently asserting that numbers appear within particular ranges of other numbers. You could abstract that into a `toBeWithinRange` matcher: ```js expect.extend({ - toBeDivisibleBy(received, argument) { - const pass = received % argument == 0; + toBeWithinRange(received, floor, ceiling) { + const pass = received >= floor && received <= ceiling; if (pass) { return { message: () => - `expected ${received} not to be divisible by ${argument}`, + `expected ${received} not to be within range ${floor} - ${ceiling}`, pass: true, }; } else { return { - message: () => `expected ${received} to be divisible by ${argument}`, + message: () => + `expected ${received} to be within range ${floor} - ${ceiling}`, pass: false, }; } }, }); -test('even and odd numbers', () => { - expect(100).toBeDivisibleBy(2); - expect(101).not.toBeDivisibleBy(2); +test('numeric ranges', () => { + expect(100).toBeWithinRange(90, 110); + expect(101).not.toBeWithinRange(0, 100); expect({apples: 6, bananas: 3}).toEqual({ - apples: expect.toBeDivisibleBy(2), - bananas: expect.not.toBeDivisibleBy(2), + apples: expect.toBeWithinRange(1, 10), + bananas: expect.not.toBeWithinRange(11, 20), }); }); ``` -`expect.extend` also supports async matchers. Async matchers return a Promise so you will need to await the returned value. Let's use an example matcher to illustrate the usage of them. We are going to implement a very similar matcher than `toBeDivisibleBy`, only difference is that the divisible number is going to be pulled from an external source. +#### Async Matchers + +`expect.extend` also supports async matchers. Async matchers return a Promise so you will need to await the returned value. Let's use an example matcher to illustrate the usage of them. We are going to implement a matcher called `toBeDivisibleByExternalValue`, where the divisible number is going to be pulled from an external source. ```js expect.extend({ @@ -91,8 +94,23 @@ test('is divisible by external value', async () => { }); ``` +#### Custom Matchers API + Matchers should return an object (or a Promise of an object) with two keys. `pass` indicates whether there was a match or not, and `message` provides a function with no arguments that returns an error message in case of failure. Thus, when `pass` is false, `message` should return the error message for when `expect(x).yourMatcher()` fails. And when `pass` is true, `message` should return the error message for when `expect(x).not.yourMatcher()` fails. +Matchers are called with the argument passed to `expect(x)` followed by the arguments passed to `.yourMatcher(y, z)`: + +```js +expect.extend({ + yourMatcher(x, y, z) { + return { + pass: true, + message: '', + }; + }, +}); +``` + These helper functions can be found on `this` inside a custom matcher: #### `this.isNot` diff --git a/docs/TestingFrameworks.md b/docs/TestingFrameworks.md index 42b49be881d7..d668e010667c 100644 --- a/docs/TestingFrameworks.md +++ b/docs/TestingFrameworks.md @@ -26,3 +26,7 @@ Although Jest may be considered a React-specific test runner, in fact it is a un ## Redux - [Writing Tests](https://redux.js.org/recipes/writing-tests) by Redux docs + +## Express.js + +- [How to test Express.js with Jest and Supertest](http://www.albertgao.xyz/2017/05/24/how-to-test-expressjs-with-jest-and-supertest/) by Albert Gao ([@albertgao](https://twitter.com/albertgao)) diff --git a/e2e/__tests__/__snapshots__/show_config.test.js.snap b/e2e/__tests__/__snapshots__/show_config.test.js.snap index e12bb522fdff..0037be96eeee 100644 --- a/e2e/__tests__/__snapshots__/show_config.test.js.snap +++ b/e2e/__tests__/__snapshots__/show_config.test.js.snap @@ -17,6 +17,8 @@ exports[`--showConfig outputs config info and exits 1`] = ` \\"errorOnDeprecated\\": false, \\"filter\\": null, \\"forceCoverageMatch\\": [], + \\"globalSetup\\": null, + \\"globalTeardown\\": null, \\"globals\\": {}, \\"haste\\": { \\"providesModuleNodeModules\\": [] diff --git a/e2e/__tests__/global_setup.test.js b/e2e/__tests__/global_setup.test.js index 8772a8e3e07c..e9f0c0a73ae9 100644 --- a/e2e/__tests__/global_setup.test.js +++ b/e2e/__tests__/global_setup.test.js @@ -14,13 +14,26 @@ const runJest = require('../runJest'); const {cleanup} = require('../Utils'); const DIR = path.join(os.tmpdir(), 'jest-global-setup'); +const project1DIR = path.join(os.tmpdir(), 'jest-global-setup-project-1'); +const project2DIR = path.join(os.tmpdir(), 'jest-global-setup-project-2'); -beforeEach(() => cleanup(DIR)); -afterAll(() => cleanup(DIR)); +beforeEach(() => { + cleanup(DIR); + cleanup(project1DIR); + cleanup(project2DIR); +}); +afterAll(() => { + cleanup(DIR); + cleanup(project1DIR); + cleanup(project2DIR); +}); test('globalSetup is triggered once before all test suites', () => { const setupPath = path.resolve(__dirname, '../global-setup/setup.js'); - const result = runJest.json('global-setup', [`--globalSetup=${setupPath}`]); + const result = runJest.json('global-setup', [ + `--globalSetup=${setupPath}`, + `--testPathPattern=__tests__`, + ]); expect(result.status).toBe(0); const files = fs.readdirSync(DIR); expect(files).toHaveLength(1); @@ -32,6 +45,7 @@ test('jest throws an error when globalSetup does not export a function', () => { const setupPath = path.resolve(__dirname, '../global-setup/invalid_setup.js'); const {status, stderr} = runJest('global-setup', [ `--globalSetup=${setupPath}`, + `--testPathPattern=__tests__`, ]); expect(status).toBe(1); @@ -55,3 +69,36 @@ test('globalSetup function gets jest config object as a parameter', () => { expect(result.stdout).toBe(testPathPattern); }); + +test('should call globalSetup function of multiple projects', () => { + const configPath = path.resolve( + __dirname, + '../global-setup/projects.jest.config.js', + ); + + const result = runJest.json('global-setup', [`--config=${configPath}`]); + + expect(result.status).toBe(0); + + expect(fs.existsSync(DIR)).toBe(true); + expect(fs.existsSync(project1DIR)).toBe(true); + expect(fs.existsSync(project2DIR)).toBe(true); +}); + +test('should not call a globalSetup of a project if there are no tests to run from this project', () => { + const configPath = path.resolve( + __dirname, + '../global-setup/projects.jest.config.js', + ); + + const result = runJest.json('global-setup', [ + `--config=${configPath}`, + '--testPathPattern=project-1', + ]); + + expect(result.status).toBe(0); + + expect(fs.existsSync(DIR)).toBe(true); + expect(fs.existsSync(project1DIR)).toBe(true); + expect(fs.existsSync(project2DIR)).toBe(false); +}); diff --git a/e2e/__tests__/global_teardown.test.js b/e2e/__tests__/global_teardown.test.js index 82bb9ccd425b..da100ff045cd 100644 --- a/e2e/__tests__/global_teardown.test.js +++ b/e2e/__tests__/global_teardown.test.js @@ -15,9 +15,19 @@ const runJest = require('../runJest'); const {cleanup} = require('../Utils'); const DIR = path.join(os.tmpdir(), 'jest-global-teardown'); +const project1DIR = path.join(os.tmpdir(), 'jest-global-teardown-project-1'); +const project2DIR = path.join(os.tmpdir(), 'jest-global-teardown-project-2'); -beforeEach(() => cleanup(DIR)); -afterAll(() => cleanup(DIR)); +beforeEach(() => { + cleanup(DIR); + cleanup(project1DIR); + cleanup(project2DIR); +}); +afterAll(() => { + cleanup(DIR); + cleanup(project1DIR); + cleanup(project2DIR); +}); test('globalTeardown is triggered once after all test suites', () => { mkdirp.sync(DIR); @@ -27,7 +37,9 @@ test('globalTeardown is triggered once after all test suites', () => { ); const result = runJest.json('global-teardown', [ `--globalTeardown=${teardownPath}`, + `--testPathPattern=__tests__`, ]); + expect(result.status).toBe(0); const files = fs.readdirSync(DIR); expect(files).toHaveLength(1); @@ -42,6 +54,7 @@ test('jest throws an error when globalTeardown does not export a function', () = ); const {status, stderr} = runJest('global-teardown', [ `--globalTeardown=${teardownPath}`, + `--testPathPattern=__tests__`, ]); expect(status).toBe(1); @@ -65,3 +78,36 @@ test('globalTeardown function gets jest config object as a parameter', () => { expect(result.stdout).toBe(testPathPattern); }); + +test('should call globalTeardown function of multiple projects', () => { + const configPath = path.resolve( + __dirname, + '../global-teardown/projects.jest.config.js', + ); + + const result = runJest.json('global-teardown', [`--config=${configPath}`]); + + expect(result.status).toBe(0); + + expect(fs.existsSync(DIR)).toBe(true); + expect(fs.existsSync(project1DIR)).toBe(true); + expect(fs.existsSync(project2DIR)).toBe(true); +}); + +test('should not call a globalTeardown of a project if there are no tests to run from this project', () => { + const configPath = path.resolve( + __dirname, + '../global-teardown/projects.jest.config.js', + ); + + const result = runJest.json('global-teardown', [ + `--config=${configPath}`, + '--testPathPattern=project-1', + ]); + + expect(result.status).toBe(0); + + expect(fs.existsSync(DIR)).toBe(true); + expect(fs.existsSync(project1DIR)).toBe(true); + expect(fs.existsSync(project2DIR)).toBe(false); +}); diff --git a/e2e/global-setup/project-1/setup.js b/e2e/global-setup/project-1/setup.js new file mode 100644 index 000000000000..aa6897cf9645 --- /dev/null +++ b/e2e/global-setup/project-1/setup.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const crypto = require('crypto'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-setup-project-1'); + +module.exports = function() { + return new Promise((resolve, reject) => { + mkdirp.sync(DIR); + const fileId = crypto.randomBytes(20).toString('hex'); + fs.writeFileSync(path.join(DIR, fileId), 'setup'); + resolve(); + }); +}; diff --git a/e2e/global-setup/project-1/setup.test.js b/e2e/global-setup/project-1/setup.test.js new file mode 100644 index 000000000000..aa8e94403a3f --- /dev/null +++ b/e2e/global-setup/project-1/setup.test.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-setup-project-1'); + +test('should exist setup file', () => { + const files = fs.readdirSync(DIR); + expect(files).toHaveLength(1); + const setup = fs.readFileSync(path.join(DIR, files[0]), 'utf8'); + expect(setup).toBe('setup'); +}); diff --git a/e2e/global-setup/project-2/setup.js b/e2e/global-setup/project-2/setup.js new file mode 100644 index 000000000000..e47105b98f13 --- /dev/null +++ b/e2e/global-setup/project-2/setup.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const crypto = require('crypto'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-setup-project-2'); + +module.exports = function() { + return new Promise((resolve, reject) => { + mkdirp.sync(DIR); + const fileId = crypto.randomBytes(20).toString('hex'); + fs.writeFileSync(path.join(DIR, fileId), 'setup'); + resolve(); + }); +}; diff --git a/e2e/global-setup/project-2/setup.test.js b/e2e/global-setup/project-2/setup.test.js new file mode 100644 index 000000000000..082cba8cfa98 --- /dev/null +++ b/e2e/global-setup/project-2/setup.test.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-setup-project-2'); + +test('should exist setup file', () => { + const files = fs.readdirSync(DIR); + expect(files).toHaveLength(1); + const setup = fs.readFileSync(path.join(DIR, files[0]), 'utf8'); + expect(setup).toBe('setup'); +}); diff --git a/e2e/global-setup/projects.jest.config.js b/e2e/global-setup/projects.jest.config.js new file mode 100644 index 000000000000..23d5f5b71db5 --- /dev/null +++ b/e2e/global-setup/projects.jest.config.js @@ -0,0 +1,19 @@ +const path = require('path'); + +module.exports = { + globalSetup: '/setup.js', + projects: [ + { + displayName: 'project-1', + globalSetup: '/setup.js', + rootDir: path.resolve(__dirname, './project-1'), + testMatch: ['/**/*.test.js'], + }, + { + displayName: 'project-2', + globalSetup: '/setup.js', + rootDir: path.resolve(__dirname, './project-2'), + testMatch: ['/**/*.test.js'], + }, + ], +}; diff --git a/e2e/global-teardown/project-1/teardown.js b/e2e/global-teardown/project-1/teardown.js new file mode 100644 index 000000000000..7486fde66812 --- /dev/null +++ b/e2e/global-teardown/project-1/teardown.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const crypto = require('crypto'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-teardown-project-1'); + +module.exports = function() { + return new Promise((resolve, reject) => { + mkdirp.sync(DIR); + const fileId = crypto.randomBytes(20).toString('hex'); + fs.writeFileSync(path.join(DIR, fileId), 'teardown'); + resolve(); + }); +}; diff --git a/e2e/global-teardown/project-1/teardown.test.js b/e2e/global-teardown/project-1/teardown.test.js new file mode 100644 index 000000000000..ac42013e1b05 --- /dev/null +++ b/e2e/global-teardown/project-1/teardown.test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-teardown-project-1'); + +test('should not exist teardown file', () => { + expect(fs.existsSync(DIR)).toBe(false); +}); diff --git a/e2e/global-teardown/project-2/teardown.js b/e2e/global-teardown/project-2/teardown.js new file mode 100644 index 000000000000..10c44b943571 --- /dev/null +++ b/e2e/global-teardown/project-2/teardown.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const crypto = require('crypto'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-teardown-project-2'); + +module.exports = function() { + return new Promise((resolve, reject) => { + mkdirp.sync(DIR); + const fileId = crypto.randomBytes(20).toString('hex'); + fs.writeFileSync(path.join(DIR, fileId), 'teardown'); + resolve(); + }); +}; diff --git a/e2e/global-teardown/project-2/teardown.test.js b/e2e/global-teardown/project-2/teardown.test.js new file mode 100644 index 000000000000..12a26bd97149 --- /dev/null +++ b/e2e/global-teardown/project-2/teardown.test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-teardown-project-2'); + +test('should not exist teardown file', () => { + expect(fs.existsSync(DIR)).toBe(false); +}); diff --git a/e2e/global-teardown/projects.jest.config.js b/e2e/global-teardown/projects.jest.config.js new file mode 100644 index 000000000000..ef827dfb2e7a --- /dev/null +++ b/e2e/global-teardown/projects.jest.config.js @@ -0,0 +1,19 @@ +const path = require('path'); + +module.exports = { + globalTeardown: '/teardown.js', + projects: [ + { + displayName: 'project-1', + globalTeardown: '/teardown.js', + rootDir: path.resolve(__dirname, './project-1'), + testMatch: ['/**/*.test.js'], + }, + { + displayName: 'project-2', + globalTeardown: '/teardown.js', + rootDir: path.resolve(__dirname, './project-2'), + testMatch: ['/**/*.test.js'], + }, + ], +}; diff --git a/jest.config.js b/jest.config.js index 3bed6dd185ee..78a79560840f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -29,6 +29,8 @@ module.exports = { '/node_modules/', '/examples/', '/e2e/.*/__tests__', + '/e2e/global-setup', + '/e2e/global-teardown', '\\.snap$', '/packages/.*/build', '/packages/.*/build-es5', diff --git a/packages/babel-jest/src/index.js b/packages/babel-jest/src/index.js index fb0ac333249e..5484019e2f4f 100644 --- a/packages/babel-jest/src/index.js +++ b/packages/babel-jest/src/index.js @@ -24,6 +24,7 @@ import babelIstanbulPlugin from 'babel-plugin-istanbul'; const BABELRC_FILENAME = '.babelrc'; const BABELRC_JS_FILENAME = '.babelrc.js'; +const BABEL_CONFIG_JS_FILENAME = 'babel.config.js'; const BABEL_CONFIG_KEY = 'babel'; const PACKAGE_JSON = 'package.json'; const THIS_FILE = fs.readFileSync(__filename); @@ -45,7 +46,13 @@ const createTransformer = (options: any): Transformer => { cache[directory] = fs.readFileSync(configFilePath, 'utf8'); break; } - const configJsFilePath = path.join(directory, BABELRC_JS_FILENAME); + let configJsFilePath = path.join(directory, BABELRC_JS_FILENAME); + if (fs.existsSync(configJsFilePath)) { + // $FlowFixMe + cache[directory] = JSON.stringify(require(configJsFilePath)); + break; + } + configJsFilePath = path.join(directory, BABEL_CONFIG_JS_FILENAME); if (fs.existsSync(configJsFilePath)) { // $FlowFixMe cache[directory] = JSON.stringify(require(configJsFilePath)); diff --git a/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap b/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap index 381b08c9f64f..73440c1c56ea 100644 --- a/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`defines asymmetric matchers 1`] = ` +exports[`defines asymmetric unary matchers 1`] = ` "expect(received).toEqual(expected) Expected value to equal: @@ -19,7 +19,7 @@ Difference: }" `; -exports[`defines asymmetric matchers that can be prefixed by not 1`] = ` +exports[`defines asymmetric unary matchers that can be prefixed by not 1`] = ` "expect(received).toEqual(expected) Expected value to equal: @@ -38,6 +38,46 @@ Difference: }" `; -exports[`is available globally 1`] = `"expected 15 to be divisible by 2"`; +exports[`defines asymmetric variadic matchers 1`] = ` +"expect(received).toEqual(expected) + +Expected value to equal: + {\\"value\\": toBeWithinRange<4, 11>} +Received: + {\\"value\\": 3} + +Difference: + +- Expected ++ Received + + Object { +- \\"value\\": toBeWithinRange<4, 11>, ++ \\"value\\": 3, + }" +`; + +exports[`defines asymmetric variadic matchers that can be prefixed by not 1`] = ` +"expect(received).toEqual(expected) + +Expected value to equal: + {\\"value\\": not.toBeWithinRange<1, 3>} +Received: + {\\"value\\": 2} + +Difference: + +- Expected ++ Received + + Object { +- \\"value\\": not.toBeWithinRange<1, 3>, ++ \\"value\\": 2, + }" +`; + +exports[`is available globally when matcher is unary 1`] = `"expected 15 to be divisible by 2"`; + +exports[`is available globally when matcher is variadic 1`] = `"expected 15 to be within range 1 - 3"`; exports[`is ok if there is no message specified 1`] = `"No message was specified for this matcher."`; diff --git a/packages/expect/src/__tests__/extend.test.js b/packages/expect/src/__tests__/extend.test.js index 7755c1085f73..8fe6b6f3c78e 100644 --- a/packages/expect/src/__tests__/extend.test.js +++ b/packages/expect/src/__tests__/extend.test.js @@ -20,9 +20,17 @@ jestExpect.extend({ return {message, pass}; }, + toBeWithinRange(actual, floor, ceiling) { + const pass = actual >= floor && actual <= ceiling; + const message = pass + ? () => `expected ${actual} not to be within range ${floor} - ${ceiling}` + : () => `expected ${actual} to be within range ${floor} - ${ceiling}`; + + return {message, pass}; + }, }); -it('is available globally', () => { +it('is available globally when matcher is unary', () => { jestExpect(15).toBeDivisibleBy(5); jestExpect(15).toBeDivisibleBy(3); jestExpect(15).not.toBeDivisibleBy(6); @@ -32,6 +40,15 @@ it('is available globally', () => { ).toThrowErrorMatchingSnapshot(); }); +it('is available globally when matcher is variadic', () => { + jestExpect(15).toBeWithinRange(10, 20); + jestExpect(15).not.toBeWithinRange(6); + + jestExpect(() => + jestExpect(15).toBeWithinRange(1, 3), + ).toThrowErrorMatchingSnapshot(); +}); + it('exposes matcherUtils in context', () => { jestExpect.extend({ _shouldNotError(actual, expected) { @@ -78,7 +95,7 @@ it('exposes an equality function to custom matchers', () => { expect(() => jestExpect().toBeOne()).not.toThrow(); }); -it('defines asymmetric matchers', () => { +it('defines asymmetric unary matchers', () => { expect(() => jestExpect({value: 2}).toEqual({value: jestExpect.toBeDivisibleBy(2)}), ).not.toThrow(); @@ -87,7 +104,7 @@ it('defines asymmetric matchers', () => { ).toThrowErrorMatchingSnapshot(); }); -it('defines asymmetric matchers that can be prefixed by not', () => { +it('defines asymmetric unary matchers that can be prefixed by not', () => { expect(() => jestExpect({value: 2}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}), ).toThrowErrorMatchingSnapshot(); @@ -95,3 +112,25 @@ it('defines asymmetric matchers that can be prefixed by not', () => { jestExpect({value: 3}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}), ).not.toThrow(); }); + +it('defines asymmetric variadic matchers', () => { + expect(() => + jestExpect({value: 2}).toEqual({value: jestExpect.toBeWithinRange(1, 3)}), + ).not.toThrow(); + expect(() => + jestExpect({value: 3}).toEqual({value: jestExpect.toBeWithinRange(4, 11)}), + ).toThrowErrorMatchingSnapshot(); +}); + +it('defines asymmetric variadic matchers that can be prefixed by not', () => { + expect(() => + jestExpect({value: 2}).toEqual({ + value: jestExpect.not.toBeWithinRange(1, 3), + }), + ).toThrowErrorMatchingSnapshot(); + expect(() => + jestExpect({value: 3}).toEqual({ + value: jestExpect.not.toBeWithinRange(5, 7), + }), + ).not.toThrow(); +}); diff --git a/packages/expect/src/jest_matchers_object.js b/packages/expect/src/jest_matchers_object.js index 1e1aee1899f7..253681620fce 100644 --- a/packages/expect/src/jest_matchers_object.js +++ b/packages/expect/src/jest_matchers_object.js @@ -59,18 +59,18 @@ export const setMatchers = ( // expect is defined class CustomMatcher extends AsymmetricMatcher { - sample: any; + sample: Array; - constructor(sample: any, inverse: boolean = false) { + constructor(inverse: boolean = false, ...sample: Array) { super(); - this.sample = sample; this.inverse = inverse; + this.sample = sample; } asymmetricMatch(other: any) { const {pass} = ((matcher( (other: any), - (this.sample: any), + ...(this.sample: any), ): any): SyncExpectationResult); return this.inverse ? !pass : pass; @@ -85,15 +85,17 @@ export const setMatchers = ( } toAsymmetricMatcher() { - return `${this.toString()}<${this.sample}>`; + return `${this.toString()}<${this.sample.join(', ')}>`; } } - expect[key] = (sample: any) => new CustomMatcher(sample); + expect[key] = (...sample: Array) => + new CustomMatcher(false, ...sample); if (!expect.not) { expect.not = {}; } - expect.not[key] = (sample: any) => new CustomMatcher(sample, true); + expect.not[key] = (...sample: Array) => + new CustomMatcher(true, ...sample); } }); diff --git a/packages/jest-cli/src/runGlobalHook.js b/packages/jest-cli/src/runGlobalHook.js new file mode 100644 index 000000000000..b3318d715346 --- /dev/null +++ b/packages/jest-cli/src/runGlobalHook.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ +import type {GlobalConfig} from 'types/Config'; +import type {Test} from 'types/TestRunner'; + +export default async ({ + allTests, + globalConfig, + moduleName, +}: { + allTests: Array, + globalConfig: GlobalConfig, + moduleName: string, +}) => { + const globalModulePaths = new Set( + allTests.map(test => test.context.config[moduleName]), + ); + + if (globalConfig[moduleName]) { + globalModulePaths.add(globalConfig[moduleName]); + } + + if (globalModulePaths.size > 0) { + await Promise.all( + Array.from(globalModulePaths).map(async modulePath => { + if (!modulePath) { + return null; + } + + // $FlowFixMe + const globalModule = require(modulePath); + + if (typeof globalModule !== 'function') { + throw new TypeError( + `${moduleName} file must export a function at ${modulePath}`, + ); + } + + return globalModule(globalConfig); + }), + ); + } +}; diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index ae7257900460..a6915dd2d906 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -21,6 +21,7 @@ import {Console, formatTestResults} from 'jest-util'; import exit from 'exit'; import fs from 'graceful-fs'; import getNoTestsFoundMessage from './getNoTestsFoundMessage'; +import runGlobalHook from './runGlobalHook'; import SearchSource from './SearchSource'; import TestScheduler from './TestScheduler'; import TestSequencer from './TestSequencer'; @@ -271,19 +272,8 @@ export default (async function runJest({ collectHandles = collectNodeHandles(); } - if (globalConfig.globalSetup) { - // $FlowFixMe - const globalSetup = require(globalConfig.globalSetup); - if (typeof globalSetup !== 'function') { - throw new TypeError( - `globalSetup file must export a function at ${ - globalConfig.globalSetup - }`, - ); - } + await runGlobalHook({allTests, globalConfig, moduleName: 'globalSetup'}); - await globalSetup(globalConfig); - } const results = await new TestScheduler( globalConfig, { @@ -294,19 +284,12 @@ export default (async function runJest({ sequencer.cacheResults(allTests, results); - if (globalConfig.globalTeardown) { - // $FlowFixMe - const globalTeardown = require(globalConfig.globalTeardown); - if (typeof globalTeardown !== 'function') { - throw new TypeError( - `globalTeardown file must export a function at ${ - globalConfig.globalTeardown - }`, - ); - } + await runGlobalHook({ + allTests, + globalConfig, + moduleName: 'globalTeardown', + }); - await globalTeardown(globalConfig); - } return processResults(results, { collectHandles, isJSON: globalConfig.json, diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 3f2962b6288a..34007e6a98b2 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -164,6 +164,8 @@ const getConfigs = ( errorOnDeprecated: options.errorOnDeprecated, filter: options.filter, forceCoverageMatch: options.forceCoverageMatch, + globalSetup: options.globalSetup, + globalTeardown: options.globalTeardown, globals: options.globals, haste: options.haste, moduleDirectories: options.moduleDirectories, diff --git a/types/Config.js b/types/Config.js index f7270b049a64..3dd379255236 100644 --- a/types/Config.js +++ b/types/Config.js @@ -252,6 +252,8 @@ export type ProjectConfig = {| errorOnDeprecated: boolean, filter: ?Path, forceCoverageMatch: Array, + globalSetup: ?string, + globalTeardown: ?string, globals: ConfigGlobals, haste: HasteConfig, moduleDirectories: Array, diff --git a/website/versioned_docs/version-22.2/Configuration.md b/website/versioned_docs/version-22.2/Configuration.md index 8617ae3bd449..7262e53505e3 100644 --- a/website/versioned_docs/version-22.2/Configuration.md +++ b/website/versioned_docs/version-22.2/Configuration.md @@ -345,7 +345,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string] diff --git a/website/versioned_docs/version-22.3/Configuration.md b/website/versioned_docs/version-22.3/Configuration.md index fcfc724f600e..c8db64b53a04 100644 --- a/website/versioned_docs/version-22.3/Configuration.md +++ b/website/versioned_docs/version-22.3/Configuration.md @@ -330,7 +330,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string] diff --git a/website/versioned_docs/version-22.4/Configuration.md b/website/versioned_docs/version-22.4/Configuration.md index fbd90babc64f..33d0e0de6a9a 100644 --- a/website/versioned_docs/version-22.4/Configuration.md +++ b/website/versioned_docs/version-22.4/Configuration.md @@ -361,7 +361,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string] diff --git a/website/versioned_docs/version-23.0/Configuration.md b/website/versioned_docs/version-23.0/Configuration.md index d8030d50b0f4..6337438eef19 100644 --- a/website/versioned_docs/version-23.0/Configuration.md +++ b/website/versioned_docs/version-23.0/Configuration.md @@ -381,7 +381,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string] diff --git a/website/versioned_docs/version-23.1/Configuration.md b/website/versioned_docs/version-23.1/Configuration.md index f165873776d6..75750c58ea40 100644 --- a/website/versioned_docs/version-23.1/Configuration.md +++ b/website/versioned_docs/version-23.1/Configuration.md @@ -381,7 +381,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string] diff --git a/website/versioned_docs/version-23.2/Configuration.md b/website/versioned_docs/version-23.2/Configuration.md index e5d4ad3bbced..e65d40559511 100644 --- a/website/versioned_docs/version-23.2/Configuration.md +++ b/website/versioned_docs/version-23.2/Configuration.md @@ -387,7 +387,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string] diff --git a/website/versioned_docs/version-23.3/Configuration.md b/website/versioned_docs/version-23.3/Configuration.md index 38245c0864a5..ed34d1b3cc0b 100644 --- a/website/versioned_docs/version-23.3/Configuration.md +++ b/website/versioned_docs/version-23.3/Configuration.md @@ -387,7 +387,7 @@ Specifies notification mode. Requires `notify: true`. - `success`: send a notification when tests pass. - `change`: send a notification when the status changed. - `success-change`: send a notification when tests pass or once when it fails. -- `failure-success`: send a notification when tests fails or once when it passes. +- `failure-change`: send a notification when tests fails or once when it passes. ### `preset` [string]