Skip to content

Commit 9229f2e

Browse files
hi-ogawacodex
andauthored
refactor!: remove sequential test/suite options in favor of concurrent (#10198)
Co-authored-by: Codex <noreply@openai.com>
1 parent 1ba7338 commit 9229f2e

13 files changed

Lines changed: 106 additions & 270 deletions

File tree

docs/api/describe.md

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,19 @@ describe.concurrent('suite', () => {
208208
})
209209
```
210210

211+
Set `concurrent` to `false` to opt out of concurrency inherited from a parent suite or [`sequence.concurrent`](/config/sequence#sequence-concurrent):
212+
213+
```ts
214+
describe.concurrent('suite', () => {
215+
test('concurrent test', async () => { /* ... */ })
216+
217+
describe('sequential suite', { concurrent: false }, () => {
218+
test('sequential test 1', async () => { /* ... */ })
219+
test('sequential test 2', async () => { /* ... */ })
220+
})
221+
})
222+
```
223+
211224
`.skip`, `.only`, and `.todo` works with concurrent suites. All the following combinations are valid:
212225

213226
```ts
@@ -230,30 +243,6 @@ describe.concurrent('suite', () => {
230243
})
231244
```
232245

233-
## describe.sequential <Deprecated /> {#describe-sequential}
234-
235-
- **Alias:** `suite.sequential`
236-
237-
::: warning DEPRECATED
238-
Use [`concurrent: false`](/api/test#concurrent) instead when you need to override inherited or configured concurrency.
239-
:::
240-
241-
`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.
242-
243-
```ts
244-
import { describe, test } from 'vitest'
245-
246-
describe.concurrent('suite', () => {
247-
test('concurrent test 1', async () => { /* ... */ })
248-
test('concurrent test 2', async () => { /* ... */ })
249-
250-
describe.sequential('', () => {
251-
test('sequential test 1', async () => { /* ... */ })
252-
test('sequential test 2', async () => { /* ... */ })
253-
})
254-
})
255-
```
256-
257246
## describe.shuffle
258247

259248
- **Alias:** `suite.shuffle`

docs/api/test.md

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -217,17 +217,13 @@ Prefer using non-nested meta, if possible.
217217

218218
Whether this test run concurrently with other concurrent tests in the suite.
219219

220-
### sequential
220+
Set `concurrent` to `false` to opt out of concurrency inherited from [`describe.concurrent`](/api/describe#describe-concurrent) or [`sequence.concurrent`](/config/sequence#sequence-concurrent):
221221

222-
- **Type:** `boolean`
223-
- **Default:** `true`
224-
- **Alias:** [`test.sequential`](#test-sequential)
225-
226-
::: warning DEPRECATED
227-
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
228-
:::
229-
230-
Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precedence.
222+
```ts
223+
test('runs sequentially', { concurrent: false }, async () => {
224+
// ...
225+
})
226+
```
231227

232228
### skip
233229

@@ -457,36 +453,6 @@ test.concurrent('test 2', async ({ expect }) => {
457453

458454
Note that if tests are synchronous, Vitest will still run them sequentially.
459455

460-
## test.sequential <Deprecated /> {#test-sequential}
461-
462-
- **Alias:** `it.sequential`
463-
464-
::: warning DEPRECATED
465-
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
466-
:::
467-
468-
`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.
469-
470-
```ts
471-
import { describe, test } from 'vitest'
472-
473-
// with config option { sequence: { concurrent: true } }
474-
test('concurrent test 1', async () => { /* ... */ })
475-
test('concurrent test 2', async () => { /* ... */ })
476-
477-
test.sequential('sequential test 1', async () => { /* ... */ })
478-
test.sequential('sequential test 2', async () => { /* ... */ })
479-
480-
// within concurrent suite
481-
describe.concurrent('suite', () => {
482-
test('concurrent test 1', async () => { /* ... */ })
483-
test('concurrent test 2', async () => { /* ... */ })
484-
485-
test.sequential('sequential test 1', async () => { /* ... */ })
486-
test.sequential('sequential test 2', async () => { /* ... */ })
487-
})
488-
```
489-
490456
## test.todo
491457

492458
- **Alias:** `it.todo`

docs/guide/migration.md

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,48 +13,27 @@ outline: deep
1313
Vitest 5.0 is currently in beta. This section tracks breaking changes as they are merged and may change before the stable release.
1414
:::
1515

16-
### String Values in `$` Test Titles Are No Longer Quoted
16+
### Removed `test.sequential`, `describe.sequential`, and `sequential` Options
1717

18-
When interpolating string values in `test.each`, `test.for`, `describe.each`, or `describe.for` titles with the `$` syntax, Vitest no longer wraps those string values in quotes.
19-
20-
This affects generated task names in reporter output, snapshots, and any tooling that matches tests by their generated title.
18+
Vitest 5.0 removes the deprecated `test.sequential`, `describe.sequential`, and `sequential` test options. Use `concurrent: false` when you need a test or suite to opt out of inherited or globally configured concurrency.
2119

2220
```ts
23-
test.for([{ name: 'Alice' }])('I am $name', () => {})
24-
// Vitest 4 → I am 'Alice'
25-
// Vitest 5 → I am Alice
21+
test.sequential('example', async () => { /* ... */ }) // [!code --]
22+
test('example', { concurrent: false }, async () => { /* ... */ }) // [!code ++]
2623
```
2724

28-
If you need quotes in the generated title, add them to the title template:
29-
3025
```ts
31-
test.for([{ name: 'Alice' }])('I am "$name"', () => {})
32-
// → I am "Alice"
26+
describe.sequential('suite', () => { /* ... */ }) // [!code --]
27+
describe('suite', { concurrent: false }, () => { /* ... */ }) // [!code ++]
3328
```
3429

35-
### `chaiConfig.truncateThreshold` No Longer Controls Test Title Value Truncation
36-
37-
Vitest now formats interpolated task title values with its display formatter based on `@vitest/pretty-format`, instead of Chai/loupe formatting.
38-
39-
Most output should stay similar, but generated titles or assertion output involving formatted values may have small formatting differences.
40-
41-
If you used `chaiConfig.truncateThreshold` to control truncation in `test.each`, `test.for`, `describe.each`, or `describe.for` titles, use `taskTitleValueFormatTruncate` instead:
42-
43-
```ts [vitest.config.ts]
44-
import { defineConfig } from 'vitest/config'
30+
The same replacement applies to option objects:
4531

46-
export default defineConfig({
47-
test: {
48-
chaiConfig: { // [!code --]
49-
truncateThreshold: 120, // [!code --]
50-
}, // [!code --]
51-
taskTitleValueFormatTruncate: 120, // [!code ++]
52-
},
53-
})
32+
```ts
33+
test('example', { sequential: true }, async () => { /* ... */ }) // [!code --]
34+
test('example', { concurrent: false }, async () => { /* ... */ }) // [!code ++]
5435
```
5536

56-
`chaiConfig.truncateThreshold` still controls truncation in assertion error messages.
57-
5837
## Migrating to Vitest 4.0 {#vitest-4}
5938

6039
::: warning Prerequisites

docs/guide/parallelism.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ describe.concurrent('user API', () => {
8080

8181
If you want *all* tests in your project to run concurrently by default, set [`sequence.concurrent`](/config/sequence#sequence-concurrent) to `true` in your config.
8282

83+
You can opt individual tests or suites out of inherited concurrency with `concurrent: false`:
84+
85+
```ts
86+
test('uses a shared resource', { concurrent: false }, async () => {
87+
// ...
88+
})
89+
90+
describe('shared resource suite', { concurrent: false }, () => {
91+
test('step 1', async () => { /* ... */ })
92+
test('step 2', async () => { /* ... */ })
93+
})
94+
```
95+
8396
### Hooks with Concurrent Tests
8497

8598
When tests run concurrently, lifecycle hooks behave differently. `beforeAll` and `afterAll` still run once for the group, but `beforeEach` and `afterEach` run for each test — potentially at the same time, since the tests themselves overlap.

packages/runner/src/suite.ts

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,7 @@ function createSuiteCollector(
390390
if (task.mode === 'run' && !handler) {
391391
task.mode = 'todo'
392392
}
393-
if (
394-
options.concurrent
395-
?? (!options.sequential && runner.config.sequence.concurrent)
396-
) {
393+
if (options.concurrent ?? runner.config.sequence.concurrent) {
397394
task.concurrent = true
398395
}
399396
task.shuffle = suiteOptions?.shuffle
@@ -450,17 +447,11 @@ function createSuiteCollector(
450447
options = Object.assign({}, suiteOptions, options)
451448
}
452449

453-
// inherit concurrent / sequential from suite
454-
const concurrent = this.concurrent ?? (!this.sequential && options?.concurrent)
455-
if (options.concurrent != null && concurrent != null) {
450+
const concurrent = this.concurrent ?? options?.concurrent
451+
if (concurrent != null) {
456452
options.concurrent = concurrent
457453
}
458454

459-
const sequential = this.sequential ?? (!this.concurrent && options?.sequential)
460-
if (options.sequential != null && sequential != null) {
461-
options.sequential = sequential
462-
}
463-
464455
const test = task(formatName(name), {
465456
...this,
466457
...options,
@@ -602,9 +593,6 @@ function createSuite() {
602593
optionsOrFactory,
603594
) as { options: SuiteOptions; handler: SuiteFactory | undefined }
604595

605-
const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false
606-
const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false
607-
608596
const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {}
609597
// inherit options from current suite
610598
options = {
@@ -630,14 +618,9 @@ function createSuite() {
630618
mode = 'todo'
631619
}
632620

633-
// inherit concurrent / sequential from suite
634-
const isConcurrent = isConcurrentSpecified || (options.concurrent && !isSequentialSpecified)
635-
const isSequential = isSequentialSpecified || (options.sequential && !isConcurrentSpecified)
636-
if (isConcurrent != null) {
637-
options.concurrent = isConcurrent && !isSequential
638-
}
639-
if (isSequential != null) {
640-
options.sequential = isSequential && !isConcurrent
621+
const concurrent = this.concurrent ?? options.concurrent
622+
if (concurrent != null) {
623+
options.concurrent = concurrent
641624
}
642625

643626
if (parentMeta) {
@@ -737,7 +720,7 @@ function createSuite() {
737720
(condition ? suite : suite.skip) as SuiteAPI
738721

739722
return createChainable(
740-
['concurrent', 'sequential', 'shuffle', 'skip', 'only', 'todo'],
723+
['concurrent', 'shuffle', 'skip', 'only', 'todo'],
741724
suiteFn,
742725
) as unknown as SuiteAPI
743726
}
@@ -968,7 +951,7 @@ export function createTaskCollector(
968951
taskFn.aroundAll = aroundAll
969952

970953
const _test = createChainable(
971-
['concurrent', 'sequential', 'skip', 'only', 'todo', 'fails'],
954+
['concurrent', 'skip', 'only', 'todo', 'fails'],
972955
taskFn,
973956
{ fixtures: new TestFixtures() },
974957
) as TestAPI

packages/runner/src/types/tasks.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export interface InternalChainableContext<API = TestAPI> {
467467

468468
type ChainableTestContextMap = Pick<
469469
Required<TestOptions>,
470-
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails'
470+
'concurrent' | 'only' | 'skip' | 'todo' | 'fails'
471471
>
472472

473473
type ChainableTestAPI<ExtraContext = object> = TypedChainableFunction<
@@ -557,13 +557,6 @@ export interface TestOptions {
557557
* Tests inherit `concurrent` from `describe()` and nested `describe()` will inherit from parent's `concurrent`.
558558
*/
559559
concurrent?: boolean
560-
/**
561-
* Whether tests run sequentially.
562-
* Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`.
563-
*
564-
* @deprecated Use `concurrent: false` instead.
565-
*/
566-
sequential?: boolean
567560
/**
568561
* Whether the test should be skipped.
569562
*/
@@ -1074,7 +1067,7 @@ interface SuiteCollectorCallable<ExtraContext = object> {
10741067

10751068
type ChainableSuiteContextMap = Pick<
10761069
Required<SuiteOptions>,
1077-
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle'
1070+
'concurrent' | 'only' | 'skip' | 'todo' | 'shuffle'
10781071
>
10791072

10801073
type ChainableSuiteAPI<ExtraContext = object> = TypedChainableFunction<

packages/vitest/src/node/ast-collect.ts

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ interface LocalCallDefinition {
4343
mode: 'run' | 'skip' | 'only' | 'todo' | 'queued'
4444
task: ParsedSuite | ParsedFile | ParsedTest
4545
dynamic: boolean
46-
concurrent: boolean
47-
sequential: boolean
46+
concurrent: boolean | undefined
4847
tags: string[]
4948
}
5049

@@ -172,8 +171,7 @@ function astParseFile(filepath: string, code: string) {
172171
mode = 'skip'
173172
}
174173
}
175-
let isConcurrent = properties.includes('concurrent')
176-
let isSequential = properties.includes('sequential')
174+
let concurrent = properties.includes('concurrent') || undefined
177175

178176
let start: number
179177
const end = node.end
@@ -249,15 +247,12 @@ function astParseFile(filepath: string, code: string) {
249247
}
250248
}
251249
}
252-
else if (prop.value?.type === 'Literal' && prop.value.value === true) {
253-
if (keyName === 'skip' || keyName === 'only' || keyName === 'todo') {
250+
else if (prop.value?.type === 'Literal') {
251+
if ((keyName === 'skip' || keyName === 'only' || keyName === 'todo') && prop.value.value === true) {
254252
mode = keyName
255253
}
256-
else if (keyName === 'concurrent') {
257-
isConcurrent = true
258-
}
259-
else if (keyName === 'sequential') {
260-
isSequential = true
254+
else if (keyName === 'concurrent' && typeof prop.value.value === 'boolean') {
255+
concurrent = prop.value.value
261256
}
262257
}
263258
}
@@ -272,8 +267,7 @@ function astParseFile(filepath: string, code: string) {
272267
mode,
273268
task: null as any,
274269
dynamic: isDynamicEach,
275-
concurrent: isConcurrent,
276-
sequential: isSequential,
270+
concurrent,
277271
tags,
278272
} satisfies LocalCallDefinition)
279273
},
@@ -416,10 +410,7 @@ function createFileTask(
416410
// Inherit tags from parent suite and merge with own tags
417411
const parentTags = latestSuite.tags || []
418412
const taskTags = unique([...parentTags, ...definition.tags])
419-
// resolve concurrent/sequential: sequential cancels inherited concurrent
420-
const concurrent = definition.sequential
421-
? undefined
422-
: (definition.concurrent || latestSuite.concurrent || undefined)
413+
const concurrent = definition.concurrent ?? latestSuite.concurrent
423414

424415
if (definition.type === 'suite') {
425416
const task: ParsedSuite = {

0 commit comments

Comments
 (0)