Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/api/describe.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,14 @@ describe.concurrent('suite', () => {
})
```

## describe.sequential
## describe.sequential <Deprecated /> {#describe-sequential}

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

::: warning DEPRECATED
Use [`concurrent: false`](/api/test#concurrent) instead when you need to override inherited or configured concurrency.
:::

`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.

```ts
Expand Down
10 changes: 9 additions & 1 deletion docs/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ Whether this test run concurrently with other concurrent tests in the suite.
- **Default:** `true`
- **Alias:** [`test.sequential`](#test-sequential)

::: warning DEPRECATED
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
:::

Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precedence.

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

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

## test.sequential
## test.sequential <Deprecated /> {#test-sequential}

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

::: warning DEPRECATED
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
:::

`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.

```ts
Expand Down
2 changes: 1 addition & 1 deletion packages/runner/src/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ function createSuiteCollector(
}
if (
options.concurrent
|| (!options.sequential && runner.config.sequence.concurrent)
?? (!options.sequential && runner.config.sequence.concurrent)
) {
task.concurrent = true
}
Expand Down
34 changes: 25 additions & 9 deletions packages/runner/src/types/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Awaitable, TestError } from '@vitest/utils'
import type { TestFixtures } from '../fixture'
import type { afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach } from '../hooks'
import type { ChainableFunction, kChainableContext } from '../utils/chain'
import type { kChainableContext, TypedChainableFunction } from '../utils/chain'

export type RunMode = 'run' | 'skip' | 'only' | 'todo' | 'queued'
export type TaskState = RunMode | 'pass' | 'fail'
Expand Down Expand Up @@ -464,8 +464,14 @@ export interface InternalChainableContext<API = TestAPI> {
/** @internal */
getFixtures: () => TestFixtures
}
type ChainableTestAPI<ExtraContext = object> = ChainableFunction<
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails',

type ChainableTestContextMap = Pick<
Required<TestOptions>,
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails'
>

type ChainableTestAPI<ExtraContext = object> = TypedChainableFunction<
ChainableTestContextMap,
TestCollectorCallable<ExtraContext>,
{
each: TestEachFunction
Expand Down Expand Up @@ -554,6 +560,8 @@ export interface TestOptions {
/**
* Whether tests run sequentially.
* Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`.
*
* @deprecated Use `concurrent: false` instead.
*/
sequential?: boolean
/**
Expand Down Expand Up @@ -799,10 +807,13 @@ export type TestAPI<ExtraContext = object> = ChainableTestAPI<ExtraContext>
suite: SuiteAPI<ExtraContext>
}

export interface InternalTestContext extends Record<
'concurrent' | 'sequential' | 'skip' | 'only' | 'todo' | 'fails' | 'each',
boolean | undefined
> {
// use mapped type to preserve TestOptions references
type InternalTestChainableContext = {
[K in keyof ChainableTestContextMap]: boolean | undefined
}

export interface InternalTestContext extends InternalTestChainableContext {
each: boolean | undefined
fixtures: TestFixtures
}

Expand Down Expand Up @@ -1061,8 +1072,13 @@ interface SuiteCollectorCallable<ExtraContext = object> {
): SuiteCollector<OverrideExtraContext>
}

type ChainableSuiteAPI<ExtraContext = object> = ChainableFunction<
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle',
type ChainableSuiteContextMap = Pick<
Required<SuiteOptions>,
'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle'
>

type ChainableSuiteAPI<ExtraContext = object> = TypedChainableFunction<
ChainableSuiteContextMap,
SuiteCollectorCallable<ExtraContext>,
{
each: TestEachFunction
Expand Down
11 changes: 11 additions & 0 deletions packages/runner/src/utils/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ export type ChainableFunction<
fn: (this: Record<T, any>, ...args: Parameters<F>) => ReturnType<F>
} & C

// this uses mapped type technique to preserve T's jsdoc for chained property function
export type TypedChainableFunction<
T,
F extends (...args: any) => any,
C = object,
> = F & {
[x in keyof T]: TypedChainableFunction<T, F, C>;
} & {
fn: (this: Record<keyof T, any>, ...args: Parameters<F>) => ReturnType<F>
} & C

export const kChainableContext: unique symbol = Symbol('kChainableContext')

export function getChainableContext(chainable: SuiteAPI): InternalChainableContext
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, expect, test } from 'vitest'

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

let count = 0

describe('sequential suite', { concurrent: false }, () => {
test('first test completes first', async ({ task }) => {
await delay(40)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(1)
})

test('second test completes second', async ({ task }) => {
await delay(30)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(2)
})
})

test('third test completes third', { concurrent: false }, async ({ task }) => {
await delay(20)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(3)
})

test('last test completes last', { concurrent: false }, async ({ task }) => {
await delay(10)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(4)
})
72 changes: 50 additions & 22 deletions test/config/test/sequence-concurrent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expect, test } from 'vitest'
import { runVitest } from '../../test-utils'

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

expect(stderr).toBe('')

expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > sequential suite > first test completes first')
expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > sequential suite > second test completes second')
expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > third test completes third')
expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > last test completes last')
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > concurrent suite > first test completes last')
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > concurrent suite > second test completes third')
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > third test completes second')
expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > last test completes first')
expect(stdout).toContain('Test Files 2 passed (2)')
expect(errorTree()).toMatchInlineSnapshot(`
{
"sequence-concurrent-true-concurrent-false.test.ts": {
"last test completes last": "passed",
"sequential suite": {
"first test completes first": "passed",
"second test completes second": "passed",
},
"third test completes third": "passed",
},
"sequence-concurrent-true-concurrent.test.ts": {
"concurrent suite": {
"first test completes last": "passed",
"second test completes third": "passed",
},
"last test completes first": "passed",
"third test completes second": "passed",
},
"sequence-concurrent-true-sequential.test.ts": {
"last test completes last": "passed",
"sequential suite": {
"first test completes first": "passed",
"second test completes second": "passed",
},
"third test completes third": "passed",
},
}
`)
})

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

expect(stderr).toBe('')

expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > sequential suite > first test completes first')
expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > sequential suite > second test completes second')
expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > third test completes third')
expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > last test completes last')
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > concurrent suite > first test completes last')
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > concurrent suite > second test completes third')
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > third test completes second')
expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > last test completes first')
expect(stdout).toContain('Test Files 2 passed (2)')
expect(errorTree()).toMatchInlineSnapshot(`
{
"sequence-concurrent-false-concurrent.test.ts": {
"concurrent suite": {
"first test completes last": "passed",
"second test completes third": "passed",
},
"last test completes first": "passed",
"third test completes second": "passed",
},
"sequence-concurrent-false-sequential.test.ts": {
"last test completes last": "passed",
"sequential suite": {
"first test completes first": "passed",
"second test completes second": "passed",
},
"third test completes third": "passed",
},
}
`)
})
19 changes: 19 additions & 0 deletions test/core/test/sequential.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ function assertConcurrent() {
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(4)
})

test('fifth test completes fifth', { concurrent: false }, async ({ task }) => {
await delay(50)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(5)
})

test('sixth test completes sixth', { concurrent: false }, ({ task }) => {
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(6)
})
}

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

describe.concurrent('describe.concurrent', assertConcurrent)
})

describe('describe concurrent false', { concurrent: false }, () => {
assertSequential()

describe('describe', assertSequential)

describe.concurrent('describe.concurrent', assertConcurrent)
})
})
Loading