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: add CallEntry.tersify() #24

Merged
merged 2 commits into from
Jan 10, 2018
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ after_script: greenkeeper-lockfile-upload
after_success:
- npm run travis-deploy-once "npm run semantic-release"
- npm install --no-save coveralls && npm run coveralls
- npm install --no-save codecov && npm run codecov
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"build-es5": "tsc -p tsconfig.es5.json",
"build-es2015": "tsc -p tsconfig.es2015.json",
"clean": "rimraf dist dist-es5 && rimraf dist-es2015",
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json",
"coverage": "nyc npm test",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"dependency-check": "dependency-check . --unused --no-dev && dependency-check . --missing --no-dev",
Expand Down Expand Up @@ -72,6 +73,6 @@
"typescript": "^2.6.2"
},
"dependencies": {
"tersify": "^0.3.0"
"tersify": "^1.0.1"
}
}
40 changes: 36 additions & 4 deletions src/createCallRecordCreator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { CallRecord } from './interfaces'
import { tersify, tersible } from 'tersify'

import { CallEntry } from './interfaces'

export function createCallRecordCreator(args: any[]) {
let resolve
Expand All @@ -7,12 +9,42 @@ export function createCallRecordCreator(args: any[]) {
resolve = a
reject = r
})
const callEntry = Object.assign(p, {
inputs: args,
getCallRecord() {
return callEntry.then(asyncOutput => {
const { inputs, output, error } = callEntry
return tersible({
inputs,
output,
error,
asyncOutput
}, () => tersify({
inputs,
output,
error,
asyncOutput
}, { maxLength: Infinity }))
}, asyncError => {
const { inputs, output, error } = callEntry
return tersible({
inputs,
output,
error,
asyncError
}, () => tersify({
inputs,
output,
error,
asyncError
}, { maxLength: Infinity }))
})
}
}) as CallEntry

return {
resolve,
reject,
callRecord: Object.assign(p, {
arguments: args
}) as CallRecord
callEntry
}
}
20 changes: 15 additions & 5 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { Tersible } from 'tersify'

export interface CallRecord extends Promise<any> {
arguments: any[],
export interface CallRecord {
inputs: any[],
output: any,
error: any,
asyncOutput: any,
asyncError: any,
tersify(): string
}

export interface CallEntry extends Promise<any> {
inputs: any[],
/**
* Synchronous result.
*/
result: any,
output: any,
/**
* Synchronous error got thrown.
*/
error: any
error: any,
getCallRecord(): Promise<CallRecord>
}

export interface Spec<T extends Function> {
spiedFn: T,
calls: CallRecord[],
calls: CallEntry[],
save(): void,
}

Expand Down
50 changes: 37 additions & 13 deletions src/spy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test } from 'ava'

import { spy } from './index'
import { tersify } from 'tersify';

function increment(x: number) { return ++x }

Expand All @@ -11,8 +12,8 @@ test('record argument and result', t => {

t.is(calls.length, 1)
const cr = calls[0]
t.is(cr.arguments[0], 1)
t.is(cr.result, 2)
t.is(cr.inputs[0], 1)
t.is(cr.output, 2)
})

function throws() { throw new Error('thrown') }
Expand All @@ -26,30 +27,39 @@ test('capture error', t => {
t.is(calls[0].error, err)
})

test('tersify for sync call', async t => {
const { fn, calls } = spy(increment)

t.is(fn(1), 2)
const record = await calls[0].getCallRecord()
t.is(record.tersify(), `{ inputs: [1], output: 2, error: undefined, asyncOutput: undefined }`)
})

// this is not a valid test as the package is used for boundary testing.
// Boundary function are not expected to make changes to the arguments
test.skip('argument should be immutable', t => {
function mutate(x) { x.a++ }
const { fn, calls } = spy(mutate)
fn({ a: 1 })
const cr = calls[0]
t.is(cr.arguments[0].a, 1)
const entry = calls[0]
t.is(entry.inputs[0].a, 1)
})

function invoke(x, cb) { cb(x) }

test('callback are spied', t => {
test('callback are spied', async t => {
const { fn, calls } = spy(invoke)
fn(1, x => t.is(x, 1))
const cr = calls[0]
t.is(cr.arguments[0], 1)
return cr.then(r => t.deepEqual(r, [1]))
const entry = calls[0]
t.is(entry.inputs[0], 1)
return entry.then(x => t.deepEqual(x, [1]))
})

function callbackOnLiterial(options) {
options.success(++options.data)
}
test('spec on jquery style callback', t => {

test('spec on jquery style callback', async t => {
const { fn, calls } = spy(callbackOnLiterial)
fn({
data: 1,
Expand All @@ -58,18 +68,18 @@ test('spec on jquery style callback', t => {
}
})

const call = calls[0]
return call.then(response => t.is(response[0], 2))
const output = await calls[0]
t.is(output[0], 2)
})


const resolve = x => Promise.resolve(x)

test('then() will receive result from promise', t => {
test('then() will receive result from promise', async t => {
const { fn, calls } = spy(resolve)
// tslint:disable-next-line
fn(1)
return calls[0].then(x => t.is(x, 1))
t.is(await calls[0], 1)
})

test('result from promise can be retrieved from await on the call', async t => {
Expand All @@ -90,3 +100,17 @@ test('catch() will receive error thrown by promise', async t => {
})
})
})

test('tersify for sync call', async t => {
const { fn, calls } = spy(reject)


return fn(1).catch(actualError => {
console.log(tersify(actualError))
return calls[0].getCallRecord()
.then(record => {
t.is(record.tersify(), `{ inputs: [1], output: {}, error: undefined, asyncError: { message: '1' } }`)
})
})
})

18 changes: 12 additions & 6 deletions src/spy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createCallRecordCreator } from './createCallRecordCreator'
import { CallRecord } from './interfaces'
import { CallEntry } from './interfaces'

export interface Spy<T> {
calls: ReadonlyArray<CallRecord>,
calls: ReadonlyArray<CallEntry>,
/**
* the spied function.
*/
Expand All @@ -27,10 +27,10 @@ function spyOnCallback(fn) {
* Spy on function that uses callback.
*/
export function spy<T extends Function>(fn: T): Spy<T> {
const calls: CallRecord[] = []
const calls: CallEntry[] = []
const spied: T = function (...args) {
const creator = createCallRecordCreator(args)
calls.push(creator.callRecord)
calls.push(creator.callEntry)

const spiedCallbacks: any[] = []
const spiedArgs = args.map(arg => {
Expand Down Expand Up @@ -64,13 +64,19 @@ export function spy<T extends Function>(fn: T): Spy<T> {
else {
try {
const result = fn(...args)
creator.callEntry.output = result
if (result && typeof result.then === 'function')
result.then(creator.resolve, creator.reject)
creator.callRecord.result = result
else {
creator.resolve()
}
return result
}
catch (error) {
creator.callRecord.error = error
creator.callEntry.error = error
// just resolve, no need to reject,
// the error is on `error` property.
creator.resolve()
throw error
}
}
Expand Down