Skip to content

Commit

Permalink
refactor: chainable factory
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Dec 20, 2021
1 parent fe053b6 commit c23479f
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 141 deletions.
28 changes: 28 additions & 0 deletions packages/vitest/src/runtime/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export type ChainableFunction<T extends string, Args extends any[], R = any> = {
(...args: Args): R
} & {
[x in T]: ChainableFunction<T, Args, R>
}

export function createChainable<T extends string, Args extends any[], R = any>(
keys: T[],
fn: (this: Record<T, boolean | undefined>, ...args: Args) => R,
): ChainableFunction<T, Args, R> {
function create(obj: Record<T, boolean | undefined>) {
const chain = function(this: any, ...args: Args) {
return fn.apply(obj, args)
}
for (const key of keys) {
Object.defineProperty(chain, key, {
get() {
return create({ ...obj, [key]: true })
},
})
}
return chain
}

const chain = create({} as any) as any
chain.fn = fn
return chain
}
153 changes: 42 additions & 111 deletions packages/vitest/src/runtime/suite.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import { nanoid } from 'nanoid/non-secure'
import type { Awaitable, ComputeMode, File, ModuleCache, ResolvedConfig, RpcCall, RpcSend, RunMode, Suite, SuiteCollector, SuiteHooks, Test, TestCollector, TestFactory, TestFunction } from '../types'
import type { ComputeMode, File, ModuleCache, ResolvedConfig, RpcCall, RpcSend, RunMode, Suite, SuiteCollector, SuiteHooks, Test, TestCollector, TestFactory, TestFunction } from '../types'
import { noop } from '../utils'
import { createChainable } from './chain'
import { collectTask, context, normalizeTest, runWithSuite } from './context'
import { getHooks, setFn, setHooks } from './map'

// apis
export const suite = createSuite()
export const test: TestCollector = createChainable(
['concurrent', 'skip', 'only', 'todo'],
function(name: string, fn?: TestFunction, timeout?: number) {
// @ts-expect-error untyped internal prop
getCurrentSuite().test.fn.call(this, name, fn, timeout)
},
)

// alias
export const describe = suite
export const it = test

// implementations
export const defaultSuite = suite('')

export function clearContext() {
Expand Down Expand Up @@ -34,18 +49,24 @@ function createSuiteCollector(name: string, factory: TestFactory = () => { }, mo

initSuite()

const test = createTestCollector((name: string, fn: () => Awaitable<void>, mode: RunMode, computeMode?: ComputeMode) => {
const test: Test = {
id: nanoid(),
type: 'test',
name,
mode,
computeMode: computeMode ?? (suiteComputeMode ?? 'serial'),
suite: undefined!,
}
setFn(test, fn)
tasks.push(test)
})
const test = createChainable(
['concurrent', 'skip', 'only', 'todo'],
function(name: string, fn?: TestFunction, timeout?: number) {
const mode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run'
const computeMode = this.concurrent ? 'concurrent' : undefined

const test: Test = {
id: nanoid(),
type: 'test',
name,
mode,
computeMode: computeMode ?? (suiteComputeMode ?? 'serial'),
suite: undefined!,
}
setFn(test, normalizeTest(fn || noop, timeout))
tasks.push(test)
},
)

const collector: SuiteCollector = {
type: 'collector',
Expand Down Expand Up @@ -107,107 +128,17 @@ function createSuiteCollector(name: string, factory: TestFactory = () => { }, mo
return collector
}

function createTestCollector(collectTest: (name: string, fn: () => Awaitable<void>, mode: RunMode, computeMode?: ComputeMode) => void): TestCollector {
function test(name: string, fn: TestFunction, timeout?: number) {
collectTest(name, normalizeTest(fn, timeout), 'run')
}
test.concurrent = concurrent
test.skip = skip
test.only = only
test.todo = todo
function concurrent(name: string, fn: TestFunction, timeout?: number) {
collectTest(name, normalizeTest(fn, timeout), 'run', 'concurrent')
}
concurrent.skip = (name: string, fn: TestFunction, timeout?: number) => collectTest(name, normalizeTest(fn, timeout), 'skip', 'concurrent')
concurrent.only = (name: string, fn: TestFunction, timeout?: number) => collectTest(name, normalizeTest(fn, timeout), 'only', 'concurrent')
concurrent.todo = todo
function skip(name: string, fn: TestFunction, timeout?: number) {
collectTest(name, normalizeTest(fn, timeout), 'skip')
}
skip.concurrent = concurrent.skip
function only(name: string, fn: TestFunction, timeout?: number) {
collectTest(name, normalizeTest(fn, timeout), 'only')
}
only.concurrent = concurrent.only
function todo(name: string) {
collectTest(name, () => { }, 'todo')
}
todo.concurrent = todo

return test
}

// apis
export const test = (function() {
function test(name: string, fn: TestFunction, timeout?: number) {
return getCurrentSuite().test(name, fn, timeout)
}
function concurrent(name: string, fn: TestFunction, timeout?: number) {
return getCurrentSuite().test.concurrent(name, fn, timeout)
}

concurrent.skip = (name: string, fn: TestFunction, timeout?: number) => getCurrentSuite().test.concurrent.skip(name, fn, timeout)
concurrent.only = (name: string, fn: TestFunction, timeout?: number) => getCurrentSuite().test.concurrent.only(name, fn, timeout)
concurrent.todo = (name: string) => getCurrentSuite().test.concurrent.todo(name)

function skip(name: string, fn: TestFunction, timeout?: number) {
return getCurrentSuite().test.skip(name, fn, timeout)
}
skip.concurrent = (name: string, fn: TestFunction, timeout?: number) => getCurrentSuite().test.skip.concurrent(name, fn, timeout)
function only(name: string, fn: TestFunction, timeout?: number) {
return getCurrentSuite().test.only(name, fn, timeout)
}
only.concurrent = (name: string, fn: TestFunction, timeout?: number) => getCurrentSuite().test.only.concurrent(name, fn, timeout)
function todo(name: string) {
return getCurrentSuite().test.todo(name)
}
todo.concurrent = (name: string) => getCurrentSuite().test.todo.concurrent(name)

test.concurrent = concurrent
test.skip = skip
test.only = only
test.todo = todo

return test
})()

function createSuite() {
function suite(suiteName: string, factory?: TestFactory) {
return createSuiteCollector(suiteName, factory, 'run')
}
function concurrent(suiteName: string, factory?: TestFactory) {
return createSuiteCollector(suiteName, factory, 'run', 'concurrent')
}
concurrent.skip = (suiteName: string, factory?: TestFactory) => createSuiteCollector(suiteName, factory, 'skip', 'concurrent')
concurrent.only = (suiteName: string, factory?: TestFactory) => createSuiteCollector(suiteName, factory, 'only', 'concurrent')
concurrent.todo = (suiteName: string) => createSuiteCollector(suiteName, undefined, 'todo')

function skip(suiteName: string, factory?: TestFactory) {
return createSuiteCollector(suiteName, factory, 'skip')
}
skip.concurrent = concurrent.skip

function only(suiteName: string, factory?: TestFactory) {
return createSuiteCollector(suiteName, factory, 'only')
}
only.concurrent = concurrent.only

function todo(suiteName: string) {
return createSuiteCollector(suiteName, undefined, 'todo')
}
todo.concurrent = concurrent.todo

suite.concurrent = concurrent
suite.skip = skip
suite.only = only
suite.todo = todo
return suite
return createChainable(
['concurrent', 'skip', 'only', 'todo'],
function(name: string, factory?: TestFactory) {
const mode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run'
const computeMode = this.concurrent ? 'concurrent' : undefined
return createSuiteCollector(name, factory, mode, computeMode)
},
)
}

// alias
export const describe = suite
export const it = test

declare global {
namespace NodeJS {
interface Process {
Expand Down
33 changes: 6 additions & 27 deletions packages/vitest/src/types/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ChainableFunction } from '../runtime/chain'
import type { Awaitable } from './general'

export type RunMode = 'run' | 'skip' | 'only' | 'todo'
Expand Down Expand Up @@ -43,33 +44,11 @@ export type Task = Test | Suite | File
export type DoneCallback = (error?: any) => void
export type TestFunction = (done: DoneCallback) => Awaitable<void>

type TestCollectorFn = (name: string, fn: TestFunction, timeout?: number) => void
interface ConcurrentCollector {
(name: string, fn: TestFunction, timeout?: number): void
only: TestCollectorFn
skip: TestCollectorFn
todo: (name: string) => void
}
interface OnlyCollector {
(name: string, fn: TestFunction, timeout?: number): void
concurrent: TestCollectorFn
}
interface SkipCollector {
(name: string, fn: TestFunction, timeout?: number): void
concurrent: TestCollectorFn
}
interface TodoCollector {
(name: string): void
concurrent: (name: string) => void
}

export interface TestCollector {
(name: string, fn: TestFunction, timeout?: number): void
concurrent: ConcurrentCollector
only: OnlyCollector
skip: SkipCollector
todo: TodoCollector
}
export type TestCollector = ChainableFunction<
'concurrent' | 'only' | 'skip'| 'todo',
[name: string, fn?: TestFunction, timeout?: number],
void
>

export type HookListener<T extends any[]> = (...args: T) => Awaitable<void>

Expand Down
21 changes: 21 additions & 0 deletions test/core/test/chainable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest'
import { createChainable } from '../../../packages/vitest/src/runtime/chain'

describe('chainable', () => {
it('creates', () => {
const chain = createChainable(['a', 'b'], function() {
return this
})

expect(chain()).toEqual({})
expect(chain.a()).toEqual({ a: true })

chain.a

expect(chain()).toEqual({})
expect(chain.b.a()).toEqual({ a: true, b: true })

expect(chain.b.a.b.a.b()).toEqual({ a: true, b: true })
expect(chain.a.a.a.a.a.a()).toEqual({ a: true })
})
})
7 changes: 4 additions & 3 deletions test/core/test/timers.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-use-before-define */

import { expect, test, vi } from 'vitest'
import { timeout } from '../src/timeout'

test('timers order: i -> t', () => {
const res: string[] = []
Expand Down Expand Up @@ -116,7 +117,7 @@ test('doesnt trigger twice', () => {

vi.useFakeTimers()

setTimeout(t, 1000)
setTimeout(t, timeout)

vi.runOnlyPendingTimers()
vi.runOnlyPendingTimers()
Expand All @@ -127,12 +128,12 @@ test('doesnt trigger twice', () => {

test('timeout cyclic', async() => {
const t = vi.fn(() => {
setTimeout(t, 1000)
setTimeout(t, timeout)
})

vi.useFakeTimers()

setTimeout(t, 1000)
setTimeout(t, timeout)

expect(() => {
vi.runAllTimers()
Expand Down

0 comments on commit c23479f

Please sign in to comment.