UNPKG

5.56 kBJavaScriptView Raw
1const { setDebugPhase, setDebugNamespace, debug } = require('./debug');
2const { normalizeUrl } = require('./request-utils');
3const FetchMock = {};
4const isName = (nameOrMatcher) =>
5 typeof nameOrMatcher === 'string' && /^[\da-zA-Z\-]+$/.test(nameOrMatcher);
6
7const filterCallsWithMatcher = function (matcher, options = {}, calls) {
8 matcher = this.generateMatcher(
9 this.sanitizeRoute(Object.assign({ matcher }, options))
10 );
11 return calls.filter(([url, options]) => matcher(normalizeUrl(url), options));
12};
13
14const formatDebug = (func) => {
15 return function (...args) {
16 setDebugPhase('inspect');
17 const result = func.call(this, ...args);
18 setDebugPhase();
19 return result;
20 };
21};
22
23FetchMock.filterCalls = function (nameOrMatcher, options) {
24 debug('Filtering fetch calls');
25 let calls = this._calls;
26 let matcher = '*';
27
28 if ([true, 'matched'].includes(nameOrMatcher)) {
29 debug(`Filter provided is ${nameOrMatcher}. Returning matched calls only`);
30 calls = calls.filter(({ isUnmatched }) => !isUnmatched);
31 } else if ([false, 'unmatched'].includes(nameOrMatcher)) {
32 debug(
33 `Filter provided is ${nameOrMatcher}. Returning unmatched calls only`
34 );
35 calls = calls.filter(({ isUnmatched }) => isUnmatched);
36 } else if (typeof nameOrMatcher === 'undefined') {
37 debug(`Filter provided is undefined. Returning all calls`);
38 calls = calls;
39 } else if (isName(nameOrMatcher)) {
40 debug(
41 `Filter provided, looks like the name of a named route. Returning only calls handled by that route`
42 );
43 calls = calls.filter(({ identifier }) => identifier === nameOrMatcher);
44 } else {
45 matcher = normalizeUrl(nameOrMatcher);
46 if (this.routes.some(({ identifier }) => identifier === matcher)) {
47 debug(
48 `Filter provided, ${nameOrMatcher}, identifies a route. Returning only calls handled by that route`
49 );
50 calls = calls.filter((call) => call.identifier === matcher);
51 }
52 }
53
54 if ((options || matcher !== '*') && calls.length) {
55 if (typeof options === 'string') {
56 options = { method: options };
57 }
58 debug(
59 'Compiling filter and options to route in order to filter all calls',
60 nameOrMatcher
61 );
62 calls = filterCallsWithMatcher.call(this, matcher, options, calls);
63 }
64 debug(`Retrieved ${calls.length} calls`);
65 return calls;
66};
67
68FetchMock.calls = formatDebug(function (nameOrMatcher, options) {
69 debug('retrieving matching calls');
70 return this.filterCalls(nameOrMatcher, options);
71});
72
73FetchMock.lastCall = formatDebug(function (nameOrMatcher, options) {
74 debug('retrieving last matching call');
75 return [...this.filterCalls(nameOrMatcher, options)].pop();
76});
77
78FetchMock.lastUrl = formatDebug(function (nameOrMatcher, options) {
79 debug('retrieving url of last matching call');
80 return (this.lastCall(nameOrMatcher, options) || [])[0];
81});
82
83FetchMock.lastOptions = formatDebug(function (nameOrMatcher, options) {
84 debug('retrieving options of last matching call');
85 return (this.lastCall(nameOrMatcher, options) || [])[1];
86});
87
88FetchMock.called = formatDebug(function (nameOrMatcher, options) {
89 debug('checking if matching call was made');
90 return Boolean(this.filterCalls(nameOrMatcher, options).length);
91});
92
93FetchMock.flush = formatDebug(async function (waitForResponseMethods) {
94 setDebugNamespace('flush');
95 debug(
96 `flushing all fetch calls. ${
97 waitForResponseMethods ? '' : 'Not '
98 }waiting for response bodies to complete download`
99 );
100
101 const queuedPromises = this._holdingPromises;
102 this._holdingPromises = [];
103 debug(`${queuedPromises.length} fetch calls to be awaited`);
104
105 await Promise.all(queuedPromises);
106 debug(`All fetch calls have completed`);
107 if (waitForResponseMethods && this._holdingPromises.length) {
108 debug(`Awaiting all fetch bodies to download`);
109 await this.flush(waitForResponseMethods);
110 debug(`All fetch bodies have completed downloading`);
111 }
112 setDebugNamespace();
113});
114
115FetchMock.done = formatDebug(function (nameOrMatcher) {
116 setDebugPhase('inspect');
117 setDebugNamespace('done');
118 debug('Checking to see if expected calls have been made');
119 let routesToCheck;
120
121 if (nameOrMatcher && typeof nameOrMatcher !== 'boolean') {
122 debug(
123 'Checking to see if expected calls have been made for single route:',
124 nameOrMatcher
125 );
126 routesToCheck = [{ identifier: nameOrMatcher }];
127 } else {
128 debug('Checking to see if expected calls have been made for all routes');
129 routesToCheck = this.routes;
130 }
131
132 // Can't use array.every because would exit after first failure, which would
133 // break the logging
134 const result = routesToCheck
135 .map(({ identifier }) => {
136 if (!this.called(identifier)) {
137 debug('No calls made for route:', identifier);
138 console.warn(`Warning: ${identifier} not called`); // eslint-disable-line
139 return false;
140 }
141
142 const expectedTimes = (
143 this.routes.find((r) => r.identifier === identifier) || {}
144 ).repeat;
145
146 if (!expectedTimes) {
147 debug(
148 'Route has been called at least once, and no expectation of more set:',
149 identifier
150 );
151 return true;
152 }
153 const actualTimes = this.filterCalls(identifier).length;
154
155 debug(`Route called ${actualTimes} times:`, identifier);
156 if (expectedTimes > actualTimes) {
157 debug(
158 `Route called ${actualTimes} times, but expected ${expectedTimes}:`,
159 identifier
160 );
161 console.warn(
162 `Warning: ${identifier} only called ${actualTimes} times, but ${expectedTimes} expected`
163 ); // eslint-disable-line
164 return false;
165 } else {
166 return true;
167 }
168 })
169 .every((isDone) => isDone);
170
171 setDebugNamespace();
172 setDebugPhase();
173 return result;
174});
175
176module.exports = FetchMock;