Skip to content

Commit

Permalink
feat: add CallEntry.tersify() (#24)
Browse files Browse the repository at this point in the history
* feat: add CallEntry.tersify()

BREAKING CHANGE:
renamed CallRecord to CallEntry
CallRecord represents  a serializable record.
renamed CallEntry properties from arguemnts and result to inputs, output to that it works better with spread operator (because arguments is reserved).

* test: add tersify test for asyncError

chore: enable codecov
  • Loading branch information
unional authored Jan 10, 2018
1 parent 4bfe226 commit c0e5327
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 32 deletions.
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

0 comments on commit c0e5327

Please sign in to comment.