Skip to content
This repository has been archived by the owner on May 22, 2024. It is now read-only.

Commit

Permalink
feat: added catch
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Jan 25, 2018
1 parent 8a67f65 commit 587e6cf
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 381 deletions.
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
"devDependencies": {
"@dxcli/dev-semantic-release": "^0.1.0",
"@dxcli/dev-tslint": "^0.0.15",
"@types/chai": "^4.1.1",
"@types/chai": "^4.1.2",
"@types/chai-as-promised": "^7.1.0",
"@types/lodash": "^4.14.93",
"@types/mocha": "^2.2.46",
"@types/lodash": "^4.14.96",
"@types/mocha": "^2.2.47",
"@types/node": "^9.3.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
Expand All @@ -27,7 +27,6 @@
"nps-utils": "^1.5.0",
"nyc": "^11.4.1",
"ts-node": "^4.1.0",
"typedoc": "^0.9.0",
"typescript": "^2.6.2"
},
"engines": {
Expand Down
102 changes: 102 additions & 0 deletions src/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// tslint:disable callable-types
// tslint:disable no-unused

import * as mocha from 'mocha'

export interface Next<O> {
(output: O): Promise<void>
}

export interface Plugin<O, A1 = undefined, A2 = undefined, A3 = undefined, A4 = undefined> {
(next: Next<O>, input: any, arg1?: A1, arg2?: A2, arg3?: A3, arg4?: A4): Promise<any>
}

export interface Plugins {[k: string]: [object]}

export interface Base<T extends Plugins> {
(): Fancy<{}, T>
register<K extends string, O, A1, A2, A3, A4>(k: K, v: Plugin<O, A1, A2, A3, A4>): Base<T & {[P in K]: [O, A1, A2, A3, A4]}>
}

export interface Callback<T, U> {
(this: T, context: U): any
}
export interface MochaCallback<U> extends Callback<mocha.ITestCallbackContext, U> {}

export type Fancy<I extends object, T extends Plugins> = {
it: {
(expectation: string, callback?: MochaCallback<I>): mocha.ITest
only(expectation: string, callback?: MochaCallback<I>): mocha.ITest
skip(expectation: string, callback?: MochaCallback<I>): void
}
run<O extends object>(opts: {addToContext: true}, cb: (context: I) => Promise<O> | O): Fancy<I & O, T>
run(cb: (context: I) => any): Fancy<I, T>
} & {[P in keyof T]: (arg1?: T[P][1], arg2?: T[P][2], arg3?: T[P][3], arg4?: T[P][4]) => Fancy<I & T[P][0], T>}

const fancy = <I extends object, T extends Plugins>(plugins: any, chain: Chain = []): Fancy<I, T> => {
const __it = (fn: typeof it) => (expectation: string, callback: MochaCallback<I>): any => {
return fn(expectation, async function () {
let ctx = {plugins}
const run = (extra = {}): any => {
ctx = assignWithProps({}, ctx, extra)
const [next, args] = chain.shift() || [null, null]
if (next) return next(run, ctx, ...args as any[])
if (callback) callback.call(this, ctx)
}
await run()
})
}
const _it = __it(it) as Fancy<I, T>['it']
_it.only = __it(it.only as any)
_it.skip = __it(it.skip as any)
return {
...Object.entries(plugins)
.reduce((fns, [k, v]) => {
fns[k] = (...args: any[]) => {
return fancy(plugins, [...chain, [v, args]])
}
return fns
}, {} as any),
run(opts: any, cb: any) {
if (!cb) {
cb = opts
opts = {}
}
return fancy(plugins, [...chain, [async (input: any, next: any) => {
let output = await cb(input)
if (opts.addToContext) next(output)
else next()
}, []]])
},
it: _it,
}
}

function base<T extends Plugins>(plugins: any): Base<T> {
const f = (() => fancy(plugins)) as any
f.register = (k: string, v: any) => base({...plugins as any, [k]: v})
return f
}

export type Chain = [Plugin<any, any, any, any, any>, any[]][]

function assignWithProps(target: any, ...sources: any[]) {
sources.forEach(source => {
if (!source) return
let descriptors = Object.keys(source).reduce((descriptors: any, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key)
return descriptors
}, {})
// by default, Object.assign copies enumerable Symbols too
Object.getOwnPropertySymbols(source).forEach(sym => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym) as any
if (descriptor.enumerable) {
descriptors[sym] = descriptor
}
})
Object.defineProperties(target, descriptors)
})
return target
}

export default base<{}>({})
20 changes: 20 additions & 0 deletions src/catch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as _ from 'lodash'

import {Plugin} from '.'
import {expect} from './chai'

export default (async (next, __, arg) => {
try {
await next({})
} catch (err) {
if (_.isRegExp(arg)) {
expect(err.message).to.match(arg)
} else if (_.isString(arg)) {
expect(err.message).to.equal(arg)
} else if (arg) {
arg(err)
} else {
throw new Error('no arg provided to catch')
}
}
}) as Plugin<{}, RegExp | string | ((err: Error) => any)>
15 changes: 8 additions & 7 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const original = process.env
import {Plugin} from '.'

export default (env: {[k: string]: string}) => ({
before() {
process.env = {...env}
},
after() {
export default (async (next, _, env) => {
const original = process.env
process.env = {...env}
try {
await next({})
} finally {
process.env = original
}
})
}) as Plugin<{}, {[k: string]: string}>
23 changes: 17 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
export * from './chai'
import base, {Base, Fancy, Next, Plugin} from './base'
import _catch from './catch'
import env from './env'
import mock from './mock'
import {stderr, stdout} from './stdmock'

import test, {Test, TestBase} from './test'
export const fancy = base
.register('stdout', stdout)
.register('stderr', stderr)
.register('mock', mock)
.register('env', env)
.register('catch', _catch)

export {
Test,
TestBase,
test,
Base,
Fancy,
Plugin,
Next,
}
export default test
export default fancy
export * from './chai'
17 changes: 8 additions & 9 deletions src/mock.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import * as _ from 'lodash'

import {Next} from './base'

/**
* mocks an object's property
*/
export default (object: any, path: string, value: any) => {
export default async (next: Next<{}>, __: any, object: any, path: string, value: any) => {
let original = _.get(object, path)
return {
before() {
original = _.get(object, path)
_.set(object, path, value)
},
finally() {
_.set(object, path, original)
}
try {
_.set(object, path, value)
await next({})
} finally {
_.set(object, path, original)
}
}
45 changes: 14 additions & 31 deletions src/stdmock.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,21 @@
import * as mock from 'stdout-stderr'

export interface Stdout {
before(): {stdout: string}
after(): void
catch(): void
finally(): void
}
import {Plugin} from './base'

export interface Stderr {
before(): {stderr: string}
after(): void
catch(): void
finally(): void
export type Return<T extends 'stdout' | 'stderr'> = {
readonly [P in T]: string
}

const create = <T extends 'stdout' | 'stderr'>(std: T) => () => {
const _finally = () => mock[std].stop()
return {
before() {
mock[std].start()
if (std === 'stdout') {
return {
get stdout() { return mock.stdout.output }
}
}
return {
get stderr() { return mock.stderr.output }
}
},
after: _finally,
catch: _finally,
finally: _finally,
const create = <T extends 'stdout' | 'stderr'>(std: T) => (async next => {
mock[std].start()
try {
await next({
get [std]() { return mock[std].output }
} as Return<T>)
} finally {
mock[std].stop()
}
}
}) as Plugin<Return<T>>

export const stdout = create('stdout') as () => Stdout
export const stderr = create('stderr') as () => Stderr
export const stdout = create('stdout')
export const stderr = create('stderr')
Loading

0 comments on commit 587e6cf

Please sign in to comment.