Skip to content

Commit 0ad2860

Browse files
authoredJan 15, 2025··
feat: add describe.for (#7253)
1 parent d4ea0b5 commit 0ad2860

File tree

5 files changed

+147
-0
lines changed

5 files changed

+147
-0
lines changed
 

‎docs/api/index.md

+36
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,11 @@ describe.todo('unimplemented suite')
948948

949949
- **Alias:** `suite.each`
950950

951+
::: tip
952+
While `describe.each` is provided for Jest compatibility,
953+
Vitest also has [`describe.for`](#describe-for) which simplifies argument types and aligns with [`test.for`](#test-for).
954+
:::
955+
951956
Use `describe.each` if you have more than one test that depends on the same data.
952957

953958
```ts
@@ -998,6 +1003,37 @@ describe.each`
9981003
You cannot use this syntax when using Vitest as [type checker](/guide/testing-types).
9991004
:::
10001005

1006+
### describe.for
1007+
1008+
- **Alias:** `suite.for`
1009+
1010+
The difference from `describe.each` is how array case is provided in the arguments.
1011+
Other non array case (including template string usage) works exactly same.
1012+
1013+
```ts
1014+
// `each` spreads array case
1015+
describe.each([
1016+
[1, 1, 2],
1017+
[1, 2, 3],
1018+
[2, 1, 3],
1019+
])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --]
1020+
test('test', () => {
1021+
expect(a + b).toBe(expected)
1022+
})
1023+
})
1024+
1025+
// `for` doesn't spread array case
1026+
describe.for([
1027+
[1, 1, 2],
1028+
[1, 2, 3],
1029+
[2, 1, 3],
1030+
])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++]
1031+
test('test', () => {
1032+
expect(a + b).toBe(expected)
1033+
})
1034+
})
1035+
```
1036+
10011037
## Setup and Teardown
10021038

10031039
These functions allow you to hook into the life cycle of tests to avoid repeating setup and teardown code. They apply to the current context: the file if they are used at the top-level or the current suite if they are inside a `describe` block. These hooks are not called, when you are running Vitest as a type checker.

‎packages/runner/src/suite.ts

+25
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,31 @@ function createSuite() {
594594
}
595595
}
596596

597+
suiteFn.for = function <T>(
598+
this: {
599+
withContext: () => SuiteAPI
600+
setContext: (key: string, value: boolean | undefined) => SuiteAPI
601+
},
602+
cases: ReadonlyArray<T>,
603+
...args: any[]
604+
) {
605+
if (Array.isArray(cases) && args.length) {
606+
cases = formatTemplateString(cases, args)
607+
}
608+
609+
return (
610+
name: string | Function,
611+
optionsOrFn: ((...args: T[]) => void) | TestOptions,
612+
fnOrOptions?: ((...args: T[]) => void) | number | TestOptions,
613+
) => {
614+
const name_ = formatName(name)
615+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions)
616+
cases.forEach((item, idx) => {
617+
suite(formatTitle(name_, toArray(item), idx), options, () => handler(item))
618+
})
619+
}
620+
}
621+
597622
suiteFn.skipIf = (condition: any) =>
598623
(condition ? suite.skip : suite) as SuiteAPI
599624
suiteFn.runIf = (condition: any) =>

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

+6
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,11 @@ interface TestForFunction<ExtraContext> {
360360
>
361361
}
362362

363+
interface SuiteForFunction {
364+
<T>(cases: ReadonlyArray<T>): EachFunctionReturn<[T]>
365+
(...args: [TemplateStringsArray, ...any]): EachFunctionReturn<any[]>
366+
}
367+
363368
interface TestCollectorCallable<C = object> {
364369
/**
365370
* @deprecated Use options as the second argument instead
@@ -525,6 +530,7 @@ type ChainableSuiteAPI<ExtraContext = object> = ChainableFunction<
525530
SuiteCollectorCallable<ExtraContext>,
526531
{
527532
each: TestEachFunction
533+
for: SuiteForFunction
528534
}
529535
>
530536

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`add(1, 1) > test 1`] = `2`;
4+
5+
exports[`add(1, 2) > test 1`] = `3`;
6+
7+
exports[`add(2, 1) > test 1`] = `3`;
8+
9+
exports[`basic case1 > test 1`] = `
10+
{
11+
"args": [
12+
"case1",
13+
],
14+
}
15+
`;
16+
17+
exports[`basic case2 > test 1`] = `
18+
{
19+
"args": [
20+
"case2",
21+
],
22+
}
23+
`;
24+
25+
exports[`template 'x' true > test 1`] = `
26+
{
27+
"args": [
28+
{
29+
"a": "x",
30+
"b": true,
31+
},
32+
],
33+
}
34+
`;
35+
36+
exports[`template 'y' false > test 1`] = `
37+
{
38+
"args": [
39+
{
40+
"a": "y",
41+
"b": false,
42+
},
43+
],
44+
}
45+
`;

‎test/core/test/test-for-suite.test.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, expectTypeOf, test } from 'vitest'
2+
3+
describe.for(['case1', 'case2'])(
4+
'basic %s',
5+
(...args) => {
6+
test('test', () => {
7+
expectTypeOf(args).toEqualTypeOf<[string]>()
8+
expect({ args }).matchSnapshot()
9+
})
10+
},
11+
)
12+
13+
describe.for`
14+
a | b
15+
${'x'} | ${true}
16+
${'y'} | ${false}
17+
`(
18+
'template $a $b',
19+
(...args) => {
20+
test('test', () => {
21+
expectTypeOf(args).toEqualTypeOf<any[]>()
22+
expect({ args }).toMatchSnapshot()
23+
})
24+
},
25+
)
26+
27+
describe.for([
28+
[1, 1],
29+
[1, 2],
30+
[2, 1],
31+
])('add(%i, %i)', ([a, b]) => {
32+
test('test', () => {
33+
expect(a + b).matchSnapshot()
34+
})
35+
})

0 commit comments

Comments
 (0)
Please sign in to comment.