Skip to content

Commit f789776

Browse files
authoredNov 22, 2022
feat: test.each support string template (#2337)
1 parent c395177 commit f789776

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed
 

‎docs/api/index.md

+38-2
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
196196
:::
197197

198198
### test.each
199-
- **Type:** `(cases: ReadonlyArray<T>) => void`
199+
- **Type:** `(cases: ReadonlyArray<T>, ...args: any[]) => void`
200200
- **Alias:** `it.each`
201201

202202
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
243243
// ✓ add(2, 1) -> 3
244244
```
245245

246+
You can also access template string table.
247+
* First row of variable name column headings separated with `|`
248+
* One or more subsequent rows of data supplied as template literal expressions using ${value} syntax.
249+
250+
```ts
251+
test.each`
252+
a | b | expected
253+
${1} | ${1} | ${2}
254+
${'a'} | ${'b'} | ${'ab'}
255+
${[]} | ${'b'} | ${'b'}
256+
${{}} | ${'b'} | ${'[object Object]b'}
257+
${{ asd: 1 }} | ${'b'} | ${'[object Object]b'}
258+
`('returns $expected when $a is added $b', ({ a, b, expected }) => {
259+
expect(a + b).toBe(expected)
260+
})
261+
```
262+
246263
If you want to have access to `TestContext`, use `describe.each` with a single test.
247264

248265
::: warning
@@ -547,7 +564,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
547564

548565
### describe.each
549566

550-
- **Type:** `(cases: ReadonlyArray<T>): (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => void`
567+
- **Type:** `(cases: ReadonlyArray<T>, ...args: any[]): (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => void`
551568

552569
Use `describe.each` if you have more than one test that depends on the same data.
553570

@@ -571,6 +588,25 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
571588
})
572589
```
573590

591+
You can also access template string table.
592+
* First row of variable name column headings separated with `|`
593+
* One or more subsequent rows of data supplied as template literal expressions using ${value} syntax.
594+
595+
```ts
596+
describe.each`
597+
a | b | expected
598+
${1} | ${1} | ${2}
599+
${'a'} | ${'b'} | ${'ab'}
600+
${[]} | ${'b'} | ${'b'}
601+
${{}} | ${'b'} | ${'[object Object]b'}
602+
${{ asd: 1 }} | ${'b'} | ${'[object Object]b'}
603+
`('describe template string add($a, $b)', ({ a, b, expected }) => {
604+
test(`returns ${expected}`, () => {
605+
expect(a + b).toBe(expected)
606+
})
607+
})
608+
```
609+
574610
::: warning
575611
You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types).
576612
:::

‎packages/vitest/src/runtime/suite.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,12 @@ function createSuite() {
180180
return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, options)
181181
}
182182

183-
suiteFn.each = function<T>(this: { withContext: () => SuiteAPI }, cases: ReadonlyArray<T>) {
183+
suiteFn.each = function<T>(this: { withContext: () => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
184184
const suite = this.withContext()
185+
186+
if (Array.isArray(cases) && args.length)
187+
cases = formatTemplateString(cases, args)
188+
185189
return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => {
186190
const arrayOnlyCases = cases.every(Array.isArray)
187191
cases.forEach((i, idx) => {
@@ -212,9 +216,12 @@ function createTest(fn: (
212216
)) {
213217
const testFn = fn as any
214218

215-
testFn.each = function<T>(this: { withContext: () => TestAPI }, cases: ReadonlyArray<T>) {
219+
testFn.each = function<T>(this: { withContext: () => TestAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
216220
const test = this.withContext()
217221

222+
if (Array.isArray(cases) && args.length)
223+
cases = formatTemplateString(cases, args)
224+
218225
return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => {
219226
const arrayOnlyCases = cases.every(Array.isArray)
220227
cases.forEach((i, idx) => {
@@ -272,3 +279,15 @@ function formatTitle(template: string, items: any[], idx: number) {
272279
}
273280
return formatted
274281
}
282+
283+
function formatTemplateString(cases: any[], args: any[]): any[] {
284+
const header = cases.join('').trim().replace(/ /g, '').split('\n').map(i => i.split('|'))[0]
285+
const res: any[] = []
286+
for (let i = 0; i < Math.floor((args.length) / header.length); i++) {
287+
const oneCase: Record<string, any> = {}
288+
for (let j = 0; j < header.length; j++)
289+
oneCase[header[j]] = args[i * header.length + j] as any
290+
res.push(oneCase)
291+
}
292+
return res
293+
}

‎packages/vitest/src/types/tasks.ts

+5
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ interface TestEachFunction {
130130
fn: (...args: T[]) => Awaitable<void>,
131131
options?: number | TestOptions,
132132
) => void
133+
(...args: [TemplateStringsArray, ...any]): (
134+
name: string,
135+
fn: (...args: any[]) => Awaitable<void>,
136+
options?: number | TestOptions,
137+
) => void
133138
}
134139

135140
type ChainableTestAPI<ExtraContext = {}> = ChainableFunction<

‎test/core/test/each.test.ts

+39
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,42 @@ test.each([
169169
expect(value1).toBeNull()
170170
expect(value2).toBeNull()
171171
})
172+
173+
describe.each`
174+
a | b | expected
175+
${1} | ${1} | ${2}
176+
${'a'} | ${'b'} | ${'ab'}
177+
${[]} | ${'b'} | ${'b'}
178+
${{}} | ${'b'} | ${'[object Object]b'}
179+
${{ asd: 1 }} | ${'b'} | ${'[object Object]b'}
180+
`('describe template string add($a, $b)', ({ a, b, expected }) => {
181+
test(`returns ${expected}`, () => {
182+
expect(a + b).toBe(expected)
183+
})
184+
})
185+
186+
test.each`
187+
a | b | expected
188+
${1} | ${1} | ${2}
189+
${'a'} | ${'b'} | ${'ab'}
190+
${[]} | ${'b'} | ${'b'}
191+
${{}} | ${'b'} | ${'[object Object]b'}
192+
${{ asd: 1 }} | ${'b'} | ${'[object Object]b'}
193+
`('returns $expected when $a is added $b', ({ a, b, expected }) => {
194+
expect(a + b).toBe(expected)
195+
})
196+
197+
test.each`
198+
a | b | expected
199+
${true} | ${true} | ${true}
200+
`('($a && $b) -> $expected', ({ a, b, expected }) => {
201+
expect(a && b).toBe(expected)
202+
})
203+
204+
test.each`
205+
a | b | expected
206+
${{ val: 1 }} | ${{ val: 2 }}} | ${3}
207+
`('($a && $b) -> $expected', ({ a, b, expected }) => {
208+
expect(a.val + b.val).toBe(expected)
209+
})
210+

0 commit comments

Comments
 (0)
Please sign in to comment.