diff --git a/CHANGELOG.md b/CHANGELOG.md index ba166e2b1565..05b5b790795f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- `[jest-each]` Add pretty-format serialising to each titles ([#6357](https://github.com/facebook/jest/pull/6357)) - `[jest-cli]` shouldRunTestSuite watch hook now receives an object with `config`, `testPath` and `duration` ([#6350](https://github.com/facebook/jest/pull/6350)) - `[jest-each]` Support one dimensional array of data ([#6351](https://github.com/facebook/jest/pull/6351)) - `[jest-watch]` create new package `jest-watch` to ease custom watch plugin development ([#6318](https://github.com/facebook/jest/pull/6318)) diff --git a/docs/GlobalAPI.md b/docs/GlobalAPI.md index 73204eaeb58a..049cf3ec692f 100644 --- a/docs/GlobalAPI.md +++ b/docs/GlobalAPI.md @@ -233,6 +233,7 @@ Use `describe.each` if you keep duplicating the same test suites with different - `table`: `Array` of Arrays with the arguments that are passed into the `fn` for each row. - `name`: `String` the title of the test suite. - Generate unique test titles by positionally injecting parameters with [`printf` formatting](https://nodejs.org/api/util.html#util_util_format_format_args): + - `%p` - [pretty-format](https://www.npmjs.com/package/pretty-format). - `%s`- String. - `%d`- Number. - `%i` - Integer. @@ -477,6 +478,7 @@ Use `test.each` if you keep duplicating the same test with different data. `test - `table`: `Array` of Arrays with the arguments that are passed into the test `fn` for each row. - `name`: `String` the title of the test block. - Generate unique test titles by positionally injecting parameters with [`printf` formatting](https://nodejs.org/api/util.html#util_util_format_format_args): + - `%p` - [pretty-format](https://www.npmjs.com/package/pretty-format). - `%s`- String. - `%d`- Number. - `%i` - Integer. diff --git a/e2e/__tests__/__snapshots__/each.test.js.snap b/e2e/__tests__/__snapshots__/each.test.js.snap index fa69c42a75f1..dbd229a5fb0f 100644 --- a/e2e/__tests__/__snapshots__/each.test.js.snap +++ b/e2e/__tests__/__snapshots__/each.test.js.snap @@ -1,5 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`formats args with pretty format when given %p 1`] = ` +"PASS __tests__/pretty.test.js + array + ✓ \\"hello\\" == \\"hello\\" + ✓ 1 == 1 + ✓ null == null + ✓ undefined == undefined + ✓ 1.2 == 1.2 + ✓ {\\"foo\\": \\"bar\\"} == {\\"foo\\": \\"bar\\"} + ✓ {\\"foo\\": [Object]} == {\\"foo\\": [Object]} + ✓ [Function noop] == [Function noop] + ✓ [] == [] + ✓ [[Object]] == [[Object]] + ✓ Infinity == Infinity + ✓ -Infinity == -Infinity + ✓ NaN == NaN + template + ✓ \\"hello\\" == \\"hello\\" + ✓ 1 == 1 + ✓ null == null + ✓ undefined == undefined + ✓ 1.2 == 1.2 + ✓ {\\"foo\\": \\"bar\\"} == {\\"foo\\": \\"bar\\"} + ✓ {\\"foo\\": [Object]} == {\\"foo\\": [Object]} + ✓ [Function noop] == [Function noop] + ✓ [] == [] + ✓ [[Object]] == [[Object]] + ✓ Infinity == Infinity + ✓ -Infinity == -Infinity + ✓ NaN == NaN + +" +`; + exports[`runs only the describe.only.each tests 1`] = ` "PASS __tests__/describe-only.test.js passes all rows expected true == true @@ -79,9 +113,9 @@ exports[`shows the correct errors in stderr when failing tests 1`] = ` ✕ The word red contains the letter 'z' ✕ The word green contains the letter 'z' ✕ The word bean contains the letter 'z' - template table describe fails on all rows expected a == b + template table describe fails on all rows expected \\"a\\" == \\"b\\" ✕ fails - template table describe fails on all rows expected c == d + template table describe fails on all rows expected \\"c\\" == \\"d\\" ✕ fails array table describe fails on all rows expected a == b ✕ fails @@ -241,7 +275,7 @@ exports[`shows the correct errors in stderr when failing tests 1`] = ` at __tests__/failure.test.js:47:28 - ● template table describe fails on all rows expected a == b › fails + ● template table describe fails on all rows expected \\"a\\" == \\"b\\" › fails expect(received).toBe(expected) // Object.is equality @@ -258,7 +292,7 @@ exports[`shows the correct errors in stderr when failing tests 1`] = ` at __tests__/failure.test.js:59:20 - ● template table describe fails on all rows expected c == d › fails + ● template table describe fails on all rows expected \\"c\\" == \\"d\\" › fails expect(received).toBe(expected) // Object.is equality diff --git a/e2e/__tests__/each.test.js b/e2e/__tests__/each.test.js index b311ef9265d0..dbf20e10bc8c 100644 --- a/e2e/__tests__/each.test.js +++ b/e2e/__tests__/each.test.js @@ -56,3 +56,10 @@ test('runs only the describe.only.each tests', () => { expect(rest).toMatchSnapshot(); expect(result.status).toBe(0); }); + +test('formats args with pretty format when given %p', () => { + const result = runJest(dir, ['pretty.test.js']); + const {rest} = extractSummary(result.stderr); + expect(rest).toMatchSnapshot(); + expect(result.status).toBe(0); +}); diff --git a/e2e/each/__tests__/pretty.test.js b/e2e/each/__tests__/pretty.test.js new file mode 100644 index 000000000000..97e045b30542 --- /dev/null +++ b/e2e/each/__tests__/pretty.test.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const noop = () => {}; + +describe('array', () => { + it.each([ + ['hello', 'hello'], + [1, 1], + [null, null], + [undefined, undefined], + [1.2, 1.2], + [{foo: 'bar'}, {foo: 'bar'}], + [{foo: {bar: 'baz'}}, {foo: {bar: 'baz'}}], + [noop, noop], + [[], []], + [[{foo: {bar: 'baz'}}], [{foo: {bar: 'baz'}}]], + [Infinity, Infinity], + [-Infinity, -Infinity], + [NaN, NaN], + ])('%p == %p', (left, right) => { + expect(left).toEqual(right); + }); +}); + +describe('template', () => { + it.each` + left | right + ${'hello'} | ${'hello'} + ${1} | ${1} + ${null} | ${null} + ${undefined} | ${undefined} + ${1.2} | ${1.2} + ${{foo: 'bar'}} | ${{foo: 'bar'}} + ${{foo: {bar: 'baz'}}} | ${{foo: {bar: 'baz'}}} + ${noop} | ${noop} + ${[]} | ${[]} + ${[{foo: {bar: 'baz'}}]} | ${[{foo: {bar: 'baz'}}]} + ${Infinity} | ${Infinity} + ${-Infinity} | ${-Infinity} + ${NaN} | ${NaN} + `('$left == $right', ({left, right}) => { + expect(left).toEqual(right); + }); +}); diff --git a/packages/jest-each/README.md b/packages/jest-each/README.md index aadfc1be5460..08b2994837f5 100644 --- a/packages/jest-each/README.md +++ b/packages/jest-each/README.md @@ -26,6 +26,7 @@ jest-each allows you to provide multiple arguments to your `test`/`describe` whi - Also under the aliases: `.xdescribe` - Asynchronous tests with `done` - Unique test titles with [`printf` formatting](https://nodejs.org/api/util.html#util_util_format_format_args): + - `%p` - [pretty-format](https://www.npmjs.com/package/pretty-format). - `%s`- String. - `%d`- Number. - `%i` - Integer. @@ -100,6 +101,7 @@ const each = require('jest-each'); - name: `String` the title of the `test`. - Generate unique test titles by positionally injecting parameters with [`printf` formatting](https://nodejs.org/api/util.html#util_util_format_format_args): + - `%p` - [pretty-format](https://www.npmjs.com/package/pretty-format). - `%s`- String. - `%d`- Number. - `%i` - Integer. @@ -119,6 +121,7 @@ const each = require('jest-each'); - name: `String` the title of the `describe` - Generate unique test titles by positionally injecting parameters with [`printf` formatting](https://nodejs.org/api/util.html#util_util_format_format_args): + - `%p` - [pretty-format](https://www.npmjs.com/package/pretty-format). - `%s`- String. - `%d`- Number. - `%i` - Integer. diff --git a/packages/jest-each/src/__tests__/array.test.js b/packages/jest-each/src/__tests__/array.test.js index c10e16ad4dae..6b8fb1ef1adc 100644 --- a/packages/jest-each/src/__tests__/array.test.js +++ b/packages/jest-each/src/__tests__/array.test.js @@ -6,6 +6,7 @@ * */ +import pretty from 'pretty-format'; import each from '../'; const noop = () => {}; @@ -140,6 +141,52 @@ describe('jest-each', () => { ); }); + test('calls global test title with %p placeholder injected at the correct positions', () => { + const globalTestMocks = getGlobalTestMocks(); + const eachObject = each.withGlobal(globalTestMocks)([ + ['string1', 'pretty1', 'string2', 'pretty2'], + ['string1', 'pretty1', 'string2', 'pretty2'], + ]); + const testFunction = get(eachObject, keyPath); + testFunction('expected string: %s %p %s %p', noop); + + const globalMock = get(globalTestMocks, keyPath); + expect(globalMock).toHaveBeenCalledTimes(2); + expect(globalMock).toHaveBeenCalledWith( + `expected string: string1 ${pretty('pretty1')} string2 ${pretty( + 'pretty2', + )}`, + expectFunction, + ); + expect(globalMock).toHaveBeenCalledWith( + `expected string: string1 ${pretty('pretty1')} string2 ${pretty( + 'pretty2', + )}`, + expectFunction, + ); + }); + + test('does not calls global test title with %p placeholder when no data is supplied at given position', () => { + const globalTestMocks = getGlobalTestMocks(); + const eachObject = each.withGlobal(globalTestMocks)([ + ['string1', 'pretty1', 'string2'], + ['string1', 'pretty1', 'string2'], + ]); + const testFunction = get(eachObject, keyPath); + testFunction('expected string: %s %p %s %p', noop); + + const globalMock = get(globalTestMocks, keyPath); + expect(globalMock).toHaveBeenCalledTimes(2); + expect(globalMock).toHaveBeenCalledWith( + `expected string: string1 ${pretty('pretty1')} string2 %p`, + expectFunction, + ); + expect(globalMock).toHaveBeenCalledWith( + `expected string: string1 ${pretty('pretty1')} string2 %p`, + expectFunction, + ); + }); + test('calls global with cb function containing all parameters of each test case when given 1d array', () => { const globalTestMocks = getGlobalTestMocks(); const testCallBack = jest.fn(); diff --git a/packages/jest-each/src/bind.js b/packages/jest-each/src/bind.js index fc3103ce8a62..544f21b9e056 100644 --- a/packages/jest-each/src/bind.js +++ b/packages/jest-each/src/bind.js @@ -12,10 +12,15 @@ import chalk from 'chalk'; import pretty from 'pretty-format'; type Table = Array>; +type PrettyArgs = { + args: Array, + title: string, +}; const EXPECTED_COLOR = chalk.green; const RECEIVED_COLOR = chalk.red; -const SUPPORTED_PLACEHOLDERS = /%[sdifjoO%]/g; +const SUPPORTED_PLACEHOLDERS = /%[sdifjoOp%]/g; +const PRETTY_PLACEHOLDER = '%p'; export default (cb: Function) => (...args: any) => function eachBind(title: string, test: Function): void { @@ -59,9 +64,41 @@ export default (cb: Function) => (...args: any) => ); }; -const arrayFormat = (str, ...args) => { - const matches = (str.match(SUPPORTED_PLACEHOLDERS) || []).length; - return util.format(str, ...args.slice(0, matches)); +const getPrettyIndexes = placeholders => + placeholders.reduce( + (indexes, placeholder, index) => + placeholder === PRETTY_PLACEHOLDER ? indexes.concat(index) : indexes, + [], + ); + +const arrayFormat = (title, ...args) => { + const placeholders = title.match(SUPPORTED_PLACEHOLDERS) || []; + const prettyIndexes = getPrettyIndexes(placeholders); + + const {title: prettyTitle, args: remainingArgs} = args.reduce( + (acc: PrettyArgs, arg, index) => { + if (prettyIndexes.indexOf(index) !== -1) { + return { + args: acc.args, + title: acc.title.replace( + PRETTY_PLACEHOLDER, + pretty(arg, {maxDepth: 1, min: true}), + ), + }; + } + + return { + args: acc.args.concat([arg]), + title: acc.title, + }; + }, + {args: [], title}, + ); + + return util.format( + prettyTitle, + ...remainingArgs.slice(0, placeholders.length - prettyIndexes.length), + ); }; const applyRestParams = (params: Array, test: Function) => { @@ -89,7 +126,8 @@ const buildTable = ( const interpolate = (title: string, data: any) => Object.keys(data).reduce( - (acc, key) => acc.replace('$' + key, data[key]), + (acc, key) => + acc.replace('$' + key, pretty(data[key], {maxDepth: 1, min: true})), title, );