import * as _ from 'lodash'; import { SinonSpy } from 'sinon'; import { flushMicrotasks, rlQueueRequest } from './fakeAsync'; export interface IMockPromiseService { promise(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedPromise; rejectedPromise(...params: any[]): IMockedPromise; flushAll(service: any): void; } export interface IMockedPromise extends SinonSpy { (...args: any[]): Promise; reject(...params: any[]): void; rejected: boolean; flush(): void; share(share?: boolean): void; } interface IMockedPromiseInternal extends IMockedPromise { rejectParams: any[]; } class MockPromiseService implements IMockPromiseService { promise(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedPromise { if (_.isUndefined(share)) { share = false; } if (_.isFunction(result)) { return this.makeDynamicMockPromise(<{ (...args: any[]): TData }>result, share); } else { return this.makeMockPromise(result, share); } } rejectedPromise(...params: any[]): IMockedPromise { let mocked: IMockedPromiseInternal = this.makeMockPromise(null, false); mocked.rejected = true; mocked.rejectParams = params; return mocked; } flushAll(service: any): void { _.each(service, (promise: IMockedPromise): void => { if (promise && _.isFunction(promise.flush)) { promise.flush(); } }) } private makeMockPromise(result: TData, share: boolean): IMockedPromiseInternal { return this.makeDynamicMockPromise(() => result, share); } private makeDynamicMockPromise(result: { (...args: any[]): TData }, shareParam: boolean): IMockedPromiseInternal { let share: boolean = shareParam; interface IRequestType { resolve: Function; reject: Function; params: any[]; promise: Promise; rejected: boolean; rejectParams: any[]; pending: boolean; }; let requests: IRequestType[] = []; let mocked: IMockedPromiseInternal; // Return a function that will build a pending promise when called let promiseBuilder: any = ((...args: any[]): Promise => { if (share && _.some(requests)) { return _.first(requests).promise; } let newRequest: IRequestType = { resolve: null, reject: null, params: args, promise: null, rejected: mocked.rejected, rejectParams: mocked.rejectParams, pending: true, }; newRequest.promise = new Promise(function (resolve, reject) { newRequest.resolve = resolve; newRequest.reject = reject; }); requests.push(newRequest); rlQueueRequest(newRequest); return newRequest.promise; }); let spiedBuilder: any = sinon.spy(promiseBuilder); mocked = > spiedBuilder; // Mark promise to be rejected mocked.reject = (...params: any[]) => { mocked.rejected = true; mocked.rejectParams = params; }; // Mark promise to be shared in builder mocked.share = (shareParam?: boolean) => { if (_.isUndefined(shareParam)) { share = true; } share = shareParam; }; // If current request, resolve and clear mocked.flush = (): void => { _.each(requests, (request: IRequestType): void => { request.pending = false; if (request.rejected) { request.reject(...request.rejectParams); } else { request.resolve(result(...request.params)); } }); requests = []; flushMicrotasks(); }; return mocked; } } export const mockPromise: IMockPromiseService = new MockPromiseService();