diff --git a/CHANGELOG.md b/CHANGELOG.md index 013b611055fe..e147eb8cde03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ * `[jest-runtime]` Support sourcemaps in transformers ([#3458](https://github.com/facebook/jest/pull/3458)) * `[jest-snapshot]` Add a serializer for `jest.fn` to allow a snapshot of a jest mock ([#4668](https://github.com/facebook/jest/pull/4668)) * `[jest-worker]` Initial version of parallel worker abstraction, say hello! ([#4497](https://github.com/facebook/jest/pull/4497)) +* `[jest-jasmine2]` Add `testLocationInResults` flag to add location information per spec to test results ([#4782](https://github.com/facebook/jest/pull/4782)) ### Chore & Maintenance * `[*]` [**BREAKING**] Drop support for Node.js version 4 diff --git a/docs/CLI.md b/docs/CLI.md index f8c954ddf83a..716948ee24d8 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -189,6 +189,19 @@ Prevent tests from printing messages through the console. Alias: `-t`. Run only tests and test suites with a name that matches the regex. For example, suppose you want to run only tests related to authorization which will have names like `"GET /api/posts with auth"`, then you can use `jest -t=auth`. +### `--testLocationInResults` + +Adds a `location` field to test results. Useful if you want to report the location of a test in a reporter. + +Note that `column` is 0-indexed while `line` is not. + +```json +{ + "column": 4, + "line": 5 +} +``` + ### `--testPathPattern=` A regexp pattern string that is matched against all tests paths before executing the test. diff --git a/docs/Configuration.md b/docs/Configuration.md index 6c8e88d18ce4..d4f2adc8fb51 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -591,7 +591,11 @@ This option allows the use of a custom results processor. This processor must be "status": "failed" | "pending" | "passed", "ancestorTitles": [string (message in describe blocks)], "failureMessages": [string], - "numPassingAsserts": number + "numPassingAsserts": number, + "location": { + "column": number, + "line": number + } }, ... ], diff --git a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap index 57d4aaf6dd63..224fad308596 100644 --- a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap +++ b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap @@ -38,6 +38,7 @@ exports[`--showConfig outputs config info and exits 1`] = ` \\"setupFiles\\": [], \\"snapshotSerializers\\": [], \\"testEnvironment\\": \\"jest-environment-jsdom\\", + \\"testLocationInResults\\": false, \\"testMatch\\": [ \\"**/__tests__/**/*.js?(x)\\", \\"**/?(*.)(spec|test).js?(x)\\" diff --git a/integration_tests/__tests__/location_in_results.test.js b/integration_tests/__tests__/location_in_results.test.js new file mode 100644 index 000000000000..d4903234a669 --- /dev/null +++ b/integration_tests/__tests__/location_in_results.test.js @@ -0,0 +1,33 @@ +/** + * 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'); + +it('defaults to null for location', () => { + const result = runJest.json('location_in_results').json; + + const assertions = result.testResults[0].assertionResults; + expect(result.success).toBe(true); + expect(result.numTotalTests).toBe(2); + expect(assertions[0].location).toBeNull(); + expect(assertions[1].location).toBeNull(); +}); + +it('adds correct location info when provided with flag', () => { + const result = runJest.json('location_in_results', [ + '--testLocationInResults', + ]).json; + + const assertions = result.testResults[0].assertionResults; + expect(result.success).toBe(true); + expect(result.numTotalTests).toBe(2); + expect(assertions[0].location).toEqual({column: 1, line: 9}); + expect(assertions[1].location).toEqual({column: 3, line: 14}); +}); diff --git a/integration_tests/location_in_results/__tests__/test.js b/integration_tests/location_in_results/__tests__/test.js new file mode 100644 index 000000000000..55c08de1379c --- /dev/null +++ b/integration_tests/location_in_results/__tests__/test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +it('no ancestors', () => { + expect(true).toBeTruthy(); +}); + +describe('nested', () => { + it('also works', () => { + expect(true).toBeTruthy(); + }); +}); diff --git a/integration_tests/location_in_results/package.json b/integration_tests/location_in_results/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/integration_tests/location_in_results/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-cli/src/cli/args.js b/packages/jest-cli/src/cli/args.js index 018b7cbd619d..3576edea56c0 100644 --- a/packages/jest-cli/src/cli/args.js +++ b/packages/jest-cli/src/cli/args.js @@ -451,6 +451,11 @@ export const options = { description: 'Exit code of `jest` command if the test run failed', type: 'string', // number }, + testLocationInResults: { + default: false, + description: 'Add `location` information to the test results', + type: 'boolean', + }, testMatch: { description: 'The glob patterns Jest uses to detect test files.', type: 'array', diff --git a/packages/jest-config/src/defaults.js b/packages/jest-config/src/defaults.js index ddf03c198094..2f35093d98ec 100644 --- a/packages/jest-config/src/defaults.js +++ b/packages/jest-config/src/defaults.js @@ -56,6 +56,7 @@ export default ({ snapshotSerializers: [], testEnvironment: 'jest-environment-jsdom', testFailureExitCode: 1, + testLocationInResults: false, testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)(spec|test).js?(x)'], testPathIgnorePatterns: [NODE_MODULES_REGEXP], testRegex: '', diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 5f75a03a233d..c90124aba4cd 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -143,6 +143,7 @@ const getConfigs = ( skipNodeResolution: options.skipNodeResolution, snapshotSerializers: options.snapshotSerializers, testEnvironment: options.testEnvironment, + testLocationInResults: options.testLocationInResults, testMatch: options.testMatch, testPathIgnorePatterns: options.testPathIgnorePatterns, testRegex: options.testRegex, diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index f12e7d20e9c1..65e6b3304f8d 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -480,6 +480,7 @@ export default function normalize(options: InitialOptions, argv: Argv) { case 'skipNodeResolution': case 'testEnvironment': case 'testFailureExitCode': + case 'testLocationInResults': case 'testNamePattern': case 'testRegex': case 'testURL': diff --git a/packages/jest-config/src/valid_config.js b/packages/jest-config/src/valid_config.js index 8652041494bf..c0f8b924916f 100644 --- a/packages/jest-config/src/valid_config.js +++ b/packages/jest-config/src/valid_config.js @@ -78,6 +78,7 @@ export default ({ snapshotSerializers: ['my-serializer-module'], testEnvironment: 'jest-environment-jsdom', testFailureExitCode: 1, + testLocationInResults: false, testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)(spec|test).js?(x)'], testNamePattern: 'test signature', testPathIgnorePatterns: [NODE_MODULES_REGEXP], diff --git a/packages/jest-jasmine2/package.json b/packages/jest-jasmine2/package.json index 9dba0d52e551..317b9d22e646 100644 --- a/packages/jest-jasmine2/package.json +++ b/packages/jest-jasmine2/package.json @@ -8,6 +8,7 @@ "license": "MIT", "main": "build/index.js", "dependencies": { + "callsites": "^2.0.0", "chalk": "^2.0.1", "expect": "^21.2.1", "graceful-fs": "^4.1.11", diff --git a/packages/jest-jasmine2/src/index.js b/packages/jest-jasmine2/src/index.js index 2c070a7886d0..bd084ac2d9a9 100644 --- a/packages/jest-jasmine2/src/index.js +++ b/packages/jest-jasmine2/src/index.js @@ -16,6 +16,7 @@ import type Runtime from 'jest-runtime'; import path from 'path'; import fs from 'fs'; +import callsites from 'callsites'; import JasmineReporter from './reporter'; import {install as jasmineAsyncInstall} from './jasmine_async'; @@ -44,6 +45,20 @@ async function jasmine2( Object.assign(environment.global, jasmineInterface); env.addReporter(jasmineInterface.jsApiReporter); + // TODO: Remove config option if V8 exposes some way of getting location of caller + // in a future version + if (config.testLocationInResults === true) { + const originalIt = environment.global.it; + environment.global.it = (...args) => { + const stack = callsites()[1]; + const it = originalIt(...args); + + it.result.__callsite = stack; + + return it; + }; + } + jasmineAsyncInstall(environment.global); environment.global.test = environment.global.it; diff --git a/packages/jest-jasmine2/src/reporter.js b/packages/jest-jasmine2/src/reporter.js index 38b06e949f02..05ef55dc3d24 100644 --- a/packages/jest-jasmine2/src/reporter.js +++ b/packages/jest-jasmine2/src/reporter.js @@ -24,6 +24,7 @@ type Suite = { }; type SpecResult = { + __callsite?: Object, description: string, duration?: Milliseconds, failedExpectations: Array, @@ -150,11 +151,19 @@ export default class Jasmine2Reporter { const duration = start ? Date.now() - start : undefined; const status = specResult.status === 'disabled' ? 'pending' : specResult.status; + const location = specResult.__callsite + ? { + column: specResult.__callsite.getColumnNumber(), + // $FlowFixMe: https://github.com/facebook/flow/issues/5213 + line: specResult.__callsite.getLineNumber(), + } + : null; const results = { ancestorTitles, duration, failureMessages: [], fullName: specResult.fullName, + location, numPassingAsserts: 0, // Jasmine2 only returns an array of failed asserts. status, title: specResult.description, diff --git a/packages/jest-util/src/format_test_results.js b/packages/jest-util/src/format_test_results.js index bbb44737e33c..95c41c8a43e0 100644 --- a/packages/jest-util/src/format_test_results.js +++ b/packages/jest-util/src/format_test_results.js @@ -62,6 +62,7 @@ function formatTestAssertion( ancestorTitles: assertion.ancestorTitles, failureMessages: null, fullName: assertion.fullName, + location: assertion.location, status: assertion.status, title: assertion.title, }; diff --git a/test_utils.js b/test_utils.js index 53ad9e0acb99..e659422f3ca9 100644 --- a/test_utils.js +++ b/test_utils.js @@ -85,6 +85,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = { skipNodeResolution: false, snapshotSerializers: [], testEnvironment: 'node', + testLocationInResults: false, testMatch: [], testPathIgnorePatterns: [], testRegex: '.test.js$', diff --git a/types/Config.js b/types/Config.js index c62d9449dea8..ed2a19cb950b 100644 --- a/types/Config.js +++ b/types/Config.js @@ -48,6 +48,7 @@ export type DefaultOptions = {| snapshotSerializers: Array, testEnvironment: string, testFailureExitCode: string | number, + testLocationInResults: boolean, testMatch: Array, testPathIgnorePatterns: Array, testRegex: string, @@ -119,6 +120,7 @@ export type InitialOptions = { snapshotSerializers?: Array, testEnvironment?: string, testFailureExitCode?: string | number, + testLocationInResults?: boolean, testMatch?: Array, testNamePattern?: string, testPathDirs?: Array, @@ -215,6 +217,7 @@ export type ProjectConfig = {| snapshotSerializers: Array, testEnvironment: string, testMatch: Array, + testLocationInResults: boolean, testPathIgnorePatterns: Array, testRegex: string, testRunner: string, diff --git a/types/TestResult.js b/types/TestResult.js index 00afe2747357..ccd568c0f402 100644 --- a/types/TestResult.js +++ b/types/TestResult.js @@ -83,11 +83,17 @@ export type Status = 'passed' | 'failed' | 'skipped' | 'pending'; export type Bytes = number; export type Milliseconds = number; +type Callsite = {| + column: number, + line: number, +|} + export type AssertionResult = {| ancestorTitles: Array, duration?: ?Milliseconds, failureMessages: Array, fullName: string, + location: ?Callsite, numPassingAsserts: number, status: Status, title: string, @@ -96,6 +102,7 @@ export type AssertionResult = {| export type FormattedAssertionResult = { failureMessages: Array | null, fullName: string, + location: ?Callsite, status: Status, title: string, };