diff --git a/docs/api/index.md b/docs/api/index.md index a744cc5c4efe..4669c0748227 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -196,7 +196,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t ::: ### test.each -- **Type:** `(cases: ReadonlyArray) => void` +- **Type:** `(cases: ReadonlyArray, ...args: any[]) => void` - **Alias:** `it.each` Use `test.each` when you need to run the same test with different variables. @@ -243,6 +243,23 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t // ✓ add(2, 1) -> 3 ``` + You can also access template string table. + * First row of variable name column headings separated with `|` + * One or more subsequent rows of data supplied as template literal expressions using ${value} syntax. + + ```ts + test.each` + a | b | expected + ${1} | ${1} | ${2} + ${'a'} | ${'b'} | ${'ab'} + ${[]} | ${'b'} | ${'b'} + ${{}} | ${'b'} | ${'[object Object]b'} + ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} + `('returns $expected when $a is added $b', ({ a, b, expected }) => { + expect(a + b).toBe(expected) + }) + ``` + If you want to have access to `TestContext`, use `describe.each` with a single test. ::: warning @@ -547,7 +564,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t ### describe.each -- **Type:** `(cases: ReadonlyArray): (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => void` +- **Type:** `(cases: ReadonlyArray, ...args: any[]): (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => void` Use `describe.each` if you have more than one test that depends on the same data. @@ -571,6 +588,25 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t }) ``` + You can also access template string table. + * First row of variable name column headings separated with `|` + * One or more subsequent rows of data supplied as template literal expressions using ${value} syntax. + + ```ts + describe.each` + a | b | expected + ${1} | ${1} | ${2} + ${'a'} | ${'b'} | ${'ab'} + ${[]} | ${'b'} | ${'b'} + ${{}} | ${'b'} | ${'[object Object]b'} + ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} + `('describe template string add($a, $b)', ({ a, b, expected }) => { + test(`returns ${expected}`, () => { + expect(a + b).toBe(expected) + }) + }) + ``` + ::: warning You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types). ::: diff --git a/packages/vitest/src/runtime/suite.ts b/packages/vitest/src/runtime/suite.ts index 3eca5afe3a8f..db07a6902559 100644 --- a/packages/vitest/src/runtime/suite.ts +++ b/packages/vitest/src/runtime/suite.ts @@ -180,8 +180,12 @@ function createSuite() { return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, options) } - suiteFn.each = function(this: { withContext: () => SuiteAPI }, cases: ReadonlyArray) { + suiteFn.each = function(this: { withContext: () => SuiteAPI }, cases: ReadonlyArray, ...args: any[]) { const suite = this.withContext() + + if (Array.isArray(cases) && args.length) + cases = formatTemplateString(cases, args) + return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => { const arrayOnlyCases = cases.every(Array.isArray) cases.forEach((i, idx) => { @@ -212,9 +216,12 @@ function createTest(fn: ( )) { const testFn = fn as any - testFn.each = function(this: { withContext: () => TestAPI }, cases: ReadonlyArray) { + testFn.each = function(this: { withContext: () => TestAPI }, cases: ReadonlyArray, ...args: any[]) { const test = this.withContext() + if (Array.isArray(cases) && args.length) + cases = formatTemplateString(cases, args) + return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => { const arrayOnlyCases = cases.every(Array.isArray) cases.forEach((i, idx) => { @@ -272,3 +279,15 @@ function formatTitle(template: string, items: any[], idx: number) { } return formatted } + +function formatTemplateString(cases: any[], args: any[]): any[] { + const header = cases.join('').trim().replace(/ /g, '').split('\n').map(i => i.split('|'))[0] + const res: any[] = [] + for (let i = 0; i < Math.floor((args.length) / header.length); i++) { + const oneCase: Record = {} + for (let j = 0; j < header.length; j++) + oneCase[header[j]] = args[i * header.length + j] as any + res.push(oneCase) + } + return res +} diff --git a/packages/vitest/src/types/tasks.ts b/packages/vitest/src/types/tasks.ts index ba2497b42bc3..21480be3b34c 100644 --- a/packages/vitest/src/types/tasks.ts +++ b/packages/vitest/src/types/tasks.ts @@ -130,6 +130,11 @@ interface TestEachFunction { fn: (...args: T[]) => Awaitable, options?: number | TestOptions, ) => void + (...args: [TemplateStringsArray, ...any]): ( + name: string, + fn: (...args: any[]) => Awaitable, + options?: number | TestOptions, + ) => void } type ChainableTestAPI = ChainableFunction< diff --git a/test/core/test/each.test.ts b/test/core/test/each.test.ts index fe156aa024d0..a54ba8ac4ded 100644 --- a/test/core/test/each.test.ts +++ b/test/core/test/each.test.ts @@ -169,3 +169,42 @@ test.each([ expect(value1).toBeNull() expect(value2).toBeNull() }) + +describe.each` +a | b | expected +${1} | ${1} | ${2} +${'a'} | ${'b'} | ${'ab'} +${[]} | ${'b'} | ${'b'} +${{}} | ${'b'} | ${'[object Object]b'} +${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} +`('describe template string add($a, $b)', ({ a, b, expected }) => { + test(`returns ${expected}`, () => { + expect(a + b).toBe(expected) + }) +}) + +test.each` +a | b | expected +${1} | ${1} | ${2} +${'a'} | ${'b'} | ${'ab'} +${[]} | ${'b'} | ${'b'} +${{}} | ${'b'} | ${'[object Object]b'} +${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} +`('returns $expected when $a is added $b', ({ a, b, expected }) => { + expect(a + b).toBe(expected) +}) + +test.each` +a | b | expected +${true} | ${true} | ${true} +`('($a && $b) -> $expected', ({ a, b, expected }) => { + expect(a && b).toBe(expected) +}) + +test.each` +a | b | expected +${{ val: 1 }} | ${{ val: 2 }}} | ${3} +`('($a && $b) -> $expected', ({ a, b, expected }) => { + expect(a.val + b.val).toBe(expected) +}) +