UNPKG

apollo-link-debounce

Version:
415 lines (367 loc) 14.2 kB
import DebounceLink from './DebounceLink'; import { ObservableEvent, TestSequenceLink, toResultValue, assertObservableSequence, } from './TestUtils'; import { gql, execute, GraphQLRequest, ApolloLink, } from '@apollo/client'; import { ExecutionResult, } from 'graphql'; describe('DebounceLink', () => { let link: ApolloLink; let testLink: TestSequenceLink; let debounceLink: DebounceLink; const DEBOUNCE_TIMEOUT = 500; function makeSimpleResponse(value: string): ExecutionResult { return { data: { hello: value, }, }; } const testResponse = makeSimpleResponse('world'); function makeSimpleSequence(response: ExecutionResult): ObservableEvent[] { return [ { type: 'next', value: response, }, { type: 'complete', }, ]; } function makeSimpleOp(sequence: ObservableEvent[], debounceKey: string, debounceTimeout: number): GraphQLRequest { return { query: gql`{ hello }`, context: { debounceKey, debounceTimeout, testSequence: sequence, }, }; } function getTestSubscriber(observedSequence: ObservableEvent[]) { return { next(value: ExecutionResult) { observedSequence.push({ type: 'next', value, }); }, error(e: Error) { observedSequence.push({ type: 'error', value: e, }); }, complete() { observedSequence.push({ type: 'complete' }); }, }; } const testSequence = makeSimpleSequence(testResponse); const op = makeSimpleOp( testSequence, 'key1', ); const testError = new Error('Hello darkness my old friend'); const testErrorSequence = [{ type: 'error', value: testError }]; const opWithError: GraphQLRequest = { query: gql`{ hello }`, context: { debounceKey: 'key1', testSequence: testErrorSequence, }, }; beforeEach(() => { jest.useFakeTimers(); testLink = new TestSequenceLink(); debounceLink = new DebounceLink(DEBOUNCE_TIMEOUT); link = ApolloLink.from([debounceLink, testLink]); }); it('forwards the operation', () => { return new Promise((resolve, reject) => { execute(link, op).subscribe({ next: (data) => undefined, error: (error) => reject(error), complete: () => { expect(testLink.operations.length).toBe(1); expect(testLink.operations[0].query).toEqual(op.query); resolve(); }, }); jest.runAllTimers(); }); }); it('forwards the operation if context.debounceKey is not defined', () => { const opWithoutKey: GraphQLRequest = { query: gql`{ hello }`, context: { testSequence: makeSimpleSequence(testResponse), }, }; return new Promise((resolve, reject) => { execute(link, opWithoutKey).subscribe({ next: (data) => undefined, error: (error) => reject(error), complete: () => { expect(testLink.operations.length).toBe(1); expect(testLink.operations[0].query).toEqual(op.query); resolve(); }, }); jest.runAllTimers(); }); }); it('calls next and complete as expected', () => { return Promise.resolve(assertObservableSequence( execute(link, op), [ { type: 'next', value: testResponse }, { type: 'complete' }, ], () => jest.runAllTimers(), )); }); it('passes through errors', () => { return Promise.resolve(assertObservableSequence( execute(link, opWithError), [ { type: 'error', value: testError }, ], () => jest.runAllTimers(), )); }); it('debounces multiple queries within the debounce interval', () => { const observedSequence: ObservableEvent[] = []; const subscriber = getTestSubscriber(observedSequence); const s1 = execute(link, op).subscribe(subscriber); jest.runTimersToTime(DEBOUNCE_TIMEOUT - 1); // check that query did not execute. expect(testLink.operations.length).toBe(0); expect(observedSequence.length).toBe(0); // make another query, different params. const op2 = makeSimpleOp( makeSimpleSequence(makeSimpleResponse('op2')), 'key1', ); const s2 = execute(link, op2).subscribe(subscriber); jest.runTimersToTime(DEBOUNCE_TIMEOUT - 1); // check that query did not execute expect(testLink.operations.length).toBe(0); expect(observedSequence.length).toBe(0); // make another query, different params const op3sequence = makeSimpleSequence(makeSimpleResponse('op3')); const op3 = makeSimpleOp( op3sequence, 'key1', ); op3.operationName = 'op3'; const s3 = execute(link, op3).subscribe(subscriber); jest.runTimersToTime(DEBOUNCE_TIMEOUT + 1); // check that all queries returned the sequence of the last query. const expectedSequence = [ toResultValue(op3sequence[0]), toResultValue(op3sequence[0]), toResultValue(op3sequence[0]), toResultValue(op3sequence[1]), toResultValue(op3sequence[1]), toResultValue(op3sequence[1]), ]; expect(testLink.operations.length).toEqual(1); expect(testLink.operations[0].operationName).toBe(op3.operationName); expect(observedSequence.length).toEqual(6); expect(observedSequence).toEqual(expectedSequence); s1.unsubscribe(); s2.unsubscribe(); s3.unsubscribe(); }); it('debounces multiple queries within the custom debounce interval provided in context', () => { const observedSequence: ObservableEvent[] = []; const subscriber = getTestSubscriber(observedSequence); const customDebounceTimeout = DEBOUNCE_TIMEOUT / 4; const op0 = makeSimpleOp( testSequence, 'key1', customDebounceTimeout, ); const s1 = execute(link, op0).subscribe(subscriber); jest.runTimersToTime(customDebounceTimeout - 1); // check that query did not execute. expect(testLink.operations.length).toBe(0); expect(observedSequence.length).toBe(0); // make another query, different params. const op2 = makeSimpleOp( makeSimpleSequence(makeSimpleResponse('op2')), 'key1', customDebounceTimeout, ); const s2 = execute(link, op2).subscribe(subscriber); jest.runTimersToTime(customDebounceTimeout - 1); // check that query did not execute expect(testLink.operations.length).toBe(0); expect(observedSequence.length).toBe(0); // make another query, different params const op3sequence = makeSimpleSequence(makeSimpleResponse('op3')); const op3 = makeSimpleOp( op3sequence, 'key1', customDebounceTimeout, ); op3.operationName = 'op3'; const s3 = execute(link, op3).subscribe(subscriber); jest.runTimersToTime(customDebounceTimeout + 1); // check that all queries returned the sequence of the last query. const expectedSequence = [ toResultValue(op3sequence[0]), toResultValue(op3sequence[0]), toResultValue(op3sequence[0]), toResultValue(op3sequence[1]), toResultValue(op3sequence[1]), toResultValue(op3sequence[1]), ]; expect(testLink.operations.length).toEqual(1); expect(testLink.operations[0].operationName).toBe(op3.operationName); expect(observedSequence.length).toEqual(6); expect(observedSequence).toEqual(expectedSequence); s1.unsubscribe(); s2.unsubscribe(); s3.unsubscribe(); }); it('does not debounce queries that are not within the interval', () => { // make one query. // run timer for debounce + 1 // check that query executed. // make one query. // run timer for debounce + 1 // check that query executed. const observedSequence: ObservableEvent[] = []; const subscriber = getTestSubscriber(observedSequence); const s1 = execute(link, op).subscribe(subscriber); jest.runTimersToTime(DEBOUNCE_TIMEOUT + 1); // check that query did not execute. expect(testLink.operations.length).toBe(1); expect(observedSequence.length).toBe(2); // make another query, different params. const op2sequence = makeSimpleSequence(testResponse); const op2 = makeSimpleOp( op2sequence, 'key1', ); const s2 = execute(link, op2).subscribe(subscriber); jest.runTimersToTime(DEBOUNCE_TIMEOUT + 1); // check that query executed expect(testLink.operations.length).toBe(2); expect(observedSequence.length).toBe(4); const expectedSequence = [ toResultValue(testSequence[0]), toResultValue(testSequence[1]), toResultValue(op2sequence[0]), toResultValue(op2sequence[1]), ]; expect(observedSequence).toEqual(expectedSequence); s1.unsubscribe(); s2.unsubscribe(); }); it('does not debounce queries with different debounceKey (even within the interval)', () => { // make query // make another query with different debounceKey // run timer for debounce +1. // check that both queries ran and returned different values const observedSequence: ObservableEvent[] = []; const subscriber = getTestSubscriber(observedSequence); const s1 = execute(link, op).subscribe(subscriber); // make another query, different debounceKey. const op2sequence = makeSimpleSequence(testResponse); const op2 = makeSimpleOp( op2sequence, 'key2', ); const observedSequence2: ObservableEvent[] = []; // Using a different subscriber, just for fun. const subscriber2 = getTestSubscriber(observedSequence2); const s2 = execute(link, op2).subscribe(subscriber2); jest.runTimersToTime(DEBOUNCE_TIMEOUT + 1); // check that both queries executed expect(testLink.operations.length).toBe(2); expect(observedSequence.length).toBe(2); expect(observedSequence2.length).toBe(2); const expectedSequence = [ toResultValue(testSequence[0]), toResultValue(testSequence[1]), ]; const expectedSequence2 = [ toResultValue(op2sequence[0]), toResultValue(op2sequence[1]), ]; expect(observedSequence).toEqual(expectedSequence); expect(observedSequence2).toEqual(expectedSequence2); s1.unsubscribe(); s2.unsubscribe(); }); it('does not make any query if you unsubscribe before interval is over', () => { // make query // run timer for debounce -1 // unsubscribe // run timer for debounce +1. // check that nothing ran const observedSequence: ObservableEvent[] = []; const subscriber = getTestSubscriber(observedSequence); const s1 = execute(link, op).subscribe(subscriber); jest.runTimersToTime(DEBOUNCE_TIMEOUT - 1); s1.unsubscribe(); jest.runTimersToTime(DEBOUNCE_TIMEOUT + 1); expect(testLink.operations.length).toBe(0); expect(observedSequence.length).toBe(0); }); it('correctly debounces a query that errors', () => { const observedSequence: ObservableEvent[] = []; const subscriber = getTestSubscriber(observedSequence); const s1 = execute(link, opWithError).subscribe(subscriber); jest.runTimersToTime(DEBOUNCE_TIMEOUT + 1); expect(testLink.operations.length).toBe(1); expect(observedSequence).toEqual(testErrorSequence); s1.unsubscribe(); }); it('runs the second to last query if the last one was unsubscribed from', () => { // make query // make another query // run timer for debounce -1 // unsubscribe from second query // run timer for debounce +1. // check that first query executed and returned value const observedSequence: ObservableEvent[] = []; const subscriber = getTestSubscriber(observedSequence); const s1 = execute(link, op).subscribe(subscriber); // make another query const op2sequence = makeSimpleSequence(testResponse); const op2 = makeSimpleOp( op2sequence, 'key1', ); const observedSequence2: ObservableEvent[] = []; // Using a different subscriber, just for fun. const subscriber2 = getTestSubscriber(observedSequence2); const s2 = execute(link, op2).subscribe(subscriber2); jest.runTimersToTime(DEBOUNCE_TIMEOUT - 1); s2.unsubscribe(); jest.runTimersToTime(DEBOUNCE_TIMEOUT + 1); expect(testLink.operations.length).toBe(1); expect(observedSequence.length).toBe(2); expect(observedSequence2.length).toBe(0); const expectedSequence = [ toResultValue(testSequence[0]), toResultValue(testSequence[1]), ]; expect(observedSequence).toEqual(expectedSequence); s1.unsubscribe(); }); });