From c243909291e28bf6c8a231a0e9673bba7fd1559a Mon Sep 17 00:00:00 2001 From: unional Date: Tue, 9 Jan 2018 17:52:08 -0800 Subject: [PATCH 1/2] 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). --- src/createCallRecordCreator.ts | 40 ++++++++++++++++++++++++++++++---- src/interfaces.ts | 20 ++++++++++++----- src/spy.spec.ts | 35 ++++++++++++++++++----------- src/spy.ts | 18 ++++++++++----- 4 files changed, 85 insertions(+), 28 deletions(-) diff --git a/src/createCallRecordCreator.ts b/src/createCallRecordCreator.ts index 2c8741e..066d75d 100644 --- a/src/createCallRecordCreator.ts +++ b/src/createCallRecordCreator.ts @@ -1,4 +1,6 @@ -import { CallRecord } from './interfaces' +import { tersify, tersible } from 'tersify' + +import { CallEntry } from './interfaces' export function createCallRecordCreator(args: any[]) { let resolve @@ -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 } } diff --git a/src/interfaces.ts b/src/interfaces.ts index 61c2c9c..37145bc 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,20 +1,30 @@ import { Tersible } from 'tersify' -export interface CallRecord extends Promise { - arguments: any[], +export interface CallRecord { + inputs: any[], + output: any, + error: any, + asyncOutput: any, + asyncError: any, + tersify(): string +} + +export interface CallEntry extends Promise { + inputs: any[], /** * Synchronous result. */ - result: any, + output: any, /** * Synchronous error got thrown. */ - error: any + error: any, + getCallRecord(): Promise } export interface Spec { spiedFn: T, - calls: CallRecord[], + calls: CallEntry[], save(): void, } diff --git a/src/spy.spec.ts b/src/spy.spec.ts index 344b0a9..22b0ece 100644 --- a/src/spy.spec.ts +++ b/src/spy.spec.ts @@ -11,8 +11,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') } @@ -26,30 +26,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, @@ -58,18 +67,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 => { diff --git a/src/spy.ts b/src/spy.ts index eaeed27..27a39a5 100644 --- a/src/spy.ts +++ b/src/spy.ts @@ -1,8 +1,8 @@ import { createCallRecordCreator } from './createCallRecordCreator' -import { CallRecord } from './interfaces' +import { CallEntry } from './interfaces' export interface Spy { - calls: ReadonlyArray, + calls: ReadonlyArray, /** * the spied function. */ @@ -27,10 +27,10 @@ function spyOnCallback(fn) { * Spy on function that uses callback. */ export function spy(fn: T): Spy { - 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 => { @@ -64,13 +64,19 @@ export function spy(fn: T): Spy { 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 } } From 41607f58561eb45c923c2854b12e8b600eca67a7 Mon Sep 17 00:00:00 2001 From: unional Date: Tue, 9 Jan 2018 19:44:39 -0800 Subject: [PATCH 2/2] test: add tersify test for asyncError chore: enable codecov --- .travis.yml | 1 + package-lock.json | 6 +++--- package.json | 3 ++- src/spy.spec.ts | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f2c6cd..a65e5e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/package-lock.json b/package-lock.json index 8745d6e..9309e45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8558,9 +8558,9 @@ } }, "tersify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tersify/-/tersify-0.3.0.tgz", - "integrity": "sha512-gdA9qXE+OOa3IumLsRXPFlRYL2e8NY0goZVPThDddoe4S5QR6dYWIR/mqooLD6LS45sWTgrpGC5367EFo9MurA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tersify/-/tersify-1.0.1.tgz", + "integrity": "sha512-9GnuwmWXuZM0BY7gS1cz1w8xdKcSUlHQjidTujHmTF2s3x6aV3QBKRSdlk/3T9HB42c5/PfIlqQej3M9ofYqtA==", "requires": { "stringify-object": "3.2.1" } diff --git a/package.json b/package.json index 4609bcc..86a8814 100644 --- a/package.json +++ b/package.json @@ -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", @@ -72,6 +73,6 @@ "typescript": "^2.6.2" }, "dependencies": { - "tersify": "^0.3.0" + "tersify": "^1.0.1" } } diff --git a/src/spy.spec.ts b/src/spy.spec.ts index 22b0ece..25d0f5d 100644 --- a/src/spy.spec.ts +++ b/src/spy.spec.ts @@ -1,6 +1,7 @@ import { test } from 'ava' import { spy } from './index' +import { tersify } from 'tersify'; function increment(x: number) { return ++x } @@ -99,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' } }`) + }) + }) +}) +