import { each, isUndefined, isFunction, some, first } from 'lodash'; import { Observable, Subject } from 'rxjs'; import { SinonSpy } from 'sinon'; import { flushMicrotasks, rlQueueRequest } from './fakeAsync'; import { IMockedPromise, mockPromise } from './mockPromise'; export { IMockedPromise } from './mockPromise'; export interface IMockAsyncService { request(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedRequest; rejectedRequest(...params: any[]): IMockedRequest; promise(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedPromise; rejectedPromise(...params: any[]): IMockedPromise; flushAll(service: any): void; } export interface IMockedRequest extends SinonSpy { (...args: any[]): Observable; reject(error: any): void; rejected: boolean; flush(): void; share(share?: boolean): void; } interface IMockedObservableInternal extends IMockedRequest { rejectParam: any; } class MockAsyncService implements IMockAsyncService { promise(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedPromise { return mockPromise.promise(result, share); } rejectedPromise(...params: any[]): IMockedPromise { return mockPromise.rejectedPromise(...params); } request(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedRequest { if (isUndefined(share)) { share = false; } if (isFunction(result)) { return this.makeDynamicMockRequest(<{ (...args: any[]): TData }>result, share); } else { return this.makeMockRequest(result, share); } } rejectedRequest(error: any): IMockedRequest { let mocked: IMockedObservableInternal = this.makeMockRequest(null, false); mocked.rejected = true; mocked.rejectParam = error; return mocked; } flushAll(service: any): void { each(service, (request: IMockedRequest): void => { if (request && isFunction(request.flush)) { request.flush(); } }) } private makeMockRequest(result: TData, share: boolean): IMockedObservableInternal { return this.makeDynamicMockRequest(() => result, share); } private makeDynamicMockRequest(result: { (...args: any[]): TData }, shareParam: boolean): IMockedObservableInternal { let share: boolean = shareParam; interface IRequestType { resolve: Function; reject: Function; params: any[]; stream: Subject; observable: Observable; rejected: boolean; rejectParam: any; pending: boolean; }; let requests: IRequestType[] = []; let mocked: IMockedObservableInternal; // Return a function that will build a pending promise when called const requestBuilder: any = ((...args: any[]): Observable => { if (share && some(requests) && first(requests).pending) { return first(requests).observable; } const newRequest: IRequestType = { resolve: null, reject: null, params: args, stream: null, observable: null, rejected: mocked.rejected, rejectParam: mocked.rejectParam, pending: true, }; newRequest.stream = new Subject(); newRequest.observable = newRequest.stream.asObservable(); requests.push(newRequest); rlQueueRequest(newRequest); return newRequest.observable; }); const spiedBuilder: any = sinon.spy(requestBuilder); mocked = > spiedBuilder; // Mark promise to be rejected mocked.reject = (error: any) => { mocked.rejected = true; mocked.rejectParam = error; }; // 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 => { if (!request.pending) { return; } request.pending = false; if (request.rejected) { request.stream.error(request.rejectParam); } else { request.stream.next(result(...request.params)); request.stream.complete(); } }); requests = []; flushMicrotasks(); }; return mocked; } } export const mock: IMockAsyncService = new MockAsyncService();