Home Reference Source Repository

src/store/__tests__/RelayPendingQueryTracker-test.js

/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @emails oncall+relay
 */

'use strict';

require('configureForRelayOSS');

jest
  .unmock('RelayPendingQueryTracker')
  .unmock('RelayTaskQueue');

const Relay = require('Relay');
const RelayFetchMode = require('RelayFetchMode');
const RelayStoreData = require('RelayStoreData');
const RelayTestUtils = require('RelayTestUtils');

const writeRelayQueryPayload = require('writeRelayQueryPayload');

describe('RelayPendingQueryTracker', () => {
  let pendingQueryTracker;

  let addPending;

  let fetchRelayQuery;

  const {getNode} = RelayTestUtils;

  beforeEach(() => {
    jest.resetModuleRegistry();

    const storeData = new RelayStoreData();
    fetchRelayQuery = storeData.getNetworkLayer().fetchRelayQuery;
    pendingQueryTracker = storeData.getPendingQueryTracker();

    addPending = ({query, fetchMode}) => {
      fetchMode = fetchMode || RelayFetchMode.CLIENT;
      return pendingQueryTracker.add({
        query,
        fetchMode,
        forceIndex: null,
      }).getResolvedPromise();
    };

    jasmine.addMatchers(RelayTestUtils.matchers);
    jasmine.addMatchers({
      toConsoleWarn() {
        return {
          compare(callback, expected) {
            const consoleWarn = console.warn;
            let pass = false;
            console.warn = (...args) => {
              if (args.length === expected.length &&
                  args.every((arg, ii) => arg === expected[ii])) {
                pass = true;
              } else {
                consoleWarn(...args);
              }
            };
            callback();
            console.warn = consoleWarn;
            return {pass};
          },
        };
      },
    });
  });

  it('calls `onSuccess` callback when inner fetch resolves', () => {
    const mockQueryA = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);

    const pendingA = addPending({query: mockQueryA});
    const mockSuccessA = jest.fn();
    pendingA.done(mockSuccessA);
    jest.runAllTimers();

    fetchRelayQuery.mock.requests[0].resolve({viewer:{}});
    jest.runAllTimers();

    expect(mockSuccessA).toBeCalled();
  });

  it('calls `writeRelayQueryPayload` when receiving data', () => {
    const mockQueryA = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);

    addPending({query: mockQueryA});
    jest.runAllTimers();

    fetchRelayQuery.mock.requests[0].resolve({viewer:{}});
    jest.runAllTimers();

    const writeCalls = writeRelayQueryPayload.mock.calls;
    expect(writeCalls.length).toBe(1);
    expect(writeCalls[0][1]).toEqualQueryRoot(mockQueryA);
    expect(writeCalls[0][2]).toEqual({viewer:{}});
  });

  it('fails if fetching throws an error', () => {
    const mockQuery = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);
    const pendingA = addPending({query: mockQuery});
    const mockFailureA = jest.fn();
    pendingA.catch(mockFailureA);

    const mockError = new Error('Expected error.');
    fetchRelayQuery.mock.requests[0].reject(mockError);
    expect(() => {
      jest.runAllTimers();
    }).toConsoleWarn([mockError.message]);

    expect(mockFailureA).toBeCalledWith(mockError);
  });

  it('fails if `writeRelayQueryPayload` throws', () => {
    const mockQuery = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);
    const pendingA = addPending({query: mockQuery});
    const mockFailureA = jest.fn();
    pendingA.catch(mockFailureA);

    const mockError = new Error('Expected error.');
    fetchRelayQuery.mock.requests[0].resolve({viewer:{}});
    writeRelayQueryPayload.mockImplementation(() => {
      throw mockError;
    });
    expect(() => {
      jest.runAllTimers();
    }).toConsoleWarn([mockError.message]);

    expect(mockFailureA).toBeCalledWith(mockError);
  });

  it('can resolve preload queries *after* they are added', () => {
    const mockQuery = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);

    addPending({
      query: mockQuery,
      fetchMode: RelayFetchMode.PRELOAD,
    });

    pendingQueryTracker.resolvePreloadQuery(
      mockQuery.getID(),
      {response: {viewer:{}}}
    );

    jest.runAllTimers();

    expect(pendingQueryTracker.hasPendingQueries()).toBeFalsy();
    const writeCalls = writeRelayQueryPayload.mock.calls;
    expect(writeCalls.length).toBe(1);
    expect(writeCalls[0][1]).toEqualQueryRoot(mockQuery);
    expect(writeCalls[0][2]).toEqual({viewer:{}});
  });

  it('can resolve preload queries *before* they are added', () => {
    const mockQuery = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);

    pendingQueryTracker.resolvePreloadQuery(
      mockQuery.getID(),
      {response: {viewer:{}}}
    );

    addPending({
      query: mockQuery,
      fetchMode: RelayFetchMode.PRELOAD,
    });

    jest.runAllTimers();

    expect(pendingQueryTracker.hasPendingQueries()).toBeFalsy();
    const writeCalls = writeRelayQueryPayload.mock.calls;
    expect(writeCalls.length).toBe(1);
    expect(writeCalls[0][1]).toEqualQueryRoot(mockQuery);
    expect(writeCalls[0][2]).toEqual({viewer:{}});
  });

  it('can reject preloaded pending queries by id', () => {
    const mockQuery = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);

    const mockPending = addPending({
      query: mockQuery,
      fetchMode: RelayFetchMode.PRELOAD,
    });
    const mockCallback = jest.fn();
    mockPending.catch(mockCallback);

    const mockError = new Error('Expected error.');
    pendingQueryTracker.rejectPreloadQuery(
      mockQuery.getID(),
      mockError
    );
    expect(() => {
      jest.runAllTimers();
    }).toConsoleWarn([mockError.message]);

    jest.runAllTimers();

    expect(pendingQueryTracker.hasPendingQueries()).toBeFalsy();
    expect(mockCallback).toBeCalledWith(mockError);
  });

  it('has pending queries when not queries are all resolved', () => {
    const mockQueryA = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);
    addPending({query: mockQueryA});
    jest.runAllTimers();

    expect(pendingQueryTracker.hasPendingQueries()).toBeTruthy();
  });

  it('has no pending queries when queries are all resolved', () => {
    const mockQueryA = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);
    addPending({query: mockQueryA});
    jest.runAllTimers();

    fetchRelayQuery.mock.requests[0].resolve({viewer:{}});
    jest.runAllTimers();

    expect(pendingQueryTracker.hasPendingQueries()).toBeFalsy();
  });

  it('has no pending queries after being reset', () => {
    const mockQueryA = getNode(Relay.QL`
      query {
        viewer{actor{id,name}}
      }
    `);
    addPending({query: mockQueryA});
    jest.runAllTimers();

    pendingQueryTracker.resetPending();

    expect(pendingQueryTracker.hasPendingQueries()).toBeFalsy();
  });
});