Home Reference Source Repository

src/store/__tests__/RelayReadyState-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';

jest.mock('warning');

const RelayReadyState = require('RelayReadyState');
const RelayTestUtils = require('RelayTestUtils');

const warning = require('warning');

describe('RelayReadyState', () => {
  let onReadyStateChange;
  let readyState;

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

    onReadyStateChange = jest.fn();
    readyState = new RelayReadyState(onReadyStateChange);

    jasmine.addMatchers(RelayTestUtils.matchers);
  });

  it('adds to event stream', () => {
    readyState.update({}, [{type: 'NETWORK_QUERY_START'}]);
    expect(onReadyStateChange).not.toBeCalled();

    jest.runAllTimers();

    expect(onReadyStateChange.mock.calls).toEqual([
      [{
        aborted: false,
        done: false,
        error: null,
        events: [
          {
            type: 'NETWORK_QUERY_START',
          },
        ],
        ready: false,
        stale: false,
      }],
    ]);

    readyState.update({}, [{type: 'NETWORK_QUERY_RECEIVED_REQUIRED'}]);
    jest.runAllTimers();

    expect(onReadyStateChange).lastCalledWith({
      aborted: false,
      done: false,
      error: null,
      events: [
        {
          type: 'NETWORK_QUERY_START',
        },
        {
          type: 'NETWORK_QUERY_RECEIVED_REQUIRED',
        },
      ],
      ready: false,
      stale: false,
    });
  });

  it('invokes the callback asynchronously when state changes', () => {
    readyState.update({ready: true});

    expect(onReadyStateChange).not.toBeCalled();

    jest.runAllTimers();

    expect(onReadyStateChange.mock.calls).toEqual([
      [{
        aborted: false,
        done: false,
        error: null,
        events: [],
        ready: true,
        stale: false,
      }],
    ]);
  });

  it('invokes the callback once per immediate', () => {
    readyState.update({ready: true, stale: true});
    readyState.update({ready: true, stale: false});
    readyState.update({done: true, ready: true});
    jest.runAllTimers();

    expect(onReadyStateChange.mock.calls).toEqual([
      [{
        aborted: false,
        done: true,
        error: null,
        events: [],
        ready: true,
        stale: false,
      }],
    ]);
  });

  it('ignores asynchronous state changes after being aborted', () => {
    readyState.update({aborted: true});
    jest.runAllTimers();

    readyState.update({ready: true});
    jest.runAllTimers();

    expect(onReadyStateChange.mock.calls).toEqual([
      [{
        aborted: true,
        done: false,
        error: null,
        events: [],
        ready: false,
        stale: false,
      }],
    ]);
  });

  it('ignores synchronous state changes after being aborted', () => {
    readyState.update({aborted: true});
    readyState.update({ready: true});
    jest.runAllTimers();

    expect(onReadyStateChange.mock.calls).toEqual([
      [{
        aborted: true,
        done: false,
        error: null,
        events: [],
        ready: false,
        stale: false,
      }],
    ]);
  });

  it('warns about state changes after being done', () => {
    readyState.update({done: true, ready: true});
    jest.runAllTimers();
    readyState.update({ready: true});

    expect([
      'RelayReadyState: Invalid state change from `%s` to `%s`.',
      JSON.stringify(
        {aborted: false, done: true, error: null, events: [], ready: true, stale: false}
      ),
      JSON.stringify({ready: true}),
    ]).toBeWarnedNTimes(1);

    expect(onReadyStateChange.mock.calls.length).toBe(1);
    jest.runAllTimers();
    expect(onReadyStateChange.mock.calls.length).toBe(1);
  });

  it('warns about state changes after encountering errors', () => {
    const error = new Error('Expected error.');
    readyState.update({error});
    jest.runAllTimers();
    readyState.update({ready: true});

    expect([
      'RelayReadyState: Invalid state change from `%s` to `%s`.',
      JSON.stringify(
        {aborted: false, done: false, error, events: [], ready: false, stale: false}
      ),
      JSON.stringify({ready: true}),
    ]).toBeWarnedNTimes(1);

    expect(onReadyStateChange.mock.calls.length).toBe(1);
    jest.runAllTimers();
    expect(onReadyStateChange.mock.calls.length).toBe(1);
  });

  it('ignores state changed to aborted when done', () => {
    readyState.update({done: true, ready: true});
    jest.runAllTimers();

    readyState.update({aborted: true});
    jest.runAllTimers();

    expect(warning).not.toBeCalled();
    expect(onReadyStateChange.mock.calls.length).toBe(1);
  });

  it('ignores state changed to aborted when an error occurred', () => {
    readyState.update({error: new Error('Expected error.')});
    jest.runAllTimers();

    readyState.update({aborted: true});
    jest.runAllTimers();

    expect(warning).not.toBeCalled();
    expect(onReadyStateChange.mock.calls.length).toBe(1);
  });

  it('ignores stale ready state change after being done', () => {
    readyState.update({done: true, ready: true});
    jest.runAllTimers();
    readyState.update({ready: true, stale: true});
    jest.runAllTimers();

    expect(warning).not.toBeCalled();
    expect(onReadyStateChange.mock.calls.length).toBe(1);
  });

  it('invokes stale ready state change after an error occurred', () => {
    const error =  new Error('Expected error.');
    readyState.update({error});
    jest.runAllTimers();
    readyState.update({ready: true, stale: true});
    jest.runAllTimers();

    expect(warning).not.toBeCalled();
    expect(onReadyStateChange.mock.calls.length).toBe(2);
    expect(onReadyStateChange.mock.calls[1]).toEqual(
      [{aborted: false, done: false, error, events: [], ready: true, stale: true}]
    );
  });
});