Skip to content

Commit 7d9b1fb

Browse files
authoredFeb 15, 2024··
feat(vitest): "test" accepts options object as the second parameter (#5142)
1 parent aa72740 commit 7d9b1fb

File tree

10 files changed

+259
-137
lines changed

10 files changed

+259
-137
lines changed
 

‎docs/api/index.md

+37-19
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,42 @@ interface TestOptions {
3232
}
3333
```
3434

35+
Vitest 1.3.0 deprecates the use of options as the last parameter. You will see a deprecation message until 2.0.0 when this syntax will be removed. If you need to pass down options, use `test` function's second argument:
36+
37+
```ts
38+
import { test } from 'vitest'
39+
40+
test('flaky test', () => {}, { retry: 3 }) // [!code --]
41+
test('flaky test', { retry: 3 }, () => {}) // [!code ++]
42+
```
43+
3544
When a test function returns a promise, the runner will wait until it is resolved to collect async expectations. If the promise is rejected, the test will fail.
3645

3746
::: tip
3847
In Jest, `TestFunction` can also be of type `(done: DoneCallback) => void`. If this form is used, the test will not be concluded until `done` is called. You can achieve the same using an `async` function, see the [Migration guide Done Callback section](/guide/migration#done-callback).
3948
:::
4049

50+
Since Vitest 1.3.0 most options support both dot-syntax and object-syntax allowing you to use whatever style you prefer.
51+
52+
:::code-group
53+
```ts [dot-syntax]
54+
import { test } from 'vitest'
55+
56+
test.skip('skipped test', () => {
57+
// some logic that fails right now
58+
})
59+
```
60+
```ts [object-syntax <Badge type="info">1.3.0+</Badge>]
61+
import { test } from 'vitest'
62+
63+
test('skipped test', { skip: true }, () => {
64+
// some logic that fails right now
65+
})
66+
```
67+
:::
68+
4169
## test
4270

43-
- **Type:** `(name: string | Function, fn: TestFunction, timeout?: number | TestOptions) => void`
4471
- **Alias:** `it`
4572

4673
`test` defines a set of related expectations. It receives the test name and a function that holds the expectations to test.
@@ -57,7 +84,6 @@ test('should work as expected', () => {
5784

5885
### test.extend <Badge type="info">0.32.3+</Badge>
5986

60-
- **Type:** `<T extends Record<string, any>>(fixtures: Fixtures<T>): TestAPI<ExtraContext & T>`
6187
- **Alias:** `it.extend`
6288

6389
Use `test.extend` to extend the test context with custom fixtures. This will return a new `test` and it's also extendable, so you can compose more fixtures or override existing ones by extending it as you need. See [Extend Test Context](/guide/test-context.html#test-extend) for more information.
@@ -87,7 +113,6 @@ myTest('add item', ({ todos }) => {
87113

88114
### test.skip
89115

90-
- **Type:** `(name: string | Function, fn: TestFunction, timeout?: number | TestOptions) => void`
91116
- **Alias:** `it.skip`
92117

93118
If you want to skip running certain tests, but you don't want to delete the code due to any reason, you can use `test.skip` to avoid running them.
@@ -115,7 +140,6 @@ test('skipped test', (context) => {
115140

116141
### test.skipIf
117142

118-
- **Type:** `(condition: any) => Test`
119143
- **Alias:** `it.skipIf`
120144

121145
In some cases you might run tests multiple times with different environments, and some of the tests might be environment-specific. Instead of wrapping the test code with `if`, you can use `test.skipIf` to skip the test whenever the condition is truthy.
@@ -136,7 +160,6 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
136160

137161
### test.runIf
138162

139-
- **Type:** `(condition: any) => Test`
140163
- **Alias:** `it.runIf`
141164

142165
Opposite of [test.skipIf](#test-skipif).
@@ -157,7 +180,6 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
157180

158181
### test.only
159182

160-
- **Type:** `(name: string | Function, fn: TestFunction, timeout?: number) => void`
161183
- **Alias:** `it.only`
162184

163185
Use `test.only` to only run certain tests in a given suite. This is useful when debugging.
@@ -182,7 +204,6 @@ In order to do that run `vitest` with specific file containing the tests in ques
182204

183205
### test.concurrent
184206

185-
- **Type:** `(name: string | Function, fn: TestFunction, timeout?: number) => void`
186207
- **Alias:** `it.concurrent`
187208

188209
`test.concurrent` marks consecutive tests to be run in parallel. It receives the test name, an async function with the tests to collect, and an optional timeout (in milliseconds).
@@ -224,7 +245,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
224245

225246
### test.sequential
226247

227-
- **Type:** `(name: string | Function, fn: TestFunction, timeout?: number) => void`
248+
- **Alias:** `it.sequential`
228249

229250
`test.sequential` marks a test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option.
230251

@@ -248,7 +269,6 @@ describe.concurrent('suite', () => {
248269

249270
### test.todo
250271

251-
- **Type:** `(name: string | Function) => void`
252272
- **Alias:** `it.todo`
253273

254274
Use `test.todo` to stub tests to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement.
@@ -260,7 +280,6 @@ test.todo('unimplemented test')
260280

261281
### test.fails
262282

263-
- **Type:** `(name: string | Function, fn: TestFunction, timeout?: number) => void`
264283
- **Alias:** `it.fails`
265284

266285
Use `test.fails` to indicate that an assertion will fail explicitly.
@@ -282,7 +301,6 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
282301

283302
### test.each
284303

285-
- **Type:** `(cases: ReadonlyArray<T>, ...args: any[]) => void`
286304
- **Alias:** `it.each`
287305

288306
Use `test.each` when you need to run the same test with different variables.
@@ -570,7 +588,7 @@ describe('numberToCurrency', () => {
570588

571589
### describe.skip
572590

573-
- **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void`
591+
- **Alias:** `suite.skip`
574592

575593
Use `describe.skip` in a suite to avoid running a particular describe block.
576594

@@ -587,7 +605,7 @@ describe.skip('skipped suite', () => {
587605

588606
### describe.skipIf
589607

590-
- **Type:** `(condition: any) => void`
608+
- **Alias:** `suite.skipIf`
591609

592610
In some cases, you might run suites multiple times with different environments, and some of the suites might be environment-specific. Instead of wrapping the suite with `if`, you can use `describe.skipIf` to skip the suite whenever the condition is truthy.
593611

@@ -607,7 +625,7 @@ You cannot use this syntax when using Vitest as [type checker](/guide/testing-ty
607625

608626
### describe.runIf
609627

610-
- **Type:** `(condition: any) => void`
628+
- **Alias:** `suite.runIf`
611629

612630
Opposite of [describe.skipIf](#describe-skipif).
613631

@@ -653,7 +671,7 @@ In order to do that run `vitest` with specific file containing the tests in ques
653671

654672
### describe.concurrent
655673

656-
- **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void`
674+
- **Alias:** `suite.concurrent`
657675

658676
`describe.concurrent` in a suite marks every tests as concurrent
659677

@@ -694,7 +712,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
694712

695713
### describe.sequential
696714

697-
- **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void`
715+
- **Alias:** `suite.sequential`
698716

699717
`describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option.
700718

@@ -712,7 +730,7 @@ describe.concurrent('suite', () => {
712730

713731
### describe.shuffle
714732

715-
- **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void`
733+
- **Alias:** `suite.shuffle`
716734

717735
Vitest provides a way to run all tests in random order via CLI flag [`--sequence.shuffle`](/guide/cli) or config option [`sequence.shuffle`](/config/#sequence-shuffle), but if you want to have only part of your test suite to run tests in random order, you can mark it with this flag.
718736

@@ -733,7 +751,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
733751

734752
### describe.todo
735753

736-
- **Type:** `(name: string | Function) => void`
754+
- **Alias:** `suite.todo`
737755

738756
Use `describe.todo` to stub suites to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement.
739757

@@ -744,7 +762,7 @@ describe.todo('unimplemented suite')
744762

745763
### describe.each
746764

747-
- **Type:** `(cases: ReadonlyArray<T>, ...args: any[]): (name: string | Function, fn: (...args: T[]) => void, options?: number | TestOptions) => void`
765+
- **Alias:** `suite.each`
748766

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

‎packages/runner/src/suite.ts

+78-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { format, isObject, noop, objDisplay, objectAttr } from '@vitest/utils'
1+
import { format, isObject, objDisplay, objectAttr } from '@vitest/utils'
22
import type { Custom, CustomAPI, File, Fixtures, RunMode, Suite, SuiteAPI, SuiteCollector, SuiteFactory, SuiteHooks, Task, TaskCustomOptions, Test, TestAPI, TestFunction, TestOptions } from './types'
33
import type { VitestRunner } from './types/runner'
44
import { createChainable } from './utils/chain'
@@ -11,11 +11,11 @@ import { getCurrentTest } from './test-state'
1111
// apis
1212
export const suite = createSuite()
1313
export const test = createTest(
14-
function (name: string | Function, fn?: TestFunction, options?: number | TestOptions) {
14+
function (name: string | Function, optionsOrFn?: TestOptions | TestFunction, optionsOrTest?: number | TestOptions | TestFunction) {
1515
if (getCurrentTest())
1616
throw new Error('Calling the test function inside another test function is not allowed. Please put it inside "describe" or "suite" so it can be properly collected.')
1717

18-
getCurrentSuite().test.fn.call(this, formatName(name), fn, options)
18+
getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn as TestOptions, optionsOrTest as TestFunction)
1919
},
2020
)
2121

@@ -56,8 +56,48 @@ export function createSuiteHooks() {
5656
}
5757
}
5858

59+
function parseArguments<T extends (...args: any[]) => any>(
60+
optionsOrFn: T | object | undefined,
61+
optionsOrTest: object | T | number | undefined,
62+
) {
63+
let options: TestOptions = {}
64+
let fn: T = (() => {}) as T
65+
66+
// it('', () => {}, { retry: 2 })
67+
if (typeof optionsOrTest === 'object') {
68+
// it('', { retry: 2 }, { retry: 3 })
69+
if (typeof optionsOrFn === 'object')
70+
throw new TypeError('Cannot use two objects as arguments. Please provide options and a function callback in that order.')
71+
// TODO: more info, add a name
72+
// console.warn('The third argument is deprecated. Please use the second argument for options.')
73+
options = optionsOrTest
74+
}
75+
// it('', () => {}, 1000)
76+
else if (typeof optionsOrTest === 'number') {
77+
options = { timeout: optionsOrTest }
78+
}
79+
// it('', { retry: 2 }, () => {})
80+
else if (typeof optionsOrFn === 'object') {
81+
options = optionsOrFn
82+
}
83+
84+
if (typeof optionsOrFn === 'function') {
85+
if (typeof optionsOrTest === 'function')
86+
throw new TypeError('Cannot use two functions as arguments. Please use the second argument for options.')
87+
fn = optionsOrFn as T
88+
}
89+
else if (typeof optionsOrTest === 'function') {
90+
fn = optionsOrTest as T
91+
}
92+
93+
return {
94+
options,
95+
handler: fn,
96+
}
97+
}
98+
5999
// implementations
60-
function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, concurrent?: boolean, sequential?: boolean, shuffle?: boolean, each?: boolean, suiteOptions?: TestOptions) {
100+
function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, shuffle?: boolean, each?: boolean, suiteOptions?: TestOptions) {
61101
const tasks: (Test | Custom | Suite | SuiteCollector)[] = []
62102
const factoryQueue: (Test | Suite | SuiteCollector)[] = []
63103

@@ -104,9 +144,11 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
104144
return task
105145
}
106146

107-
const test = createTest(function (name: string | Function, fn = noop, options = {}) {
108-
if (typeof options === 'number')
109-
options = { timeout: options }
147+
const test = createTest(function (name: string | Function, optionsOrFn?: TestOptions | TestFunction, optionsOrTest?: number | TestOptions | TestFunction) {
148+
let { options, handler } = parseArguments(
149+
optionsOrFn,
150+
optionsOrTest,
151+
)
110152

111153
// inherit repeats, retry, timeout from suite
112154
if (typeof suiteOptions === 'object')
@@ -118,7 +160,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
118160

119161
const test = task(
120162
formatName(name),
121-
{ ...this, ...options, handler: fn as any },
163+
{ ...this, ...options, handler },
122164
) as unknown as Test
123165

124166
test.type = 'test'
@@ -193,12 +235,14 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
193235
}
194236

195237
function createSuite() {
196-
function suiteFn(this: Record<string, boolean | undefined>, name: string | Function, factory?: SuiteFactory, options: number | TestOptions = {}) {
238+
function suiteFn(this: Record<string, boolean | undefined>, name: string | Function, factoryOrOptions?: SuiteFactory | TestOptions, optionsOrFactory: number | TestOptions | SuiteFactory = {}) {
197239
const mode: RunMode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run'
198240
const currentSuite = getCurrentSuite()
199241

200-
if (typeof options === 'number')
201-
options = { timeout: options }
242+
let { options, handler: factory } = parseArguments(
243+
factoryOrOptions,
244+
optionsOrFactory,
245+
)
202246

203247
// inherit options from current suite
204248
if (currentSuite?.options)
@@ -208,7 +252,7 @@ function createSuite() {
208252
options.concurrent = this.concurrent || (!this.sequential && options?.concurrent)
209253
options.sequential = this.sequential || (!this.concurrent && options?.sequential)
210254

211-
return createSuiteCollector(formatName(name), factory, mode, this.concurrent, this.sequential, this.shuffle, this.each, options)
255+
return createSuiteCollector(formatName(name), factory, mode, this.shuffle, this.each, options)
212256
}
213257

214258
suiteFn.each = function<T>(this: { withContext: () => SuiteAPI; setContext: (key: string, value: boolean | undefined) => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
@@ -218,14 +262,20 @@ function createSuite() {
218262
if (Array.isArray(cases) && args.length)
219263
cases = formatTemplateString(cases, args)
220264

221-
return (name: string | Function, fn: (...args: T[]) => void, options?: number | TestOptions) => {
265+
return (name: string | Function, optionsOrFn: ((...args: T[]) => void) | TestOptions, fnOrOptions?: ((...args: T[]) => void) | number | TestOptions) => {
222266
const _name = formatName(name)
223267
const arrayOnlyCases = cases.every(Array.isArray)
268+
269+
const { options, handler } = parseArguments(
270+
optionsOrFn,
271+
fnOrOptions,
272+
)
273+
224274
cases.forEach((i, idx) => {
225275
const items = Array.isArray(i) ? i : [i]
226276
arrayOnlyCases
227-
? suite(formatTitle(_name, items, idx), () => fn(...items), options)
228-
: suite(formatTitle(_name, items, idx), () => fn(i), options)
277+
? suite(formatTitle(_name, items, idx), options, () => handler(...items))
278+
: suite(formatTitle(_name, items, idx), options, () => handler(i))
229279
})
230280

231281
this.setContext('each', undefined)
@@ -254,15 +304,21 @@ export function createTaskCollector(
254304
if (Array.isArray(cases) && args.length)
255305
cases = formatTemplateString(cases, args)
256306

257-
return (name: string | Function, fn: (...args: T[]) => void, options?: number | TestOptions) => {
307+
return (name: string | Function, optionsOrFn: ((...args: T[]) => void) | TestOptions, fnOrOptions?: ((...args: T[]) => void) | number | TestOptions) => {
258308
const _name = formatName(name)
259309
const arrayOnlyCases = cases.every(Array.isArray)
310+
311+
const { options, handler } = parseArguments(
312+
optionsOrFn,
313+
fnOrOptions,
314+
)
315+
260316
cases.forEach((i, idx) => {
261317
const items = Array.isArray(i) ? i : [i]
262318

263319
arrayOnlyCases
264-
? test(formatTitle(_name, items, idx), () => fn(...items), options)
265-
: test(formatTitle(_name, items, idx), () => fn(i), options)
320+
? test(formatTitle(_name, items, idx), options, () => handler(...items))
321+
: test(formatTitle(_name, items, idx), options, () => handler(i))
266322
})
267323

268324
this.setContext('each', undefined)
@@ -279,8 +335,8 @@ export function createTaskCollector(
279335
taskFn.extend = function (fixtures: Fixtures<Record<string, any>>) {
280336
const _context = mergeContextFixtures(fixtures, context)
281337

282-
return createTest(function fn(name: string | Function, fn?: TestFunction, options?: number | TestOptions) {
283-
getCurrentSuite().test.fn.call(this, formatName(name), fn, options)
338+
return createTest(function fn(name: string | Function, optionsOrFn?: TestOptions | TestFunction, optionsOrTest?: number | TestOptions | TestFunction) {
339+
getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn as TestOptions, optionsOrTest as TestFunction)
284340
}, _context)
285341
}
286342

@@ -299,8 +355,8 @@ function createTest(fn: (
299355
(
300356
this: Record<'concurrent' | 'sequential' | 'skip' | 'only' | 'todo' | 'fails' | 'each', boolean | undefined> & { fixtures?: FixtureItem[] },
301357
title: string,
302-
fn?: TestFunction,
303-
options?: number | TestOptions
358+
optionsOrFn?: TestOptions | TestFunction,
359+
optionsOrTest?: number | TestOptions | TestFunction,
304360
) => void
305361
), context?: Record<string, any>) {
306362
return createTaskCollector(fn, context) as TestAPI

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

+55-37
Original file line numberDiff line numberDiff line change
@@ -112,51 +112,48 @@ type ExtractEachCallbackArgs<T extends ReadonlyArray<any>> = {
112112
? 10
113113
: 'fallback']
114114

115-
interface SuiteEachFunction {
116-
<T extends any[] | [any]>(cases: ReadonlyArray<T>): (
115+
interface EachFunctionReturn<T extends any[]> {
116+
/**
117+
* @deprecated Use options as the second argument instead
118+
*/
119+
(
117120
name: string | Function,
118121
fn: (...args: T) => Awaitable<void>,
119-
) => void
120-
<T extends ReadonlyArray<any>>(cases: ReadonlyArray<T>): (
122+
options: TestOptions,
123+
): void
124+
(
121125
name: string | Function,
122-
fn: (...args: ExtractEachCallbackArgs<T>) => Awaitable<void>,
123-
) => void
124-
<T>(cases: ReadonlyArray<T>): (
126+
fn: (...args: T) => Awaitable<void>,
127+
options?: number | TestOptions,
128+
): void
129+
(
125130
name: string | Function,
126-
fn: (...args: T[]) => Awaitable<void>,
127-
) => void
131+
options: TestOptions,
132+
fn: (...args: T) => Awaitable<void>,
133+
): void
128134
}
129135

130136
interface TestEachFunction {
131-
<T extends any[] | [any]>(cases: ReadonlyArray<T>): (
132-
name: string | Function,
133-
fn: (...args: T) => Awaitable<void>,
134-
options?: number | TestOptions,
135-
) => void
136-
<T extends ReadonlyArray<any>>(cases: ReadonlyArray<T>): (
137-
name: string | Function,
138-
fn: (...args: ExtractEachCallbackArgs<T>) => Awaitable<void>,
139-
options?: number | TestOptions,
140-
) => void
141-
<T>(cases: ReadonlyArray<T>): (
142-
name: string | Function,
143-
fn: (...args: T[]) => Awaitable<void>,
144-
options?: number | TestOptions,
145-
) => void
146-
(...args: [TemplateStringsArray, ...any]): (
147-
name: string | Function,
148-
fn: (...args: any[]) => Awaitable<void>,
149-
options?: number | TestOptions,
150-
) => void
137+
<T extends any[] | [any]>(cases: ReadonlyArray<T>): EachFunctionReturn<T>
138+
<T extends ReadonlyArray<any>>(cases: ReadonlyArray<T>): EachFunctionReturn<ExtractEachCallbackArgs<T>>
139+
<T>(cases: ReadonlyArray<T>): EachFunctionReturn<T[]>
140+
(...args: [TemplateStringsArray, ...any]): EachFunctionReturn<any[]>
141+
}
142+
143+
interface TestCollectorCallable<C = {}> {
144+
/**
145+
* @deprecated Use options as the second argument instead
146+
*/
147+
<ExtraContext extends C>(name: string | Function, fn: TestFunction<ExtraContext>, options: TestOptions): void
148+
<ExtraContext extends C>(name: string | Function, fn?: TestFunction<ExtraContext>, options?: number | TestOptions): void
149+
<ExtraContext extends C>(name: string | Function, options?: TestOptions, fn?: TestFunction<ExtraContext>): void
151150
}
152151

153152
type ChainableTestAPI<ExtraContext = {}> = ChainableFunction<
154153
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails',
155-
[name: string | Function, fn?: TestFunction<ExtraContext>, options?: number | TestOptions],
156-
void,
154+
TestCollectorCallable<ExtraContext>,
157155
{
158156
each: TestEachFunction
159-
<T extends ExtraContext>(name: string | Function, fn?: TestFunction<T>, options?: number | TestOptions): void
160157
}
161158
>
162159

@@ -189,10 +186,25 @@ export interface TestOptions {
189186
* Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`.
190187
*/
191188
sequential?: boolean
189+
/**
190+
* Whether the test should be skipped.
191+
*/
192+
skip?: boolean
193+
/**
194+
* Should this test be the only one running in a suite.
195+
*/
196+
only?: boolean
197+
/**
198+
* Whether the test should be skipped and marked as a todo.
199+
*/
200+
todo?: boolean
201+
/**
202+
* Whether the test is expected to fail. If it does, the test will pass, otherwise it will fail.
203+
*/
204+
fails?: boolean
192205
}
193206

194207
interface ExtendedAPI<ExtraContext> {
195-
each: TestEachFunction
196208
skipIf: (condition: any) => ChainableTestAPI<ExtraContext>
197209
runIf: (condition: any) => ChainableTestAPI<ExtraContext>
198210
}
@@ -224,18 +236,24 @@ export type Fixtures<T extends Record<string, any>, ExtraContext = {}> = {
224236

225237
export type InferFixturesTypes<T> = T extends TestAPI<infer C> ? C : T
226238

239+
interface SuiteCollectorCallable<ExtraContext = {}> {
240+
/**
241+
* @deprecated Use options as the second argument instead
242+
*/
243+
(name: string | Function, fn: SuiteFactory<ExtraContext>, options: TestOptions): SuiteCollector<ExtraContext>
244+
(name: string | Function, fn?: SuiteFactory<ExtraContext>, options?: number | TestOptions): SuiteCollector<ExtraContext>
245+
(name: string | Function, options: TestOptions, fn?: SuiteFactory<ExtraContext>): SuiteCollector<ExtraContext>
246+
}
247+
227248
type ChainableSuiteAPI<ExtraContext = {}> = ChainableFunction<
228249
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle',
229-
[name: string | Function, factory?: SuiteFactory<ExtraContext>, options?: number | TestOptions],
230-
SuiteCollector<ExtraContext>,
250+
SuiteCollectorCallable<ExtraContext>,
231251
{
232252
each: TestEachFunction
233-
<T extends ExtraContext>(name: string | Function, factory?: SuiteFactory<T>): SuiteCollector<T>
234253
}
235254
>
236255

237256
export type SuiteAPI<ExtraContext = {}> = ChainableSuiteAPI<ExtraContext> & {
238-
each: SuiteEachFunction
239257
skipIf: (condition: any) => ChainableSuiteAPI<ExtraContext>
240258
runIf: (condition: any) => ChainableSuiteAPI<ExtraContext>
241259
}

‎packages/runner/src/utils/chain.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
export type ChainableFunction<T extends string, Args extends any[], R = any, E = {}> = {
2-
(...args: Args): R
1+
export type ChainableFunction<T extends string, F extends (...args: any) => any, C = {}> = F & {
2+
[x in T]: ChainableFunction<T, F, C>
33
} & {
4-
[x in T]: ChainableFunction<T, Args, R, E>
5-
} & {
6-
fn: (this: Record<T, any>, ...args: Args) => R
7-
} & E
4+
fn: (this: Record<T, any>, ...args: Parameters<F>) => ReturnType<F>
5+
} & C
86

9-
export function createChainable<T extends string, Args extends any[], R = any, E = {}>(
7+
export function createChainable<T extends string, Args extends any[], R = any>(
108
keys: T[],
119
fn: (this: Record<T, any>, ...args: Args) => R,
12-
): ChainableFunction<T, Args, R, E> {
10+
): ChainableFunction<T, (...args: Args) => R> {
1311
function create(context: Record<T, any>) {
1412
const chain = function (this: any, ...args: Args) {
1513
return fn.apply(context, args)

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ export interface BenchmarkResult extends TinybenchResult {
5454
export type BenchFunction = (this: BenchFactory) => Promise<void> | void
5555
type ChainableBenchmarkAPI = ChainableFunction<
5656
'skip' | 'only' | 'todo',
57-
[name: string | Function, fn?: BenchFunction, options?: BenchOptions],
58-
void
57+
(name: string | Function, fn?: BenchFunction, options?: BenchOptions) => void
5958
>
6059
export type BenchmarkAPI = ChainableBenchmarkAPI & {
6160
skipIf: (condition: any) => ChainableBenchmarkAPI
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import { describe, expect, it } from 'vitest'
22

3-
describe(
4-
'suite name',
5-
() => {
6-
let outerCount = 0
3+
describe('suite name', { retry: 9 }, () => {
4+
let outerCount = 0
75

8-
it('should retry until success', () => {
9-
outerCount++
10-
expect(outerCount).toBe(5)
11-
})
6+
it('should retry until success', () => {
7+
outerCount++
8+
expect(outerCount).toBe(5)
9+
})
1210

13-
describe('nested', () => {
14-
let innerCount = 0
11+
describe('nested', () => {
12+
let innerCount = 0
1513

16-
it('should retry until success (nested)', () => {
17-
innerCount++
18-
expect(innerCount).toBe(5)
19-
})
14+
it('should retry until success (nested)', () => {
15+
innerCount++
16+
expect(innerCount).toBe(5)
2017
})
21-
},
22-
{ retry: 9 },
23-
)
18+
})
19+
})

‎test/core/test/repeats.test.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@ const testNumbers: number[] = []
66
describe('testing it/test', () => {
77
const result = [1, 1, 1, 1, 1, 2, 2, 2]
88

9-
test('test 1', () => {
9+
test('test 1', { repeats: 4 }, () => {
1010
testNumbers.push(1)
11-
}, { repeats: 4 })
11+
})
1212

13-
test('test 2', () => {
13+
test('test 2', { repeats: 2 }, () => {
1414
testNumbers.push(2)
15-
}, { repeats: 2 })
15+
})
1616

17-
test.fails('test 3', () => {
17+
test.fails('test 3', { repeats: 0 }, () => {
1818
testNumbers.push(3)
1919
expect(testNumbers).toStrictEqual(result)
20-
}, { repeats: 0 })
20+
})
2121

2222
afterAll(() => {
2323
result.push(3)
@@ -27,11 +27,11 @@ describe('testing it/test', () => {
2727

2828
const describeNumbers: number[] = []
2929

30-
describe('testing describe', () => {
30+
describe('testing describe', { repeats: 2 }, () => {
3131
test('test 1', () => {
3232
describeNumbers.push(1)
3333
})
34-
}, { repeats: 2 })
34+
})
3535

3636
afterAll(() => {
3737
expect(describeNumbers).toStrictEqual([1, 1, 1])
@@ -42,24 +42,24 @@ const retryNumbers: number[] = []
4242
describe('testing repeats with retry', () => {
4343
describe('normal test', () => {
4444
const result = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
45-
test.fails('test 1', () => {
45+
test.fails('test 1', { repeats: 4, retry: 1 }, () => {
4646
retryNumbers.push(1)
4747
expect(1).toBe(2)
48-
}, { repeats: 4, retry: 1 })
48+
})
4949

5050
afterAll(() => {
5151
expect(retryNumbers).toStrictEqual(result)
5252
})
5353
})
5454

55-
test('should not reset retry count', () => {
55+
test('should not reset retry count', { repeats: 2, retry: 1 }, () => {
5656
expect(getCurrentTest()!.result?.retryCount).toBe(3)
57-
}, { repeats: 2, retry: 1 })
57+
})
5858
})
5959

6060
const nestedDescribeNumbers: number[] = []
6161

62-
describe('testing nested describe', () => {
62+
describe('testing nested describe', { repeats: 1 }, () => {
6363
test ('test 1', () => {
6464
nestedDescribeNumbers.push(1)
6565
})
@@ -69,7 +69,7 @@ describe('testing nested describe', () => {
6969
nestedDescribeNumbers.push(2)
7070
})
7171

72-
describe('nested 2', () => {
72+
describe('nested 2', { repeats: 2 }, () => {
7373
test('test 3', () => {
7474
nestedDescribeNumbers.push(3)
7575
})
@@ -79,10 +79,10 @@ describe('testing nested describe', () => {
7979
nestedDescribeNumbers.push(4)
8080
})
8181
}, 100)
82-
}, { repeats: 2 })
82+
})
8383
})
8484

8585
afterAll(() => {
8686
expect(nestedDescribeNumbers).toStrictEqual([1, 1, 2, 2, 3, 3, 3, 4, 4, 4])
8787
})
88-
}, { repeats: 1 })
88+
})

‎test/core/test/retry-only.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { describe, expect, it } from 'vitest'
22

3-
describe.only('description.only retry', () => {
3+
describe.only('description.only retry', { retry: 1 }, () => {
44
let count4 = 0
55
let count5 = 0
66
it('test should inherit options from the description block if missing', () => {
77
count4 += 1
88
expect(count4).toBe(2)
99
})
1010

11-
it('test should not inherit options from the description block if exists', () => {
11+
it('test should not inherit options from the description block if exists', { retry: 4 }, () => {
1212
count5 += 1
1313
expect(count5).toBe(5)
14-
}, { retry: 4 })
15-
}, { retry: 1 })
14+
})
15+
})

‎test/core/test/retry.test.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
11
import { describe, expect, it } from 'vitest'
22

33
let count1 = 0
4-
it('retry test', () => {
4+
it('retry test', { retry: 2 }, () => {
55
count1 += 1
66
expect(count1).toBe(3)
7-
}, { retry: 2 })
7+
})
88

99
let count2 = 0
10-
it.fails('retry test fails', () => {
10+
it.fails('retry test fails', { retry: 1 }, () => {
1111
count2 += 1
1212
expect(count2).toBe(3)
13-
}, { retry: 1 })
13+
})
1414

1515
let count3 = 0
16-
it('retry test fails', () => {
16+
it('retry test fails', { retry: 10 }, () => {
1717
count3 += 1
1818
expect(count3).toBe(3)
19-
}, { retry: 10 })
19+
})
2020

2121
it('result', () => {
2222
expect(count1).toEqual(3)
2323
expect(count2).toEqual(2)
2424
expect(count3).toEqual(3)
2525
})
2626

27-
describe('description retry', () => {
27+
describe('description retry', { retry: 2 }, () => {
2828
let count4 = 0
2929
let count5 = 0
3030
it('test should inherit options from the description block if missing', () => {
3131
count4 += 1
3232
expect(count4).toBe(2)
3333
})
3434

35-
it('test should not inherit options from the description block if exists', () => {
35+
it('test should not inherit options from the description block if exists', { retry: 5 }, () => {
3636
count5 += 1
3737
expect(count5).toBe(5)
38-
}, { retry: 5 })
39-
}, { retry: 2 })
38+
})
39+
})
4040

4141
describe.each([
4242
{ a: 1, b: 1, expected: 2 },
4343
{ a: 1, b: 2, expected: 3 },
4444
{ a: 2, b: 1, expected: 3 },
45-
])('describe object add($a, $b)', ({ a, b, expected }) => {
45+
])('describe object add($a, $b)', { retry: 2 }, ({ a, b, expected }) => {
4646
let flag1 = false
4747
let flag2 = false
4848
let flag3 = false
@@ -63,4 +63,4 @@ describe.each([
6363
expect(a + b).not.toBeLessThan(expected)
6464
expect(flag3).toBe(false)
6565
})
66-
}, { retry: 2 })
66+
})

‎test/core/test/test-options.test.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, expect, test } from 'vitest'
2+
3+
const fail = () => expect.fail('expected to be skipped')
4+
5+
describe('all test variations are allowed', () => {
6+
test('skipped by default')
7+
8+
test.skip('skipped explicitly', fail)
9+
test.skip('skipped explicitly', fail, 1000)
10+
test('skipped explicitly via options', { skip: true }, fail)
11+
test('skipped explicitly via options as the last argument', fail, { skip: true })
12+
13+
test.todo('todo explicitly', fail)
14+
test.todo('todo explicitly', fail, 1000)
15+
test('todo explicitly via options', { todo: true }, fail)
16+
test('todo explicitly via options as the last argument', fail, { todo: true })
17+
18+
test.fails('fails by default', fail)
19+
test.fails('fails by default', fail, 1000)
20+
test('fails explicitly via options', { fails: true }, fail)
21+
test('fails explicitly via options as the last argument', fail, { fails: true })
22+
})
23+
24+
describe('only is allowed explicitly', () => {
25+
test('not only by default', fail)
26+
test.only('only explicitly', () => {})
27+
})
28+
29+
describe('only is allowed via options', () => {
30+
test('not only by default', fail)
31+
test('only via options', { only: true }, () => {})
32+
})
33+
34+
describe('only is allowed via option as the last argument', () => {
35+
test('not only by default', fail)
36+
test('only via options as the last argument', () => {}, { only: true })
37+
})

0 commit comments

Comments
 (0)
Please sign in to comment.