Home Reference Source Repository

src/container/__tests__/RelayRenderer_render-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('RelayRenderer');

const React = require('React');
const ReactDOM = require('ReactDOM');
const ReactTestUtils = require('ReactTestUtils');
const Relay = require('Relay');
const RelayEnvironment = require('RelayEnvironment');
const RelayQueryConfig = require('RelayQueryConfig');
const RelayRenderer = require('RelayRenderer');
const StaticContainer = require('StaticContainer.react');

describe('RelayRenderer.render', () => {
  let MockContainer;

  let container;
  let queryConfig;
  let environment;
  let renderedComponent;

  function renderElement(element) {
    renderedComponent = ReactDOM.render(element, container);
  }

  function getRenderOutput() {
    return ReactTestUtils.findRenderedComponentWithType(
      renderedComponent,
      StaticContainer
    );
  }

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

    const MockComponent = React.createClass({render: () => <div />});
    MockContainer = Relay.createContainer(MockComponent, {
      fragments: {},
    });

    container = document.createElement('div');
    queryConfig = RelayQueryConfig.genMockInstance();
    environment = new RelayEnvironment();

    jasmine.addMatchers({
      toBeUpdated() {
        return {
          compare(actual) {
            return {
              pass: actual.props.shouldUpdate,
            };
          },
        };
      },
      toBeRenderedChild() {
        return {
          compare(actual) {
            return {
              pass: getRenderOutput().props.children === actual,
            };
          },
        };
      },
    });
  });

  it('renders when mounted before a request is sent', () => {
    const initialView = <div />;
    const render = jest.fn(() => initialView);
    renderElement(
      <RelayRenderer
        Container={MockContainer}
        queryConfig={queryConfig}
        environment={environment}
        render={render}
      />
    );
    expect(render).toBeCalled();
    expect(initialView).toBeRenderedChild();
  });

  it('renders when updated before the initial request is sent', () => {
    renderElement(
      <RelayRenderer
        Container={MockContainer}
        queryConfig={queryConfig}
        environment={environment}
      />
    );
    const loadingView = <div />;
    renderElement(
      <RelayRenderer
        Container={MockContainer}
        queryConfig={RelayQueryConfig.genMockInstance()}
        environment={environment}
        render={() => loadingView}
      />
    );
    // Since RelayRenderer has not yet sent a request, view gets to update.
    expect(getRenderOutput()).toBeUpdated();
  });

  it('does not render when updated after the initial request is sent', () => {
    renderElement(
      <RelayRenderer
        Container={MockContainer}
        queryConfig={queryConfig}
        environment={environment}
      />
    );
    environment.primeCache.mock.requests[0].block();

    const loadingView = <div />;
    renderElement(
      <RelayRenderer
        Container={MockContainer}
        queryConfig={RelayQueryConfig.genMockInstance()}
        environment={environment}
        render={() => loadingView}
      />
    );
    // RelayRenderer does not synchronously update because the ready state (and
    // therefore render arguments) for the new `queryConfig` is not yet known.
    expect(getRenderOutput()).not.toBeUpdated();
    environment.primeCache.mock.requests[1].block();
    expect(loadingView).toBeRenderedChild();
  });

  it('renders whenever updated after request is sent', () => {
    const render = jest.fn();
    function update() {
      renderElement(
        <RelayRenderer
          Container={MockContainer}
          queryConfig={queryConfig}
          environment={environment}
          render={render}
        />
      );
    }
    update();
    environment.primeCache.mock.requests[0].block();

    expect(render.mock.calls.length).toBe(2);

    update();
    update();
    update();

    expect(render.mock.calls.length).toBe(5);
  });

  it('renders once after each ready state change', () => {
    const render = jest.fn();

    renderElement(
      <RelayRenderer
        Container={MockContainer}
        queryConfig={queryConfig}
        environment={environment}
        render={render}
      />
    );

    const request = environment.primeCache.mock.requests[0];

    expect(render.mock.calls.length).toBe(1);

    request.block();
    expect(render.mock.calls.length).toBe(2);

    request.resolve();
    expect(render.mock.calls.length).toBe(3);

    request.succeed();
    expect(render.mock.calls.length).toBe(4);
  });

  describe('GC integration', () => {
    let garbageCollector;

    beforeEach(() => {
      const storeData = environment.getStoreData();
      storeData.initializeGarbageCollector(jest.fn());
      garbageCollector = storeData.getGarbageCollector();
    });

    it('acquires a GC hold when mounted', () => {
      garbageCollector.acquireHold = jest.fn();
      renderElement(
        <RelayRenderer
          Container={MockContainer}
          queryConfig={queryConfig}
          environment={environment}
        />
      );
      expect(garbageCollector.acquireHold).toBeCalled();
    });

    it('releases its GC hold when unmounted', () => {
      const release = jest.fn();
      garbageCollector.acquireHold =
        jest.fn(() => ({release}));
      renderElement(
        <RelayRenderer
          Container={MockContainer}
          queryConfig={queryConfig}
          environment={environment}
        />
      );
      expect(release).not.toBeCalled();
      ReactDOM.unmountComponentAtNode(container);
      expect(release).toBeCalled();
    });
  });
});