UNPKG

apollo-link-debounce

Version:
159 lines (144 loc) 4.91 kB
import { ApolloLink, Operation, Observable, NextLink, } from '@apollo/client'; import { ExecutionResult, } from 'graphql'; import { ExecutionResultDataDefault, } from 'graphql/execution/execute'; export interface ObservableValue { value?: ExecutionResult | Error; delay?: number; type: 'next' | 'error' | 'complete'; } export interface Unsubscribable { unsubscribe: () => void; } export interface NextEvent { type: 'next'; delay?: number; value: ExecutionResult; } export interface ErrorEvent { type: 'error'; delay?: number; value: Error; } export interface CompleteEvent { type: 'complete'; delay?: number; } export type ObservableEvent = NextEvent | ErrorEvent | CompleteEvent; export class TestLink extends ApolloLink { public operations: Operation[]; constructor() { super(); this.operations = []; } public request (operation: Operation) { this.operations.push(operation); // TODO(helfer): Throw an error if neither testError nor testResponse is defined return new Observable(observer => { if (operation.getContext().testError) { setTimeout(() => observer.error(operation.getContext().testError), 0); return; } setTimeout(() => observer.next(operation.getContext().testResponse), 0); setTimeout(() => observer.complete(), 0); }); } } export class TestSequenceLink extends ApolloLink { public operations: Operation[]; constructor() { super(); this.operations = []; } public request (operation: Operation, forward: NextLink) { if (!operation.getContext().testSequence) { return forward(operation); } this.operations.push(operation); // TODO(helfer): Throw an error if neither testError nor testResponse is defined return new Observable(observer => { operation.getContext().testSequence.forEach((event: ObservableEvent) => { if (event.type === 'error') { setTimeout(() => observer.error(event.value), event.delay || 0); return; } if (event.type === 'next') { setTimeout(() => observer.next(event.value), event.delay || 0); } if (event.type === 'complete') { setTimeout(() => observer.complete(), event.delay || 0); } }); }); } } export function mergeObservables(...observables: Observable<ExecutionResult>[]) { return new Observable(observer => { const numObservables = observables.length; let completedObservables = 0; observables.forEach(o => { o.subscribe({ next: observer.next.bind(observer), error: observer.error.bind(observer), complete: () => { completedObservables++; if (completedObservables === numObservables) { observer.complete(); } }, }); }); // TODO(helfer): unsubscribe }); } export function toResultValue(e: ObservableEvent): ObservableEvent { const obj = { ...e }; delete obj.delay; return obj; } export const assertObservableSequence = ( observable: Observable<ExecutionResult>, sequence: ObservableValue[], initializer: (sub: Unsubscribable) => void = () => undefined, ): Promise<boolean | Error> => { let index = 0; if (sequence.length === 0) { throw new Error('Observable sequence must have at least one element'); } return new Promise((resolve, reject) => { const sub = observable.subscribe({ next: (value) => { expect({ type: 'next', value }).toEqual(sequence[index]); index++; if (index === sequence.length) { resolve(true); } }, error: (value) => { expect({ type: 'error', value }).toEqual(sequence[index]); index++; // This check makes sure that there is no next element in // the sequence. If there is, it will print a somewhat useful error expect(undefined).toEqual(sequence[index]); resolve(true); }, complete: () => { expect({ type: 'complete' }).toEqual(sequence[index]); index++; // This check makes sure that there is no next element in // the sequence. If there is, it will print a somewhat useful error expect(undefined).toEqual(sequence[index]); resolve(true); }, }); initializer(sub); }); };