1 | import { each, isUndefined, isFunction, some, first } from 'lodash';
|
2 | import { Observable, Subject } from 'rxjs';
|
3 | import { SinonSpy } from 'sinon';
|
4 | import { flushMicrotasks, rlQueueRequest } from './fakeAsync';
|
5 | import { IMockedPromise, mockPromise } from './mockPromise';
|
6 |
|
7 | export { IMockedPromise } from './mockPromise';
|
8 |
|
9 | export interface IMockAsyncService {
|
10 | request<TData>(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedRequest<TData>;
|
11 | rejectedRequest<TData>(...params: any[]): IMockedRequest<TData>;
|
12 | promise<TData>(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedPromise<TData>;
|
13 | rejectedPromise<TData>(...params: any[]): IMockedPromise<TData>;
|
14 | flushAll(service: any): void;
|
15 | }
|
16 |
|
17 | export interface IMockedRequest<TData> extends SinonSpy {
|
18 | (...args: any[]): Observable<TData>;
|
19 | reject(error: any): void;
|
20 | rejected: boolean;
|
21 | flush(): void;
|
22 | share(share?: boolean): void;
|
23 | }
|
24 |
|
25 | interface IMockedObservableInternal<TData> extends IMockedRequest<TData> {
|
26 | rejectParam: any;
|
27 | }
|
28 |
|
29 | class MockAsyncService implements IMockAsyncService {
|
30 | promise<TData>(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedPromise<TData> {
|
31 | return mockPromise.promise(result, share);
|
32 | }
|
33 |
|
34 | rejectedPromise<TData>(...params: any[]): IMockedPromise<TData> {
|
35 | return mockPromise.rejectedPromise(...params);
|
36 | }
|
37 |
|
38 | request<TData>(result?: TData | { (...args: any[]): TData }, share?: boolean): IMockedRequest<TData> {
|
39 | if (isUndefined(share)) {
|
40 | share = false;
|
41 | }
|
42 |
|
43 | if (isFunction(result)) {
|
44 | return this.makeDynamicMockRequest(<{ (...args: any[]): TData }>result, share);
|
45 | } else {
|
46 | return this.makeMockRequest(<TData>result, share);
|
47 | }
|
48 | }
|
49 |
|
50 | rejectedRequest<TData>(error: any): IMockedRequest<TData> {
|
51 | let mocked: IMockedObservableInternal<TData> = this.makeMockRequest(null, false);
|
52 | mocked.rejected = true;
|
53 | mocked.rejectParam = error;
|
54 | return mocked;
|
55 | }
|
56 |
|
57 | flushAll(service: any): void {
|
58 | each(service, (request: IMockedRequest<any>): void => {
|
59 | if (request && isFunction(request.flush)) {
|
60 | request.flush();
|
61 | }
|
62 | })
|
63 | }
|
64 |
|
65 | private makeMockRequest<TData>(result: TData, share: boolean): IMockedObservableInternal<TData> {
|
66 | return this.makeDynamicMockRequest(() => result, share);
|
67 | }
|
68 |
|
69 | private makeDynamicMockRequest<TData>(result: { (...args: any[]): TData }, shareParam: boolean): IMockedObservableInternal<TData> {
|
70 | let share: boolean = shareParam;
|
71 | interface IRequestType {
|
72 | resolve: Function;
|
73 | reject: Function;
|
74 | params: any[];
|
75 | stream: Subject<TData>;
|
76 | observable: Observable<TData>;
|
77 | rejected: boolean;
|
78 | rejectParam: any;
|
79 | pending: boolean;
|
80 | };
|
81 |
|
82 | let requests: IRequestType[] = [];
|
83 | let mocked: IMockedObservableInternal<TData>;
|
84 |
|
85 |
|
86 | const requestBuilder: any = ((...args: any[]): Observable<TData> => {
|
87 | if (share && some(requests) && first(requests).pending) {
|
88 | return first(requests).observable;
|
89 | }
|
90 |
|
91 | const newRequest: IRequestType = {
|
92 | resolve: null,
|
93 | reject: null,
|
94 | params: args,
|
95 | stream: null,
|
96 | observable: null,
|
97 | rejected: mocked.rejected,
|
98 | rejectParam: mocked.rejectParam,
|
99 | pending: true,
|
100 | };
|
101 |
|
102 | newRequest.stream = new Subject<TData>();
|
103 | newRequest.observable = newRequest.stream.asObservable();
|
104 |
|
105 | requests.push(newRequest);
|
106 |
|
107 | rlQueueRequest(newRequest);
|
108 |
|
109 | return newRequest.observable;
|
110 | });
|
111 |
|
112 | const spiedBuilder: any = sinon.spy(requestBuilder);
|
113 | mocked = <IMockedObservableInternal<TData>> spiedBuilder;
|
114 |
|
115 |
|
116 | mocked.reject = (error: any) => {
|
117 | mocked.rejected = true;
|
118 | mocked.rejectParam = error;
|
119 | };
|
120 |
|
121 |
|
122 | mocked.share = (shareParam?: boolean) => {
|
123 | if (isUndefined(shareParam)) {
|
124 | share = true;
|
125 | }
|
126 |
|
127 | share = shareParam;
|
128 | };
|
129 |
|
130 |
|
131 | mocked.flush = (): void => {
|
132 | each(requests, (request: IRequestType): void => {
|
133 | if (!request.pending) {
|
134 | return;
|
135 | }
|
136 |
|
137 | request.pending = false;
|
138 | if (request.rejected) {
|
139 | request.stream.error(request.rejectParam);
|
140 | } else {
|
141 | request.stream.next(result(...request.params));
|
142 | request.stream.complete();
|
143 | }
|
144 | });
|
145 | requests = [];
|
146 | flushMicrotasks();
|
147 | };
|
148 |
|
149 | return mocked;
|
150 | }
|
151 | }
|
152 |
|
153 | export const mock: IMockAsyncService = new MockAsyncService();
|