diff --git a/.eslintrc.js b/.eslintrc.js index 053fc383039e..fc737bf0a8f2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -63,6 +63,8 @@ module.exports = { 'packages/jest-editor-support/src/Process.js', 'packages/jest-editor-support/src/Runner.js', 'packages/jest-editor-support/src/Settings.js', + 'packages/jest-editor-support/src/Snapshot.js', + 'packages/jest-editor-support/src/__tests__/Snapshot-test.js', 'packages/jest-jasmine2/src/jasmine/Env.js', 'packages/jest-jasmine2/src/jasmine/Spec.js', 'packages/jest-jasmine2/src/jasmine/Suite.js', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a59916191d85..00d138b99214 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # Pings orta when PRs are to this module -packages/jest-editor-support/* @orta +packages/jest-editor-support/src/* @orta diff --git a/.vscode/settings.json b/.vscode/settings.json index f25eef6ad02f..d0188f92d5cb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,16 +1,18 @@ { - "jest.pathToJest": "yarn jest --", "editor.rulers": [80], "files.exclude": { "**/.git": true, "**/node_modules": true, "**/build": true }, + "editor.formatOnSave": true, + "flow.useNPMPackagedFlow": true, + "javascript.validate.enable": false, + "jest.pathToJest": "yarn jest --", + "prettier.eslintIntegration": true, "prettier.parser": "flow", "prettier.printWidth": 80, - "prettier.singleQuote": true, - "prettier.trailingComma": "all", "prettier.semi": true, - "editor.formatOnSave": true, - "prettier.eslintIntegration": true + "prettier.singleQuote": true, + "prettier.trailingComma": "all" } diff --git a/appveyor.yml b/appveyor.yml index 6c905cf67bab..7e1d847a2f78 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,7 @@ init: install: - ps: Install-Product node $env:nodejs_version x64 - node --version - - curl -fsSL -o yarn.js https://github.com/yarnpkg/yarn/releases/download/v0.28.4/yarn-0.28.4.js + - curl -fsSL -o yarn.js https://github.com/yarnpkg/yarn/releases/download/v1.1.0/yarn-1.1.0.js - node ./yarn.js --version - node ./yarn.js install - node ./yarn.js run build @@ -20,7 +20,7 @@ cache: - .eslintcache test_script: - - node ./yarn.js run jest -- --color + - node ./yarn.js run jest --color # Don't actually build. build: off diff --git a/docs/en/GlobalAPI.md b/docs/en/GlobalAPI.md index 8e9128735a6e..7fa2785f45b9 100644 --- a/docs/en/GlobalAPI.md +++ b/docs/en/GlobalAPI.md @@ -288,7 +288,8 @@ test('did not rain', () => { The first argument is the test name; the second argument is a function that contains the expectations to test. -If a promise is returned from `test`, Jest will wait for the promise to resolve before letting the test complete. +> Note: If a **promise is returned** from `test`, Jest will wait for the promise to resolve before letting the test complete. +Jest will also wait if you **provide an argument to the test function**, usually called `done`. This could be handy when you want to test callbacks. See how to test async code [here](http://facebook.github.io/jest/docs/en/asynchronous.html#callbacks). For example, let's say `fetchBeverageList()` returns a promise that is supposed to resolve to a list that has `lemon` in it. You can test this with: diff --git a/fixtures/failing_jsons/monorepo_root_1.json b/fixtures/failing_jsons/monorepo_root_1.json new file mode 100644 index 000000000000..bb381772295f --- /dev/null +++ b/fixtures/failing_jsons/monorepo_root_1.json @@ -0,0 +1,474 @@ +{ + "numFailedTestSuites": 4, + "numFailedTests": 2, + "numPassedTestSuites": 4, + "numPassedTests": 46, + "numPendingTestSuites": 0, + "numPendingTests": 0, + "numRuntimeErrorTestSuites": 2, + "numTotalTestSuites": 8, + "numTotalTests": 48, + "snapshot": { + "added": 0, + "didUpdate": false, + "failure": false, + "filesAdded": 0, + "filesRemoved": 0, + "filesUnmatched": 0, + "filesUpdated": 0, + "matched": 0, + "total": 0, + "unchecked": 0, + "unmatched": 0, + "updated": 0 + }, + "startTime": 1506696062972, + "success": false, + "testResults": [ + { + "assertionResults": [ + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": + "testing EthNumber... can take various format bigNumber can take", + "status": "passed", + "title": "can take various format bigNumber can take" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [ + "Error: Failed: that's right\n at stackFormatter (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at expectationResultFactory (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:591)\n at Spec.Object..Spec.addExpectationResult (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Spec.js:73:70)\n at Env.fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Env.js:510:25)\n at fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/jasmine_light.js:116:23)\n at Object. (/Z/Y/packages/Y-core/src/eth/__tests__/types.test.ts:19:9)\n at Object.asyncFn (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine_async.js:124:345)\n at resolve (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/queue_runner.js:46:12)\n at Promise ()\n at mapper (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/queue_runner.js:34:499)" + ], + "fullName": "testing EthNumber... should fail", + "status": "failed", + "title": "should fail" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": + "testing EthNumber... can handle empty and null as expected", + "status": "passed", + "title": "can handle empty and null as expected" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... should prevent toNumber for money", + "status": "passed", + "title": "should prevent toNumber for money" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... can understand unit", + "status": "passed", + "title": "can understand unit" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... can compare across unit", + "status": "passed", + "title": "can compare across unit" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... can operate across unit", + "status": "passed", + "title": "can operate across unit" + }, + { + "ancestorTitles": ["testing Address"], + "failureMessages": [], + "fullName": "testing Address can prevent invalid address", + "status": "passed", + "title": "can prevent invalid address" + }, + { + "ancestorTitles": ["testing Address"], + "failureMessages": [], + "fullName": + "testing Address can accept valid address with or without prefix", + "status": "passed", + "title": "can accept valid address with or without prefix" + }, + { + "ancestorTitles": ["testing Address"], + "failureMessages": [], + "fullName": "testing Address can detect if address is empty", + "status": "passed", + "title": "can detect if address is empty" + } + ], + "endTime": 1506696066220, + "message": + " ● testing EthNumber... › should fail\n\n Failed: that's right\n \n at stackFormatter (node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at Object. (packages/Y-core/src/eth/__tests__/types.test.ts:19:9)\n at Promise ()\n", + "name": "/X/packages/Y-core/src/eth/__tests__/types.test.ts", + "startTime": 1506696063929, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry simple curry", + "status": "passed", + "title": "simple curry" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry can parse middleware", + "status": "passed", + "title": "can parse middleware" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry can be used many times", + "status": "passed", + "title": "can be used many times" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry can be used with deferred functions many times", + "status": "passed", + "title": "can be used with deferred functions many times" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [ + "Error: Failed: intentionally failed\n at stackFormatter (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at expectationResultFactory (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:591)\n at Spec.Object..Spec.addExpectationResult (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Spec.js:73:70)\n at Env.fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Env.js:510:25)\n at fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/jasmine_light.js:116:23)\n at Object. (/Z/Y/packages/Y-keeper/src/redux/doable/__tests__/learn-ramda.test.ts:75:9)\n at Object.asyncFn (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine_async.js:124:345)\n at resolve (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/queue_runner.js:46:12)\n at Promise ()\n at mapper (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/queue_runner.js:34:499)" + ], + "fullName": "curry keeper jest test", + "status": "failed", + "title": "keeper jest test" + } + ], + "endTime": 1506696066416, + "message": + " ● curry › keeper jest test\n\n Failed: intentionally failed\n \n at stackFormatter (node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at Object. (packages/Y-keeper/src/redux/doable/__tests__/learn-ramda.test.ts:75:9)\n at Promise ()\n", + "name": + "/X/packages/Y-keeper/src/redux/doable/__tests__/learn-ramda.test.ts", + "startTime": 1506696063883, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker can create account and generate mnemonic phrase", + "status": "passed", + "title": "can create account and generate mnemonic phrase" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker same mnemonic will generate same keys and same order", + "status": "passed", + "title": "same mnemonic will generate same keys and same order" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker different mnemonic will generate different keys", + "status": "passed", + "title": "different mnemonic will generate different keys" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker can add more accounts, while to be restorable", + "status": "passed", + "title": "can add more accounts, while to be restorable" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": "testing HDKeyMaker has the right type", + "status": "passed", + "title": "has the right type" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker can serialize then deserialize correctly", + "status": "passed", + "title": "can serialize then deserialize correctly" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": "testing HDKeyMaker satisfy KeyMakerCreatable interface", + "status": "passed", + "title": "satisfy KeyMakerCreatable interface" + } + ], + "endTime": 1506696066829, + "message": "", + "name": "/X/packages/Y-key-hd/src/__tests__/HDKeyMaker.test.ts", + "startTime": 1506696066442, + "status": "passed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["createGateMonitor"], + "failureMessages": [], + "fullName": "createGateMonitor can log/profile all actions", + "status": "passed", + "title": "can log/profile all actions" + }, + { + "ancestorTitles": ["createGateMonitor"], + "failureMessages": [], + "fullName": "createGateMonitor can log/profile doable async actions", + "status": "passed", + "title": "can log/profile doable async actions" + } + ], + "endTime": 1506696066914, + "message": "", + "name": + "/X/packages/Y-keeper/src/redux/middlewares/__tests__/createGateMonitor.test.ts", + "startTime": 1506696066251, + "status": "passed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["test protocols"], + "failureMessages": [], + "fullName": "test protocols can create KeyMaker", + "status": "passed", + "title": "can create KeyMaker" + }, + { + "ancestorTitles": ["test protocols"], + "failureMessages": [], + "fullName": + "test protocols can pass a keyMakerCreatible class for creation", + "status": "passed", + "title": "can pass a keyMakerCreatible class for creation" + }, + { + "ancestorTitles": ["test protocols"], + "failureMessages": [], + "fullName": + "test protocols can pass a keyMakerCreatible class for deserialization", + "status": "passed", + "title": "can pass a keyMakerCreatible class for deserialization" + } + ], + "endTime": 1506696066958, + "message": "", + "name": "/X/packages/Y-core/src/protocols/__tests__/Keys.test.ts", + "startTime": 1506696066853, + "status": "passed", + "summary": "" + }, + { + "assertionResults": [], + "coverage": {}, + "endTime": 1506696067888, + "message": + " ● Test suite failed to run\n\n Cannot find module 'StyleSheet' from 'react-native-implementation.js'\n \n at Resolver.resolveModule (node_modules/jest-runtime/node_modules/jest-resolve/build/index.js:191:17)\n at Object.get StyleSheet [as StyleSheet] (packages/Y-app-vault/native/node_modules/react-native/Libraries/react-native/react-native-implementation.js:95:25)\n at Object. (packages/Y-app-vault/native/app/App.js:56:24)\n", + "name": "/X/packages/Y-app-vault/native/__tests__/index.ios.js", + "startTime": 1506696067888, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [], + "coverage": {}, + "endTime": 1506696067888, + "message": + " ● Test suite failed to run\n\n Cannot find module 'StyleSheet' from 'react-native-implementation.js'\n \n at Resolver.resolveModule (node_modules/jest-runtime/node_modules/jest-resolve/build/index.js:191:17)\n at Object.get StyleSheet [as StyleSheet] (packages/Y-app-vault/native/node_modules/react-native/Libraries/react-native/react-native-implementation.js:95:25)\n at Object. (packages/Y-app-vault/native/app/App.js:56:24)\n", + "name": "/X/packages/Y-app-vault/native/__tests__/index.android.js", + "startTime": 1506696067888, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["DoableHelper"], + "failureMessages": [], + "fullName": "DoableHelper can build Doable with simple do", + "status": "passed", + "title": "can build Doable with simple do" + }, + { + "ancestorTitles": ["DoableHelper"], + "failureMessages": [], + "fullName": "DoableHelper can build async Doable", + "status": "passed", + "title": "can build async Doable" + }, + { + "ancestorTitles": ["DoableHelper"], + "failureMessages": [], + "fullName": "DoableHelper can build doable with static result", + "status": "passed", + "title": "can build doable with static result" + }, + { + "ancestorTitles": ["DoableHelper"], + "failureMessages": [], + "fullName": "DoableHelper can build doable with reducer", + "status": "passed", + "title": "can build doable with reducer" + }, + { + "ancestorTitles": ["DoableHelper"], + "failureMessages": [], + "fullName": "DoableHelper can build with both do and reducer", + "status": "passed", + "title": "can build with both do and reducer" + }, + { + "ancestorTitles": ["DoableHelper"], + "failureMessages": [], + "fullName": "DoableHelper can test if action is a doableAction", + "status": "passed", + "title": "can test if action is a doableAction" + }, + { + "ancestorTitles": ["DoableHelper"], + "failureMessages": [], + "fullName": "DoableHelper an empty doable doesn't make action doable", + "status": "passed", + "title": "an empty doable doesn't make action doable" + }, + { + "ancestorTitles": ["DoableAction"], + "failureMessages": [], + "fullName": "DoableAction can be bind with store dispatch", + "status": "passed", + "title": "can be bind with store dispatch" + }, + { + "ancestorTitles": ["DoableActionInvoker"], + "failureMessages": [], + "fullName": "DoableActionInvoker will skip non-DoableAction", + "status": "passed", + "title": "will skip non-DoableAction" + }, + { + "ancestorTitles": ["DoableActionInvoker"], + "failureMessages": [], + "fullName": "DoableActionInvoker can invoke DoableAction", + "status": "passed", + "title": "can invoke DoableAction" + }, + { + "ancestorTitles": ["DoableActionInvoker"], + "failureMessages": [], + "fullName": "DoableActionInvoker can chain with other middlewares", + "status": "passed", + "title": "can chain with other middlewares" + }, + { + "ancestorTitles": ["DoableActionInvoker"], + "failureMessages": [], + "fullName": + "DoableActionInvoker won't execute the action already executed", + "status": "passed", + "title": "won't execute the action already executed" + }, + { + "ancestorTitles": ["DoableActionInvoker"], + "failureMessages": [], + "fullName": "DoableActionInvoker can handle action exception", + "status": "passed", + "title": "can handle action exception" + }, + { + "ancestorTitles": ["DoableActionInvoker"], + "failureMessages": [], + "fullName": "DoableActionInvoker can invoke asyn action", + "status": "passed", + "title": "can invoke asyn action" + }, + { + "ancestorTitles": ["DoableActionLogging"], + "failureMessages": [], + "fullName": "DoableActionLogging can log sync actions", + "status": "passed", + "title": "can log sync actions" + }, + { + "ancestorTitles": ["DoableActionLogging"], + "failureMessages": [], + "fullName": + "DoableActionLogging can log async actions upon actual completion", + "status": "passed", + "title": "can log async actions upon actual completion" + }, + { + "ancestorTitles": ["DoableActionReducer"], + "failureMessages": [], + "fullName": "DoableActionReducer can invoke DoableAction's reducer", + "status": "passed", + "title": "can invoke DoableAction's reducer" + }, + { + "ancestorTitles": ["DoableActionReducer"], + "failureMessages": [], + "fullName": + "DoableActionReducer can merge from DoableAction's doResult", + "status": "passed", + "title": "can merge from DoableAction's doResult" + }, + { + "ancestorTitles": ["DoableActionReducer"], + "failureMessages": [], + "fullName": + "DoableActionReducer will return original state for actions without reducer property", + "status": "passed", + "title": + "will return original state for actions without reducer property" + }, + { + "ancestorTitles": ["more complex cases"], + "failureMessages": [], + "fullName": + "more complex cases can handle nested dispatches in sync operation", + "status": "passed", + "title": "can handle nested dispatches in sync operation" + }, + { + "ancestorTitles": ["more complex cases"], + "failureMessages": [], + "fullName": + "more complex cases can handle nested dispatches in async operation", + "status": "passed", + "title": "can handle nested dispatches in async operation" + } + ], + "endTime": 1506696067828, + "message": "", + "name": + "/X/packages/Y-keeper/src/redux/doable/__tests__/ReduxDoable.test.ts", + "startTime": 1506696063895, + "status": "passed", + "summary": "" + } + ], + "wasInterrupted": false +} diff --git a/fixtures/failing_jsons/monorepo_root_2.json b/fixtures/failing_jsons/monorepo_root_2.json new file mode 100644 index 000000000000..457eb1e6462e --- /dev/null +++ b/fixtures/failing_jsons/monorepo_root_2.json @@ -0,0 +1,246 @@ +{ + "numFailedTestSuites": 2, + "numFailedTests": 2, + "numPassedTestSuites": 2, + "numPassedTests": 21, + "numPendingTestSuites": 0, + "numPendingTests": 0, + "numRuntimeErrorTestSuites": 0, + "numTotalTestSuites": 4, + "numTotalTests": 23, + "snapshot": { + "added": 0, + "didUpdate": false, + "failure": false, + "filesAdded": 0, + "filesRemoved": 0, + "filesUnmatched": 0, + "filesUpdated": 0, + "matched": 0, + "total": 0, + "unchecked": 0, + "unmatched": 0, + "updated": 0 + }, + "startTime": 1506716960031, + "success": false, + "testResults": [ + { + "assertionResults": [ + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry simple curry", + "status": "passed", + "title": "simple curry" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry can parse middleware", + "status": "passed", + "title": "can parse middleware" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry can be used many times", + "status": "passed", + "title": "can be used many times" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [], + "fullName": "curry can be used with deferred functions many times", + "status": "passed", + "title": "can be used with deferred functions many times" + }, + { + "ancestorTitles": ["curry"], + "failureMessages": [ + "Error: Failed: intentionally failed\n at stackFormatter (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at expectationResultFactory (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:591)\n at Spec.Object..Spec.addExpectationResult (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Spec.js:73:70)\n at Env.fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Env.js:510:25)\n at fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/jasmine_light.js:116:23)\n at Object. (/Z/Y/packages/Y-keeper/src/redux/doable/__tests__/learn-ramda.test.ts:75:9)\n at Object.asyncFn (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine_async.js:124:345)\n at resolve (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/queue_runner.js:46:12)\n at tryCallTwo (/Z/Y/node_modules/promise/lib/core.js:45:5)\n at doResolve (/Z/Y/node_modules/promise/lib/core.js:200:13)" + ], + "fullName": "curry keeper jest test", + "status": "failed", + "title": "keeper jest test" + } + ], + "endTime": 1506716960706, + "message": + " ● curry › keeper jest test\n\n Failed: intentionally failed\n \n at stackFormatter (node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at Object. (packages/Y-keeper/src/redux/doable/__tests__/learn-ramda.test.ts:75:9)\n at tryCallTwo (node_modules/promise/lib/core.js:45:5)\n at doResolve (node_modules/promise/lib/core.js:200:13)\n", + "name": + "/X/packages/Y-keeper/src/redux/doable/__tests__/learn-ramda.test.ts", + "startTime": 1506716960103, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": + "testing EthNumber... can take various format bigNumber can take", + "status": "passed", + "title": "can take various format bigNumber can take" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": + "testing EthNumber... can handle empty and null as expected", + "status": "passed", + "title": "can handle empty and null as expected" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... should prevent toNumber for money", + "status": "passed", + "title": "should prevent toNumber for money" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... can understand unit", + "status": "passed", + "title": "can understand unit" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... can compare across unit", + "status": "passed", + "title": "can compare across unit" + }, + { + "ancestorTitles": ["testing EthNumber..."], + "failureMessages": [], + "fullName": "testing EthNumber... can operate across unit", + "status": "passed", + "title": "can operate across unit" + }, + { + "ancestorTitles": ["testing Address"], + "failureMessages": [], + "fullName": "testing Address can prevent invalid address", + "status": "passed", + "title": "can prevent invalid address" + }, + { + "ancestorTitles": ["testing Address"], + "failureMessages": [], + "fullName": + "testing Address can accept valid address with or without prefix", + "status": "passed", + "title": "can accept valid address with or without prefix" + }, + { + "ancestorTitles": ["testing Address"], + "failureMessages": [], + "fullName": "testing Address can detect if address is empty", + "status": "passed", + "title": "can detect if address is empty" + } + ], + "endTime": 1506716961514, + "message": "", + "name": "/X/packages/Y-core/src/eth/__tests__/types.test.ts", + "startTime": 1506716960759, + "status": "passed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": [], + "failureMessages": [], + "fullName": "renders correctly", + "status": "passed", + "title": "renders correctly" + }, + { + "ancestorTitles": [], + "failureMessages": [ + "Error: Failed: make jest fail for react-native\n at stackFormatter (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at expectationResultFactory (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:591)\n at Spec.Object..Spec.addExpectationResult (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Spec.js:73:70)\n at Env.fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/Env.js:510:25)\n at fail (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine/jasmine_light.js:116:23)\n at Object. (/Z/Y/packages/Y-app-vault/native/__tests__/index.ios.js:14:1)\n at Object.asyncFn (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/jasmine_async.js:124:345)\n at resolve (/Z/Y/node_modules/jest/node_modules/jest-jasmine2/build/queue_runner.js:46:12)\n at tryCallTwo (/Z/Y/node_modules/promise/lib/core.js:45:5)\n at doResolve (/Z/Y/node_modules/promise/lib/core.js:200:13)" + ], + "fullName": "testing jest with react-native", + "status": "failed", + "title": "testing jest with react-native" + } + ], + "endTime": 1506716961734, + "message": + " ● testing jest with react-native\n\n Failed: make jest fail for react-native\n \n at stackFormatter (node_modules/jest/node_modules/jest-jasmine2/build/expectation_result_factory.js:49:427)\n at Object. (packages/Y-app-vault/native/__tests__/index.ios.js:14:1)\n at tryCallTwo (node_modules/promise/lib/core.js:45:5)\n at doResolve (node_modules/promise/lib/core.js:200:13)\n", + "name": "/X/packages/Y-app-vault/native/__tests__/index.ios.js", + "startTime": 1506716961553, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker can create account and generate mnemonic phrase", + "status": "passed", + "title": "can create account and generate mnemonic phrase" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker same mnemonic will generate same keys and same order", + "status": "passed", + "title": "same mnemonic will generate same keys and same order" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker different mnemonic will generate different keys", + "status": "passed", + "title": "different mnemonic will generate different keys" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker can add more accounts, while to be restorable", + "status": "passed", + "title": "can add more accounts, while to be restorable" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": "testing HDKeyMaker has the right type", + "status": "passed", + "title": "has the right type" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": + "testing HDKeyMaker can serialize then deserialize correctly", + "status": "passed", + "title": "can serialize then deserialize correctly" + }, + { + "ancestorTitles": ["testing HDKeyMaker"], + "failureMessages": [], + "fullName": "testing HDKeyMaker satisfy KeyMakerCreatable interface", + "status": "passed", + "title": "satisfy KeyMakerCreatable interface" + } + ], + "endTime": 1506716961992, + "message": "", + "name": "/X/packages/Y-key-hd/src/__tests__/HDKeyMaker.test.ts", + "startTime": 1506716961763, + "status": "passed", + "summary": "" + } + ], + "wasInterrupted": false +} diff --git a/integration_tests/__tests__/request_animation_frame.test.js b/integration_tests/__tests__/request_animation_frame.test.js new file mode 100644 index 000000000000..383291d1f604 --- /dev/null +++ b/integration_tests/__tests__/request_animation_frame.test.js @@ -0,0 +1,19 @@ +/** + * 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 + */ +'use strict'; + +const runJest = require('../runJest'); + +test('requestAnimationFrame', () => { + const result = runJest('request_animation_frame', ['--verbose']); + const stderr = result.stderr.toString(); + + expect(stderr).toMatch('requestAnimationFrame test'); + expect(result.status).toBe(0); +}); diff --git a/integration_tests/request_animation_frame/__tests__/request_animation_frame.test.js b/integration_tests/request_animation_frame/__tests__/request_animation_frame.test.js new file mode 100644 index 000000000000..8f1ff5cbfabf --- /dev/null +++ b/integration_tests/request_animation_frame/__tests__/request_animation_frame.test.js @@ -0,0 +1,21 @@ +/** + * 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. + */ + +/* eslint-env browser */ + +'use strict'; + +test('requestAnimationFrame test', done => { + expect.hasAssertions(); + + requestAnimationFrame(timestamp => { + expect(true).toBe(true); + expect(timestamp).toBeGreaterThan(0); + + done(); + }); +}); diff --git a/integration_tests/request_animation_frame/package.json b/integration_tests/request_animation_frame/package.json new file mode 100644 index 000000000000..0ded940b7cb7 --- /dev/null +++ b/integration_tests/request_animation_frame/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "jsdom" + } +} diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index 1c7e541cdad8..c98f2be7cb3a 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -229,6 +229,17 @@ Received: \\"abc\\"" `; +exports[`.toBe() fails for: "with +trailing space" and "without trailing space" 1`] = ` +"expect(received).toBe(expected) + +Expected value to be (using ===): + \\"without trailing space\\" +Received: + \\"with +trailing space\\"" +`; + exports[`.toBe() fails for: [] and [] 1`] = ` "expect(received).toBe(expected) diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index e19cd982b900..4fbb763be944 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -120,6 +120,7 @@ describe('.toBe()', () => { [{a: 1}, {a: 1}], [{a: 1}, {a: 5}], ['abc', 'cde'], + ['with \ntrailing space', 'without trailing space'], [[], []], [null, undefined], ].forEach(([a, b]) => { diff --git a/packages/expect/src/to_throw_matchers.js b/packages/expect/src/to_throw_matchers.js index 60231cf7bcf0..3a448df231d6 100644 --- a/packages/expect/src/to_throw_matchers.js +++ b/packages/expect/src/to_throw_matchers.js @@ -13,7 +13,6 @@ import getType from 'jest-get-type'; import {escapeStrForRegex} from 'jest-regex-util'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import { - RECEIVED_BG, RECEIVED_COLOR, highlightTrailingWhitespace, matcherHint, @@ -173,7 +172,7 @@ const printActualErrorMessage = error => { `Instead, it threw:\n` + RECEIVED_COLOR( ' ' + - highlightTrailingWhitespace(message, RECEIVED_BG) + + highlightTrailingWhitespace(message) + formatStackTrace( stack, { diff --git a/packages/jest-config/src/__tests__/normalize.test.js b/packages/jest-config/src/__tests__/normalize.test.js index 2b4b2782beb6..c654d4b03f87 100644 --- a/packages/jest-config/src/__tests__/normalize.test.js +++ b/packages/jest-config/src/__tests__/normalize.test.js @@ -583,6 +583,9 @@ describe('testEnvironment', () => { if (name === 'jest-environment-jsdom') { return 'node_modules/jest-environment-jsdom'; } + if (name.startsWith('/root')) { + return name; + } return findNodeModule(name); }); }); @@ -612,6 +615,18 @@ describe('testEnvironment', () => { ), ).toThrowErrorMatchingSnapshot(); }); + + it('works with rootDir', () => { + const {options} = normalize( + { + rootDir: '/root', + testEnvironment: '/testEnvironment.js', + }, + {}, + ); + + expect(options.testEnvironment).toEqual('/root/testEnvironment.js'); + }); }); describe('babel-jest', () => { diff --git a/packages/jest-config/src/utils.js b/packages/jest-config/src/utils.js index 5afccaa0b8ff..b9fde85b7407 100644 --- a/packages/jest-config/src/utils.js +++ b/packages/jest-config/src/utils.js @@ -100,7 +100,7 @@ export const _replaceRootDirTags = (rootDir: string, config: any) => { * 1. looks for relative to Jest. */ export const getTestEnvironment = (config: Object) => { - const env = config.testEnvironment; + const env = _replaceRootDirInPath(config.rootDir, config.testEnvironment); let module = Resolver.findNodeModule(`jest-environment-${env}`, { basedir: config.rootDir, }); diff --git a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.js.snap b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.js.snap index ad01ffbebaa7..e6dc7169be86 100644 --- a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.js.snap +++ b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.js.snap @@ -169,7 +169,18 @@ exports[`context number of lines: null (5 default) 1`] = ` }" `; -exports[`falls back to not call toJSON if objects look identical 1`] = ` +exports[`falls back to not call toJSON if it throws and then objects have differences 1`] = ` +"- Expected ++ Received + + Object { +- \\"line\\": 1, ++ \\"line\\": 2, + \\"toJSON\\": [Function toJSON], + }" +`; + +exports[`falls back to not call toJSON if serialization has no differences but then objects have differences 1`] = ` "Compared values serialize to the same structure. Printing internal object structure without calling \`toJSON\` instead. @@ -183,4 +194,17 @@ exports[`falls back to not call toJSON if objects look identical 1`] = ` }" `; -exports[`prints a fallback message if two objects truly look identical 1`] = `"Compared values have no visual difference."`; +exports[`highlight only the last in odd length of leading spaces (expanded) 1`] = ` +"- Expected ++ Received + +
+-   attributes.reduce(function (props, attribute) {
+-    props[attribute.name] = attribute.value;
++   attributes.reduce((props, {name, value}) => {
++   props[name] = value;
+    return props;
+-  }, {});
++ }, {});
+  
" +`; diff --git a/packages/jest-diff/src/__tests__/diff.test.js b/packages/jest-diff/src/__tests__/diff.test.js index e33c4b32be33..5733e3e17a8f 100644 --- a/packages/jest-diff/src/__tests__/diff.test.js +++ b/packages/jest-diff/src/__tests__/diff.test.js @@ -10,6 +10,8 @@ const stripAnsi = require('strip-ansi'); const diff = require('../'); +const NO_DIFF_MESSAGE = 'Compared values have no visual difference.'; + const stripped = (a, b, options) => stripAnsi(diff(a, b, options)); const unexpanded = {expand: false}; @@ -17,10 +19,6 @@ const expanded = {expand: true}; const elementSymbol = Symbol.for('react.element'); -const toJSON = function toJSON() { - return 'apple'; -}; - describe('different types', () => { [ [1, 'a', 'number', 'string'], @@ -58,10 +56,13 @@ describe('no visual difference', () => { ].forEach(values => { test(`'${JSON.stringify(values[0])}' and '${JSON.stringify( values[1], - )}'`, () => { - expect(stripped(values[0], values[1])).toBe( - 'Compared values have no visual difference.', - ); + )}' (unexpanded)`, () => { + expect(stripped(values[0], values[1], unexpanded)).toBe(NO_DIFF_MESSAGE); + }); + test(`'${JSON.stringify(values[0])}' and '${JSON.stringify( + values[1], + )}' (expanded)`, () => { + expect(stripped(values[0], values[1], expanded)).toBe(NO_DIFF_MESSAGE); }); }); @@ -69,41 +70,59 @@ describe('no visual difference', () => { const arg1 = new Map([[1, 'foo'], [2, 'bar']]); const arg2 = new Map([[2, 'bar'], [1, 'foo']]); - expect(stripped(arg1, arg2)).toBe( - 'Compared values have no visual difference.', - ); + expect(stripped(arg1, arg2)).toBe(NO_DIFF_MESSAGE); }); test('Set value order should be irrelevant', () => { const arg1 = new Set([1, 2]); const arg2 = new Set([2, 1]); - expect(stripped(arg1, arg2)).toBe( - 'Compared values have no visual difference.', - ); + expect(stripped(arg1, arg2)).toBe(NO_DIFF_MESSAGE); }); }); test('oneline strings', () => { // oneline strings don't produce a diff currently. expect(diff('ab', 'aa')).toBe(null); - expect(diff('a', 'a')).toMatch(/no visual difference/); expect(diff('123456789', '234567890')).toBe(null); // if either string is oneline expect(diff('oneline', 'multi\nline')).toBe(null); expect(diff('multi\nline', 'oneline')).toBe(null); }); -test('falls back to not call toJSON if objects look identical', () => { - const a = {line: 1, toJSON}; - const b = {line: 2, toJSON}; - expect(diff(a, b)).toMatchSnapshot(); -}); +describe('falls back to not call toJSON', () => { + describe('if serialization has no differences', () => { + const toJSON = function toJSON() { + return 'it’s all the same to me'; + }; -test('prints a fallback message if two objects truly look identical', () => { - const a = {line: 2, toJSON}; - const b = {line: 2, toJSON}; - expect(diff(a, b)).toMatchSnapshot(); + test('but then objects have differences', () => { + const a = {line: 1, toJSON}; + const b = {line: 2, toJSON}; + expect(diff(a, b)).toMatchSnapshot(); + }); + test('and then objects have no differences', () => { + const a = {line: 2, toJSON}; + const b = {line: 2, toJSON}; + expect(stripped(a, b)).toBe(NO_DIFF_MESSAGE); + }); + }); + describe('if it throws', () => { + const toJSON = function toJSON() { + throw new Error('catch me if you can'); + }; + + test('and then objects have differences', () => { + const a = {line: 1, toJSON}; + const b = {line: 2, toJSON}; + expect(diff(a, b)).toMatchSnapshot(); + }); + test('and then objects have no differences', () => { + const a = {line: 2, toJSON}; + const b = {line: 2, toJSON}; + expect(stripped(a, b)).toBe(NO_DIFF_MESSAGE); + }); + }); }); // Some of the following assertions seem complex, but compare to alternatives: @@ -761,6 +780,40 @@ describe('background color of spaces', () => { }); }); +describe('highlight only the last in odd length of leading spaces', () => { + const pre5 = { + $$typeof: elementSymbol, + props: { + children: [ + 'attributes.reduce(function (props, attribute) {', + ' props[attribute.name] = attribute.value;', // 3 leading spaces + ' return props;', // 2 leading spaces + ' }, {});', // 1 leading space + ].join('\n'), + }, + type: 'pre', + }; + const pre6 = { + $$typeof: elementSymbol, + props: { + children: [ + 'attributes.reduce((props, {name, value}) => {', + ' props[name] = value;', // from 3 to 2 leading spaces + ' return props;', // unchanged 2 leading spaces + '}, {});', // from 1 to 0 leading spaces + ].join('\n'), + }, + type: 'pre', + }; + const received = diff(pre5, pre6, expanded); + test('(expanded)', () => { + expect(received).toMatchSnapshot(); + }); + test('(unexpanded)', () => { + expect(diff(pre5, pre6, unexpanded)).toBe(received); + }); +}); + test('collapses big diffs to patch format', () => { const result = diff( {test: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}, diff --git a/packages/jest-diff/src/diff_strings.js b/packages/jest-diff/src/diff_strings.js index e10574117086..1afc95ecbbb4 100644 --- a/packages/jest-diff/src/diff_strings.js +++ b/packages/jest-diff/src/diff_strings.js @@ -76,7 +76,12 @@ const highlightLeadingTrailingSpaces = ( line: string, bgColor: Function, ): string => - highlightTrailingSpaces(line.replace(/^\s+/, bgColor('$&')), bgColor); + // If line consists of ALL spaces: highlight all of them. + highlightTrailingSpaces(line, bgColor).replace( + // If line has an ODD length of leading spaces: highlight only the LAST. + /^(\s\s)*(\s)(?=[^\s])/, + '$1' + bgColor('$2'), + ); const getAnnotation = (options: ?DiffOptions): string => chalk.green('- ' + ((options && options.aAnnotation) || 'Expected')) + diff --git a/packages/jest-docblock/README.md b/packages/jest-docblock/README.md index 41f021a912c2..4ffee198f817 100644 --- a/packages/jest-docblock/README.md +++ b/packages/jest-docblock/README.md @@ -30,6 +30,7 @@ Pragmas can also take arguments: `jest-docblock` can: * extract the docblock from some code as a string * parse a docblock string's pragmas into an object +* print an object and some comments back to a string ## Installation ```sh @@ -56,13 +57,21 @@ const code = ` } `; -const { extract, parse } = require("jest-docblock"); +const { extract, strip, parse, parseWithComments, print } = require("jest-docblock"); const docblock = extract(code); console.log(docblock); // "/**\n * Everything is awesome!\n * \n * @everything is:awesome\n * @flow\n */" +const stripped = strip(code); +console.log(stripped); // "export const everything = Object.create(null);\n export default function isAwesome(something) {\n return something === everything;\n }" + const pragmas = parse(docblock); console.log(pragmas); // { everything: "is:awesome", flow: "" } + +const parsed = parseWithComments(docblock); +console.log(parsed); // { comments: "Everything is awesome!", pragmas: { everything: "is:awesome", flow: "" } } + +console.log(print({pragmas, comments: "hi!"})) // /**\n * hi!\n *\n * @everything is:awesome\n * @flow\n */; ``` ## API Documentation @@ -70,5 +79,14 @@ console.log(pragmas); // { everything: "is:awesome", flow: "" } ### `extract(contents: string): string` Extracts a docblock from some file contents. Returns the docblock contained in `contents`. If `contents` did not contain a docblock, it will return the empty string (`""`). +### `strip(contents: string): string` +Strips the top docblock from a file and return the result. If a file does not have a docblock at the top, then return the file unchanged. + ### `parse(docblock: string): {[key: string]: string}` Parses the pragmas in a docblock string into an object whose keys are the pragma tags and whose values are the arguments to those pragmas. + +### `parseWithComments(docblock: string): { comments: string, pragmas: {[key: string]: string} }` +Similar to `parse` except this method also returns the comments from the docblock. Useful when used with `print()`. + +### `print({ comments?: string, pragmas?: {[key: string]: string} }): string` +Prints an object of key-value pairs back into a docblock. If `comments` are provided, they will be positioned on the top of the docblock. diff --git a/packages/jest-docblock/package.json b/packages/jest-docblock/package.json index 1c8921a08ade..6739455f2511 100644 --- a/packages/jest-docblock/package.json +++ b/packages/jest-docblock/package.json @@ -6,5 +6,8 @@ "url": "https://github.com/facebook/jest.git" }, "license": "MIT", - "main": "build/index.js" + "main": "build/index.js", + "dependencies": { + "detect-newline": "^2.1.0" + } } diff --git a/packages/jest-docblock/src/__tests__/index.test.js b/packages/jest-docblock/src/__tests__/index.test.js index c44de6118221..0a00ac6b2107 100644 --- a/packages/jest-docblock/src/__tests__/index.test.js +++ b/packages/jest-docblock/src/__tests__/index.test.js @@ -10,8 +10,8 @@ 'use strict'; -const os = require('os'); -const docblock = require('../'); +import os from 'os'; +import * as docblock from '..'; describe('docblock', () => { it('extracts valid docblock with line comment', () => { @@ -212,4 +212,201 @@ describe('docblock', () => { providesModule: 'apple/banana', }); }); + + it('extracts comments from docblock', () => { + const code = + '/**' + + os.EOL + + ' * hello world' + + os.EOL + + ' * @flow yes' + + os.EOL + + ' */'; + expect(docblock.parseWithComments(code)).toEqual({ + comments: 'hello world', + pragmas: {flow: 'yes'}, + }); + }); + + it('extracts multiline comments from docblock', () => { + const code = + '/**' + + os.EOL + + ' * hello' + + os.EOL + + ' * world' + + os.EOL + + ' * @flow yes' + + os.EOL + + ' */'; + expect(docblock.parseWithComments(code)).toEqual({ + comments: 'hello' + os.EOL + 'world', + pragmas: {flow: 'yes'}, + }); + }); + + it('preserves leading whitespace in multiline comments from docblock', () => { + const code = + '/**' + os.EOL + ' * hello' + os.EOL + ' * world' + os.EOL + ' */'; + + expect(docblock.parseWithComments(code).comments).toEqual( + ' hello' + os.EOL + ' world', + ); + }); + + it('extracts comments from beginning and end of docblock', () => { + const code = + '/**' + + os.EOL + + ' * hello' + + os.EOL + + ' * @flow yes' + + os.EOL + + ' * ' + + os.EOL + + ' * world' + + os.EOL + + ' */'; + expect(docblock.parseWithComments(code)).toEqual({ + comments: 'hello' + os.EOL + os.EOL + 'world', + pragmas: {flow: 'yes'}, + }); + }); + + it('extracts docblock comments as CRLF when docblock contains CRLF', () => { + const code = '/**\r\n * foo\r\n * bar\r\n*/'; + expect(docblock.parseWithComments(code)).toEqual({ + comments: 'foo\r\nbar', + pragmas: {}, + }); + }); + + it('extracts docblock comments as LF when docblock contains LF', () => { + const code = '/**\n * foo\n * bar\n*/'; + expect(docblock.parseWithComments(code)).toEqual({ + comments: 'foo\nbar', + pragmas: {}, + }); + }); + + it('strips the docblock out of a file that contains a top docblock', () => { + const code = '/**\n * foo\n * bar\n*/\nthe rest'; + expect(docblock.strip(code)).toEqual('\nthe rest'); + }); + + it('returns a file unchanged if there is no top docblock to strip', () => { + const code = 'someCodeAtTheTop();\n/** docblock */'; + expect(docblock.strip(code)).toEqual(code); + }); + + it('prints docblocks with no pragmas as empty string', () => { + const pragmas = {}; + expect(docblock.print({pragmas})).toEqual(''); + }); + + it('prints docblocks with one pragma on one line', () => { + const pragmas = {flow: ''}; + expect(docblock.print({pragmas})).toEqual('/** @flow */'); + }); + + it('prints docblocks with multiple pragmas on multiple lines', () => { + const pragmas = { + flow: '', + format: '', + }; + expect(docblock.print({pragmas})).toEqual( + '/**' + os.EOL + ' * @flow' + os.EOL + ' * @format' + os.EOL + ' */', + ); + }); + + it('prints docblocks with pragmas', () => { + const pragmas = { + flow: 'foo', + providesModule: 'x/y/z', + }; + expect(docblock.print({pragmas})).toEqual( + '/**' + + os.EOL + + ' * @flow foo' + + os.EOL + + ' * @providesModule x/y/z' + + os.EOL + + ' */', + ); + }); + + it('prints docblocks with comments', () => { + const pragmas = {flow: 'foo'}; + const comments = 'hello'; + expect(docblock.print({comments, pragmas})).toEqual( + '/**' + + os.EOL + + ' * hello' + + os.EOL + + ' *' + + os.EOL + + ' * @flow foo' + + os.EOL + + ' */', + ); + }); + + it('prints docblocks with comments and no keys', () => { + const pragmas = {}; + const comments = 'Copyright 2004-present Facebook. All Rights Reserved.'; + expect(docblock.print({comments, pragmas})).toEqual( + '/**' + os.EOL + ' * ' + comments + os.EOL + ' */', + ); + }); + + it('prints docblocks with multiline comments', () => { + const pragmas = {}; + const comments = 'hello' + os.EOL + 'world'; + expect(docblock.print({comments, pragmas})).toEqual( + '/**' + os.EOL + ' * hello' + os.EOL + ' * world' + os.EOL + ' */', + ); + }); + + it('prints docblocks that are parseable', () => { + const pragmas = {a: 'b', c: ''}; + const comments = 'hello world!'; + const formatted = docblock.print({comments, pragmas}); + const parsed = docblock.parse(formatted); + expect(parsed).toEqual(pragmas); + }); + + it('can augment existing docblocks with comments', () => { + const before = + '/**' + os.EOL + ' * Legalese' + os.EOL + ' * @flow' + os.EOL + ' */'; + const {comments, pragmas} = docblock.parseWithComments(before); + pragmas.format = ''; + const after = docblock.print({comments, pragmas}); + expect(after).toEqual( + '/**' + + os.EOL + + ' * Legalese' + + os.EOL + + ' *' + + os.EOL + + ' * @flow' + + os.EOL + + ' * @format' + + os.EOL + + ' */', + ); + }); + + it('prints docblocks using CRLF if comments contains CRLF', () => { + const pragmas = {}; + const comments = 'hello\r\nworld'; + const formatted = docblock.print({comments, pragmas}); + expect(formatted).toEqual('/**\r\n * hello\r\n * world\r\n */'); + }); + + it('prints docblocks using LF if comments contains LF', () => { + const pragmas = {}; + const comments = 'hello\nworld'; + const formatted = docblock.print({comments, pragmas}); + expect(formatted).toEqual('/**\n * hello\n * world\n */'); + }); }); diff --git a/packages/jest-docblock/src/index.js b/packages/jest-docblock/src/index.js index 714c20032fdf..771693acd332 100644 --- a/packages/jest-docblock/src/index.js +++ b/packages/jest-docblock/src/index.js @@ -7,26 +7,42 @@ * @flow */ +import detectNewline from 'detect-newline'; +import {EOL} from 'os'; + const commentEndRe = /\*\/$/; const commentStartRe = /^\/\*\*/; const docblockRe = /^\s*(\/\*\*?(.|\r?\n)*?\*\/)/; const lineCommentRe = /\/\/([^\r\n]*)/g; const ltrimRe = /^\s*/; +const rtrimRe = /\s*$/; +const ltrimNewlineRe = /^(\r?\n)+/; const multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; const propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g; -const stringStartRe = /(\r?\n|^) *\*/g; -const wsRe = /[\t ]+/g; +const stringStartRe = /(\r?\n|^) *\* ?/g; -function extract(contents: string): string { +export function extract(contents: string): string { const match = contents.match(docblockRe); return match ? match[0].replace(ltrimRe, '') || '' : ''; } -function parse(docblock: string): {[key: string]: string} { +export function strip(contents: string) { + const match = contents.match(docblockRe); + return match && match[0] ? contents.substring(match[0].length) : contents; +} + +export function parse(docblock: string): {[key: string]: string} { + return parseWithComments(docblock).pragmas; +} + +export function parseWithComments( + docblock: string, +): {comments: string, pragmas: {[key: string]: string}} { + const line = detectNewline(docblock) || EOL; + docblock = docblock .replace(commentStartRe, '') .replace(commentEndRe, '') - .replace(wsRe, ' ') .replace(lineCommentRe, '') .replace(stringStartRe, '$1'); @@ -34,17 +50,63 @@ function parse(docblock: string): {[key: string]: string} { let prev = ''; while (prev !== docblock) { prev = docblock; - docblock = docblock.replace(multilineRe, '\n$1 $2\n'); + docblock = docblock.replace(multilineRe, `${line}$1 $2${line}`); } - docblock = docblock.trim(); + docblock = docblock.replace(ltrimNewlineRe, '').replace(rtrimRe, ''); const result = Object.create(null); + const comments = docblock.replace(propertyRe, ''); + let match; while ((match = propertyRe.exec(docblock))) { result[match[1]] = match[2]; } - return result; + return {comments, pragmas: result}; } -exports.extract = extract; -exports.parse = parse; +export function print({ + comments = '', + pragmas = {}, +}: { + comments?: string, + pragmas?: {[key: string]: string}, +}): string { + const line = detectNewline(comments) || EOL; + const head = '/**'; + const start = ' *'; + const tail = ' */'; + + const keys = Object.keys(pragmas); + + const printedObject = keys + .map(key => start + ' ' + printKeyValue(key, pragmas[key]) + line) + .join(''); + + if (!comments) { + if (keys.length === 0) { + return ''; + } + if (keys.length === 1) { + return `${head} ${printKeyValue(keys[0], pragmas[keys[0]])}${tail}`; + } + } + + const printedComments = + comments + .split(line) + .map(textLine => `${start} ${textLine}`) + .join(line) + line; + + return ( + head + + line + + (comments ? printedComments : '') + + (comments && keys.length ? start + line : '') + + printedObject + + tail + ); +} + +function printKeyValue(key, value) { + return `@${key} ${value}`.trim(); +} diff --git a/packages/jest-editor-support/index.d.ts b/packages/jest-editor-support/index.d.ts index 20692498c733..dd6ea3c589f9 100644 --- a/packages/jest-editor-support/index.d.ts +++ b/packages/jest-editor-support/index.d.ts @@ -5,11 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import {EventEmitter} from 'events'; +import { EventEmitter } from 'events'; export class Runner extends EventEmitter { constructor(workspace: ProjectWorkspace); - start(): void; + watchMode: boolean; + start(watchMode?: boolean): void; closeProcess(): void; runJestWithUpdateForSnapshots(completion: any): void; } @@ -28,7 +29,7 @@ export class ProjectWorkspace { rootPath: string, pathToJest: string, pathToConfig: string, - localJestMajorVersin: number + localJestMajorVersin: number, ); pathToJest: string; rootPath: string; @@ -61,15 +62,19 @@ export class Expect extends Node {} export class TestReconciler { stateForTestFile(file: string): TestReconcilationState; - stateForTestAssertion(file: string, name: string): TestFileAssertionStatus | null; - failedStatuses(): Array; - updateFileWithJestStatus(data): void; + assertionsForTestFile(file: string): TestAssertionStatus[] | null; + stateForTestAssertion( + file: string, + name: string, + ): TestFileAssertionStatus | null; + updateFileWithJestStatus(data): TestFileAssertionStatus[]; } -export type TestReconcilationState = "Unknown" | - "KnownSuccess" | - "KnownFail" | - "KnownSkip"; +export type TestReconcilationState = + | 'Unknown' + | 'KnownSuccess' + | 'KnownFail' + | 'KnownSkip'; export interface TestFileAssertionStatus { file: string; @@ -91,7 +96,7 @@ export interface JestFileResults { name: string; summary: string; message: string; - status: "failed" | "passed"; + status: 'failed' | 'passed'; startTime: number; endTime: number; assertionResults: Array; @@ -100,7 +105,7 @@ export interface JestFileResults { export interface JestAssertionResults { name: string; title: string; - status: "failed" | "passed"; + status: 'failed' | 'passed'; failureMessages: string[]; } diff --git a/packages/jest-editor-support/package.json b/packages/jest-editor-support/package.json index 3af7167b2330..79415dfbdd73 100644 --- a/packages/jest-editor-support/package.json +++ b/packages/jest-editor-support/package.json @@ -8,7 +8,9 @@ "license": "MIT", "main": "build/index.js", "dependencies": { - "babylon": "^6.14.1" + "babylon": "^6.14.1", + "babel-traverse": "^6.14.1", + "jest-snapshot": "^21.1.0" }, "typings": "index.d.ts" } diff --git a/packages/jest-editor-support/src/Runner.js b/packages/jest-editor-support/src/Runner.js index 9bbee12b2864..048950e28850 100644 --- a/packages/jest-editor-support/src/Runner.js +++ b/packages/jest-editor-support/src/Runner.js @@ -27,6 +27,7 @@ export default class Runner extends EventEmitter { workspace: ProjectWorkspace, args: Array, ) => ChildProcess; + watchMode: boolean; constructor(workspace: ProjectWorkspace, options?: Options) { super(); @@ -35,21 +36,19 @@ export default class Runner extends EventEmitter { this.outputPath = tmpdir() + '/jest_runner.json'; } - start() { + start(watchMode: boolean = true) { if (this.debugprocess) { return; } + + this.watchMode = watchMode; + // Handle the arg change on v18 const belowEighteen = this.workspace.localJestMajorVersion < 18; const outputArg = belowEighteen ? '--jsonOutputFile' : '--outputFile'; - const args = [ - '--json', - '--useStderr', - '--watch', - outputArg, - this.outputPath, - ]; + const args = ['--json', '--useStderr', outputArg, this.outputPath]; + if (this.watchMode) args.push('--watch'); this.debugprocess = this._createProcess(this.workspace, args); this.debugprocess.stdout.on('data', (data: Buffer) => { diff --git a/packages/jest-editor-support/src/Settings.js b/packages/jest-editor-support/src/Settings.js index 5b7a3c46bd0b..9ad13b186346 100644 --- a/packages/jest-editor-support/src/Settings.js +++ b/packages/jest-editor-support/src/Settings.js @@ -32,6 +32,8 @@ type ConfigRepresentation = { testMatch: Array, }; +type ConfigRepresentations = Array; + export default class Settings extends EventEmitter { getConfigProcess: ChildProcess; jestVersionMajor: number | null; @@ -39,6 +41,7 @@ export default class Settings extends EventEmitter { workspace: ProjectWorkspace, args: Array, ) => ChildProcess; + configs: ConfigRepresentations; settings: ConfigRepresentation; workspace: ProjectWorkspace; @@ -52,21 +55,20 @@ export default class Settings extends EventEmitter { testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)(spec|test).js?(x)'], testRegex: '(/__tests__/.*|\\.(test|spec))\\.jsx?$', }; + + this.configs = [this.settings]; } - getConfig(completed: any) { + getConfigs(completed: any) { this.getConfigProcess = this._createProcess(this.workspace, [ '--showConfig', ]); this.getConfigProcess.stdout.on('data', (data: Buffer) => { - const {config, version} = JSON.parse(data.toString()); - // We can give warnings to versions under 17 now - // See https://github.com/facebook/jest/issues/2343 for moving this into - // the config object - - this.jestVersionMajor = parseInt(version.split('.').shift(), 10); - this.settings = config; + const settings = JSON.parse(data.toString()); + this.jestVersionMajor = parseInt(settings.version.split('.').shift(), 10); + this.configs = + this.jestVersionMajor >= 21 ? settings.configs : [settings.config]; }); // They could have an older build of Jest which @@ -75,4 +77,11 @@ export default class Settings extends EventEmitter { completed(); }); } + + getConfig(completed: any, index: number = 0) { + this.getConfigs(() => { + this.settings = this.configs[index]; + completed(); + }); + } } diff --git a/packages/jest-editor-support/src/Snapshot.js b/packages/jest-editor-support/src/Snapshot.js new file mode 100644 index 000000000000..8b445a88a028 --- /dev/null +++ b/packages/jest-editor-support/src/Snapshot.js @@ -0,0 +1,167 @@ +/** + * 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. + * + * @flow + */ + +'use strict'; + +import traverse from 'babel-traverse'; +import {getASTfor} from './parsers/babylon_parser'; +import {utils} from 'jest-snapshot'; + +type Node = any; + +type SnapshotMetadata = { + exists: true | false, + name: string, + node: Node, + content?: string, +}; + +const describeVariants = Object.assign( + (Object.create(null): {[string]: boolean}), + { + describe: true, + fdescribe: true, + xdescribe: true, + }, +); +const base = Object.assign((Object.create(null): {[string]: boolean}), { + describe: true, + it: true, + test: true, +}); +const decorators = Object.assign((Object.create(null): {[string]: boolean}), { + only: true, + skip: true, +}); + +const validParents = Object.assign( + (Object.create(null): any), + base, + describeVariants, + Object.assign((Object.create(null): {[string]: boolean}), { + fit: true, + xit: true, + xtest: true, + }), +); + +const isValidMemberExpression = node => + node.object && + base[node.object.name] && + node.property && + decorators[node.property.name]; + +const isDescribe = node => + describeVariants[node.name] || + (isValidMemberExpression(node) && node.object.name === 'describe'); + +const isValidParent = parent => + parent.callee && + (validParents[parent.callee.name] || isValidMemberExpression(parent.callee)); + +const getArrayOfParents = path => { + const result = []; + let parent = path.parentPath; + while (parent) { + result.unshift(parent.node); + parent = parent.parentPath; + } + return result; +}; + +const buildName: ( + snapshotNode: Node, + parents: Array, + position: number, +) => string = (snapshotNode, parents, position) => { + const fullName = parents.map(parent => parent.arguments[0].value).join(' '); + + let describeLess = ''; + if (!isDescribe(parents[0].callee)) { + // If `it` or `test` exists without a surrounding `describe` + // then `test ` is prepended to the snapshot fullName. + describeLess = 'test '; + } + + return utils.testNameToKey(describeLess + fullName, position); +}; + +export default class Snapshot { + _parser: Function; + _matchers: Array; + constructor(parser: any, customMatchers?: Array) { + this._parser = parser || getASTfor; + this._matchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot'].concat( + customMatchers || [], + ); + } + + getMetadata(filePath: string): Array { + const fileNode = this._parser(filePath); + const state = { + found: [], + }; + const Visitors = { + Identifier(path, state, matchers) { + if (matchers.includes(path.node.name)) { + state.found.push({ + node: path.node, + parents: getArrayOfParents(path), + }); + } + }, + }; + + traverse(fileNode, { + enter: path => { + const visitor = Visitors[path.node.type]; + if (visitor != null) { + visitor(path, state, this._matchers); + } + }, + }); + + const snapshotPath = utils.getSnapshotPath(filePath); + const snapshots = utils.getSnapshotData(snapshotPath, 'none').data; + let lastParent = null; + let count = 1; + + return state.found.map((snapshotNode, index) => { + const parents = snapshotNode.parents.filter(isValidParent); + const innerAssertion = parents[parents.length - 1]; + + if (lastParent !== innerAssertion) { + lastParent = innerAssertion; + count = 1; + } + + const result = { + content: undefined, + count: count++, + exists: false, + name: '', + node: snapshotNode.node, + }; + + if (!innerAssertion || isDescribe(innerAssertion.callee)) { + // An expectation inside describe never gets executed. + return result; + } + + result.name = buildName(snapshotNode, parents, result.count); + + if (snapshots[result.name]) { + result.exists = true; + result.content = snapshots[result.name]; + } + return result; + }); + } +} diff --git a/packages/jest-editor-support/src/__tests__/Snapshot-test.js b/packages/jest-editor-support/src/__tests__/Snapshot-test.js new file mode 100644 index 000000000000..ca076a7ae800 --- /dev/null +++ b/packages/jest-editor-support/src/__tests__/Snapshot-test.js @@ -0,0 +1,128 @@ +/** + * 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 path = require('path'); +const Snapshot = require('../Snapshot'); +const snapshotHelper = new Snapshot(); +const snapshotFixturePath = path.resolve(__dirname, 'fixtures/snapshots'); + +test('nodescribe.example', () => { + const filePath = path.join(snapshotFixturePath, 'nodescribe.example'); + const results = snapshotHelper.getMetadata(filePath); + const allAssertion = [ + 'fit', + 'it', + 'it.only', + 'it.skip', + 'test', + 'test.only', + 'test.skip', + 'xit', + 'xtest', + ]; + + const expectations = Object.create(null); + allAssertion.forEach(assertion => { + expectations['test ' + assertion + ' 1'] = { + assertion, + checked: false, + number: 1, + }; + expectations['test ' + assertion + ' 2'] = { + assertion, + checked: false, + number: 2, + }; + }); + + results.forEach(result => { + const check = expectations[result.name]; + check.checked = result.content === `${check.assertion} ${check.number}`; + }); + + expect( + Object.keys(expectations) + .map(key => expectations[key]) + .filter(expectation => !expectation.checked).length, + ).toBe(0); +}); + +test('describe.example', () => { + const filePath = path.join(snapshotFixturePath, 'describe.example'); + const results = snapshotHelper.getMetadata(filePath); + const allDescribe = [ + 'describe', + 'describe.only', + 'describe.skip', + 'fdescribe', + 'xdescribe', + ]; + const allAssertion = [ + 'fit', + 'it', + 'it.only', + 'it.skip', + 'test', + 'test.only', + 'test.skip', + 'xit', + 'xtest', + ]; + + const expectations = Object.create(null); + + allDescribe.forEach(describe => { + allAssertion.forEach(assertion => { + expectations[describe.toUpperCase() + ' ' + assertion + ' 1'] = { + assertion, + checked: false, + describe, + number: 1, + }; + + expectations[describe.toUpperCase() + ' ' + assertion + ' 2'] = { + assertion, + checked: false, + describe, + number: 2, + }; + }); + }); + + results.forEach(result => { + const check = expectations[result.name]; + check.checked = + result.content === `${check.number} ${check.assertion} ${check.describe}`; + }); + expect( + Object.keys(expectations) + .map(key => expectations[key]) + .filter(expectation => !expectation.checked).length, + ).toBe(0); +}); + +test('nested.example', () => { + const filePath = path.join(snapshotFixturePath, 'nested.example'); + const results = snapshotHelper.getMetadata(filePath); + expect(results[0].content).toBe('first nested'); + expect(results[1].content).toBe('second nested'); + + expect(results[0].name).toBe( + 'outer describe outer it inner describe inner it 1', + ); + expect(results[1].name).toBe( + 'outer describe outer it inner describe inner it 2', + ); + + expect(results[0].node.loc.start).toEqual({column: 21, line: 5}); + expect(results[0].node.loc.end).toEqual({column: 36, line: 5}); + expect(results[1].node.loc.start).toEqual({column: 21, line: 6}); + expect(results[1].node.loc.end).toEqual({column: 36, line: 6}); +}); diff --git a/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/describe.example.snap b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/describe.example.snap new file mode 100644 index 000000000000..d60e6dc0998d --- /dev/null +++ b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/describe.example.snap @@ -0,0 +1,91 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`DESCRIBE fit 1`] = `1 fit describe`; +exports[`DESCRIBE fit 2`] = `2 fit describe`; +exports[`DESCRIBE it 1`] = `1 it describe`; +exports[`DESCRIBE it 2`] = `2 it describe`; +exports[`DESCRIBE it.only 1`] = `1 it.only describe`; +exports[`DESCRIBE it.only 2`] = `2 it.only describe`; +exports[`DESCRIBE it.skip 1`] = `1 it.skip describe`; +exports[`DESCRIBE it.skip 2`] = `2 it.skip describe`; +exports[`DESCRIBE test 1`] = `1 test describe`; +exports[`DESCRIBE test 2`] = `2 test describe`; +exports[`DESCRIBE test.only 1`] = `1 test.only describe`; +exports[`DESCRIBE test.only 2`] = `2 test.only describe`; +exports[`DESCRIBE test.skip 1`] = `1 test.skip describe`; +exports[`DESCRIBE test.skip 2`] = `2 test.skip describe`; +exports[`DESCRIBE xit 1`] = `1 xit describe`; +exports[`DESCRIBE xit 2`] = `2 xit describe`; +exports[`DESCRIBE xtest 1`] = `1 xtest describe`; +exports[`DESCRIBE xtest 2`] = `2 xtest describe`; +exports[`DESCRIBE.ONLY fit 1`] = `1 fit describe.only`; +exports[`DESCRIBE.ONLY fit 2`] = `2 fit describe.only`; +exports[`DESCRIBE.ONLY it 1`] = `1 it describe.only`; +exports[`DESCRIBE.ONLY it 2`] = `2 it describe.only`; +exports[`DESCRIBE.ONLY it.only 1`] = `1 it.only describe.only`; +exports[`DESCRIBE.ONLY it.only 2`] = `2 it.only describe.only`; +exports[`DESCRIBE.ONLY it.skip 1`] = `1 it.skip describe.only`; +exports[`DESCRIBE.ONLY it.skip 2`] = `2 it.skip describe.only`; +exports[`DESCRIBE.ONLY test 1`] = `1 test describe.only`; +exports[`DESCRIBE.ONLY test 2`] = `2 test describe.only`; +exports[`DESCRIBE.ONLY test.only 1`] = `1 test.only describe.only`; +exports[`DESCRIBE.ONLY test.only 2`] = `2 test.only describe.only`; +exports[`DESCRIBE.ONLY test.skip 1`] = `1 test.skip describe.only`; +exports[`DESCRIBE.ONLY test.skip 2`] = `2 test.skip describe.only`; +exports[`DESCRIBE.ONLY xit 1`] = `1 xit describe.only`; +exports[`DESCRIBE.ONLY xit 2`] = `2 xit describe.only`; +exports[`DESCRIBE.ONLY xtest 1`] = `1 xtest describe.only`; +exports[`DESCRIBE.ONLY xtest 2`] = `2 xtest describe.only`; +exports[`DESCRIBE.SKIP fit 1`] = `1 fit describe.skip`; +exports[`DESCRIBE.SKIP fit 2`] = `2 fit describe.skip`; +exports[`DESCRIBE.SKIP it 1`] = `1 it describe.skip`; +exports[`DESCRIBE.SKIP it 2`] = `2 it describe.skip`; +exports[`DESCRIBE.SKIP it.only 1`] = `1 it.only describe.skip`; +exports[`DESCRIBE.SKIP it.only 2`] = `2 it.only describe.skip`; +exports[`DESCRIBE.SKIP it.skip 1`] = `1 it.skip describe.skip`; +exports[`DESCRIBE.SKIP it.skip 2`] = `2 it.skip describe.skip`; +exports[`DESCRIBE.SKIP test 1`] = `1 test describe.skip`; +exports[`DESCRIBE.SKIP test 2`] = `2 test describe.skip`; +exports[`DESCRIBE.SKIP test.only 1`] = `1 test.only describe.skip`; +exports[`DESCRIBE.SKIP test.only 2`] = `2 test.only describe.skip`; +exports[`DESCRIBE.SKIP test.skip 1`] = `1 test.skip describe.skip`; +exports[`DESCRIBE.SKIP test.skip 2`] = `2 test.skip describe.skip`; +exports[`DESCRIBE.SKIP xit 1`] = `1 xit describe.skip`; +exports[`DESCRIBE.SKIP xit 2`] = `2 xit describe.skip`; +exports[`DESCRIBE.SKIP xtest 1`] = `1 xtest describe.skip`; +exports[`DESCRIBE.SKIP xtest 2`] = `2 xtest describe.skip`; +exports[`FDESCRIBE fit 1`] = `1 fit fdescribe`; +exports[`FDESCRIBE fit 2`] = `2 fit fdescribe`; +exports[`FDESCRIBE it 1`] = `1 it fdescribe`; +exports[`FDESCRIBE it 2`] = `2 it fdescribe`; +exports[`FDESCRIBE it.only 1`] = `1 it.only fdescribe`; +exports[`FDESCRIBE it.only 2`] = `2 it.only fdescribe`; +exports[`FDESCRIBE it.skip 1`] = `1 it.skip fdescribe`; +exports[`FDESCRIBE it.skip 2`] = `2 it.skip fdescribe`; +exports[`FDESCRIBE test 1`] = `1 test fdescribe`; +exports[`FDESCRIBE test 2`] = `2 test fdescribe`; +exports[`FDESCRIBE test.only 1`] = `1 test.only fdescribe`; +exports[`FDESCRIBE test.only 2`] = `2 test.only fdescribe`; +exports[`FDESCRIBE test.skip 1`] = `1 test.skip fdescribe`; +exports[`FDESCRIBE test.skip 2`] = `2 test.skip fdescribe`; +exports[`FDESCRIBE xit 1`] = `1 xit fdescribe`; +exports[`FDESCRIBE xit 2`] = `2 xit fdescribe`; +exports[`FDESCRIBE xtest 1`] = `1 xtest fdescribe`; +exports[`FDESCRIBE xtest 2`] = `2 xtest fdescribe`; +exports[`XDESCRIBE fit 1`] = `1 fit xdescribe`; +exports[`XDESCRIBE fit 2`] = `2 fit xdescribe`; +exports[`XDESCRIBE it 1`] = `1 it xdescribe`; +exports[`XDESCRIBE it 2`] = `2 it xdescribe`; +exports[`XDESCRIBE it.only 1`] = `1 it.only xdescribe`; +exports[`XDESCRIBE it.only 2`] = `2 it.only xdescribe`; +exports[`XDESCRIBE it.skip 1`] = `1 it.skip xdescribe`; +exports[`XDESCRIBE it.skip 2`] = `2 it.skip xdescribe`; +exports[`XDESCRIBE test 1`] = `1 test xdescribe`; +exports[`XDESCRIBE test 2`] = `2 test xdescribe`; +exports[`XDESCRIBE test.only 1`] = `1 test.only xdescribe`; +exports[`XDESCRIBE test.only 2`] = `2 test.only xdescribe`; +exports[`XDESCRIBE test.skip 1`] = `1 test.skip xdescribe`; +exports[`XDESCRIBE test.skip 2`] = `2 test.skip xdescribe`; +exports[`XDESCRIBE xit 1`] = `1 xit xdescribe`; +exports[`XDESCRIBE xit 2`] = `2 xit xdescribe`; +exports[`XDESCRIBE xtest 1`] = `1 xtest xdescribe`; +exports[`XDESCRIBE xtest 2`] = `2 xtest xdescribe`; diff --git a/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/nested.example.snap b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/nested.example.snap new file mode 100644 index 000000000000..ee8a6ffce3a2 --- /dev/null +++ b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/nested.example.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`outer describe outer it inner describe inner it 1`] = `first nested`; +exports[`outer describe outer it inner describe inner it 2`] = `second nested`; diff --git a/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/nodescribe.example.snap b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/nodescribe.example.snap new file mode 100644 index 000000000000..36680c31ce0c --- /dev/null +++ b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/__snapshots__/nodescribe.example.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`test fit 1`]=`fit 1`; +exports[`test fit 2`]=`fit 2`; +exports[`test it 1`]=`it 1`; +exports[`test it 2`]=`it 2`; +exports[`test it.only 1`]=`it.only 1`; +exports[`test it.only 2`]=`it.only 2`; +exports[`test it.skip 1`]=`it.skip 1`; +exports[`test it.skip 2`]=`it.skip 2`; +exports[`test test 1`]=`test 1`; +exports[`test test 2`]=`test 2`; +exports[`test test.only 1`]=`test.only 1`; +exports[`test test.only 2`]=`test.only 2`; +exports[`test test.skip 1`]=`test.skip 1`; +exports[`test test.skip 2`]=`test.skip 2`; +exports[`test xit 1`]=`xit 1`; +exports[`test xit 2`]=`xit 2`; +exports[`test xtest 1`]=`xtest 1`; +exports[`test xtest 2`]=`xtest 2`; diff --git a/packages/jest-editor-support/src/__tests__/fixtures/snapshots/describe.example b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/describe.example new file mode 100644 index 000000000000..5380757ef2e8 --- /dev/null +++ b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/describe.example @@ -0,0 +1,194 @@ +describe('DESCRIBE', () => { + fit('fit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it('it', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.only('it.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.skip('it.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test('test', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.only('test.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.skip('test.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xit('xit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xtest('xtest', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) +}) + +describe.only('DESCRIBE.ONLY', () => { + fit('fit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it('it', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.only('it.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.skip('it.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test('test', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.only('test.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.skip('test.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xit('xit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xtest('xtest', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) +}) + +describe.skip('DESCRIBE.SKIP', () => { + fit('fit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it('it', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.only('it.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.skip('it.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test('test', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.only('test.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.skip('test.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xit('xit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xtest('xtest', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) +}) + +fdescribe('FDESCRIBE', () => { + fit('fit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it('it', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.only('it.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.skip('it.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test('test', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.only('test.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.skip('test.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xit('xit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xtest('xtest', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) +}) + +xdescribe('XDESCRIBE', () => { + fit('fit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it('it', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.only('it.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + it.skip('it.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test('test', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.only('test.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + test.skip('test.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xit('xit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + xtest('xtest', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) +}) diff --git a/packages/jest-editor-support/src/__tests__/fixtures/snapshots/nested.example b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/nested.example new file mode 100644 index 000000000000..260bd5a556a4 --- /dev/null +++ b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/nested.example @@ -0,0 +1,10 @@ +describe('outer describe', () => { + it('outer it', () => { + describe('inner describe', () => { + it('inner it', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); + }) + }) + }) +}) diff --git a/packages/jest-editor-support/src/__tests__/fixtures/snapshots/nodescribe.example b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/nodescribe.example new file mode 100644 index 000000000000..90634fdc7560 --- /dev/null +++ b/packages/jest-editor-support/src/__tests__/fixtures/snapshots/nodescribe.example @@ -0,0 +1,36 @@ +fit('fit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +it('it', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +it.only('it.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +it.skip('it.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +test('test', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +test.only('test.only', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +test.skip('test.skip', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +xit('xit', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) +xtest('xtest', () => { + expect(fake).toMatchSnapshot(); + expect(fake).toMatchSnapshot(); +}) diff --git a/packages/jest-editor-support/src/__tests__/settings.test.js b/packages/jest-editor-support/src/__tests__/settings.test.js index e0372357c29c..55debc02f7c1 100644 --- a/packages/jest-editor-support/src/__tests__/settings.test.js +++ b/packages/jest-editor-support/src/__tests__/settings.test.js @@ -26,7 +26,7 @@ describe('Settings', () => { expect(settings.settings).toEqual(expect.any(Object)); }); - it('reads and parses the config', () => { + it('[jest 20] reads and parses the config', () => { const workspace = new ProjectWorkspace( 'root_path', 'path_to_jest', @@ -55,6 +55,64 @@ describe('Settings', () => { expect(settings.settings).toEqual(config); }); + it('[jest 21] reads and parses the config', () => { + const workspace = new ProjectWorkspace( + 'root_path', + 'path_to_jest', + 'test', + 1000, + ); + const completed = jest.fn(); + const configs = [{cacheDirectory: '/tmp/jest', name: '[md5 hash]'}]; + const json = { + configs, + version: '21.0.0', + }; + + const mockProcess: any = new EventEmitter(); + mockProcess.stdout = new EventEmitter(); + const createProcess = () => mockProcess; + const buffer = makeBuffer(JSON.stringify(json)); + const settings = new Settings(workspace, {createProcess}); + + settings.getConfig(completed); + settings.getConfigProcess.stdout.emit('data', buffer); + settings.getConfigProcess.emit('close'); + + expect(completed).toHaveBeenCalled(); + expect(settings.jestVersionMajor).toBe(21); + expect(settings.settings).toEqual(configs[0]); + }); + + it('[jest 21] reads and parses the configs', () => { + const workspace = new ProjectWorkspace( + 'root_path', + 'path_to_jest', + 'test', + 1000, + ); + const completed = jest.fn(); + const configs = [{cacheDirectory: '/tmp/jest', name: '[md5 hash]'}]; + const json = { + configs, + version: '21.0.0', + }; + + const mockProcess: any = new EventEmitter(); + mockProcess.stdout = new EventEmitter(); + const createProcess = () => mockProcess; + const buffer = makeBuffer(JSON.stringify(json)); + const settings = new Settings(workspace, {createProcess}); + + settings.getConfigs(completed); + settings.getConfigProcess.stdout.emit('data', buffer); + settings.getConfigProcess.emit('close'); + + expect(completed).toHaveBeenCalled(); + expect(settings.jestVersionMajor).toBe(21); + expect(settings.configs).toEqual(configs); + }); + it('calls callback even if no data is sent', () => { const workspace = new ProjectWorkspace( 'root_path', diff --git a/packages/jest-editor-support/src/__tests__/test_reconciler.test.js b/packages/jest-editor-support/src/__tests__/test_reconciler.test.js index e06e6966424e..b979aa0a50b7 100644 --- a/packages/jest-editor-support/src/__tests__/test_reconciler.test.js +++ b/packages/jest-editor-support/src/__tests__/test_reconciler.test.js @@ -10,26 +10,38 @@ import fs from 'fs'; import path from 'path'; import TestReconciler from '../test_reconciler'; +import type {TestFileAssertionStatus, TestAssertionStatus} from '../types'; const fixtures = path.resolve(__dirname, '../../../../fixtures'); -const reconcilerWithFile = (file: string): TestReconciler => { - const parser = new TestReconciler(); +function reconcilerWithFile( + parser: TestReconciler, + file: string, +): TestFileAssertionStatus[] { const exampleJSON = fs.readFileSync(`${fixtures}/failing_jsons/${file}`); const json = JSON.parse(exampleJSON.toString()); - parser.updateFileWithJestStatus(json); - return parser; -}; + if (!parser) console.error('no parser for ', file); + return parser.updateFileWithJestStatus(json); +} describe('Test Reconciler', () => { let parser: TestReconciler; + let results: TestFileAssertionStatus[]; + const dangerFilePath = '/Users/orta/dev/projects/danger/' + 'danger-js/source/ci_source/_tests/_travis.test.js'; describe('for a simple project', () => { + beforeAll(() => { + parser = new TestReconciler(); + results = reconcilerWithFile(parser, 'failing_jest_json.json'); + }); + + it('returns expected result for all test suites', () => { + expect(results.length).toEqual(5); + }); it('passes a passing method', () => { - parser = reconcilerWithFile('failing_jest_json.json'); const testName = 'does not validate without josh'; const status: any = parser.stateForTestAssertion( dangerFilePath, @@ -40,7 +52,6 @@ describe('Test Reconciler', () => { }); it('fails a failing method in the same file', () => { - parser = reconcilerWithFile('failing_jest_json.json'); const testName = 'validates when all Travis environment' + ' vars are set and Josh K says so'; @@ -60,7 +71,6 @@ Expected value to be falsy, instead received }); it('skips a skipped method', () => { - parser = reconcilerWithFile('failing_jest_json.json'); const testName = 'does not pull it out of the env'; const status: any = parser.stateForTestAssertion( dangerFilePath, @@ -70,11 +80,101 @@ Expected value to be falsy, instead received expect(status.line).toBeNull(); }); }); + + describe('in a monorepo project', () => { + beforeEach(() => { + parser = new TestReconciler(); + results = reconcilerWithFile(parser, 'monorepo_root_1.json'); + }); + + it('did processed all test suits including the suites failed to run', () => { + expect(results.length).toEqual(8); + const failed = results.filter(r => r.status === 'KnownFail'); + expect(failed.length).toEqual(4); + //2 of them is failed suite, i.e. no assertions + expect( + failed.filter(r => !r.assertions || r.assertions.length === 0).length, + ).toEqual(2); + }); + it('did catch the passed tests', () => { + const succeededSuites = results.filter(r => r.status === 'KnownSuccess'); + expect(succeededSuites.length).toEqual(4); + + const succeededTests = results + .map(r => r.assertions || []) + .reduce((sum: number, assertions: TestAssertionStatus[]) => { + const success = assertions.filter(a => a.status === 'KnownSuccess'); + return sum + success.length; + }, 0); + expect(succeededTests).toEqual(46); + }); + describe('when test updated', () => { + const targetTests = { + failedThenRemoved: [ + '/X/packages/Y-core/src/eth/__tests__/types.test.ts', + 'should fail', + ], + missingThenFailed: [ + '/X/packages/Y-app-vault/native/__tests__/index.ios.js', + 'testing jest with react-native', + ], + missingThenFixed: [ + '/X/packages/Y-app-vault/native/__tests__/index.ios.js', + 'renders correctly', + ], + passed: [ + '/X/packages/Y-keeper/src/redux/middlewares/__tests__/createGateMonitor.test.ts', + 'can log/profile doable async actions', + ], + }; + + function verifyTest(key: string, expectedStatus?: string) { + const test = parser.stateForTestAssertion( + targetTests[key][0], + targetTests[key][1], + ); + if (!test && !expectedStatus) { + return; + } + if (expectedStatus && test) { + expect(test.status).toEqual(expectedStatus); + return; + } + expect(key + ': ' + JSON.stringify(test)).toEqual(expectedStatus); // failed! + } + + it('verify before update occurred', () => { + verifyTest('missingThenFixed', undefined); + verifyTest('missingThenFailed', undefined); + verifyTest('failedThenRemoved', 'KnownFail'); + verifyTest('passed', 'KnownSuccess'); + }); + + it('new file can update existing result', () => { + //in file 2 we fixed 2 failed suites and removed 1 failed test + //let's check the failed tests are now passed, while the previously + //passed test should still be accessible + const results2 = reconcilerWithFile(parser, 'monorepo_root_2.json'); + expect(results2.length).toEqual(4); + + verifyTest('missingThenFixed', 'KnownSuccess'); + verifyTest('missingThenFailed', 'KnownFail'); + verifyTest('failedThenRemoved', undefined); + verifyTest('passed', 'KnownSuccess'); + }); + }); + }); }); describe('Terse Messages', () => { + let parser: TestReconciler; + + beforeEach(() => { + parser = new TestReconciler(); + const _ = reconcilerWithFile(parser, 'failing_expects.json'); + }); + it('handles shrinking a snapshot message', () => { - const parser = reconcilerWithFile('failing_expects.json'); const file = '/Users/orta/dev/projects/artsy/js/' + 'libs/jest-snapshots-svg/src/_tests/example.test.ts'; diff --git a/packages/jest-editor-support/src/index.js b/packages/jest-editor-support/src/index.js index db2b32dd2c33..146f57f90b6e 100644 --- a/packages/jest-editor-support/src/index.js +++ b/packages/jest-editor-support/src/index.js @@ -12,6 +12,7 @@ import * as Process from './Process'; import ProjectWorkspace from './project_workspace'; import Runner from './Runner'; import Settings from './Settings'; +import Snapshot from './Snapshot'; import {Expect, ItBlock, Node} from './parsers/parser_nodes'; import {parse} from './parsers/babylon_parser'; import TestReconciler from './test_reconciler'; @@ -24,6 +25,7 @@ module.exports = { ProjectWorkspace, Runner, Settings, + Snapshot, TestReconciler, parse, }; diff --git a/packages/jest-editor-support/src/parsers/babylon_parser.js b/packages/jest-editor-support/src/parsers/babylon_parser.js index 314d9b28335b..9e3c38fe982d 100644 --- a/packages/jest-editor-support/src/parsers/babylon_parser.js +++ b/packages/jest-editor-support/src/parsers/babylon_parser.js @@ -11,20 +11,24 @@ import {readFileSync} from 'fs'; import {parse as babylonParse} from 'babylon'; import {Expect, ItBlock} from './parser_nodes'; +import type {File as BabylonFile} from 'babylon'; export type BabylonParserResult = { expects: Array, itBlocks: Array, }; +export const getASTfor = (file: string): BabylonFile => { + const data = readFileSync(file).toString(); + const config = {plugins: ['*'], sourceType: 'module'}; + return babylonParse(data, config); +}; + export const parse = (file: string): BabylonParserResult => { const itBlocks: ItBlock[] = []; const expects: Expect[] = []; - const data = readFileSync(file).toString(); - - const config = {plugins: ['*'], sourceType: 'module'}; - const ast = babylonParse(data, config); + const ast = getASTfor(file); // An `it`/`test` was found in the AST // So take the AST node and create an object for us diff --git a/packages/jest-editor-support/src/test_reconciler.js b/packages/jest-editor-support/src/test_reconciler.js index 1f019323b9f4..cdfad3ba37ac 100644 --- a/packages/jest-editor-support/src/test_reconciler.js +++ b/packages/jest-editor-support/src/test_reconciler.js @@ -28,21 +28,21 @@ import path from 'path'; * at a file level, generating useful error messages and providing a nice API. */ export default class TestReconciler { - fileStatuses: any; - fails: Array; - passes: Array; - skips: Array; + fileStatuses: {[key: string]: TestFileAssertionStatus}; constructor() { this.fileStatuses = {}; } - updateFileWithJestStatus(results: FormattedTestResults) { - this.fails = []; - this.passes = []; - this.skips = []; - + // the processed test results will be returned immediately instead of saved in + // instance properties. This is 1) to prevent race condition 2) the data is already + // stored in the this.fileStatuses, no dup is better 3) client will most likely need to process + // all the results anyway. + updateFileWithJestStatus( + results: FormattedTestResults, + ): TestFileAssertionStatus[] { // Loop through all files inside the report from Jest + const statusList: TestFileAssertionStatus[] = []; results.testResults.forEach(file => { // Did the file pass/fail? const status = this.statusToReconcilationState(file.status); @@ -54,27 +54,9 @@ export default class TestReconciler { status, }; this.fileStatuses[file.name] = fileStatus; - - if (status === 'KnownFail') { - this.fails.push(fileStatus); - } else if (status === 'KnownSuccess') { - this.passes.push(fileStatus); - } else if (status === 'KnownSkip') { - this.skips.push(fileStatus); - } + statusList.push(fileStatus); }); - } - - failedStatuses(): Array { - return this.fails || []; - } - - passedStatuses(): Array { - return this.passes || []; - } - - skipedStatuses(): Array { - return this.skips || []; + return statusList; } // A failed test also contains the stack trace for an `expect` @@ -162,12 +144,17 @@ export default class TestReconciler { return results.status; } + assertionsForTestFile(file: string): TestAssertionStatus[] | null { + const results = this.fileStatuses[file]; + return results ? results.assertions : null; + } + stateForTestAssertion( file: string, name: string, ): TestAssertionStatus | null { const results = this.fileStatuses[file]; - if (!results) { + if (!results || !results.assertions) { return null; } const assertion = results.assertions.find(a => a.title === name); diff --git a/packages/jest-environment-jsdom/src/index.js b/packages/jest-environment-jsdom/src/index.js index 2f957d2e9ac9..f03b6543c92b 100644 --- a/packages/jest-environment-jsdom/src/index.js +++ b/packages/jest-environment-jsdom/src/index.js @@ -22,6 +22,7 @@ class JSDOMEnvironment { moduleMocker: ?ModuleMocker; constructor(config: ProjectConfig): void { + const jsdomInitialized = process.hrtime(); // lazy require this.document = JSDom.jsdom('', { url: config.testURL, @@ -32,6 +33,16 @@ class JSDOMEnvironment { this.global.Error.stackTraceLimit = 100; installCommonGlobals(global, config.globals); + if (!global.requestAnimationFrame) { + global.requestAnimationFrame = callback => { + const hr = process.hrtime(jsdomInitialized); + const hrInNano = hr[0] * 1e9 + hr[1]; + const hrInMicro = hrInNano / 1e6; + + return global.setTimeout(callback, 0, hrInMicro); + }; + } + this.moduleMocker = new mock.ModuleMocker(global); this.fakeTimers = new FakeTimers(global, this.moduleMocker, config); } diff --git a/packages/jest-haste-map/src/worker.js b/packages/jest-haste-map/src/worker.js index 1b6edcf963a3..8e39d12cfa32 100644 --- a/packages/jest-haste-map/src/worker.js +++ b/packages/jest-haste-map/src/worker.js @@ -11,7 +11,7 @@ import type {SerializableError} from 'types/TestResult'; import type {HasteImpl, WorkerMessage, WorkerCallback} from './types'; import path from 'path'; -import docblock from 'jest-docblock'; +import * as docblock from 'jest-docblock'; import fs from 'graceful-fs'; import H from './constants'; import extractRequires from './lib/extract_requires'; diff --git a/packages/jest-matcher-utils/src/index.js b/packages/jest-matcher-utils/src/index.js index c0f99dc331aa..89f2442880e5 100644 --- a/packages/jest-matcher-utils/src/index.js +++ b/packages/jest-matcher-utils/src/index.js @@ -27,9 +27,7 @@ const PLUGINS = [ ]; export const EXPECTED_COLOR = chalk.green; -export const EXPECTED_BG = chalk.bgGreen; export const RECEIVED_COLOR = chalk.red; -export const RECEIVED_BG = chalk.bgRed; const NUMBERS = [ 'zero', @@ -76,15 +74,13 @@ export const stringify = (object: any, maxDepth?: number = 10): string => { : result; }; -export const highlightTrailingWhitespace = ( - text: string, - bgColor: Function, -): string => text.replace(/\s+$/gm, bgColor('$&')); +export const highlightTrailingWhitespace = (text: string): string => + text.replace(/\s+$/gm, chalk.inverse('$&')); export const printReceived = (object: any) => - highlightTrailingWhitespace(RECEIVED_COLOR(stringify(object)), RECEIVED_BG); + RECEIVED_COLOR(highlightTrailingWhitespace(stringify(object))); export const printExpected = (value: any) => - highlightTrailingWhitespace(EXPECTED_COLOR(stringify(value)), EXPECTED_BG); + EXPECTED_COLOR(highlightTrailingWhitespace(stringify(value))); export const printWithType = ( name: string, diff --git a/packages/jest-runner/src/run_test.js b/packages/jest-runner/src/run_test.js index 5bb9e878e023..eb0a50fcd7fe 100644 --- a/packages/jest-runner/src/run_test.js +++ b/packages/jest-runner/src/run_test.js @@ -24,7 +24,7 @@ import { } from 'jest-util'; import jasmine2 from 'jest-jasmine2'; import {getTestEnvironment} from 'jest-config'; -import docblock from 'jest-docblock'; +import * as docblock from 'jest-docblock'; // The default jest-runner is required because it is the default test runner // and required implicitly through the `testRunner` ProjectConfig option. diff --git a/packages/jest-runtime/package.json b/packages/jest-runtime/package.json index 01a5d47218ce..5f9e653132f3 100644 --- a/packages/jest-runtime/package.json +++ b/packages/jest-runtime/package.json @@ -8,7 +8,6 @@ "license": "MIT", "main": "build/index.js", "dependencies": { - "babel-core": "^6.0.0", "babel-jest": "^21.2.0", "babel-plugin-istanbul": "^4.0.0", "chalk": "^2.0.1", @@ -27,9 +26,13 @@ "yargs": "^9.0.0" }, "devDependencies": { + "babel-core": "^6.0.0", "jest-environment-jsdom": "^21.2.1", "jest-environment-node": "^21.2.1" }, + "peerDependencies": { + "babel-core": "^6.0.0 || ^7.0.0-alpha || ^7.0.0-beta || ^7.0.0" + }, "bin": { "jest-runtime": "./bin/jest-runtime.js" } diff --git a/packages/jest-snapshot/src/index.js b/packages/jest-snapshot/src/index.js index 3d723f365128..bd59c992baa5 100644 --- a/packages/jest-snapshot/src/index.js +++ b/packages/jest-snapshot/src/index.js @@ -22,13 +22,13 @@ import { } from 'jest-matcher-utils'; import SnapshotState from './State'; import {addSerializer, getSerializers} from './plugins'; -import {SNAPSHOT_EXTENSION} from './utils'; +import * as utils from './utils'; const fileExists = (filePath: Path, hasteFS: HasteFS): boolean => hasteFS.exists(filePath) || fs.existsSync(filePath); const cleanup = (hasteFS: HasteFS, update: SnapshotUpdateState) => { - const pattern = '\\.' + SNAPSHOT_EXTENSION + '$'; + const pattern = '\\.' + utils.SNAPSHOT_EXTENSION + '$'; const files = hasteFS.matchFiles(pattern); const filesRemoved = files .filter( @@ -37,7 +37,7 @@ const cleanup = (hasteFS: HasteFS, update: SnapshotUpdateState) => { path.resolve( path.dirname(snapshotFile), '..', - path.basename(snapshotFile, '.' + SNAPSHOT_EXTENSION), + path.basename(snapshotFile, '.' + utils.SNAPSHOT_EXTENSION), ), hasteFS, ), @@ -148,11 +148,12 @@ const toThrowErrorMatchingSnapshot = function(received: any, expected: void) { }; module.exports = { - EXTENSION: SNAPSHOT_EXTENSION, + EXTENSION: utils.SNAPSHOT_EXTENSION, SnapshotState, addSerializer, cleanup, getSerializers, toMatchSnapshot, toThrowErrorMatchingSnapshot, + utils, }; diff --git a/packages/jest-test-typescript-parser/package.json b/packages/jest-test-typescript-parser/package.json index 8ccc4ca289a1..bd1128d967e3 100644 --- a/packages/jest-test-typescript-parser/package.json +++ b/packages/jest-test-typescript-parser/package.json @@ -9,6 +9,6 @@ "main": "build/index.js", "dependencies": { "jest-editor-support": "^21.2.0", - "typescript": "^2.2.1" + "typescript": "^2.5.3" } } diff --git a/yarn.lock b/yarn.lock index f37aaa12e644..d1eade80989c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -908,7 +908,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.14.1, babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -1971,6 +1971,10 @@ detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + detective@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1" @@ -5833,9 +5837,9 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^2.2.1, typescript@^2.2.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34" +typescript@^2.2.2, typescript@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d" ua-parser-js@^0.7.9: version "0.7.14"