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 @@
+
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]