@vyer-technologies/relay-offline
Version:
Forked from @wora Relay Offline Capabilities
284 lines • 11.9 kB
JavaScript
;
import invariant from 'fbjs/lib/invariant';
import jest from 'jest-mock';
import areEqual from 'fbjs/lib/areEqual';
import { QueryResponseCache, Observable, Network, createOperationDescriptor, getRequest, } from 'relay-runtime';
import { Store, RecordSource, Environment } from '../lib';
import { createPersistedRecordSource, createPersistedStore } from '@vyer-technologies/relay-store/test';
const MAX_SIZE = 10;
const MAX_TTL = 5 * 60 * 1000;
function mockInstanceMethod(object, key) {
object[key] = jest.fn(object[key].bind(object));
}
function mockDisposableMethod(object, key) {
const fn = object[key].bind(object);
object[key] = jest.fn((...args) => {
const disposable = fn(...args);
const dispose = jest.fn(() => disposable.dispose());
object[key].mock.dispose = dispose;
return { dispose };
});
const mockClear = object[key].mockClear.bind(object[key]);
object[key].mockClear = () => {
mockClear();
object[key].mock.dispose = null;
};
}
function mockObservableMethod(object, key) {
const fn = object[key].bind(object);
const subscriptions = [];
object[key] = jest.fn((...args) => fn(...args).do({
start: (subscription) => {
subscriptions.push(subscription);
},
}));
object[key].mock.subscriptions = subscriptions;
const mockClear = object[key].mockClear.bind(object[key]);
object[key].mockClear = () => {
mockClear();
object[key].mock.subscriptions = [];
};
}
export function createMockEnvironment(config) {
const store = config?.store ??
new Store(new RecordSource({ storage: createPersistedStore() }), { storage: createPersistedRecordSource() }, { queryCacheExpirationTime: null });
const cache = new QueryResponseCache({
size: MAX_SIZE,
ttl: MAX_TTL,
});
let pendingRequests = [];
let pendingOperations = [];
const queuePendingOperation = (query, variables) => {
const operationDescriptor = createOperationDescriptor(getRequest(query), variables);
pendingOperations = pendingOperations.concat([operationDescriptor]);
};
let resolversQueue = [];
const queueOperationResolver = (resolver) => {
resolversQueue = resolversQueue.concat([resolver]);
};
const execute = (request, variables, cacheConfig) => {
const { id, text } = request;
const cacheID = id ?? text;
let cachedPayload = null;
if ((cacheConfig?.force == null || cacheConfig?.force === false) && cacheID != null) {
cachedPayload = cache.get(cacheID, variables);
}
if (cachedPayload !== null) {
return Observable.from(cachedPayload);
}
const currentOperation = pendingOperations.find((op) => op.request.node.params === request && areEqual(op.request.variables, variables));
if (currentOperation != null && resolversQueue.length > 0) {
const currentResolver = resolversQueue[0];
const result = currentResolver(currentOperation);
if (result != null) {
resolversQueue = resolversQueue.filter((res) => res !== currentResolver);
pendingOperations = pendingOperations.filter((op) => op !== currentOperation);
if (result instanceof Error) {
return Observable.create((sink) => {
sink.error(result);
});
}
else {
return Observable.from(result);
}
}
}
return Observable.create((sink) => {
const nextRequest = { request, variables, cacheConfig, sink };
pendingRequests = pendingRequests.concat([nextRequest]);
return () => {
pendingRequests = pendingRequests.filter((pending) => !areEqual(pending, nextRequest));
pendingOperations = pendingOperations.filter((op) => op !== currentOperation);
};
});
};
function getConcreteRequest(input) {
if (input.kind === 'Request') {
return input;
}
else {
const operationDescriptor = input;
invariant(pendingOperations.includes(operationDescriptor), 'RelayModernMockEnvironment: Operation "%s" was not found in the list of pending operations', operationDescriptor.request.node.operation.name);
return operationDescriptor.request.node;
}
}
function getRequests(input) {
let concreteRequest;
let operationDescriptor;
if (input.kind === 'Request') {
concreteRequest = input;
}
else {
operationDescriptor = input;
concreteRequest = operationDescriptor.request.node;
}
const foundRequests = pendingRequests.filter((pending) => {
if (!areEqual(pending.request, concreteRequest.params)) {
return false;
}
if (operationDescriptor) {
return areEqual(operationDescriptor.request.variables, pending.variables);
}
else {
return true;
}
});
invariant(foundRequests.length, 'MockEnvironment: Cannot respond to request, it has not been requested yet.');
foundRequests.forEach((foundRequest) => {
invariant(foundRequest.sink, 'MockEnvironment: Cannot respond to `%s`, it has not been requested yet.', concreteRequest.params.name);
});
return foundRequests;
}
function ensureValidPayload(payload) {
invariant(typeof payload === 'object' && payload !== null && payload.hasOwnProperty('data'), 'MockEnvironment(): Expected payload to be an object with a `data` key.');
return payload;
}
const cachePayload = (request, variables, payload) => {
const { id, text } = getConcreteRequest(request).params;
const cacheID = id ?? text;
invariant(cacheID != null, 'CacheID should not be null');
cache.set(cacheID, variables, payload);
};
const clearCache = () => {
cache.clear();
};
const isLoading = (request, variables, cacheConfig) => {
return pendingRequests.some((pending) => areEqual(pending.request, getConcreteRequest(request).params) &&
areEqual(pending.variables, variables) &&
areEqual(pending.cacheConfig, cacheConfig ?? {}));
};
const reject = (request, error) => {
const rejectError = typeof error === 'string' ? new Error(error) : error;
getRequests(request).forEach((foundRequest) => {
const { sink } = foundRequest;
invariant(sink !== null, 'Sink should be defined.');
sink.error(rejectError);
});
};
const nextValue = (request, payload) => {
getRequests(request).forEach((foundRequest) => {
const { sink } = foundRequest;
invariant(sink !== null, 'Sink should be defined.');
sink.next(ensureValidPayload(payload));
});
};
const complete = (request) => {
getRequests(request).forEach((foundRequest) => {
const { sink } = foundRequest;
invariant(sink !== null, 'Sink should be defined.');
sink.complete();
});
};
const resolve = (request, payload) => {
getRequests(request).forEach((foundRequest) => {
const { sink } = foundRequest;
invariant(sink !== null, 'Sink should be defined.');
sink.next(ensureValidPayload(payload));
sink.complete();
});
};
const getMostRecentOperation = () => {
const mostRecentOperation = pendingOperations[pendingOperations.length - 1];
invariant(mostRecentOperation != null, 'RelayModernMockEnvironment: There are no pending operations in the list');
return mostRecentOperation;
};
const findOperation = (findFn) => {
const pendingOperation = pendingOperations.find(findFn);
invariant(pendingOperation != null, 'RelayModernMockEnvironment: Operation was not found in the list of pending operations');
return pendingOperation;
};
const environment = new Environment({
configName: 'RelayModernMockEnvironment',
network: Network.create(execute, execute),
store,
...config,
});
const createExecuteProxy = (env, fn) => {
return (...argumentsList) => {
const [{ operation }] = argumentsList;
pendingOperations = pendingOperations.concat([operation]);
return fn.apply(env, argumentsList);
};
};
let promiseHydrate;
const hydrate = () => promiseHydrate;
const createHydrateProxy = (env, fn) => {
return (...argumentsList) => {
promiseHydrate = fn.apply(env, argumentsList);
return promiseHydrate;
};
};
environment.hydrate = createHydrateProxy(environment, environment.hydrate);
environment.execute = createExecuteProxy(environment, environment.execute);
environment.executeMutation = createExecuteProxy(environment, environment.executeMutation);
if (global?.process?.env?.NODE_ENV === 'test') {
mockDisposableMethod(environment, 'applyUpdate');
mockInstanceMethod(environment, 'commitPayload');
mockInstanceMethod(environment, 'getStore');
mockInstanceMethod(environment, 'lookup');
mockInstanceMethod(environment, 'check');
mockInstanceMethod(environment, 'hydrate');
mockDisposableMethod(environment, 'subscribe');
mockDisposableMethod(environment, 'retain');
mockObservableMethod(environment, 'execute');
mockObservableMethod(environment, 'executeWithSource');
mockObservableMethod(environment, 'executeMutation');
mockInstanceMethod(store, 'getSource');
mockInstanceMethod(store, 'lookup');
mockInstanceMethod(store, 'notify');
mockInstanceMethod(store, 'publish');
mockDisposableMethod(store, 'retain');
mockDisposableMethod(store, 'subscribe');
}
const mock = {
cachePayload,
clearCache,
isLoading,
reject,
resolve,
nextValue,
complete,
hydrate,
getMostRecentOperation,
resolveMostRecentOperation(payload) {
const operation = getMostRecentOperation();
const data = typeof payload === 'function' ? payload(operation) : payload;
return resolve(operation, data);
},
rejectMostRecentOperation(error) {
const operation = getMostRecentOperation();
const rejector = typeof error === 'function' ? error(operation) : error;
return reject(operation, rejector);
},
findOperation,
queuePendingOperation,
getAllOperations() {
return pendingOperations;
},
queueOperationResolver,
};
environment.mock = mock;
environment.mockClear = () => {
environment.applyUpdate.mockClear();
environment.commitPayload.mockClear();
environment.getStore.mockClear();
environment.lookup.mockClear();
environment.check.mockClear();
environment.subscribe.mockClear();
environment.retain.mockClear();
environment.hydrate.mockClear();
environment.execute.mockClear();
environment.executeMutation.mockClear();
store.getSource.mockClear();
store.lookup.mockClear();
store.notify.mockClear();
store.publish.mockClear();
store.retain.mockClear();
store.subscribe.mockClear();
cache.clear();
pendingOperations = [];
pendingRequests = [];
};
return environment;
}
//# sourceMappingURL=createMockEnvironment.js.map