Skip to content

Commit ca168a1

Browse files
authored
fix: inherit concurrent/sequential in nested suites (#4482)
1 parent 0ccf5ac commit ca168a1

File tree

6 files changed

+107
-20
lines changed

6 files changed

+107
-20
lines changed

docs/api/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t
654654

655655
- **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void`
656656

657-
`describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequential within `describe.concurrent` or with the `--sequence.concurrent` command option.
657+
`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.
658658

659659
```ts
660660
describe.concurrent('suite', () => {

packages/runner/src/suite.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
8080
meta: options.meta ?? Object.create(null),
8181
}
8282
const handler = options.handler
83-
if (options.concurrent || (!sequential && (concurrent || runner.config.sequence.concurrent)))
83+
if (options.concurrent || (!options.sequential && runner.config.sequence.concurrent))
8484
task.concurrent = true
8585
if (shuffle)
8686
task.shuffle = true
@@ -104,14 +104,18 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
104104
return task
105105
}
106106

107-
const test = createTest(function (name: string | Function, fn = noop, options) {
107+
const test = createTest(function (name: string | Function, fn = noop, options = {}) {
108108
if (typeof options === 'number')
109109
options = { timeout: options }
110110

111111
// inherit repeats, retry, timeout from suite
112112
if (typeof suiteOptions === 'object')
113113
options = Object.assign({}, suiteOptions, options)
114114

115+
// inherit concurrent / sequential from suite
116+
options.concurrent = this.concurrent || (!this.sequential && options?.concurrent)
117+
options.sequential = this.sequential || (!this.concurrent && options?.sequential)
118+
115119
const test = task(
116120
formatName(name),
117121
{ ...this, ...options, handler: fn as any },
@@ -188,7 +192,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
188192
}
189193

190194
function createSuite() {
191-
function suiteFn(this: Record<string, boolean | undefined>, name: string | Function, factory?: SuiteFactory, options?: number | TestOptions) {
195+
function suiteFn(this: Record<string, boolean | undefined>, name: string | Function, factory?: SuiteFactory, options: number | TestOptions = {}) {
192196
const mode: RunMode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run'
193197
const currentSuite = getCurrentSuite()
194198

@@ -199,7 +203,11 @@ function createSuite() {
199203
if (currentSuite?.options)
200204
options = { ...currentSuite.options, ...options }
201205

202-
return createSuiteCollector(formatName(name), factory, mode, this.concurrent, this.sequence, this.shuffle, this.each, options)
206+
// inherit concurrent / sequential from current suite
207+
options.concurrent = this.concurrent || (!this.sequential && options?.concurrent)
208+
options.sequential = this.sequential || (!this.concurrent && options?.sequential)
209+
210+
return createSuiteCollector(formatName(name), factory, mode, this.concurrent, this.sequential, this.shuffle, this.each, options)
203211
}
204212

205213
suiteFn.each = function<T>(this: { withContext: () => SuiteAPI; setContext: (key: string, value: boolean | undefined) => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {

packages/runner/src/types/tasks.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,16 @@ export interface TestOptions {
178178
* @default 0
179179
*/
180180
repeats?: number
181+
/**
182+
* Whether tests run concurrently.
183+
* Tests inherit `concurrent` from `describe()` and nested `describe()` will inherit from parent's `concurrent`.
184+
*/
185+
concurrent?: boolean
186+
/**
187+
* Whether tests run sequentially.
188+
* Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`.
189+
*/
190+
sequential?: boolean
181191
}
182192

183193
interface ExtendedAPI<ExtraContext> {

packages/vitest/src/types/config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,11 @@ export type RuntimeConfig = Pick<
813813
| 'restoreMocks'
814814
| 'fakeTimers'
815815
| 'maxConcurrency'
816-
> & { sequence?: { hooks?: SequenceHooks } }
816+
> & {
817+
sequence?: {
818+
concurrent?: boolean
819+
hooks?: SequenceHooks
820+
}
821+
}
817822

818823
export type { UserWorkspaceConfig } from '../config'
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, test, vi } from 'vitest'
2+
3+
vi.setConfig({
4+
sequence: {
5+
concurrent: true,
6+
},
7+
})
8+
9+
const delay = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout))
10+
11+
let count = 0
12+
13+
describe.sequential('running sequential suite when sequence.concurrent is true', () => {
14+
test('first test completes first', async ({ task }) => {
15+
await delay(50)
16+
expect(task.concurrent).toBeFalsy()
17+
expect(++count).toBe(1)
18+
})
19+
20+
test('second test completes second', ({ task }) => {
21+
expect(task.concurrent).toBeFalsy()
22+
expect(++count).toBe(2)
23+
})
24+
})

test/core/test/sequential.test.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,59 @@ import { describe, expect, test } from 'vitest'
22

33
const delay = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout))
44

5-
let count = 0
6-
7-
describe.concurrent('', () => {
8-
describe.sequential('', () => {
9-
test('should pass', async ({ task }) => {
10-
await delay(50)
11-
expect(task.concurrent).toBeFalsy()
12-
expect(++count).toBe(1)
13-
})
14-
15-
test('should pass', ({ task }) => {
16-
expect(task.concurrent).toBeFalsy()
17-
expect(++count).toBe(2)
18-
})
5+
function assertSequential() {
6+
let count = 0
7+
8+
test('first test completes first', async ({ task }) => {
9+
await delay(50)
10+
expect(task.concurrent).toBeFalsy()
11+
expect(++count).toBe(1)
12+
})
13+
14+
test('second test completes second', ({ task }) => {
15+
expect(task.concurrent).toBeFalsy()
16+
expect(++count).toBe(2)
17+
})
18+
19+
test.concurrent('third test completes fourth', async ({ task }) => {
20+
await delay(50)
21+
expect(task.concurrent).toBe(true)
22+
expect(++count).toBe(4)
23+
})
24+
25+
test.concurrent('fourth test completes third', ({ task }) => {
26+
expect(task.concurrent).toBe(true)
27+
expect(++count).toBe(3)
28+
})
29+
}
30+
31+
function assertConcurrent() {
32+
let count = 0
33+
34+
test('first test completes second', async ({ task }) => {
35+
await delay(50)
36+
expect(task.concurrent).toBe(true)
37+
expect(++count).toBe(2)
38+
})
39+
40+
test('second test completes first', ({ task }) => {
41+
expect(task.concurrent).toBe(true)
42+
expect(++count).toBe(1)
43+
})
44+
}
45+
46+
assertSequential()
47+
48+
describe.concurrent('describe.concurrent', () => {
49+
assertConcurrent()
50+
51+
describe('describe', assertConcurrent)
52+
53+
describe.sequential('describe.sequential', () => {
54+
assertSequential()
55+
56+
describe('describe', assertSequential)
57+
58+
describe.concurrent('describe.concurrent', assertConcurrent)
1959
})
2060
})

0 commit comments

Comments
 (0)