Skip to content

Commit

Permalink
fix: inherit concurrent/sequential in nested suites (#4482)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsyddall authored Nov 15, 2023
1 parent 0ccf5ac commit ca168a1
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 20 deletions.
2 changes: 1 addition & 1 deletion docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t

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

`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.
`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
describe.concurrent('suite', () => {
Expand Down
16 changes: 12 additions & 4 deletions packages/runner/src/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
meta: options.meta ?? Object.create(null),
}
const handler = options.handler
if (options.concurrent || (!sequential && (concurrent || runner.config.sequence.concurrent)))
if (options.concurrent || (!options.sequential && runner.config.sequence.concurrent))
task.concurrent = true
if (shuffle)
task.shuffle = true
Expand All @@ -104,14 +104,18 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
return task
}

const test = createTest(function (name: string | Function, fn = noop, options) {
const test = createTest(function (name: string | Function, fn = noop, options = {}) {
if (typeof options === 'number')
options = { timeout: options }

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

// inherit concurrent / sequential from suite
options.concurrent = this.concurrent || (!this.sequential && options?.concurrent)
options.sequential = this.sequential || (!this.concurrent && options?.sequential)

const test = task(
formatName(name),
{ ...this, ...options, handler: fn as any },
Expand Down Expand Up @@ -188,7 +192,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
}

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

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

return createSuiteCollector(formatName(name), factory, mode, this.concurrent, this.sequence, this.shuffle, this.each, options)
// inherit concurrent / sequential from current suite
options.concurrent = this.concurrent || (!this.sequential && options?.concurrent)
options.sequential = this.sequential || (!this.concurrent && options?.sequential)

return createSuiteCollector(formatName(name), factory, mode, this.concurrent, this.sequential, this.shuffle, this.each, options)
}

suiteFn.each = function<T>(this: { withContext: () => SuiteAPI; setContext: (key: string, value: boolean | undefined) => SuiteAPI }, cases: ReadonlyArray<T>, ...args: any[]) {
Expand Down
10 changes: 10 additions & 0 deletions packages/runner/src/types/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ export interface TestOptions {
* @default 0
*/
repeats?: number
/**
* Whether tests run concurrently.
* Tests inherit `concurrent` from `describe()` and nested `describe()` will inherit from parent's `concurrent`.
*/
concurrent?: boolean
/**
* Whether tests run sequentially.
* Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`.
*/
sequential?: boolean
}

interface ExtendedAPI<ExtraContext> {
Expand Down
7 changes: 6 additions & 1 deletion packages/vitest/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,11 @@ export type RuntimeConfig = Pick<
| 'restoreMocks'
| 'fakeTimers'
| 'maxConcurrency'
> & { sequence?: { hooks?: SequenceHooks } }
> & {
sequence?: {
concurrent?: boolean
hooks?: SequenceHooks
}
}

export type { UserWorkspaceConfig } from '../config'
24 changes: 24 additions & 0 deletions test/core/test/sequential-sequence-concurrent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, test, vi } from 'vitest'

vi.setConfig({
sequence: {
concurrent: true,
},
})

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

let count = 0

describe.sequential('running sequential suite when sequence.concurrent is true', () => {
test('first test completes first', async ({ task }) => {
await delay(50)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(1)
})

test('second test completes second', ({ task }) => {
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(2)
})
})
68 changes: 54 additions & 14 deletions test/core/test/sequential.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,59 @@ import { describe, expect, test } from 'vitest'

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

let count = 0

describe.concurrent('', () => {
describe.sequential('', () => {
test('should pass', async ({ task }) => {
await delay(50)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(1)
})

test('should pass', ({ task }) => {
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(2)
})
function assertSequential() {
let count = 0

test('first test completes first', async ({ task }) => {
await delay(50)
expect(task.concurrent).toBeFalsy()
expect(++count).toBe(1)
})

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

test.concurrent('third test completes fourth', async ({ task }) => {
await delay(50)
expect(task.concurrent).toBe(true)
expect(++count).toBe(4)
})

test.concurrent('fourth test completes third', ({ task }) => {
expect(task.concurrent).toBe(true)
expect(++count).toBe(3)
})
}

function assertConcurrent() {
let count = 0

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

test('second test completes first', ({ task }) => {
expect(task.concurrent).toBe(true)
expect(++count).toBe(1)
})
}

assertSequential()

describe.concurrent('describe.concurrent', () => {
assertConcurrent()

describe('describe', assertConcurrent)

describe.sequential('describe.sequential', () => {
assertSequential()

describe('describe', assertSequential)

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

0 comments on commit ca168a1

Please sign in to comment.