Skip to content

Commit 9387f57

Browse files
hi-ogawacodexsheremet-va
authored
fix: global sequence.concurrent: true with top-level test(..., { concurrent: false }) + depreacte sequential test API and options (#10194)
Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>
1 parent 2f89271 commit 9387f57

8 files changed

Lines changed: 151 additions & 34 deletions

File tree

docs/api/describe.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,14 @@ describe.concurrent('suite', () => {
230230
})
231231
```
232232

233-
## describe.sequential
233+
## describe.sequential <Deprecated /> {#describe-sequential}
234234

235235
- **Alias:** `suite.sequential`
236236

237+
::: warning DEPRECATED
238+
Use [`concurrent: false`](/api/test#concurrent) instead when you need to override inherited or configured concurrency.
239+
:::
240+
237241
`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.
238242

239243
```ts

docs/api/test.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ Whether this test run concurrently with other concurrent tests in the suite.
223223
- **Default:** `true`
224224
- **Alias:** [`test.sequential`](#test-sequential)
225225

226+
::: warning DEPRECATED
227+
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
228+
:::
229+
226230
Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precedence.
227231

228232
### skip
@@ -453,10 +457,14 @@ test.concurrent('test 2', async ({ expect }) => {
453457

454458
Note that if tests are synchronous, Vitest will still run them sequentially.
455459

456-
## test.sequential
460+
## test.sequential <Deprecated /> {#test-sequential}
457461

458462
- **Alias:** `it.sequential`
459463

464+
::: warning DEPRECATED
465+
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
466+
:::
467+
460468
`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.
461469

462470
```ts

packages/runner/src/suite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ function createSuiteCollector(
392392
}
393393
if (
394394
options.concurrent
395-
|| (!options.sequential && runner.config.sequence.concurrent)
395+
?? (!options.sequential && runner.config.sequence.concurrent)
396396
) {
397397
task.concurrent = true
398398
}

packages/runner/src/types/tasks.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Awaitable, TestError } from '@vitest/utils'
22
import type { TestFixtures } from '../fixture'
33
import type { afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach } from '../hooks'
4-
import type { ChainableFunction, kChainableContext } from '../utils/chain'
4+
import type { kChainableContext, TypedChainableFunction } from '../utils/chain'
55

66
export type RunMode = 'run' | 'skip' | 'only' | 'todo' | 'queued'
77
export type TaskState = RunMode | 'pass' | 'fail'
@@ -464,8 +464,14 @@ export interface InternalChainableContext<API = TestAPI> {
464464
/** @internal */
465465
getFixtures: () => TestFixtures
466466
}
467-
type ChainableTestAPI<ExtraContext = object> = ChainableFunction<
468-
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails',
467+
468+
type ChainableTestContextMap = Pick<
469+
Required<TestOptions>,
470+
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails'
471+
>
472+
473+
type ChainableTestAPI<ExtraContext = object> = TypedChainableFunction<
474+
ChainableTestContextMap,
469475
TestCollectorCallable<ExtraContext>,
470476
{
471477
each: TestEachFunction
@@ -554,6 +560,8 @@ export interface TestOptions {
554560
/**
555561
* Whether tests run sequentially.
556562
* Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`.
563+
*
564+
* @deprecated Use `concurrent: false` instead.
557565
*/
558566
sequential?: boolean
559567
/**
@@ -799,10 +807,13 @@ export type TestAPI<ExtraContext = object> = ChainableTestAPI<ExtraContext>
799807
suite: SuiteAPI<ExtraContext>
800808
}
801809

802-
export interface InternalTestContext extends Record<
803-
'concurrent' | 'sequential' | 'skip' | 'only' | 'todo' | 'fails' | 'each',
804-
boolean | undefined
805-
> {
810+
// use mapped type to preserve TestOptions references
811+
type InternalTestChainableContext = {
812+
[K in keyof ChainableTestContextMap]: boolean | undefined
813+
}
814+
815+
export interface InternalTestContext extends InternalTestChainableContext {
816+
each: boolean | undefined
806817
fixtures: TestFixtures
807818
}
808819

@@ -1061,8 +1072,13 @@ interface SuiteCollectorCallable<ExtraContext = object> {
10611072
): SuiteCollector<OverrideExtraContext>
10621073
}
10631074

1064-
type ChainableSuiteAPI<ExtraContext = object> = ChainableFunction<
1065-
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle',
1075+
type ChainableSuiteContextMap = Pick<
1076+
Required<SuiteOptions>,
1077+
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle'
1078+
>
1079+
1080+
type ChainableSuiteAPI<ExtraContext = object> = TypedChainableFunction<
1081+
ChainableSuiteContextMap,
10661082
SuiteCollectorCallable<ExtraContext>,
10671083
{
10681084
each: TestEachFunction

packages/runner/src/utils/chain.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ export type ChainableFunction<
1010
fn: (this: Record<T, any>, ...args: Parameters<F>) => ReturnType<F>
1111
} & C
1212

13+
// this uses mapped type technique to preserve T's jsdoc for chained property function
14+
export type TypedChainableFunction<
15+
T,
16+
F extends (...args: any) => any,
17+
C = object,
18+
> = F & {
19+
[x in keyof T]: TypedChainableFunction<T, F, C>;
20+
} & {
21+
fn: (this: Record<keyof T, any>, ...args: Parameters<F>) => ReturnType<F>
22+
} & C
23+
1324
export const kChainableContext: unique symbol = Symbol('kChainableContext')
1425

1526
export function getChainableContext(chainable: SuiteAPI): InternalChainableContext
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, expect, test } from 'vitest'
2+
3+
const delay = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout))
4+
5+
let count = 0
6+
7+
describe('sequential suite', { concurrent: false }, () => {
8+
test('first test completes first', async ({ task }) => {
9+
await delay(40)
10+
expect(task.concurrent).toBeFalsy()
11+
expect(++count).toBe(1)
12+
})
13+
14+
test('second test completes second', async ({ task }) => {
15+
await delay(30)
16+
expect(task.concurrent).toBeFalsy()
17+
expect(++count).toBe(2)
18+
})
19+
})
20+
21+
test('third test completes third', { concurrent: false }, async ({ task }) => {
22+
await delay(20)
23+
expect(task.concurrent).toBeFalsy()
24+
expect(++count).toBe(3)
25+
})
26+
27+
test('last test completes last', { concurrent: false }, async ({ task }) => {
28+
await delay(10)
29+
expect(task.concurrent).toBeFalsy()
30+
expect(++count).toBe(4)
31+
})

test/config/test/sequence-concurrent.test.ts

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expect, test } from 'vitest'
33
import { runVitest } from '../../test-utils'
44

55
test('should run suites and tests concurrently unless sequential specified when sequence.concurrent is true', async () => {
6-
const { stderr, stdout } = await runVitest({
6+
const { stderr, errorTree } = await runVitest({
77
root: './fixtures/sequence-concurrent',
88
include: ['sequence-concurrent-true-*.test.ts'],
99
sequence: {
@@ -12,20 +12,38 @@ test('should run suites and tests concurrently unless sequential specified when
1212
})
1313

1414
expect(stderr).toBe('')
15-
16-
expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > sequential suite > first test completes first')
17-
expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > sequential suite > second test completes second')
18-
expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > third test completes third')
19-
expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > last test completes last')
20-
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > concurrent suite > first test completes last')
21-
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > concurrent suite > second test completes third')
22-
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > third test completes second')
23-
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > last test completes first')
24-
expect(stdout).toContain('Test Files 2 passed (2)')
15+
expect(errorTree()).toMatchInlineSnapshot(`
16+
{
17+
"sequence-concurrent-true-concurrent-false.test.ts": {
18+
"last test completes last": "passed",
19+
"sequential suite": {
20+
"first test completes first": "passed",
21+
"second test completes second": "passed",
22+
},
23+
"third test completes third": "passed",
24+
},
25+
"sequence-concurrent-true-concurrent.test.ts": {
26+
"concurrent suite": {
27+
"first test completes last": "passed",
28+
"second test completes third": "passed",
29+
},
30+
"last test completes first": "passed",
31+
"third test completes second": "passed",
32+
},
33+
"sequence-concurrent-true-sequential.test.ts": {
34+
"last test completes last": "passed",
35+
"sequential suite": {
36+
"first test completes first": "passed",
37+
"second test completes second": "passed",
38+
},
39+
"third test completes third": "passed",
40+
},
41+
}
42+
`)
2543
})
2644

2745
test('should run suites and tests sequentially unless concurrent specified when sequence.concurrent is false', async () => {
28-
const { stderr, stdout } = await runVitest({
46+
const { stderr, errorTree } = await runVitest({
2947
root: './fixtures/sequence-concurrent',
3048
include: ['sequence-concurrent-false-*.test.ts'],
3149
sequence: {
@@ -34,14 +52,24 @@ test('should run suites and tests sequentially unless concurrent specified when
3452
})
3553

3654
expect(stderr).toBe('')
37-
38-
expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > sequential suite > first test completes first')
39-
expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > sequential suite > second test completes second')
40-
expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > third test completes third')
41-
expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > last test completes last')
42-
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > concurrent suite > first test completes last')
43-
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > concurrent suite > second test completes third')
44-
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > third test completes second')
45-
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > last test completes first')
46-
expect(stdout).toContain('Test Files 2 passed (2)')
55+
expect(errorTree()).toMatchInlineSnapshot(`
56+
{
57+
"sequence-concurrent-false-concurrent.test.ts": {
58+
"concurrent suite": {
59+
"first test completes last": "passed",
60+
"second test completes third": "passed",
61+
},
62+
"last test completes first": "passed",
63+
"third test completes second": "passed",
64+
},
65+
"sequence-concurrent-false-sequential.test.ts": {
66+
"last test completes last": "passed",
67+
"sequential suite": {
68+
"first test completes first": "passed",
69+
"second test completes second": "passed",
70+
},
71+
"third test completes third": "passed",
72+
},
73+
}
74+
`)
4775
})

test/core/test/sequential.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ function assertConcurrent() {
5252
expect(task.concurrent).toBeFalsy()
5353
expect(++count).toBe(4)
5454
})
55+
56+
test('fifth test completes fifth', { concurrent: false }, async ({ task }) => {
57+
await delay(50)
58+
expect(task.concurrent).toBeFalsy()
59+
expect(++count).toBe(5)
60+
})
61+
62+
test('sixth test completes sixth', { concurrent: false }, ({ task }) => {
63+
expect(task.concurrent).toBeFalsy()
64+
expect(++count).toBe(6)
65+
})
5566
}
5667

5768
assertSequential()
@@ -68,4 +79,12 @@ describe.concurrent('describe.concurrent', () => {
6879

6980
describe.concurrent('describe.concurrent', assertConcurrent)
7081
})
82+
83+
describe('describe concurrent false', { concurrent: false }, () => {
84+
assertSequential()
85+
86+
describe('describe', assertSequential)
87+
88+
describe.concurrent('describe.concurrent', assertConcurrent)
89+
})
7190
})

0 commit comments

Comments
 (0)