diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a6ce07e7b5a..2f72957d6254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes +- `[jest-each]` Add each array validation check ([#7033](https://github.com/facebook/jest/pull/7033)) - `[jest-haste-map]` [**BREAKING**] Replace internal data structures to improve performance ([#6960](https://github.com/facebook/jest/pull/6960)) - `[jest-haste-map]` Do not visit again files with the same sha-1 ([#6990](https://github.com/facebook/jest/pull/6990)) - `[jest-jasmine2]` Fix memory leak in Error objects hold by the framework ([#6965](https://github.com/facebook/jest/pull/6965)) diff --git a/packages/jest-each/src/__tests__/__snapshots__/array.test.js.snap b/packages/jest-each/src/__tests__/__snapshots__/array.test.js.snap new file mode 100644 index 000000000000..75cc81d64769 --- /dev/null +++ b/packages/jest-each/src/__tests__/__snapshots__/array.test.js.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`jest-each .describe throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; + +exports[`jest-each .describe.only throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; + +exports[`jest-each .fdescribe throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; + +exports[`jest-each .fit throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; + +exports[`jest-each .it throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; + +exports[`jest-each .it.only throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; + +exports[`jest-each .test throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; + +exports[`jest-each .test.only throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template String. + +Instead was called with: undefined +" +`; diff --git a/packages/jest-each/src/__tests__/array.test.js b/packages/jest-each/src/__tests__/array.test.js index 1077ffecd629..594b25ef0161 100644 --- a/packages/jest-each/src/__tests__/array.test.js +++ b/packages/jest-each/src/__tests__/array.test.js @@ -47,6 +47,19 @@ describe('jest-each', () => { ['describe', 'only'], ].forEach(keyPath => { describe(`.${keyPath.join('.')}`, () => { + test('throws an error when not called with an array', () => { + const globalTestMocks = getGlobalTestMocks(); + const eachObject = each.withGlobal(globalTestMocks)(undefined); + const testFunction = get(eachObject, keyPath); + + testFunction('expected string', noop); + const globalMock = get(globalTestMocks, keyPath); + + expect(() => + globalMock.mock.calls[0][1](), + ).toThrowErrorMatchingSnapshot(); + }); + test('calls global with given title', () => { const globalTestMocks = getGlobalTestMocks(); const eachObject = each.withGlobal(globalTestMocks)([[]]); diff --git a/packages/jest-each/src/bind.js b/packages/jest-each/src/bind.js index fe4565b4c8c9..88d68c11cecd 100644 --- a/packages/jest-each/src/bind.js +++ b/packages/jest-each/src/bind.js @@ -23,12 +23,35 @@ const SUPPORTED_PLACEHOLDERS = /%[sdifjoOp%]/g; const PRETTY_PLACEHOLDER = '%p'; const INDEX_PLACEHOLDER = '%#'; +const errorWithStack = (message, callsite) => { + const error = new Error(message); + if (Error.captureStackTrace) { + Error.captureStackTrace(error, callsite); + } + return error; +}; + export default (cb: Function, supportsDone: boolean = true) => (...args: any) => function eachBind(title: string, test: Function, timeout: number): void { if (args.length === 1) { - const table: Table = args[0].every(Array.isArray) - ? args[0] - : args[0].map(entry => [entry]); + const [tableArg] = args; + + if (!Array.isArray(tableArg)) { + const error = errorWithStack( + '`.each` must be called with an Array or Tagged Template String.\n\n' + + `Instead was called with: ${pretty(tableArg, { + maxDepth: 1, + min: true, + })}\n`, + eachBind, + ); + return cb(title, () => { + throw error; + }); + } + const table: Table = tableArg.every(Array.isArray) + ? tableArg + : tableArg.map(entry => [entry]); return table.forEach((row, i) => cb( arrayFormat(title, i, ...row), @@ -47,7 +70,7 @@ export default (cb: Function, supportsDone: boolean = true) => (...args: any) => const missingData = data.length % keys.length; if (missingData > 0) { - const error = new Error( + const error = errorWithStack( 'Not enough arguments supplied for given headings:\n' + EXPECTED_COLOR(keys.join(' | ')) + '\n\n' + @@ -58,12 +81,9 @@ export default (cb: Function, supportsDone: boolean = true) => (...args: any) => 'argument', missingData, )}`, + eachBind, ); - if (Error.captureStackTrace) { - Error.captureStackTrace(error, eachBind); - } - return cb(title, () => { throw error; });