From 4067dd5a0d8e946ef37581d5bee381e789c2c94a Mon Sep 17 00:00:00 2001 From: Homa Wong Date: Sat, 13 Jan 2018 14:04:28 -0800 Subject: [PATCH 1/2] feat: remove spy() It does not really belongs to this package. It is moved to `komondor` --- src/CallEntry.ts | 14 --- src/CallRecord.ts | 47 -------- src/createCallRecordCreator.ts | 60 ---------- src/index.ts | 2 - src/spy.spec.ts | 197 --------------------------------- src/spy.ts | 90 --------------- 6 files changed, 410 deletions(-) delete mode 100644 src/CallEntry.ts delete mode 100644 src/CallRecord.ts delete mode 100644 src/createCallRecordCreator.ts delete mode 100644 src/spy.spec.ts delete mode 100644 src/spy.ts diff --git a/src/CallEntry.ts b/src/CallEntry.ts deleted file mode 100644 index 1630149..0000000 --- a/src/CallEntry.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CallRecord } from './CallRecord' - -export interface CallEntry extends Promise { - inputs: any[], - /** - * Synchronous result. - */ - output: any, - /** - * Synchronous error got thrown. - */ - error: any, - getCallRecord(): Promise -} diff --git a/src/CallRecord.ts b/src/CallRecord.ts deleted file mode 100644 index db37704..0000000 --- a/src/CallRecord.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - tersible, - tersify, - // @ts-ignore - Tersify, - // @ts-ignore - TersifyOptions -} from 'tersify' - -export interface CallRecordData { - inputs: any[], - output: any, - error: any, - /** - * Name of the invoked callback on object literal input. - * This property only exists for object literal input. - */ - invokedCallback?: string, - asyncOutput?: any, - asyncError?: any -} - -export interface CallRecord { - inputs: any[], - output: any, - error: any, - invokedCallback: string, - asyncOutput: any, - asyncError: any, - tersify(): string -} - -export const CallRecord = { - /** - * Creates a call record object. - */ - create({ inputs, output, error, invokedCallback, asyncOutput, asyncError }: CallRecordData) { - return tersible({ inputs, output, error, invokedCallback, asyncOutput, asyncError }, - () => { - const obj = { inputs, output, error } as CallRecord - if (invokedCallback !== undefined) obj.invokedCallback = invokedCallback - if (asyncError !== undefined) obj.asyncError = asyncError - if (asyncOutput !== undefined) obj.asyncOutput = asyncOutput - return tersify(obj, { maxLength: Infinity }) - }) - } -} diff --git a/src/createCallRecordCreator.ts b/src/createCallRecordCreator.ts deleted file mode 100644 index 4ac09ca..0000000 --- a/src/createCallRecordCreator.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { CallRecord } from './CallRecord' -import { CallEntry } from './CallEntry' - -export function createCallRecordCreator(args: any[]) { - let resolve - let reject - let invokedCallback - const p = new Promise((a, r) => { - resolve = (resolved) => { - if (resolved) { - invokedCallback = resolved.key - a(resolved.results) - } - else { - a() - } - } - reject = r - }) - const callEntry = Object.assign(p, { - inputs: args, - getCallRecord() { - const inputs = trimCallbacks(callEntry.inputs) - const { output, error } = callEntry - return callEntry.then(asyncOutput => { - const record: any = { inputs, output, error, asyncOutput } - if (invokedCallback) record.invokedCallback = invokedCallback - return CallRecord.create(record) - }, asyncError => { - const record: any = { inputs, output, error, asyncError } - if (invokedCallback) record.invokedCallback = invokedCallback - return CallRecord.create(record) - }) - } - }) as CallEntry - - return { - resolve, - reject, - callEntry - } -} - -const callbackLiteral = { tersify() { return 'callback' } } - -function trimCallbacks(inputs: any[]) { - return inputs.map(arg => { - if (typeof arg === 'function') { - return callbackLiteral - } - if (typeof arg === 'object') { - Object.keys(arg).forEach(key => { - if (typeof arg[key] === 'function') { - arg[key] = callbackLiteral - } - }) - } - return arg - }) -} diff --git a/src/index.ts b/src/index.ts index 52754f5..952ac18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,5 @@ -export * from './CallRecord' export * from './createSatisfier' export * from './interfaces' export * from './isInRange' export * from './isInInterval' export * from './isTypeOf' -export * from './spy' diff --git a/src/spy.spec.ts b/src/spy.spec.ts deleted file mode 100644 index bc840e1..0000000 --- a/src/spy.spec.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { test } from 'ava' - -import { spy } from './index' - -function increment(x: number) { return ++x } - -test('record argument and result', t => { - const { fn, calls } = spy(increment) - - t.is(fn(1), 2) - - t.is(calls.length, 1) - const cr = calls[0] - t.is(cr.inputs[0], 1) - t.is(cr.output, 2) -}) - -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 }`) -}) - -function throws() { throw new Error('thrown') } - -test('capture error', t => { - const { fn, calls } = spy(throws) - - const err = t.throws(fn) - - t.is(calls.length, 1) - t.is(calls[0].error, err) -}) - -test('tersify for throws call', async t => { - const { fn, calls } = spy(throws) - - t.throws(fn) - - const record = await calls[0].getCallRecord() - t.is(record.tersify(), `{ inputs: [], error: { message: 'thrown' } }`) -}) - -// 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 entry = calls[0] - t.is(entry.inputs[0].a, 1) -}) - -function callback(x, cb) { cb(x) } - -test('callback are spied', async t => { - const { fn, calls } = spy(callback) - fn(1, x => t.is(x, 1)) - const entry = calls[0] - t.is(entry.inputs[0], 1) - return entry.then(x => t.deepEqual(x, [1])) -}) - -test('tersify for callback', async t => { - const { fn, calls } = spy(callback) - - fn(1, x => t.is(x, 1)) - - const record = await calls[0].getCallRecord() - t.is(record.tersify(), `{ inputs: [1, callback], asyncOutput: [1] }`) -}) - -function callbackLiteral(options) { - options.success(options.data + 1) -} - -test('spec on jquery style callback', async t => { - const { fn, calls } = spy(callbackLiteral) - fn({ - data: 1, - fail: () => { - t.fail('fail callback should not be called') - }, - success: (result) => { - t.is(result, 2) - } - }) - - const entry = calls[0] - const output = await entry - t.is(output[0], 2) - const record = await entry.getCallRecord() - t.is(record.invokedCallback, 'success') -}) - -test('tersify for callback', async t => { - const { fn, calls } = spy(callbackLiteral) - fn({ - data: 1, - success: (result) => { - t.is(result, 2) - } - }) - const record = await calls[0].getCallRecord() - t.is(record.tersify(), `{ inputs: [{ data: 1, success: callback }], invokedCallback: 'success', asyncOutput: [2] }`) -}) - -function callbackLiteralFail(options) { - options.fail(options.data + 1) -} - -test('spec on jquery style callback failing', async t => { - const { fn, calls } = spy(callbackLiteralFail) - fn({ - data: 1, - success: () => { - t.fail('success callback should not be called') - }, - fail: (result) => { - t.is(result, 2) - } - }) - - const entry = calls[0] - const output = await entry - t.is(output[0], 2) - const record = await entry.getCallRecord() - t.is(record.invokedCallback, 'fail') -}) - -test('tersify for failing callback for object literal input', async t => { - const { fn, calls } = spy(callbackLiteralFail) - fn({ - data: 1, - success: () => { - t.fail('success callback should not be called') - }, - fail: (result) => { - t.is(result, 2) - } - }) - const record = await calls[0].getCallRecord() - t.is(record.tersify(), `{ inputs: [{ data: 1, success: callback, fail: callback }], invokedCallback: 'fail', asyncOutput: [2] }`) -}) - -const resolve = x => Promise.resolve(x) - -test('then() will receive result from promise', async t => { - const { fn, calls } = spy(resolve) - // tslint:disable-next-line - fn(1) - return calls[0].then(actual => { - t.is(actual, 1) - }) -}) - -test('result from promise can be retrieved from await on the call', async t => { - const { fn, calls } = spy(resolve) - // tslint:disable-next-line - fn(1) - t.is(await calls[0], 1) -}) - -const reject = x => Promise.reject(new Error(x)) - -test('catch() will receive error thrown by promise', async t => { - const { fn, calls } = spy(reject) - // tslint:disable-next-line - return fn(1).catch(actualError => { - return calls[0].catch(err => { - t.is(err, actualError) - }) - }) -}) - -test('tersify for resolve call', async t => { - const { fn, calls } = spy(resolve) - // tslint:disable-next-line - fn(1) - return calls[0].getCallRecord() - .then(record => { - t.is(record.tersify(), `{ inputs: [1], output: {}, asyncOutput: 1 }`) - }) -}) - - -test('tersify for reject call', async t => { - const { fn, calls } = spy(reject) - return fn(1).catch(() => { - return calls[0].getCallRecord() - .then(record => { - t.is(record.tersify(), `{ inputs: [1], output: {}, asyncError: { message: '1' } }`) - }) - }) -}) diff --git a/src/spy.ts b/src/spy.ts deleted file mode 100644 index 06a1f23..0000000 --- a/src/spy.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { CallEntry } from './CallEntry' -import { createCallRecordCreator } from './createCallRecordCreator' - -export interface Spy { - calls: ReadonlyArray, - /** - * the spied function. - */ - fn: T -} - -function spyOnCallback(fn, key) { - let callback - return Object.assign( - (...args) => { - // callback is always assigned as it is used internally. - callback(...args) - fn(...args) - }, { - key, - called(cb) { - callback = cb - } - }) -} - -/** - * Spy on function that uses callback. - */ -export function spy(fn: T): Spy { - const calls: CallEntry[] = [] - const spied: T = function (...args) { - const creator = createCallRecordCreator(args) - calls.push(creator.callEntry) - - const spiedCallbacks: any[] = [] - const spiedArgs = args.map(arg => { - if (typeof arg === 'function') { - const spied = spyOnCallback(arg, undefined) - spiedCallbacks.push(spied) - return spied - } - if (typeof arg === 'object') { - Object.keys(arg).forEach(key => { - if (typeof arg[key] === 'function') { - const spied = spyOnCallback(arg[key], key) - spiedCallbacks.push(spied) - arg[key] = spied - } - }) - } - return arg - }) - if (spiedCallbacks.length > 0) { - new Promise(a => { - spiedCallbacks.forEach(s => { - s.called((...results) => { - a({ results, key: s.key }) - }) - }) - }).then(creator.resolve, creator.reject) - - return fn(...spiedArgs) - } - else { - try { - const result = fn(...args) - creator.callEntry.output = result - if (result && typeof result.then === 'function') - result.then(results => ({ results })).then(creator.resolve, creator.reject) - else { - creator.resolve() - } - return result - } - catch (error) { - creator.callEntry.error = error - // just resolve, no need to reject, - // the error is on `error` property. - creator.resolve() - throw error - } - } - } as any - - return { - calls, - fn: spied - } -} From faf2796502517f8fd72b7ea36b365b338aaf4a26 Mon Sep 17 00:00:00 2001 From: Homa Wong Date: Sat, 13 Jan 2018 14:09:19 -0800 Subject: [PATCH 2/2] fix: remove spec --- src/interfaces.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 7a61ae0..dd20c0c 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,13 +1,5 @@ import { Tersible } from 'tersify' -import { CallEntry } from './CallEntry' - -export interface Spec { - spiedFn: T, - calls: CallEntry[], - save(): void, -} - export type Predicate = (value: any) => boolean export type TersiblePredicate = Tersible