UNPKG

16.9 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @format
8 *
9 */
10// flowlint ambiguous-object-type:error
11'use strict';
12/* global jest */
13
14var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
15
16var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
17
18function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
19
20function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
21
22var areEqual = require("fbjs/lib/areEqual");
23
24var invariant = require("fbjs/lib/invariant");
25
26var _require = require('relay-runtime'),
27 RecordSource = _require.RecordSource,
28 Store = _require.Store,
29 QueryResponseCache = _require.QueryResponseCache,
30 Observable = _require.Observable,
31 Environment = _require.Environment,
32 Network = _require.Network,
33 createOperationDescriptor = _require.createOperationDescriptor,
34 getRequest = _require.getRequest;
35
36var MAX_SIZE = 10;
37var MAX_TTL = 5 * 60 * 1000; // 5 min
38
39function mockInstanceMethod(object, key) {
40 object[key] = jest.fn(object[key].bind(object));
41}
42
43function mockDisposableMethod(object, key) {
44 var fn = object[key].bind(object);
45 object[key] = jest.fn(function () {
46 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
47 args[_key] = arguments[_key];
48 }
49
50 var disposable = fn.apply(void 0, args);
51 var dispose = jest.fn(function () {
52 return disposable.dispose();
53 });
54 object[key].mock.dispose = dispose;
55 return {
56 dispose: dispose
57 };
58 });
59 var mockClear = object[key].mockClear.bind(object[key]);
60
61 object[key].mockClear = function () {
62 mockClear();
63 object[key].mock.dispose = null;
64 };
65}
66
67function mockObservableMethod(object, key) {
68 var fn = object[key].bind(object);
69 object[key] = jest.fn(function () {
70 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
71 args[_key2] = arguments[_key2];
72 }
73
74 return fn.apply(void 0, args)["do"]({
75 start: function start(subscription) {
76 object[key].mock.subscriptions.push(subscription);
77 }
78 });
79 });
80 object[key].mock.subscriptions = [];
81 var mockClear = object[key].mockClear.bind(object[key]);
82
83 object[key].mockClear = function () {
84 mockClear();
85 object[key].mock.subscriptions = [];
86 };
87}
88
89/**
90 * Creates an instance of the `Environment` interface defined in
91 * RelayStoreTypes with a mocked network layer.
92 *
93 * Usage:
94 *
95 * ```
96 * const environment = RelayModernMockEnvironment.createMockEnvironment();
97 * ```
98 *
99 * Mock API:
100 *
101 * Helpers are available as `environment.mock.<helper>`:
102 *
103 * - `isLoading(query, variables): boolean`: Determine whether the given query
104 * is currently being loaded (not yet rejected/resolved).
105 * - `reject(query, error: Error): void`: Reject a query that has been fetched
106 * by the environment.
107 * - `resolve(query, payload: PayloadData): void`: Resolve a query that has been
108 * fetched by the environment.
109 * - `nextValue(...) - will add payload to the processing, but won't complete
110 * the request ()
111 * - getAllOperations() - every time there is an operation created by
112 * the Relay Component (query, mutation, subscription) this operation will be
113 * added to the internal list on the Mock Environment. This method will return
114 * an array of all pending operations in the order they occurred.
115 * - findOperation(findFn) - should find operation if findFn(...) return `true`
116 * for it. Otherwise, it will throw.
117 * - getMostRecentOperation(...) - should return the most recent operation
118 * generated by Relay Component.
119 * - resolveMostRecentOperation(...) - is accepting `GraphQLSingularResponse` or a
120 * callback function that will receive `operation` and should return
121 * `GraphQLSingularResponse`
122 * - rejectMostRecentOperation(...) - should reject the most recent operation
123 * with a specific error
124 */
125function createMockEnvironment(config) {
126 var _config$store, _global, _global$process, _global$process$env;
127
128 var store = (_config$store = config === null || config === void 0 ? void 0 : config.store) !== null && _config$store !== void 0 ? _config$store : new Store(new RecordSource());
129 var cache = new QueryResponseCache({
130 size: MAX_SIZE,
131 ttl: MAX_TTL
132 });
133 var pendingRequests = [];
134 var pendingOperations = [];
135
136 var queuePendingOperation = function queuePendingOperation(query, variables) {
137 var operationDescriptor = createOperationDescriptor(getRequest(query), variables);
138 pendingOperations = pendingOperations.concat([operationDescriptor]);
139 };
140
141 var resolversQueue = [];
142
143 var queueOperationResolver = function queueOperationResolver(resolver) {
144 resolversQueue = resolversQueue.concat([resolver]);
145 }; // Mock the network layer
146
147
148 var execute = function execute(request, variables, cacheConfig) {
149 var id = request.id,
150 text = request.text;
151 var cacheID = id !== null && id !== void 0 ? id : text;
152 var cachedPayload = null;
153
154 if (((cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.force) == null || (cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.force) === false) && cacheID != null) {
155 cachedPayload = cache.get(cacheID, variables);
156 }
157
158 if (cachedPayload !== null) {
159 return Observable.from(cachedPayload);
160 }
161
162 var currentOperation = pendingOperations.find(function (op) {
163 return op.request.node.params === request && areEqual(op.request.variables, variables);
164 }); // Handle network responses added by
165
166 if (currentOperation != null && resolversQueue.length > 0) {
167 var currentResolver = resolversQueue[0];
168 var result = currentResolver(currentOperation);
169
170 if (result != null) {
171 resolversQueue = resolversQueue.filter(function (res) {
172 return res !== currentResolver;
173 });
174 pendingOperations = pendingOperations.filter(function (op) {
175 return op !== currentOperation;
176 });
177
178 if (result instanceof Error) {
179 return Observable.create(function (sink) {
180 sink.error(result);
181 });
182 } else {
183 return Observable.from(result);
184 }
185 }
186 }
187
188 return Observable.create(function (sink) {
189 var nextRequest = {
190 request: request,
191 variables: variables,
192 cacheConfig: cacheConfig,
193 sink: sink
194 };
195 pendingRequests = pendingRequests.concat([nextRequest]);
196 return function () {
197 pendingRequests = pendingRequests.filter(function (pending) {
198 return !areEqual(pending, nextRequest);
199 });
200 pendingOperations = pendingOperations.filter(function (op) {
201 return op !== currentOperation;
202 });
203 };
204 });
205 };
206
207 function getConcreteRequest(input) {
208 if (input.kind === 'Request') {
209 var _request = input;
210 return _request;
211 } else {
212 var operationDescriptor = input;
213 !pendingOperations.includes(operationDescriptor) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayModernMockEnvironment: Operation "%s" was not found in the list of pending operations', operationDescriptor.request.node.operation.name) : invariant(false) : void 0;
214 return operationDescriptor.request.node;
215 }
216 } // The same request may be made by multiple query renderers
217
218
219 function getRequests(input) {
220 var concreteRequest;
221 var operationDescriptor;
222
223 if (input.kind === 'Request') {
224 concreteRequest = input;
225 } else {
226 operationDescriptor = input;
227 concreteRequest = operationDescriptor.request.node;
228 }
229
230 var foundRequests = pendingRequests.filter(function (pending) {
231 if (!areEqual(pending.request, concreteRequest.params)) {
232 return false;
233 }
234
235 if (operationDescriptor) {
236 // If we handling `OperationDescriptor` we also need to check variables
237 // and return only pending request with equal variables
238 return areEqual(operationDescriptor.request.variables, pending.variables);
239 } else {
240 // In the case we received `ConcreteRequest` as input we will return
241 // all pending request, even if they have different variables
242 return true;
243 }
244 });
245 !foundRequests.length ? process.env.NODE_ENV !== "production" ? invariant(false, 'MockEnvironment: Cannot respond to request, it has not been requested yet.') : invariant(false) : void 0;
246 foundRequests.forEach(function (foundRequest) {
247 !foundRequest.sink ? process.env.NODE_ENV !== "production" ? invariant(false, 'MockEnvironment: Cannot respond to `%s`, it has not been requested yet.', concreteRequest.params.name) : invariant(false) : void 0;
248 });
249 return foundRequests;
250 }
251
252 function ensureValidPayload(payload) {
253 !(typeof payload === 'object' && payload !== null && payload.hasOwnProperty('data')) ? process.env.NODE_ENV !== "production" ? invariant(false, 'MockEnvironment(): Expected payload to be an object with a `data` key.') : invariant(false) : void 0;
254 return payload;
255 }
256
257 var cachePayload = function cachePayload(request, variables, payload) {
258 var _getConcreteRequest$p = getConcreteRequest(request).params,
259 id = _getConcreteRequest$p.id,
260 text = _getConcreteRequest$p.text;
261 var cacheID = id !== null && id !== void 0 ? id : text;
262 !(cacheID != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'CacheID should not be null') : invariant(false) : void 0;
263 cache.set(cacheID, variables, payload);
264 };
265
266 var clearCache = function clearCache() {
267 cache.clear();
268 }; // Helper to determine if a given query/variables pair is pending
269
270
271 var isLoading = function isLoading(request, variables, cacheConfig) {
272 return pendingRequests.some(function (pending) {
273 return areEqual(pending.request, getConcreteRequest(request).params) && areEqual(pending.variables, variables) && areEqual(pending.cacheConfig, cacheConfig !== null && cacheConfig !== void 0 ? cacheConfig : {});
274 });
275 }; // Helpers to reject or resolve the payload for an individual request.
276
277
278 var reject = function reject(request, error) {
279 var rejectError = typeof error === 'string' ? new Error(error) : error;
280 getRequests(request).forEach(function (foundRequest) {
281 var sink = foundRequest.sink;
282 !(sink !== null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Sink should be defined.') : invariant(false) : void 0;
283 sink.error(rejectError);
284 });
285 };
286
287 var nextValue = function nextValue(request, payload) {
288 getRequests(request).forEach(function (foundRequest) {
289 var sink = foundRequest.sink;
290 !(sink !== null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Sink should be defined.') : invariant(false) : void 0;
291 sink.next(ensureValidPayload(payload));
292 });
293 };
294
295 var complete = function complete(request) {
296 getRequests(request).forEach(function (foundRequest) {
297 var sink = foundRequest.sink;
298 !(sink !== null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Sink should be defined.') : invariant(false) : void 0;
299 sink.complete();
300 });
301 };
302
303 var resolve = function resolve(request, payload) {
304 getRequests(request).forEach(function (foundRequest) {
305 var sink = foundRequest.sink;
306 !(sink !== null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Sink should be defined.') : invariant(false) : void 0;
307 sink.next(ensureValidPayload(payload));
308 sink.complete();
309 });
310 };
311
312 var getMostRecentOperation = function getMostRecentOperation() {
313 var mostRecentOperation = pendingOperations[pendingOperations.length - 1];
314 !(mostRecentOperation != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayModernMockEnvironment: There are no pending operations in the list') : invariant(false) : void 0;
315 return mostRecentOperation;
316 };
317
318 var findOperation = function findOperation(findFn) {
319 var pendingOperation = pendingOperations.find(findFn);
320 !(pendingOperation != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayModernMockEnvironment: Operation was not found in the list of pending operations') : invariant(false) : void 0;
321 return pendingOperation;
322 }; // $FlowExpectedError
323
324
325 var environment = new Environment(_objectSpread({
326 configName: 'RelayModernMockEnvironment',
327 network: Network.create(execute, execute),
328 store: store
329 }, config));
330
331 var createExecuteProxy = function createExecuteProxy(env, fn) {
332 return function () {
333 for (var _len3 = arguments.length, argumentsList = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
334 argumentsList[_key3] = arguments[_key3];
335 }
336
337 var operation = argumentsList[0].operation;
338 pendingOperations = pendingOperations.concat([operation]);
339 return fn.apply(env, argumentsList);
340 };
341 }; // $FlowExpectedError
342
343
344 environment.execute = createExecuteProxy(environment, environment.execute); // $FlowExpectedError
345
346 environment.executeMutation = createExecuteProxy(environment, environment.executeMutation);
347
348 if (((_global = global) === null || _global === void 0 ? void 0 : (_global$process = _global.process) === null || _global$process === void 0 ? void 0 : (_global$process$env = _global$process.env) === null || _global$process$env === void 0 ? void 0 : _global$process$env.NODE_ENV) === 'test') {
349 // Mock all the functions with their original behavior
350 mockDisposableMethod(environment, 'applyUpdate');
351 mockInstanceMethod(environment, 'commitPayload');
352 mockInstanceMethod(environment, 'getStore');
353 mockInstanceMethod(environment, 'lookup');
354 mockInstanceMethod(environment, 'check');
355 mockDisposableMethod(environment, 'subscribe');
356 mockDisposableMethod(environment, 'retain');
357 mockObservableMethod(environment, 'execute');
358 mockObservableMethod(environment, 'executeMutation');
359 mockInstanceMethod(store, 'getSource');
360 mockInstanceMethod(store, 'lookup');
361 mockInstanceMethod(store, 'notify');
362 mockInstanceMethod(store, 'publish');
363 mockDisposableMethod(store, 'retain');
364 mockDisposableMethod(store, 'subscribe');
365 }
366
367 var mock = {
368 cachePayload: cachePayload,
369 clearCache: clearCache,
370 isLoading: isLoading,
371 reject: reject,
372 resolve: resolve,
373 nextValue: nextValue,
374 complete: complete,
375 getMostRecentOperation: getMostRecentOperation,
376 resolveMostRecentOperation: function resolveMostRecentOperation(payload) {
377 var operation = getMostRecentOperation();
378 var data = typeof payload === 'function' ? payload(operation) : payload;
379 return resolve(operation, data);
380 },
381 rejectMostRecentOperation: function rejectMostRecentOperation(error) {
382 var operation = getMostRecentOperation();
383 var rejector = typeof error === 'function' ? error(operation) : error;
384 return reject(operation, rejector);
385 },
386 findOperation: findOperation,
387 queuePendingOperation: queuePendingOperation,
388 getAllOperations: function getAllOperations() {
389 return pendingOperations;
390 },
391 queueOperationResolver: queueOperationResolver
392 }; // $FlowExpectedError
393
394 environment.mock = mock; // $FlowExpectedError
395
396 environment.mockClear = function () {
397 environment.applyUpdate.mockClear();
398 environment.commitPayload.mockClear();
399 environment.getStore.mockClear();
400 environment.lookup.mockClear();
401 environment.check.mockClear();
402 environment.subscribe.mockClear();
403 environment.retain.mockClear();
404 environment.execute.mockClear();
405 environment.executeMutation.mockClear();
406 store.getSource.mockClear();
407 store.lookup.mockClear();
408 store.notify.mockClear();
409 store.publish.mockClear();
410 store.retain.mockClear();
411 store.subscribe.mockClear();
412 cache.clear();
413 pendingOperations = [];
414 pendingRequests = [];
415 };
416
417 return environment;
418}
419
420module.exports = {
421 createMockEnvironment: createMockEnvironment
422};
\No newline at end of file