diff --git a/.github/jest-logo.png b/.github/jest-logo.png deleted file mode 100644 index 4819bbfd5e5e..000000000000 Binary files a/.github/jest-logo.png and /dev/null differ diff --git a/.gitignore b/.gitignore index 7a5f528116a1..6f9aaa4fc704 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,4 @@ npm-debug.log coverage lerna-debug.log npm-debug.log* -website/core/metadata*.js -website/src/jest/blog -website/src/jest/docs /danger/node_modules/ diff --git a/README.md b/README.md index 68503938ecca..cedc77ebdf20 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Jest logo](.github/jest-logo.png?raw=true) +# Jest [![Build Status](https://travis-ci.org/facebook/jest.svg?branch=master)](https://travis-ci.org/facebook/jest) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/8n38o44k585hhvhd/branch/master?svg=true)](https://ci.appveyor.com/project/Daniel15/jest/branch/master) [![npm version](https://badge.fury.io/js/jest-cli.svg)](http://badge.fury.io/js/jest-cli) @@ -90,7 +90,7 @@ Jest can be used in projects that use [webpack](https://webpack.github.io/) to m To use TypeScript in your tests, install the `ts-jest` package: ``` -npm install --save-dev test-jest +npm install --save-dev ts-jest ``` then modify your `package.json` so the `jest` section looks something like: diff --git a/circle.yml b/circle.yml index 220365ebe6bb..32073051ed13 100644 --- a/circle.yml +++ b/circle.yml @@ -23,4 +23,4 @@ deployment: - git config --global user.email "jest-bot@users.noreply.github.com" - git config --global user.name "Website Deployment Script" - echo "machine github.com login jest-bot password $GITHUB_TOKEN" > ~/.netrc - - cd website && GIT_USER=jest-bot DEPLOY_USER=jest-bot npm run gh-pages + - cd website && GIT_USER=jest-bot npm run gh-pages diff --git a/docs/Configuration.md b/docs/Configuration.md index 8406be27450a..de2479cffd2f 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -252,7 +252,7 @@ Default: `[]` A list of paths to snapshot serializer modules Jest should use for snapshot testing. -Jest has default serializers for built-in javascript types and for react +Jest has default serializers for built-in JavaScript types and for React elements. See [snapshot test tutorial](/jest/docs/tutorial-react-native.html#snapshot-test) for more information. Example serializer module: @@ -304,6 +304,8 @@ Pretty foo: Object { } ``` +To make a dependency explicit instead of implicit, you can call [`expect.addSnapshotSerializer`](/jest/docs/expect.html#expectaddsnapshotserializerserializer) to add a module for an individual test file instead of adding its path to `snapshotSerializers` in Jest configuration. + ### `testEnvironment` [string] Default: `"jsdom"` @@ -311,6 +313,20 @@ The test environment that will be used for testing. The default environment in J You can create your own module that will be used for setting up the test environment. The module must export a class with `runScript` and `dispose` methods. See the [node](https://github.com/facebook/jest/blob/master/packages/jest-environment-node/src/index.js) or [jsdom](https://github.com/facebook/jest/blob/master/packages/jest-environment-jsdom/src/index.js) environments as examples. +### `testMatch` [array] +(default: `[ '**/__tests__/**/*.js?(x)', '**/?(*.)(spec|test).js?(x)' ]`) + +The glob patterns Jest uses to detect test files. By default it looks for `.js` and `.jsx` files +inside of `__tests__` folders, as well as any files with a suffix of `.test` or `.spec` +(e.g. `Component.test.js` or `Component.spec.js`). It will also find files called `test.js` +or `spec.js`. + +See the [micromatch](https://github.com/jonschlinkert/micromatch) package +for details of the patterns you can specify. + +See also [`testRegex` [string]](#testregex-string), but note that you +cannot specify both options. + ### `testPathDirs` [array] Default: `[""]` @@ -331,7 +347,8 @@ Default: `(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$` The pattern Jest uses to detect test files. By default it looks for `.js` and `.jsx` files inside of `__tests__` folders, as well as any files with a suffix of `.test` or `.spec` (e.g. `Component.test.js` or `Component.spec.js`). It will also find files called `test.js` -or `spec.js`. +or `spec.js`. See also [`testMatch` [array]](#testmatch-array-string), but note +that you cannot specify both options. ### `testResultsProcessor` [string] Default: `undefined` diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 8e9e36cc03c7..6b2b955aca30 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -239,6 +239,26 @@ test('onPress gets called with the right thing', () => { }) ``` +### `expect.addSnapshotSerializer(serializer)` + +You can call `expect.addSnapshotSerializer` to add a module that formats application-specific data structures. + +For an individual test file, an added module precedes any modules from `snapshotSerializers` configuration, which precede the default snapshot serializers for built-in JavaScript types and for React elements. The last module added is the first module tested. + +```js +import serializer from 'my-serializer-module'; +expect.addSnapshotSerializer(serializer); + +// affects expect(value).toMatchSnapshot() assertions in the test file +``` + +If you add a snapshot serializer in individual test files instead of to adding it to `snapshotSerializers` configuration: + +* You make the dependency explicit instead of implicit. +* You avoid limits to configuration that might cause you to eject from [create-react-app](https://github.com/facebookincubator/create-react-app). + +See [configuring package.json](/jest/docs/configuration.html#snapshotserializers-array-string) for more information. + ### `.not` If you know how to test something, `.not` lets you test its opposite. For example, this code tests that the best La Croix flavor is not coconut: diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index 262f5a188dbf..3e078b5cb34e 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -60,7 +60,7 @@ Returns the `jest` object for chaining. *Note: this method was previously called `autoMockOn`. When using `babel-jest`, calls to `enableAutomock` will automatically be hoisted to the top of the code block. Use `autoMockOn` if you want to explicitly avoid this behavior.* ### `jest.fn(implementation)` -Returns a new, unused [mock function](#mock-functions). Optionally takes a mock implementation. +Returns a new, unused [mock function](/jest/docs/mock-function-api.html). Optionally takes a mock implementation. ```js const mockFn = jest.fn(); @@ -187,7 +187,7 @@ In these rare scenarios you can use this API to manually fill the slot in the mo Returns the `jest` object for chaining. -*Note It is recommended to use [`jest.mock()`](#jest-mock-modulename-factory) instead. The `jest.mock` API's second argument is a module factory instead of the expected exported module object.* +*Note It is recommended to use [`jest.mock()`](#jestmockmodulename-factory-options) instead. The `jest.mock` API's second argument is a module factory instead of the expected exported module object.* ### `jest.unmock(moduleName)` Indicates that the module system should never return a mocked version of the specified module from `require()` (e.g. that it should always return the real module). diff --git a/docs/SnapshotTesting.md b/docs/SnapshotTesting.md index 00fd55be416b..6cd2adddf81a 100644 --- a/docs/SnapshotTesting.md +++ b/docs/SnapshotTesting.md @@ -35,8 +35,8 @@ exports[`Link renders correctly 1`] = ` + onMouseEnter={[Function]} + onMouseLeave={[Function]}> Facebook `; diff --git a/docs/TutorialAsync.md b/docs/TutorialAsync.md index c14ce35b61c4..be68af1a0483 100644 --- a/docs/TutorialAsync.md +++ b/docs/TutorialAsync.md @@ -108,11 +108,12 @@ and enable the feature in your `.babelrc` file. Errors can be handled in the standard JavaScript way: Either using `.catch()` directly on a Promise or through `try-catch` when using async/await. Note that -if a Promise throws and the error is not handled, the test will fail. +if a Promise throws and the error is not handled, the test will fail. `expect.assertion(1)` makes sure that expectation was checked once. In this example it will fail if promise was resolved without throwing. ```js // Testing for async errors can be done using `catch`. it('tests error with promises', () => { + expect.assertions(1) return user.getUserName(3) .catch(e => expect(e).toEqual({ error: 'User with 3 not found.', @@ -121,6 +122,7 @@ it('tests error with promises', () => { // Or try-catch. it('tests error with async/await', async () => { + expect.assertions(1) try { await user.getUserName(2); } catch (object) { diff --git a/docs/Webpack.md b/docs/Webpack.md index 80ccbcdc24b1..d18591fade70 100644 --- a/docs/Webpack.md +++ b/docs/Webpack.md @@ -212,7 +212,7 @@ thus requires ES modules to be transpiled to CommonJS modules. As such, if you are using webpack 2, you most likely will want to configure Babel to transpile ES modules to CommonJS modules only in the `test` environment. -```js +```json // .babelrc { "presets": [ @@ -226,3 +226,23 @@ ES modules to CommonJS modules only in the `test` environment. } } ``` + +If you use dynamic imports (`import('some-file.js').then(module => ...)`), you +need to enable the `dynamic-import-node` plugin. + +```json +// .babelrc +{ + "presets": [ + ["es2015", {"modules": false}] + ], + + "plugins": ["syntax-dynamic-import"], + + "env": { + "test": { + "plugins": ["dynamic-import-node"] + } + } +} +``` diff --git a/examples/typescript/package.json b/examples/typescript/package.json index f54642873ab3..6348d70a2207 100644 --- a/examples/typescript/package.json +++ b/examples/typescript/package.json @@ -16,6 +16,6 @@ "transform": { "^.+\\.(ts|tsx)$": "/preprocessor.js" }, - "testRegex": "/__tests__/.*\\.(ts|tsx|js)$" + "testMatch": ["**/__tests__/*.(ts|tsx|js)"] } } diff --git a/integration_tests/__tests__/__snapshots__/snapshot-serializers-test.js.snap b/integration_tests/__tests__/__snapshots__/snapshot-serializers-test.js.snap index 678707fadbed..94b03800d8f1 100644 --- a/integration_tests/__tests__/__snapshots__/snapshot-serializers-test.js.snap +++ b/integration_tests/__tests__/__snapshots__/snapshot-serializers-test.js.snap @@ -14,6 +14,24 @@ Object { } } bProp={foo: 8} /> +", + "snapshot serializers works with prepended plugins from expect method called once 1": " +
+", + "snapshot serializers works with prepended plugins from expect method called twice 1": " +
", "snapshot serializers works with second plugin 1": "bar: 2", } diff --git a/integration_tests/__tests__/config-test.js b/integration_tests/__tests__/config-test.js index ddbf852542c9..5014d2083549 100644 --- a/integration_tests/__tests__/config-test.js +++ b/integration_tests/__tests__/config-test.js @@ -18,7 +18,7 @@ test('config as JSON', () => { const result = runJest('verbose_reporter', [ '--config=' + JSON.stringify({ testEnvironment: 'node', - testRegex: 'banana strawbery kiwi', + testMatch: ['banana strawbery kiwi'], }), ]); const stdout = result.stdout.toString(); diff --git a/integration_tests/__tests__/snapshot-serializers-test.js b/integration_tests/__tests__/snapshot-serializers-test.js index 17b9aae61782..84ce62844e94 100644 --- a/integration_tests/__tests__/snapshot-serializers-test.js +++ b/integration_tests/__tests__/snapshot-serializers-test.js @@ -20,8 +20,8 @@ const snapshotPath = path.resolve(snapshotsDir, 'snapshot-test.js.snap'); const runAndAssert = () => { const result = runJest.json('snapshot-serializers'); const json = result.json; - expect(json.numTotalTests).toBe(5); - expect(json.numPassedTests).toBe(5); + expect(json.numTotalTests).toBe(7); + expect(json.numPassedTests).toBe(7); expect(json.numFailedTests).toBe(0); expect(json.numPendingTests).toBe(0); expect(result.status).toBe(0); diff --git a/integration_tests/snapshot-serializers/__tests__/snapshot-test.js b/integration_tests/snapshot-serializers/__tests__/snapshot-test.js index 875687ac59b5..57db72fd01f9 100644 --- a/integration_tests/snapshot-serializers/__tests__/snapshot-test.js +++ b/integration_tests/snapshot-serializers/__tests__/snapshot-test.js @@ -56,4 +56,40 @@ describe('snapshot serializers', () => { }; expect(test).toMatchSnapshot(); }); + + it('works with prepended plugins from expect method called once', () => { + const test = { + $$typeof: Symbol.for('react.test.json'), + children: null, + props: { + aProp: {a: 6}, + bProp: {foo: 8}, + }, + type: 'div', + }; + // Add plugin that overrides foo specified by Jest config in package.json + expect.addSnapshotSerializer({ + print: (val, serialize) => `Foo: ${serialize(val.foo)}`, + test: val => val && val.hasOwnProperty('foo'), + }); + expect(test).toMatchSnapshot(); + }); + + it('works with prepended plugins from expect method called twice', () => { + const test = { + $$typeof: Symbol.for('react.test.json'), + children: null, + props: { + aProp: {a: 6}, + bProp: {foo: 8}, + }, + type: 'div', + }; + // Add plugin that overrides preceding added plugin + expect.addSnapshotSerializer({ + print: (val, serialize) => `FOO: ${serialize(val.foo)}`, + test: val => val && val.hasOwnProperty('foo'), + }); + expect(test).toMatchSnapshot(); + }); }); diff --git a/integration_tests/typescript-coverage/package.json b/integration_tests/typescript-coverage/package.json index ee9d5db999dc..38d2afc377cc 100644 --- a/integration_tests/typescript-coverage/package.json +++ b/integration_tests/typescript-coverage/package.json @@ -4,7 +4,7 @@ "transform": { "^.+\\.(ts|js)$": "/typescript-preprocessor.js" }, - "testRegex": "/__tests__/.*\\.(ts|tsx|js)$", + "testMatch": ["**/__tests__/*.(ts|tsx|js)"], "testEnvironment": "node", "moduleFileExtensions": [ "ts", diff --git a/package.json b/package.json index 8d1b6d97a88e..486d65c32ddf 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,6 @@ "\\.snap$", "/packages/.*/build" ], - "testRegex": ".*-test\\.js" + "testMatch": ["**/*-test.js"] } } diff --git a/packages/jest-cli/README.md b/packages/jest-cli/README.md new file mode 100644 index 000000000000..91dc5675a4f8 --- /dev/null +++ b/packages/jest-cli/README.md @@ -0,0 +1,11 @@ +# Jest + +🃏 Painless JavaScript Testing + +- **👩🏻‍💻 Easy Setup**: Jest is a complete and easy to set up JavaScript testing solution. In fact, Jest works out of the box for any React project. + +- **🏃🏽 Instant Feedback**: Failed tests run first. Fast interactive mode can switch between running all tests or only test files related to changed files. + +- **📸 Snapshot Testing**: Jest can [capture snapshots](http://facebook.github.io/jest/docs/snapshot-testing.html) of React trees or other serializable values to simplify UI testing. + +Read More: http://facebook.github.io/jest/ diff --git a/packages/jest-cli/package.json b/packages/jest-cli/package.json index d03e541095eb..26f324692c19 100644 --- a/packages/jest-cli/package.json +++ b/packages/jest-cli/package.json @@ -23,8 +23,8 @@ "jest-snapshot": "^18.1.0", "jest-util": "^18.1.0", "node-notifier": "^5.0.1", + "micromatch": "^2.3.11", "string-length": "^1.0.1", - "strip-ansi": "^3.0.1", "throat": "^3.0.0", "which": "^1.1.1", "worker-farm": "^1.3.1", @@ -44,13 +44,31 @@ "url": "https://github.com/facebook/jest/issues" }, "homepage": "http://facebook.github.io/jest/", + "license": "BSD-3-Clause", "keywords": [ + "ava", + "babel", + "coverage", + "easy", + "expect", "facebook", + "immersive", + "instant", + "jasmine", "jest", + "jsdom", + "mocha", + "mocking", + "painless", + "qunit", + "runner", + "sandboxed", + "snapshot", + "tap", + "tape", "test", - "unit", - "jasmine", - "mock" - ], - "license": "BSD-3-Clause" + "testing", + "typescript", + "watch" + ] } diff --git a/packages/jest-cli/src/SearchSource.js b/packages/jest-cli/src/SearchSource.js index dfbe4a3825f8..427b21228d88 100644 --- a/packages/jest-cli/src/SearchSource.js +++ b/packages/jest-cli/src/SearchSource.js @@ -12,9 +12,11 @@ import type {Config} from 'types/Config'; import type {HasteContext} from 'types/HasteMap'; -import type {Path} from 'types/Config'; +import type {Glob, Path} from 'types/Config'; import type {ResolveModuleConfig} from 'jest-resolve'; +const micromatch = require('micromatch'); + const DependencyResolver = require('jest-resolve-dependencies'); const chalk = require('chalk'); @@ -26,6 +28,7 @@ const { } = require('jest-util'); type SearchSourceConfig = { + testMatch: Array, testPathDirs: Array, testRegex: string, testPathIgnorePatterns: Array, @@ -69,14 +72,32 @@ const pluralize = ( ending: string, ) => `${count} ${word}${count === 1 ? '' : ending}`; +const globsToMatcher = (globs: ?Array) => { + if (globs == null || globs.length === 0) { + return () => true; + } + + const matchers = globs.map(each => micromatch.matcher(each)); + return (path: Path) => matchers.some(each => each(path)); +}; + +const regexToMatcher = (testRegex: string) => { + if (!testRegex) { + return () => true; + } + + const regex = new RegExp(pathToRegex(testRegex)); + return (path: Path) => regex.test(path); +}; + class SearchSource { _hasteContext: HasteContext; _config: SearchSourceConfig; _options: ResolveModuleConfig; _testPathDirPattern: RegExp; - _testRegex: RegExp; _testIgnorePattern: ?RegExp; _testPathCases: { + testMatch: (path: Path) => boolean, testPathDirs: (path: Path) => boolean, testRegex: (path: Path) => boolean, testPathIgnorePatterns: (path: Path) => boolean, @@ -98,18 +119,18 @@ class SearchSource { dir => escapePathForRegex(dir), ).join('|')); - this._testRegex = new RegExp(pathToRegex(config.testRegex)); const ignorePattern = config.testPathIgnorePatterns; this._testIgnorePattern = ignorePattern.length ? new RegExp(ignorePattern.join('|')) : null; this._testPathCases = { + testMatch: globsToMatcher(config.testMatch), testPathDirs: path => this._testPathDirPattern.test(path), testPathIgnorePatterns: path => ( !this._testIgnorePattern || !this._testIgnorePattern.test(path) ), - testRegex: path => this._testRegex.test(path), + testRegex: regexToMatcher(config.testRegex), }; } diff --git a/packages/jest-cli/src/__tests__/SearchSource-test.js b/packages/jest-cli/src/__tests__/SearchSource-test.js index a80038a61e3b..a85d256027d3 100644 --- a/packages/jest-cli/src/__tests__/SearchSource-test.js +++ b/packages/jest-cli/src/__tests__/SearchSource-test.js @@ -16,6 +16,7 @@ const skipOnWindows = require('skipOnWindows'); const rootDir = path.resolve(__dirname, 'test_root'); const testRegex = path.sep + '__testtests__' + path.sep; +const testMatch = ['**/__testtests__/**/*']; const maxWorkers = 1; let findMatchingTests; @@ -50,10 +51,24 @@ describe('SearchSource', () => { }); }); - it('supports ../ paths and unix separators', () => { + // micromatch doesn't support '..' through the globstar ('**') to avoid + // infinite recursion. + + it('supports ../ paths and unix separators via textRegex', () => { if (process.platform !== 'win32') { - const path = '/path/to/__tests__/foo/bar/baz/../../../test.js'; - expect(searchSource.isTestFilePath(path)).toEqual(true); + config = normalizeConfig({ + name, + rootDir: '.', + testMatch: null, + testPathDirs: [], + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', + }); + Runtime.createHasteContext(config, {maxWorkers}).then(hasteMap => { + searchSource = new SearchSource(hasteMap, config); + + const path = '/path/to/__tests__/foo/bar/baz/../../../test.js'; + expect(searchSource.isTestFilePath(path)).toEqual(true); + }); } }); @@ -80,11 +95,12 @@ describe('SearchSource', () => { ); }); - it('finds tests matching a pattern', () => { + it('finds tests matching a pattern via testRegex', () => { const config = normalizeConfig({ moduleFileExtensions: ['js', 'jsx', 'txt'], name, rootDir, + testMatch: null, testRegex: 'not-really-a-test', }); return findMatchingTests(config).then(data => { @@ -97,11 +113,30 @@ describe('SearchSource', () => { }); }); - it('finds tests matching a JS pattern', () => { + it('finds tests matching a pattern via testMatch', () => { + const config = normalizeConfig({ + moduleFileExtensions: ['js', 'jsx', 'txt'], + name, + rootDir, + testMatch: ['**/not-really-a-test.txt'], + testRegex: '', + }); + return findMatchingTests(config).then(data => { + const relPaths = data.paths.map(absPath => ( + path.relative(rootDir, absPath) + )); + expect(relPaths).toEqual([ + path.normalize('__testtests__/not-really-a-test.txt'), + ]); + }); + }); + + it('finds tests matching a JS regex pattern', () => { const config = normalizeConfig({ moduleFileExtensions: ['js', 'jsx'], name, rootDir, + testMatch: null, testRegex: 'test\.jsx?', }); return findMatchingTests(config).then(data => { @@ -115,10 +150,30 @@ describe('SearchSource', () => { }); }); - it('finds tests with default file extensions', () => { + it('finds tests matching a JS glob pattern', () => { + const config = normalizeConfig({ + moduleFileExtensions: ['js', 'jsx'], + name, + rootDir, + testMatch: ['**/test.js?(x)'], + testRegex: '', + }); + return findMatchingTests(config).then(data => { + const relPaths = data.paths.map(absPath => ( + path.relative(rootDir, absPath) + )); + expect(relPaths.sort()).toEqual([ + path.normalize('__testtests__/test.js'), + path.normalize('__testtests__/test.jsx'), + ]); + }); + }); + + it('finds tests with default file extensions using testRegex', () => { const config = normalizeConfig({ name, rootDir, + testMatch: null, testRegex, }); return findMatchingTests(config).then(data => { @@ -132,12 +187,30 @@ describe('SearchSource', () => { }); }); + it('finds tests with default file extensions using testMatch', () => { + const config = normalizeConfig({ + name, + rootDir, + testMatch, + testRegex: '', + }); + return findMatchingTests(config).then(data => { + const relPaths = data.paths.map(absPath => ( + path.relative(rootDir, absPath) + )); + expect(relPaths.sort()).toEqual([ + path.normalize('__testtests__/test.js'), + path.normalize('__testtests__/test.jsx'), + ]); + }); + }); + it('finds tests with similar but custom file extensions', () => { const config = normalizeConfig({ moduleFileExtensions: ['jsx'], name, rootDir, - testRegex, + testMatch, }); return findMatchingTests(config).then(data => { const relPaths = data.paths.map(absPath => ( @@ -154,7 +227,7 @@ describe('SearchSource', () => { moduleFileExtensions: ['foobar'], name, rootDir, - testRegex, + testMatch, }); return findMatchingTests(config).then(data => { const relPaths = data.paths.map(absPath => ( @@ -165,11 +238,30 @@ describe('SearchSource', () => { ]); }); }); + it('finds tests with many kinds of file extensions', () => { const config = normalizeConfig({ moduleFileExtensions: ['js', 'jsx'], name, rootDir, + testMatch, + }); + return findMatchingTests(config).then(data => { + const relPaths = data.paths.map(absPath => ( + path.relative(rootDir, absPath) + )); + expect(relPaths.sort()).toEqual([ + path.normalize('__testtests__/test.js'), + path.normalize('__testtests__/test.jsx'), + ]); + }); + }); + + it('finds tests using a regex only', () => { + const config = normalizeConfig({ + name, + rootDir, + testMatch: null, testRegex, }); return findMatchingTests(config).then(data => { @@ -182,6 +274,24 @@ describe('SearchSource', () => { ]); }); }); + + it('finds tests using a glob only', () => { + const config = normalizeConfig({ + name, + rootDir, + testMatch, + testRegex: '', + }); + return findMatchingTests(config).then(data => { + const relPaths = data.paths.map(absPath => ( + path.relative(rootDir, absPath) + )); + expect(relPaths.sort()).toEqual([ + path.normalize('__testtests__/test.js'), + path.normalize('__testtests__/test.jsx'), + ]); + }); + }); }); describe('findRelatedTests', () => { @@ -233,7 +343,7 @@ describe('SearchSource', () => { moduleFileExtensions: ['js', 'jsx', 'foobar'], name, rootDir, - testRegex, + testMatch, }); Runtime.createHasteContext(config, {maxWorkers}).then(hasteMap => { searchSource = new SearchSource(hasteMap, config); diff --git a/packages/jest-cli/src/assets/jest_logo.png b/packages/jest-cli/src/assets/jest_logo.png index c52bf15030c8..5c80cb522bd5 100644 Binary files a/packages/jest-cli/src/assets/jest_logo.png and b/packages/jest-cli/src/assets/jest_logo.png differ diff --git a/packages/jest-config/src/__tests__/__snapshots__/normalize-test.js.snap b/packages/jest-config/src/__tests__/__snapshots__/normalize-test.js.snap index d8619097a319..a49a145a3fb1 100644 --- a/packages/jest-config/src/__tests__/__snapshots__/normalize-test.js.snap +++ b/packages/jest-config/src/__tests__/__snapshots__/normalize-test.js.snap @@ -44,3 +44,13 @@ exports[`testEnvironment throws on invalid environment names 1`] = ` https://facebook.github.io/jest/docs/configuration.html " `; + +exports[`testMatch throws if testRegex and testMatch are both specified 1`] = ` +"● Validation Error: + + Configuration options testMatch and testRegex cannot be used together. + + Configuration Documentation: + https://facebook.github.io/jest/docs/configuration.html +" +`; diff --git a/packages/jest-config/src/__tests__/normalize-test.js b/packages/jest-config/src/__tests__/normalize-test.js index 68e78baced1e..2f2ed779cdcc 100644 --- a/packages/jest-config/src/__tests__/normalize-test.js +++ b/packages/jest-config/src/__tests__/normalize-test.js @@ -555,6 +555,36 @@ describe('Upgrade help', () => { }); }); +describe('testMatch', () => { + it('testMatch default not applied if testRegex is set', () => { + const config = normalize({ + rootDir: '/root', + testRegex: '.*', + }); + + expect(config.testMatch.length).toBe(0); + }); + + it('testRegex default not applied if testMatch is set', () => { + const config = normalize({ + rootDir: '/root', + testMatch: ['**/*.js'], + }); + + expect(config.testRegex).toBe(''); + }); + + it('throws if testRegex and testMatch are both specified', () => { + expect(() => { + normalize({ + rootDir: '/root', + testMatch: ['**/*.js'], + testRegex: '.*', + }); + }).toThrowErrorMatchingSnapshot(); + }); +}); + describe('preset', () => { beforeAll(() => { jest.mock( diff --git a/packages/jest-config/src/defaults.js b/packages/jest-config/src/defaults.js index f3aac087d609..5985647540dd 100644 --- a/packages/jest-config/src/defaults.js +++ b/packages/jest-config/src/defaults.js @@ -48,9 +48,13 @@ module.exports = ({ resetModules: false, snapshotSerializers: [], testEnvironment: 'jest-environment-jsdom', + testMatch: [ + '**/__tests__/**/*.js?(x)', + '**/?(*.)(spec|test).js?(x)', + ], testPathDirs: [''], testPathIgnorePatterns: [NODE_MODULES_REGEXP], - testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', + testRegex: '', testResultsProcessor: null, testURL: 'about:blank', timers: 'real', diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index 91637f39cf70..9500821aeec5 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -345,6 +345,7 @@ function normalize(config: InitialConfig, argv: Object = {}) { case 'resetMocks': case 'resetModules': case 'rootDir': + case 'testMatch': case 'testEnvironment': case 'testRegex': case 'testReporter': @@ -361,6 +362,19 @@ function normalize(config: InitialConfig, argv: Object = {}) { return newConfig; }, newConfig); + if (config.testRegex && config.testMatch) { + throw createConfigError( + ` Configuration options ${chalk.bold('testMatch')} and` + + ` ${chalk.bold('testRegex')} cannot be used together.` + ); + } + + if (config.testRegex && (!config.testMatch)) { + // Prevent the default testMatch conflicting with any explicitly + // configured `testRegex` value + newConfig.testMatch = []; + } + // If argv.json is set, coverageReporters shouldn't print a text report. if (argv.json) { newConfig.coverageReporters = (newConfig.coverageReporters || []) diff --git a/packages/jest-config/src/validConfig.js b/packages/jest-config/src/validConfig.js index 52b2c450f0e2..3a62fc23cfae 100644 --- a/packages/jest-config/src/validConfig.js +++ b/packages/jest-config/src/validConfig.js @@ -65,6 +65,7 @@ module.exports = ({ silent: true, snapshotSerializers: ['my-serializer-module'], testEnvironment: 'jest-environment-jsdom', + testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)(spec|test).js?(x)'], testNamePattern: 'test signature', testPathDirs: [''], testPathIgnorePatterns: [NODE_MODULES_REGEXP], diff --git a/packages/jest-editor-support/src/Settings.js b/packages/jest-editor-support/src/Settings.js index 3a2c7005851b..cf4089b0d0d1 100644 --- a/packages/jest-editor-support/src/Settings.js +++ b/packages/jest-editor-support/src/Settings.js @@ -27,8 +27,11 @@ const {jestChildProcessWithArgs} = require('./Process'); // For now, this is all we care about inside the config +type Glob = string; + type ConfigRepresentation = { testRegex: string, + testMatch: Array } module.exports = class Settings extends EventEmitter { @@ -44,6 +47,10 @@ module.exports = class Settings extends EventEmitter { // Defaults for a Jest project this.settings = { + testMatch: [ + '**/__tests__/**/*.js?(x)', + '**/?(*.)(spec|test).js?(x)', + ], testRegex: '(/__tests__/.*|\\.(test|spec))\\.jsx?$', }; } @@ -67,7 +74,7 @@ module.exports = class Settings extends EventEmitter { .trim(); this.jestVersionMajor = parseInt(version, 10); } - + // Pull out the data for the config if (string.includes('config =')) { const jsonString = string.split('config =') diff --git a/packages/jest-jasmine2/src/__mocks__/jest-snapshot.js b/packages/jest-jasmine2/src/__mocks__/jest-snapshot.js deleted file mode 100644 index 21525ebd45f8..000000000000 --- a/packages/jest-jasmine2/src/__mocks__/jest-snapshot.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const snapshot = jest.genMockFromModule('jest-snapshot'); -let plugins = []; - -snapshot.addPlugins = p => { - plugins = plugins.concat(p); -}; -snapshot.getPlugins = p => plugins; -snapshot.__reset = () => plugins = []; - -module.exports = snapshot; diff --git a/packages/jest-jasmine2/src/__tests__/setup-jest-globals-test.js b/packages/jest-jasmine2/src/__tests__/setup-jest-globals-test.js deleted file mode 100644 index b1549db27d7a..000000000000 --- a/packages/jest-jasmine2/src/__tests__/setup-jest-globals-test.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -describe('addPlugins', () => { - const setup = require('../setup-jest-globals'); - - beforeEach(() => { - require('jest-snapshot').__reset(); - }); - - const test = serializers => { - const {getPlugins} = require('jest-snapshot'); - const config = { - snapshotSerializers: [], - }; - setup({config}); - expect(getPlugins()).toEqual(config.snapshotSerializers); - }; - - it('should add plugins from an empty array', () => test([])); - it('should add a single plugin', () => test(['foo'])); - it('should add multiple plugins', () => test(['foo', 'bar'])); -}); diff --git a/packages/jest-jasmine2/src/jest-expect.js b/packages/jest-jasmine2/src/jest-expect.js index bc737fb425e4..a7b81b2de09e 100644 --- a/packages/jest-jasmine2/src/jest-expect.js +++ b/packages/jest-jasmine2/src/jest-expect.js @@ -15,6 +15,7 @@ import type {RawMatcherFn} from 'types/Matchers'; const expect = require('jest-matchers'); const { + addSerializer, toMatchSnapshot, toThrowErrorMatchingSnapshot, } = require('jest-snapshot'); @@ -33,6 +34,8 @@ module.exports = (config: Config) => { }); expect.extend({toMatchSnapshot, toThrowErrorMatchingSnapshot}); + expect.addSnapshotSerializer = addSerializer; + const jasmine = global.jasmine; jasmine.addMatchers = (jasmineMatchersObject: JasmineMatchersObject) => { const jestMatchersObject = Object.create(null); diff --git a/packages/jest-jasmine2/src/setup-jest-globals.js b/packages/jest-jasmine2/src/setup-jest-globals.js index 8affc557634e..5d25c1742d6e 100644 --- a/packages/jest-jasmine2/src/setup-jest-globals.js +++ b/packages/jest-jasmine2/src/setup-jest-globals.js @@ -13,7 +13,7 @@ import type {Config, Path} from 'types/Config'; const {getState, setState} = require('jest-matchers'); -const {initializeSnapshotState, addPlugins} = require('jest-snapshot'); +const {initializeSnapshotState, addSerializer} = require('jest-snapshot'); const { EXPECTED_COLOR, RECEIVED_COLOR, @@ -98,7 +98,12 @@ type Options = { }; module.exports = ({testPath, config}: Options) => { - addPlugins(config.snapshotSerializers); + // Jest tests snapshotSerializers in order preceding built-in serializers. + // Therefore, add in reverse because the last added is the first tested. + config.snapshotSerializers.concat().reverse().forEach(path => { + // $FlowFixMe + addSerializer(require(path)); + }); setState({testPath}); patchJasmine(); const snapshotState = initializeSnapshotState( diff --git a/packages/jest-matchers/package.json b/packages/jest-matchers/package.json index b1f5b13b743b..dadee19df1f3 100644 --- a/packages/jest-matchers/package.json +++ b/packages/jest-matchers/package.json @@ -10,7 +10,6 @@ "dependencies": { "jest-diff": "^18.1.0", "jest-matcher-utils": "^18.1.0", - "jest-util": "^18.1.0", - "pretty-format": "^18.1.0" + "jest-util": "^18.1.0" } } diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap index 75bbdc7baa0d..367d2cc60dc2 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap @@ -2605,6 +2605,23 @@ Difference:  ]" `; +exports[`toMatchObject() {pass: false} expect({"a": "a", "c": "d"}).toMatchObject({"a": Any}) 1`] = ` +"expect(received).toMatchObject(expected) + +Expected value to match object: + {\\"a\\": Any} +Received: + {\\"a\\": \\"a\\", \\"c\\": \\"d\\"} +Difference: +- Expected ++ Received + + Object { +- \\"a\\": Any, ++ \\"a\\": \\"a\\", + }" +`; + exports[`toMatchObject() {pass: false} expect({"a": "b", "c": "d"}).toMatchObject({"a": "b!", "c": "d"}) 1`] = ` "expect(received).toMatchObject(expected) @@ -2790,6 +2807,29 @@ Difference:  }" `; +exports[`toMatchObject() {pass: false} expect({"a": [3, 4, 5], "b": "b"}).toMatchObject({"a": {"b": Any}}) 1`] = ` +"expect(received).toMatchObject(expected) + +Expected value to match object: + {\\"a\\": {\\"b\\": Any}} +Received: + {\\"a\\": [3, 4, 5], \\"b\\": \\"b\\"} +Difference: +- Expected ++ Received + + Object { +- \\"a\\": Object { +- \\"b\\": Any, +- }, ++ \\"a\\": Array [ ++ 3, ++ 4, ++ 5, ++ ], + }" +`; + exports[`toMatchObject() {pass: false} expect({"a": 1, "b": 1, "c": 1, "d": {"e": {"f": 555}}}).toMatchObject({"d": {"e": {"f": 222}}}) 1`] = ` "expect(received).toMatchObject(expected) @@ -2983,6 +3023,24 @@ Received: {\\"a\\": [3, 4, 5], \\"b\\": \\"b\\"}" `; +exports[`toMatchObject() {pass: true} expect({"a": {"x": "x", "y": "y"}}).toMatchObject({"a": {"x": Any}}) 1`] = ` +"expect(received).not.toMatchObject(expected) + +Expected value not to match object: + {\\"a\\": {\\"x\\": Any}} +Received: + {\\"a\\": {\\"x\\": \\"x\\", \\"y\\": \\"y\\"}}" +`; + +exports[`toMatchObject() {pass: true} expect({"a": 1, "c": 2}).toMatchObject({"a": Any}) 1`] = ` +"expect(received).not.toMatchObject(expected) + +Expected value not to match object: + {\\"a\\": Any} +Received: + {\\"a\\": 1, \\"c\\": 2}" +`; + exports[`toMatchObject() {pass: true} expect({"a": 2015-11-30T00:00:00.000Z, "b": "b"}).toMatchObject({"a": 2015-11-30T00:00:00.000Z}) 1`] = ` "expect(received).not.toMatchObject(expected) diff --git a/packages/jest-matchers/src/__tests__/matchers-test.js b/packages/jest-matchers/src/__tests__/matchers-test.js index fb818495f7ec..2e2279739014 100644 --- a/packages/jest-matchers/src/__tests__/matchers-test.js +++ b/packages/jest-matchers/src/__tests__/matchers-test.js @@ -679,6 +679,8 @@ describe('toMatchObject()', () => { [{a: 'b', t: {x: {r: 'r'}, z: 'z'}}, {t: {x: {r: 'r'}}}], [{a: [3, 4, 5], b: 'b'}, {a: [3, 4, 5]}], [{a: [3, 4, 5, 'v'], b: 'b'}, {a: [3, 4, 5, 'v']}], + [{a: 1, c: 2}, {a: jestExpect.any(Number)}], + [{a: {x: 'x', y: 'y'}}, {a: {x: jestExpect.any(String)}}], [new Date('2015-11-30'), new Date('2015-11-30')], [{a: new Date('2015-11-30'), b: 'b'}, {a: new Date('2015-11-30')}], [{a: null, b: 'b'}, {a: null}], @@ -698,12 +700,14 @@ describe('toMatchObject()', () => { [ [{a: 'b', c: 'd'}, {e: 'b'}], [{a: 'b', c: 'd'}, {a: 'b!', c: 'd'}], + [{a: 'a', c: 'd'}, {a: jestExpect.any(Number)}], [{a: 'b', t: {x: {r: 'r'}, z: 'z'}}, {a: 'b', t: {z: [3]}}], [{a: 'b', t: {x: {r: 'r'}, z: 'z'}}, {t: {l: {r: 'r'}}}], [{a: [3, 4, 5], b: 'b'}, {a: [3, 4, 5, 6]}], [{a: [3, 4, 5], b: 'b'}, {a: [3, 4]}], [{a: [3, 4, 'v'], b: 'b'}, {a: ['v']}], [{a: [3, 4, 5], b: 'b'}, {a: {b: 4}}], + [{a: [3, 4, 5], b: 'b'}, {a: {b: jestExpect.any(String)}}], [[1, 2], [1, 3]], [new Date('2015-11-30'), new Date('2015-10-10')], [{a: new Date('2015-11-30'), b: 'b'}, {a: new Date('2015-10-10')}], diff --git a/packages/jest-matchers/src/jasmine-utils.js b/packages/jest-matchers/src/jasmine-utils.js index 1273fda9c2af..b77d3c818c7c 100644 --- a/packages/jest-matchers/src/jasmine-utils.js +++ b/packages/jest-matchers/src/jasmine-utils.js @@ -297,4 +297,5 @@ module.exports = { hasProperty, isA, isUndefined, + asymmetricMatch, }; diff --git a/packages/jest-matchers/src/matchers.js b/packages/jest-matchers/src/matchers.js index 253d96dd1fb2..f8e418837514 100644 --- a/packages/jest-matchers/src/matchers.js +++ b/packages/jest-matchers/src/matchers.js @@ -31,6 +31,8 @@ const { } = require('jest-matcher-utils'); const { equals, + asymmetricMatch, + isUndefined, } = require('./jasmine-utils'); type ContainIterable = ( @@ -561,6 +563,11 @@ const matchers: MatchersObject = { const compare = (expected: any, received: any): boolean => { + const asymmetricResult = asymmetricMatch(expected, received); + if (!isUndefined(asymmetricResult)) { + return asymmetricResult; + } + if (typeof received !== typeof expected) { return false; } diff --git a/packages/jest-matchers/src/toThrowMatchers.js b/packages/jest-matchers/src/toThrowMatchers.js index cd6bfe4d70bf..83bf9ae94bdb 100644 --- a/packages/jest-matchers/src/toThrowMatchers.js +++ b/packages/jest-matchers/src/toThrowMatchers.js @@ -156,7 +156,7 @@ const printActualErrorMessage = error => { formatStackTrace(stack, { noStackTrace: false, rootDir: process.cwd(), - testRegex: '', + testMatch: [], }), ) ); diff --git a/packages/jest-runtime/package.json b/packages/jest-runtime/package.json index 5ea030c1c5b9..81da75b180a1 100644 --- a/packages/jest-runtime/package.json +++ b/packages/jest-runtime/package.json @@ -17,7 +17,6 @@ "jest-file-exists": "^17.0.0", "jest-haste-map": "^18.1.0", "jest-resolve": "^18.1.0", - "jest-snapshot": "^18.1.0", "jest-util": "^18.1.0", "micromatch": "^2.3.11", "strip-bom": "3.0.0", diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 5e9b730e7fc2..29dc52d0af77 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -27,7 +27,6 @@ const shouldInstrument = require('./shouldInstrument'); const transform = require('./transform'); const { createDirectory, - replacePathSepForRegex, } = require('jest-util'); type Module = {| @@ -90,7 +89,6 @@ class Runtime { _shouldAutoMock: boolean; _shouldMockModuleCache: BooleanObject; _shouldUnmockTransitiveDependenciesCache: BooleanObject; - _testRegex: RegExp; _transitiveShouldMock: BooleanObject; _unmockList: ?RegExp; _virtualMocks: BooleanObject; @@ -115,7 +113,6 @@ class Runtime { this._mocksPattern = config.mocksPattern ? new RegExp(config.mocksPattern) : null; this._shouldAutoMock = config.automock; - this._testRegex = new RegExp(replacePathSepForRegex(config.testRegex)); this._virtualMocks = Object.create(null); this._mockMetaDataCache = Object.create(null); diff --git a/packages/jest-runtime/src/shouldInstrument.js b/packages/jest-runtime/src/shouldInstrument.js index 2f1530ec7091..228d4e14756a 100644 --- a/packages/jest-runtime/src/shouldInstrument.js +++ b/packages/jest-runtime/src/shouldInstrument.js @@ -23,6 +23,13 @@ const shouldInstrument = (filename: Path, config: Config): boolean => { return false; } + if ( + config.testMatch && + config.testMatch.length && + micromatch.any(filename, config.testMatch)) { + return false; + } + if ( // This configuration field contains an object in the form of: // {'path/to/file.js': true} diff --git a/packages/jest-runtime/src/transform.js b/packages/jest-runtime/src/transform.js index 12ed496cc660..1c52d9819650 100644 --- a/packages/jest-runtime/src/transform.js +++ b/packages/jest-runtime/src/transform.js @@ -61,6 +61,7 @@ const getCacheKey = ( moduleFileExtensions: config.moduleFileExtensions, moduleNameMapper: config.moduleNameMapper, rootDir: config.rootDir, + testMatch: config.testMatch, testPathDirs: config.testPathDirs, testRegex: config.testRegex, transformIgnorePatterns: config.transformIgnorePatterns, diff --git a/packages/jest-snapshot/src/__tests__/plugins-test.js b/packages/jest-snapshot/src/__tests__/plugins-test.js index 057ff7435a52..26bd654c6665 100644 --- a/packages/jest-snapshot/src/__tests__/plugins-test.js +++ b/packages/jest-snapshot/src/__tests__/plugins-test.js @@ -10,23 +10,27 @@ beforeEach(() => jest.resetModules()); -const testPath = serializers => { - const {addPlugins, getPlugins} = require('../plugins'); - const serializerPaths = serializers.map(s => - require.resolve(`./plugins/${s}`), +const testPath = names => { + const {addSerializer, getSerializers} = require('../plugins'); + const prev = getSerializers(); + const added = names.map( + name => require(require.resolve(`./plugins/${name}`)) ); - addPlugins(serializerPaths); - const expected = serializerPaths.map(p => require(p)); - const plugins = getPlugins(); - expect(plugins.length).toBe(serializers.length + 2); - plugins.splice(-2, 2); - expect(plugins).toEqual(expected); + // Jest tests snapshotSerializers in order preceding built-in serializers. + // Therefore, add in reverse because the last added is the first tested. + added.concat().reverse().forEach( + serializer => addSerializer(serializer) + ); + + const next = getSerializers(); + expect(next.length).toBe(added.length + prev.length); + expect(next).toEqual(added.concat(prev)); }; it('gets plugins', () => { - const {getPlugins} = require('../plugins'); - const plugins = getPlugins(); + const {getSerializers} = require('../plugins'); + const plugins = getSerializers(); expect(plugins.length).toBe(2); }); diff --git a/packages/jest-snapshot/src/index.js b/packages/jest-snapshot/src/index.js index f80db28e8a7e..acc9db2c01a3 100644 --- a/packages/jest-snapshot/src/index.js +++ b/packages/jest-snapshot/src/index.js @@ -17,7 +17,7 @@ const fileExists = require('jest-file-exists'); const fs = require('fs'); const path = require('path'); const SnapshotState = require('./State'); -const {getPlugins, addPlugins} = require('./plugins'); +const {addSerializer, getSerializers} = require('./plugins'); const { EXPECTED_COLOR, @@ -153,9 +153,9 @@ const toThrowErrorMatchingSnapshot = function(received: any, expected: void) { module.exports = { EXTENSION: SNAPSHOT_EXTENSION, SnapshotState, - addPlugins, + addSerializer, cleanup, - getPlugins, + getSerializers, initializeSnapshotState, toMatchSnapshot, toThrowErrorMatchingSnapshot, diff --git a/packages/jest-snapshot/src/plugins.js b/packages/jest-snapshot/src/plugins.js index bda1d3569ea4..481155b9cd93 100644 --- a/packages/jest-snapshot/src/plugins.js +++ b/packages/jest-snapshot/src/plugins.js @@ -9,15 +9,14 @@ */ 'use strict'; -import type {Path} from 'types/Config'; - const ReactElementPlugin = require('pretty-format/build/plugins/ReactElement'); const ReactTestComponentPlugin = require('pretty-format/build/plugins/ReactTestComponent'); let PLUGINS = [ReactElementPlugin, ReactTestComponentPlugin]; -exports.addPlugins = (plugins: Array) => - // $FlowFixMe - PLUGINS = plugins.map(plugin => require(plugin)).concat(PLUGINS); +// Prepend to list so the last added is the first tested. +exports.addSerializer = (plugin: any) => { + PLUGINS = [plugin].concat(PLUGINS); +}; -exports.getPlugins = () => PLUGINS; +exports.getSerializers = () => PLUGINS; diff --git a/packages/jest-snapshot/src/utils.js b/packages/jest-snapshot/src/utils.js index cff1b3509ddc..61c7d5c02b21 100644 --- a/packages/jest-snapshot/src/utils.js +++ b/packages/jest-snapshot/src/utils.js @@ -18,7 +18,7 @@ const path = require('path'); const prettyFormat = require('pretty-format'); const fs = require('fs'); const naturalCompare = require('natural-compare'); -const getPlugins = require('./plugins').getPlugins; +const getSerializers = require('./plugins').getSerializers; const SNAPSHOT_EXTENSION = 'snap'; @@ -61,7 +61,7 @@ const addExtraLineBreaks = const serialize = (data: any): string => { return addExtraLineBreaks(prettyFormat(data, { escapeRegex: true, - plugins: getPlugins(), + plugins: getSerializers(), printFunctionName: false, })); }; diff --git a/packages/jest-util/package.json b/packages/jest-util/package.json index c6d2cd757720..08604486d199 100644 --- a/packages/jest-util/package.json +++ b/packages/jest-util/package.json @@ -14,6 +14,7 @@ "jest-mock": "^18.0.0", "jest-validate": "^18.1.0", "leven": "^2.0.0", + "micromatch": "^2.3.11", "mkdirp": "^0.5.1" } } diff --git a/packages/jest-util/src/messages.js b/packages/jest-util/src/messages.js index adf538f0bbb3..9030c1d9b854 100644 --- a/packages/jest-util/src/messages.js +++ b/packages/jest-util/src/messages.js @@ -9,10 +9,11 @@ */ 'use strict'; -import type {Config, Path} from 'types/Config'; +import type {Config, Glob, Path} from 'types/Config'; import type {AssertionResult, TestResult} from 'types/TestResult'; const chalk = require('chalk'); +const micromatch = require('micromatch'); const path = require('path'); const separateMessageFromStack = require('./separateMessageFromStack'); @@ -119,7 +120,11 @@ const formatPaths = ( let filePath = path.relative(config.rootDir, match[2]); // highlight paths from the current test file if ( - config.testRegex && new RegExp(config.testRegex).test(filePath) || + ( + config.testMatch && + config.testMatch.length && + micromatch(filePath, config.testMatch) + ) || filePath === relativeTestPath ) { filePath = chalk.reset.cyan(filePath); @@ -130,7 +135,7 @@ const formatPaths = ( type StackTraceOptions = { noStackTrace: boolean, rootDir: string, - testRegex: string, + testMatch: Array, }; const formatStackTrace = ( diff --git a/packages/jest-validate/src/validate.js b/packages/jest-validate/src/validate.js index 9df182460bf3..b2b6778ab774 100644 --- a/packages/jest-validate/src/validate.js +++ b/packages/jest-validate/src/validate.js @@ -16,7 +16,13 @@ const defaultConfig = require('./defaultConfig'); const _validate = (config: Object, options: ValidationOptions) => { for (const key in config) { - if (hasOwnProperty.call(options.exampleConfig, key)) { + if ( + options.deprecatedConfig && + key in options.deprecatedConfig && + typeof options.deprecate === 'function' + ) { + options.deprecate(config, key, options.deprecatedConfig, options); + } else if (hasOwnProperty.call(options.exampleConfig, key)) { if ( typeof options.condition === 'function' && typeof options.error === 'function' && @@ -24,12 +30,6 @@ const _validate = (config: Object, options: ValidationOptions) => { ) { options.error(key, config[key], options.exampleConfig[key], options); } - } else if ( - options.deprecatedConfig && - key in options.deprecatedConfig && - typeof options.deprecate === 'function' - ) { - options.deprecate(config, key, options.deprecatedConfig, options); } else { options.unknown && options.unknown(config, options.exampleConfig, key, options); diff --git a/packages/jest/README.md b/packages/jest/README.md new file mode 100644 index 000000000000..91dc5675a4f8 --- /dev/null +++ b/packages/jest/README.md @@ -0,0 +1,11 @@ +# Jest + +🃏 Painless JavaScript Testing + +- **👩🏻‍💻 Easy Setup**: Jest is a complete and easy to set up JavaScript testing solution. In fact, Jest works out of the box for any React project. + +- **🏃🏽 Instant Feedback**: Failed tests run first. Fast interactive mode can switch between running all tests or only test files related to changed files. + +- **📸 Snapshot Testing**: Jest can [capture snapshots](http://facebook.github.io/jest/docs/snapshot-testing.html) of React trees or other serializable values to simplify UI testing. + +Read More: http://facebook.github.io/jest/ diff --git a/packages/jest/package.json b/packages/jest/package.json index c0217aa4f622..adb24aa4b171 100644 --- a/packages/jest/package.json +++ b/packages/jest/package.json @@ -16,5 +16,32 @@ "type": "git", "url": "https://github.com/facebook/jest" }, - "license": "BSD-3-Clause" + "homepage": "http://facebook.github.io/jest/", + "license": "BSD-3-Clause", + "keywords": [ + "ava", + "babel", + "coverage", + "easy", + "expect", + "facebook", + "immersive", + "instant", + "jasmine", + "jest", + "jsdom", + "mocha", + "mocking", + "painless", + "qunit", + "runner", + "sandboxed", + "snapshot", + "tap", + "tape", + "test", + "testing", + "typescript", + "watch" + ] } diff --git a/types/Config.js b/types/Config.js index 19a82fe5a4b2..26e05a41adf9 100644 --- a/types/Config.js +++ b/types/Config.js @@ -42,6 +42,7 @@ export type DefaultConfig = {| resetModules: boolean, snapshotSerializers: Array, testEnvironment: string, + testMatch: Array, testPathDirs: Array, testPathIgnorePatterns: Array, testRegex: string, @@ -93,6 +94,7 @@ export type Config = {| silent: boolean, snapshotSerializers: Array, testEnvironment: string, + testMatch: Array, testNamePattern: string, testPathDirs: Array, testPathIgnorePatterns: Array, @@ -152,6 +154,7 @@ export type InitialConfig = {| silent?: boolean, snapshotSerializers?: Array, testEnvironment?: string, + testMatch?: Array, testNamePattern?: string, testPathDirs?: Array, testPathIgnorePatterns?: Array, diff --git a/website/publish-gh-pages.js b/website/publish-gh-pages.js index 16637d05c77b..75cd0beeea19 100755 --- a/website/publish-gh-pages.js +++ b/website/publish-gh-pages.js @@ -5,9 +5,10 @@ require(`shelljs/global`); const GIT_USER = process.env.GIT_USER; -const DEPLOY_USER = process.env.DEPLOY_USER; +const CIRCLE_BRANCH = process.env.CIRCLE_BRANCH; const CIRCLE_PROJECT_USERNAME = process.env.CIRCLE_PROJECT_USERNAME; const CIRCLE_PROJECT_REPONAME = process.env.CIRCLE_PROJECT_REPONAME; +const CI_PULL_REQUEST = process.env.CI_PULL_REQUEST; const remoteBranch = `https://${GIT_USER}@github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}.git`; if (!which(`git`)) { @@ -15,6 +16,13 @@ if (!which(`git`)) { exit(1); } +if (CI_PULL_REQUEST || CIRCLE_BRANCH !== `master` || CIRCLE_PROJECT_USERNAME !== `facebook`) { + echo(`Skipping deploy`); + exit(0); +} + +echo(`Building branch ${CIRCLE_BRANCH}, preparing to push to gh-pages`); + // Clear out existing build folder rm(`-rf`, `build`); mkdir(`-p`, `build`); @@ -40,7 +48,7 @@ if (exec(`git checkout origin/gh-pages`).code + cd(`../..`); if (exec(`node server/generate.js`).code) { - echo(`Error: generate failed`); + echo(`Error: Generating HTML failed`); exit(1); } @@ -49,10 +57,10 @@ cd(`build/${CIRCLE_PROJECT_REPONAME}-gh-pages`); exec(`git add --all`); exec(`git commit -m "update website"`); -if (exec(`git push git@github.com:${DEPLOY_USER}/${CIRCLE_PROJECT_REPONAME}.git gh-pages`).code !== 0) { +if (exec(`git push origin gh-pages`).code !== 0) { echo(`Error: Git push failed`); exit(1); } else { - echo(`Website is live at: https://${DEPLOY_USER}.github.io/${CIRCLE_PROJECT_REPONAME}/`); + echo(`Website is live at: https://${CIRCLE_PROJECT_USERNAME}.github.io/${CIRCLE_PROJECT_REPONAME}/`); exit(0); } diff --git a/website/server/generate.js b/website/server/generate.js index 81a9c0ff8f71..326ce76120ef 100644 --- a/website/server/generate.js +++ b/website/server/generate.js @@ -3,6 +3,7 @@ const glob = require('glob'); const fs = require('fs.extra'); const mkdirp = require('mkdirp'); const server = require('./server.js'); +const feed = require('./feed'); // Sadly, our setup fatals when doing multiple concurrent requests // I don't have the time to dig into why, it's easier to just serialize @@ -31,6 +32,14 @@ const queue = (function() { return {push}; })(); +queue.push(cb => { + mkdirp.sync('build/jest/blog/'); + fs.writeFileSync('build/jest/blog/feed.xml', feed('rss')); + fs.writeFileSync('build/jest/blog/atom.xml', feed('atom')); + console.log('Generated RSS feed'); + cb(); +}); + glob('src/**/*.*', (er, files) => { files.forEach(file => { let targetFile = file.replace(/^src/, 'build'); @@ -53,6 +62,7 @@ glob('src/**/*.*', (er, files) => { }); queue.push(cb => { + console.log('Generated website'); server.close(); cb(); }); diff --git a/website/siteConfig.js b/website/siteConfig.js index 96609b786554..0ac77724da6c 100644 --- a/website/siteConfig.js +++ b/website/siteConfig.js @@ -163,6 +163,11 @@ const users = [ image: '/jest/img/logos/soundcloud.png', infoLink: 'https://soundcloud.com/', }, + { + caption: 'Sprout Social', + image: '/jest/img/logos/sproutsocial.png', + infoLink: 'https://sproutsocial.com/', + }, { caption: 'Trivago', image: '/jest/img/logos/trivago.png', diff --git a/website/src/jest/img/logos/sproutsocial.png b/website/src/jest/img/logos/sproutsocial.png new file mode 100644 index 000000000000..0d17efd6cdf4 Binary files /dev/null and b/website/src/jest/img/logos/sproutsocial.png differ