Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tasks mode #3

Merged
merged 3 commits into from Dec 4, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 38 additions & 33 deletions src/run.ts
Expand Up @@ -5,26 +5,20 @@ import chai from 'chai'
import fg from 'fast-glob'
import { clearContext, defaultSuite } from './suite'
import { context } from './context'
import { File, Options, Suite, Task, TaskResult } from './types'
import { File, Options, Suite, Task } from './types'
import { afterEachHook, afterFileHook, afterAllHook, afterSuiteHook, beforeEachHook, beforeFileHook, beforeAllHook, beforeSuiteHook } from './hooks'
import { SnapshotPlugin } from './snapshot/index'

export async function runTasks(tasks: Task[]) {
const results: TaskResult[] = []

for (const task of tasks) {
await beforeEachHook.fire(task)
task.result = {}
try {
await task.fn()
}
catch (e) {
task.result.error = e
}
await afterEachHook.fire(task)
export async function runTask(task: Task) {
await beforeEachHook.fire(task)
task.result = {}
try {
await task.fn()
}

return results
catch (e) {
task.result.error = e
}
await afterEachHook.fire(task)
}

// TODO: REPORTER
Expand Down Expand Up @@ -76,28 +70,35 @@ export async function runFile(file: File, options: RunOptions = {}) {
indent += 1
}

if ((suite.mode === 'run' && !options?.onlyMode) || suite.mode === 'only') {
// TODO: If there is a task with 'only', skip all others
await runTasks(tasks)
if (suite.mode === 'todo') {
// TODO: In Jest, these suites are collected and printed together at the end of the report
log(`${' '.repeat(indent * 2)}${c.inverse(c.gray(' TODO '))}`)
}
else {
const runSuite = (suite.mode === 'run' && !options?.onlyMode) || suite.mode === 'only' || tasks.find(t => t.mode === 'only')

for (const t of tasks) {
if (t.result && t.result.error === undefined) {
log(`${' '.repeat(indent * 2)}${c.inverse(c.green(' PASS '))} ${c.green(t.name)}`)
if (runSuite && (((t.mode === 'run' && !options?.onlyMode) || t.mode === 'only') || suite.mode === 'only')) {
await runTask(t)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of running all tasks for a suite and then reporting, I'm reporting as soon as the task is done. We need to revert this change if there was a reason for running every task first

Copy link
Member

@antfu antfu Dec 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the new goal is to implement a reporter interface and decouple the printing and the runner. We indeed should print ASAP and even provide progress states.


if (t.result && t.result.error === undefined) {
log(`${' '.repeat(indent * 2)}${c.inverse(c.green(' PASS '))} ${c.green(t.name)}`)
}
else {
console.error(`${' '.repeat(indent * 2)}${c.inverse(c.red(' FAIL '))} ${c.red(t.name)}`)
console.error(' '.repeat((indent + 2) * 2) + c.red(String(t.result!.error)))
process.exitCode = 1
}
}
else if (t.mode === 'todo') {
log(`${' '.repeat(indent * 2)}${c.inverse(c.gray(' TODO '))} ${c.green(t.name)}`)
}
else {
console.error(`${' '.repeat(indent * 2)}${c.inverse(c.red(' FAIL '))} ${c.red(t.name)}`)
console.error(' '.repeat((indent + 2) * 2) + c.red(String(t.result!.error)))
process.exitCode = 1
// Only mode or direct skip
log(`${' '.repeat(indent * 2)}${c.inverse(c.gray(' SKIP '))} ${c.green(t.name)}`)
}
}
}
else if (suite.mode === 'todo') {
// TODO: In Jest, these suites are collected and printed together at the end of the report
log(`${' '.repeat(indent * 2)}${c.inverse(c.gray(' TODO '))}`)
}
else {
// suite.mode is 'skip' or 'run' in onlyMode
log(`${' '.repeat(indent * 2)}${c.inverse(c.gray(' SKIP '))}`)
}

if (suite.name)
indent -= 1
Expand Down Expand Up @@ -158,5 +159,9 @@ export async function run(options: Options = {}) {
}

function isOnlyMode(files: File[]) {
return !!files.find(file => file.suites.find(suite => suite.mode === 'only'))
return !!files.find(
file => file.collected.find(
([suite, tasks]) => suite.mode === 'only' || tasks.find(t => t.mode === 'only'),
),
)
}
39 changes: 31 additions & 8 deletions src/suite.ts
@@ -1,16 +1,25 @@
import { context } from './context'
import { Task, Suite, SuiteMode, TestFactory } from './types'
import { Task, Suite, RunMode, TestFactory, TestFunction } from './types'

export const defaultSuite = suite('')
export const test = (name: string, fn: () => Promise<void> | void) => (context.currentSuite || defaultSuite).test(name, fn)
export const test = (name: string, fn: TestFunction) => (context.currentSuite || defaultSuite).test(name, fn)
test.skip = function skip(name: string, fn: TestFunction) {
(context.currentSuite || defaultSuite).test.skip(name, fn)
}
test.only = function only(name: string, fn: TestFunction) {
(context.currentSuite || defaultSuite).test.only(name, fn)
}
test.todo = function todo(name: string) {
(context.currentSuite || defaultSuite).test.todo(name)
}

export function clearContext() {
context.suites.length = 0
defaultSuite.clear()
context.currentSuite = defaultSuite
}

function processSuite(mode: SuiteMode, suiteName: string, factory?: TestFactory) {
function createSuite(mode: RunMode, suiteName: string, factory?: TestFactory) {
const queue: Task[] = []
const factoryQueue: Task[] = []

Expand All @@ -22,15 +31,29 @@ function processSuite(mode: SuiteMode, suiteName: string, factory?: TestFactory)
clear,
}

function test(name: string, fn: () => Promise<void> | void) {
function createTask(mode: RunMode, name: string, fn: TestFunction) {
const task: Task = {
suite,
mode,
name,
fn,
}
queue.push(task)
}

function test(name: string, fn: TestFunction) {
createTask(mode, name, fn)
}
test.skip = function skip(name: string, fn: TestFunction) {
createTask('skip', name, fn)
}
test.only = function only(name: string, fn: TestFunction) {
createTask('only', name, fn)
}
test.todo = function todo(name: string) {
createTask('todo', name, () => { })
}

function clear() {
queue.length = 0
factoryQueue.length = 0
Expand All @@ -50,19 +73,19 @@ function processSuite(mode: SuiteMode, suiteName: string, factory?: TestFactory)
}

export function suite(suiteName: string, factory?: TestFactory) {
return processSuite('run', suiteName, factory)
return createSuite('run', suiteName, factory)
}

suite.skip = function skip(suiteName: string, factory?: TestFactory) {
return processSuite('skip', suiteName, factory)
return createSuite('skip', suiteName, factory)
}

suite.only = function skip(suiteName: string, factory?: TestFactory) {
return processSuite('only', suiteName, factory)
return createSuite('only', suiteName, factory)
}

suite.todo = function skip(suiteName: string) {
return processSuite('todo', suiteName)
return createSuite('todo', suiteName)
}

// alias
Expand Down
18 changes: 14 additions & 4 deletions src/types.ts
Expand Up @@ -14,25 +14,35 @@ export interface TaskResult {
error?: unknown
}

export type RunMode = 'run' | 'skip' | 'only' | 'todo'

export interface Task {
name: string
mode: RunMode
suite: Suite
fn: () => Promise<void> | void
file?: File
result?: TaskResult
}

export type SuiteMode = 'run' | 'skip' | 'only' | 'todo'
export type TestFunction = () => Promise<void> | void

export interface Test {
(name: string, fn: TestFunction): void
only: (name: string, fn: TestFunction) => void
skip: (name: string, fn: TestFunction) => void
todo: (name: string) => void
}

export interface Suite {
name: string
mode: SuiteMode
test: (name: string, fn: () => Promise<void> | void) => void
mode: RunMode
test: Test
collect: () => Promise<Task[]>
clear: () => void
}

export type TestFactory = (test: Suite['test']) => Promise<void> | void
export type TestFactory = (test: (name: string, fn: TestFunction) => void) => Promise<void> | void

export interface File {
filepath: string
Expand Down
10 changes: 9 additions & 1 deletion test/modes.test.ts
@@ -1,9 +1,17 @@
import { it, describe, assert } from '../src'

describe.skip('skipped suite', () => {
it('no fail as it is skipped', () => {
it('no fail as suite is skipped', () => {
assert.equal(Math.sqrt(4), 3)
})
})

describe.todo('unimplemented suite')

describe('task modes', () => {
it.skip('no fail as it task is skipped', () => {
assert.equal(Math.sqrt(4), 3)
})

it.todo('unimplemented task')
})