text: /**
   * 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('RelayContainerComparators')
    .mock('warning');
  
  const GraphQLStoreQueryResolver = require('GraphQLStoreQueryResolver');
  const QueryBuilder = require('QueryBuilder');
  const React = require('React');
  const ReactTestUtils = require('ReactTestUtils');
  const Relay = require('Relay');
  const RelayEnvironment = require('RelayEnvironment');
  const RelayMutation = require('RelayMutation');
  const RelayQuery = require('RelayQuery');
  const RelayRoute = require('RelayRoute');
  const RelayTestUtils = require('RelayTestUtils');
  
  const warning = require('warning');
  
  describe('RelayContainer', function() {
    let MockContainer;
    let MockComponent;
    let RelayTestRenderer;
  
    let environment;
    let mockBarPointer;
    let mockFooFragment;
    let mockFooPointer;
    let mockRoute;
    let render;
  
    const {getNode, getPointer} = RelayTestUtils;
  
    beforeEach(function() {
      jest.resetModules();
  
      render = jest.fn(function() {
        // Make it easier to expect prop values.
        render.mock.calls[render.mock.calls.length - 1].props = this.props;
        return <div />;
      });
      MockComponent = React.createClass({render});
      MockContainer = Relay.createContainer(MockComponent, {
        fragments: {
          foo: jest.fn(
            () => Relay.QL`fragment on Node{id,name}`
          ),
          bar: jest.fn(
            () => Relay.QL`fragment on Node @relay(plural:true){id,name}`
          ),
        },
      });
      MockContainer.mock = {render};
  
      environment = new RelayEnvironment();
      mockRoute = RelayRoute.genMockInstance();
      mockFooFragment = getNode(MockContainer.getFragment('foo').getFragment({}));
      mockFooPointer = getPointer('42', mockFooFragment);
      const mockBarFragment =
        getNode(MockContainer.getFragment('bar').getFragment());
      mockBarPointer = getPointer('42', mockBarFragment);
  
      RelayTestRenderer = RelayTestUtils.createRenderer();
  
      jasmine.addMatchers(RelayTestUtils.matchers);
    });
  
    describe('fragments', () => {
      it('throws if fragments are missing from spec', () => {
        expect(() => {
          Relay.createContainer(MockComponent, {});
        }).toFailInvariant(
          'Relay.createContainer(MockComponent, ...): Missing `fragments`, ' +
          'which is expected to be an object mapping from `propName` to: ' +
          '() => Relay.QL`...`'
        );
      });
  
      it('throws if container defines invalid `Relay.QL` fragment', () => {
        const BadContainer = Relay.createContainer(MockComponent, {
          fragments: {
            viewer: () => Relay.QL`query{node(id:"123"){id}}`,
          },
        });
        const badFragmentReference = BadContainer.getFragment('viewer');
        expect(() => {
          badFragmentReference.getFragment();
        }).toFailInvariant(
          'Relay.QL defined on container `Relay(MockComponent)` named `viewer` ' +
          'is not a valid fragment. A typical fragment is defined using: ' +
          'Relay.QL`fragment on Type {...}`'
        );
      });
  
      it('throws if container defines a fragment without function', () => {
        const BadContainer = Relay.createContainer(MockComponent, {
          fragments: {
            viewer: Relay.QL`
              fragment on Viewer {
                newsFeed
              }
            `,
          },
        });
        expect(() => {
          BadContainer.getFragment('viewer');
        }).toFailInvariant(
          'RelayContainer: Expected `Relay(MockComponent).fragments.viewer` to ' +
          'be a function returning a fragment. Example: ' +
          '`viewer: () => Relay.QL`fragment on ...`'
        );
      });
  
      it('throws if fragment and variable names are not unique', () => {
        Relay.createContainer(MockComponent, {
          initialVariables: {
            badName: '100',
          },
          fragments: {
            badName: () => Relay.QL`
              fragment on Actor {
                profilePicture(size:$badName) {
                  uri
                }
              }
            `,
          },
        });
        expect([
          'Relay.createContainer(%s, ...): `%s` is used both ' +
          'as a fragment name and variable name. Please give them unique names.',
          'MockComponent',
          'badName',
        ]).toBeWarnedNTimes(1);
      });
  
      it('creates query for a container without fragments', () => {
        // Test that scalar constants are substituted, not only query fragments.
        const MockProfilePhoto = Relay.createContainer(MockComponent, {
          initialVariables: {
            testPhotoSize: '100',
          },
          fragments: {
            photo: () => Relay.QL`
              fragment on Actor {
                profilePicture(size:$testPhotoSize) {
                  uri
                }
              }
            `,
          },
        });
        const fragment = getNode(
          MockProfilePhoto.getFragment('photo'),
          {}
        );
        expect(fragment).toEqualQueryNode(getNode(Relay.QL`
          fragment on Actor {
            profilePicture(size: "100") {
              uri
            }
          }
        `));
      });
  
      it('creates query for a container with fragments', () => {
        const anotherComponent = React.createClass({render: () => null});
        const MockProfile = Relay.createContainer(MockComponent, {
          fragments: {
            user: () => Relay.QL`
              fragment on Actor {
                id
                name
                ${MockProfileLink.getFragment('user')}
              }
            `,
          },
        });
        const MockProfileLink = Relay.createContainer(anotherComponent, {
          fragments: {
            user: () => Relay.QL`
              fragment on Actor {
                id
                url
              }
            `,
          },
        });
        const fragment = getNode(
          MockProfile.getFragment('user'),
          {}
        );
        expect(fragment).toEqualQueryNode(getNode(Relay.QL`
          fragment on Actor {
            id
            __typename
            name
            ${Relay.QL`
              fragment on Actor {
                id,
                __typename,
                url,
              }
            `},
          }
        `));
      });
  
      it('returns whether a named fragment is defined', () => {
        expect(MockContainer.hasFragment('foo')).toBe(true);
        expect(MockContainer.hasFragment('bar')).toBe(true);
        expect(MockContainer.hasFragment('baz')).toBe(false);
      });
    });
  
    describe('conditional fragments', () => {
      let MockProfile;
      let profileFragment;
  
      beforeEach(() => {
        MockProfile = Relay.createContainer(MockComponent, {
          fragments: {
            viewer: () => Relay.QL`
              fragment on Viewer {
                primaryEmail
              }
            `,
          },
        });
        profileFragment = QueryBuilder.createFragment({
          name: 'Test',
          type: 'Viewer',
          children: [QueryBuilder.createField({fieldName: 'primaryEmail'})],
        });
      });
  
      it('can conditionally include a fragment based on variables', () => {
        const MockSideshow = Relay.createContainer(MockComponent, {
          initialVariables: {
            hasSideshow: null,
          },
          fragments: {
            viewer: variables => Relay.QL`
              fragment on Viewer {
                ${MockProfile.getFragment('viewer').if(variables.hasSideshow)}
              }
            `,
          },
        });
  
        // hasSideshow: true
        let fragment = getNode(
          MockSideshow.getFragment('viewer', {
            hasSideshow: QueryBuilder.createCallVariable('sideshow'),
          }),
          {sideshow: true}
        );
        const expected = RelayQuery.Fragment.build(
          'Test',
          'Viewer',
          [getNode(profileFragment)]
        );
        expect(fragment).toEqualQueryNode(expected);
  
        // hasSideshow: false
        fragment = getNode(
          MockSideshow.getFragment('viewer', {
            hasSideshow: QueryBuilder.createCallVariable('sideshow'),
          }),
          {sideshow: false}
        );
        expect(fragment.getChildren().length).toBe(0);
      });
  
      it('can conditionally exclude a fragment based on variables', () => {
        const MockSideshow = Relay.createContainer(MockComponent, {
          initialVariables: {
            hasSideshow: null,
          },
          fragments: {
            viewer: variables => Relay.QL`
              fragment on Viewer {
                ${MockProfile
                  .getFragment('viewer')
                  .unless(variables.hasSideshow)}
              }
            `,
          },
        });
  
        // hasSideshow: true
        let fragment = getNode(
          MockSideshow.getFragment('viewer', {hasSideshow: true}),
          {}
        );
        expect(fragment.getChildren().length).toBe(0);
  
        // hasSideshow: false
        fragment = getNode(
          MockSideshow.getFragment('viewer', {hasSideshow: false}),
          {}
        );
        const expected = RelayQuery.Fragment.build(
          'Test',
          'Viewer',
          [getNode(profileFragment)],
        );
        expect(fragment).toEqualQueryNode(expected);
      });
    });
  
    it('throws if rendered without a relay context', () => {
      const ShallowRenderer = ReactTestUtils.createRenderer();
      expect(() => ShallowRenderer.render(
        <MockContainer foo={mockFooPointer} />
      )).toFailInvariant(
        'RelayContainer: `Relay(MockComponent)` was rendered with invalid ' +
        'Relay context `undefined`. Make sure the `relay` property on the ' +
        'React context conforms to the `RelayEnvironment` interface.'
      );
    });
  
    it('throws if rendered with an invalid relay context', () => {
      const fakeContext = {};
      const ShallowRenderer = ReactTestUtils.createRenderer();
      expect(() => ShallowRenderer.render(
        <MockContainer foo={mockFooPointer} />,
        {relay: fakeContext}
      )).toFailInvariant(
        'RelayContainer: `Relay(MockComponent)` was rendered with invalid ' +
        'Relay context `[object Object]`. Make sure the `relay` property on ' +
        'the React context conforms to the `RelayEnvironment` interface.'
      );
    });
  
    it('throws if rendered without a route', () => {
      const ShallowRenderer = ReactTestUtils.createRenderer();
      const relay = {
        environment,
        variables: {},
      };
      expect(() => ShallowRenderer.render(
        <MockContainer foo={mockFooPointer} />,
        {relay}
      )).toFailInvariant(
        'RelayContainer: `Relay(MockComponent)` was rendered without a valid ' +
        'route. Make sure the route is valid, and make sure that it is ' +
        'correctly set on the parent component\'s context ' +
        '(e.g. using <RelayRootContainer>).'
      );
    });
  
    describe('props.relay.variables', () => {
      it('starts with initial variables', () => {
        MockContainer = Relay.createContainer(MockComponent, {
          initialVariables: {
            public: 'instance',
            private: 'instance',
          },
          fragments: {
            foo: jest.fn(
              () => Relay.QL`fragment on Node{id,name}`
            ),
          },
        });
        MockContainer.mock = {render};
        RelayTestRenderer.render(
          () => <MockContainer foo={mockFooPointer} />,
          environment,
          mockRoute
        );
        const props = MockContainer.mock.render.mock.calls[0].props;
        expect(props.relay.variables).toEqual({
          public: 'instance',
          private: 'instance',
        });
      });
  
      it('starts with initial + parent variables', () => {
        MockContainer = Relay.createContainer(MockComponent, {
          initialVariables: {
            public: 'instance',
            private: 'instance',
          },
          fragments: {
            foo: jest.fn(
              () => Relay.QL`fragment on Node{id,name}`
            ),
          },
        });
        MockContainer.mock = {render};
        RelayTestRenderer.render(
          () => <MockContainer foo={mockFooPointer} public="parent" />,
          environment,
          mockRoute
        );
        const props = MockContainer.mock.render.mock.calls[0].props;
        expect(props.relay.variables).toEqual({
          public: 'parent',
          private: 'instance',
        });
      });
  
      it('starts with prepared initial variables', () => {
        const prepareVariables = jest.fn((vars, route) => {
          expect(route.name).toBe(mockRoute.name);
          return {
            ...vars,
            private: 'prepared',
          };
        });
        MockContainer = Relay.createContainer(MockComponent, {
          initialVariables: {
            public: 'instance',
            private: 'instance',
          },
          prepareVariables,
          fragments: {
            foo: jest.fn(
              () => Relay.QL`fragment on Node{id,name}`
            ),
          },
        });
        MockContainer.mock = {render};
        RelayTestRenderer.render(
          () => <MockContainer foo={mockFooPointer} />,
          environment,
          mockRoute
        );
        const props = MockContainer.mock.render.mock.calls[0].props;
        expect(props.relay.variables).toEqual({
          public: 'instance',
          private: 'prepared',
        });
      });
  
      it('starts with prepared initial + parent variables', () => {
        const prepareVariables = jest.fn((vars, route) => {
          expect(route.name).toBe(mockRoute.name);
          return {
            ...vars,
            private: 'prepared',
          };
        });
        MockContainer = Relay.createContainer(MockComponent, {
          initialVariables: {
            public: 'instance',
            private: 'instance',
          },
          prepareVariables,
          fragments: {
            foo: jest.fn(
              () => Relay.QL`fragment on Node{id,name}`
            ),
          },
        });
        MockContainer.mock = {render};
        RelayTestRenderer.render(
          () => <MockContainer foo={mockFooPointer} public="parent" />,
          environment,
          mockRoute
        );
        const props = MockContainer.mock.render.mock.calls[0].props;
        expect(props.relay.variables).toEqual({
          public: 'parent',
          private: 'prepared',
        });
      });
  
      it('updates with prepared initial + parent variables', () => {
        const prepareVariables = jest.fn((vars, route) => {
          expect(route.name).toBe(mockRoute.name);
          return {
            ...vars,
            private: 'prepared1',
          };
        });
        MockContainer = Relay.createContainer(MockComponent, {
          initialVariables: {
            public: 'instance',
            private: 'instance',
          },
          prepareVariables,
          fragments: {
            foo: jest.fn(
              () => Relay.QL`fragment on Node{id,name}`
            ),
          },
        });
        MockContainer.mock = {render};
        RelayTestRenderer.render(
          () => <MockContainer foo={mockFooPointer} public="parent1" />,
          environment,
          mockRoute
        );
        const props = MockContainer.mock.render.mock.calls[0].props;
        expect(props.relay.variables).toEqual({
          public: 'parent1',
          private: 'prepared1',
        });
  
        prepareVariables.mockImplementation(vars => ({
          ...vars,
          private: 'prepared2',
        }));
        RelayTestRenderer.render(
          () => <MockContainer foo={mockFooPointer} public="parent2" />,
          environment,
          mockRoute
        );
        const nextProps = MockContainer.mock.render.mock.calls[1].props;
        expect(nextProps.relay.variables).toEqual({
          public: 'parent2',
          private: 'prepared2',
        });
      });
    });
  
    describe('props.relay.applyUpdate', () => {
      it('forwards to the underlying RelayEnvironment', () => {
        const mockMutation = new RelayMutation();
        environment.applyUpdate = jest.fn();
        render.mockImplementation(function() {
          this.props.relay.applyUpdate(mockMutation);
        });
        RelayTestRenderer.render(
          () => <MockContainer />,
          environment,
          mockRoute
        );
        expect(environment.applyUpdate.mock.calls[0][0]).toBe(mockMutation);
      });
    });
  
    describe('props.relay.commitUpdate', () => {
      it('forwards to the underlying RelayEnvironment', () => {
        const mockMutation = new RelayMutation();
        environment.commitUpdate = jest.fn();
        render.mockImplementation(function() {
          this.props.relay.commitUpdate(mockMutation);
        });
        RelayTestRenderer.render(
          () => <MockContainer />,
          environment,
          mockRoute
        );
        expect(environment.commitUpdate.mock.calls[0][0]).toBe(mockMutation);
      });
    });
  
    it('creates resolvers for each query prop with a fragment pointer', () => {
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRoute
      );
      expect(environment.getFragmentResolver.mock.calls.length).toBe(1);
      expect(GraphQLStoreQueryResolver.mock.instances.length).toBe(1);
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} bar={[mockBarPointer]} />,
        environment,
        mockRoute
      );
      // `foo` resolver is re-used, `bar` is added
      expect(environment.getFragmentResolver.mock.calls.length).toBe(2);
      expect(GraphQLStoreQueryResolver.mock.instances.length).toBe(2);
    });
  
    it('recreates resolvers when relay context changes', () => {
      const environmentA = new RelayEnvironment();
      const environmentB = new RelayEnvironment();
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environmentA,
        mockRoute
      );
  
      expect(environmentA.getFragmentResolver.mock.calls.length).toBe(1);
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(1);
      expect(mockResolvers[0].dispose).not.toBeCalled();
      environmentA.getFragmentResolver.mockClear();
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environmentB,
        mockRoute
      );
  
      expect(environmentA.getFragmentResolver.mock.calls.length).toBe(0);
      expect(environmentB.getFragmentResolver.mock.calls.length).toBe(1);
      expect(mockResolvers.length).toBe(2);
      expect(mockResolvers[1].mock.store).toBe(environmentB.getStoreData());
      expect(mockResolvers[0].dispose).toBeCalled();
      expect(mockResolvers[1].dispose).not.toBeCalled();
    });
  
    it('reuses resolvers even if route changes', () => {
      const MockRouteA = RelayRoute.genMock();
      const MockRouteB = RelayRoute.genMock();
  
      const mockRouteA = new MockRouteA();
      const mockRouteB = new MockRouteB();
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRouteA
      );
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRouteB
      );
  
      expect(environment.getFragmentResolver.mock.calls.length).toBe(1);
      expect(GraphQLStoreQueryResolver.mock.instances.length).toBe(1);
      expect(GraphQLStoreQueryResolver.mock.instances[0].dispose).not.toBeCalled();
    });
  
    it('resolves each prop with a query', () => {
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRoute
      );
      const fragment = getNode(MockContainer.getFragment('foo'));
  
      expect(environment.getFragmentResolver.mock.calls.length).toBe(1);
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(1);
      expect(mockResolvers[0].resolve.mock.calls[0][0])
        .toEqualQueryNode(fragment);
      expect(mockResolvers[0].resolve.mock.calls[0][1])
        .toEqual(mockFooPointer.__dataID__);
    });
  
    it('re-resolves props when notified of changes', () => {
      const mockData = {__dataID__: '42', id: '42', name: 'Tim'};
  
      GraphQLStoreQueryResolver.mockDefaultResolveImplementation(() => mockData);
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRoute
      );
  
      expect(environment.getFragmentResolver.mock.calls.length).toBe(1);
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      mockResolvers[0].mock.callback();
  
      expect(mockResolvers.length).toBe(1);
      expect(mockResolvers[0].dispose.mock.calls.length).toBe(0);
      expect(mockResolvers[0].resolve.mock.calls.length).toBe(2);
    });
  
    it('re-resolves props when relay context changes', () => {
      const environmentA = new RelayEnvironment();
      const environmentB = new RelayEnvironment();
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environmentA,
        mockRoute
      );
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environmentB,
        mockRoute
      );
  
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(2);
      expect(mockResolvers[0].resolve.mock.calls.length).toBe(1);
      expect(mockResolvers[1].resolve.mock.calls.length).toBe(1);
    });
  
    it('re-resolves props when route changes', () => {
      const MockRouteA = RelayRoute.genMock();
      const MockRouteB = RelayRoute.genMock();
  
      const mockRouteA = new MockRouteA();
      const mockRouteB = new MockRouteB();
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRouteA
      );
  
      expect(environment.getFragmentResolver.mock.calls.length).toBe(1);
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(1);
      expect(mockResolvers[0].resolve.mock.calls.length).toBe(1);
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRouteB
      );
  
      expect(mockResolvers.length).toBe(1);
      expect(mockResolvers[0].resolve.mock.calls.length).toBe(2);
    });
  
    it('updates relay.route when route changes', () => {
      const MockRouteA = RelayRoute.genMock();
      const MockRouteB = RelayRoute.genMock();
  
      const mockRouteA = new MockRouteA();
      const mockRouteB = new MockRouteB();
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRouteA
      );
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRouteB
      );
  
      const routeAProps = MockContainer.mock.render.mock.calls[0].props;
      const routeBProps = MockContainer.mock.render.mock.calls[1].props;
      expect(routeAProps.relay.route).toBe(mockRouteA);
      expect(routeBProps.relay.route).toBe(mockRouteB);
    });
  
    it('resolves with most recent props', () => {
      const fooFragment = getNode(MockContainer.getFragment('foo'));
      const mockPointerA = getPointer('42', mockFooFragment);
      const mockPointerB = getPointer('43', mockFooFragment);
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockPointerA} />,
        environment,
        mockRoute
      );
      RelayTestRenderer.render(
        () => <MockContainer foo={mockPointerB} />,
        environment,
        mockRoute
      );
  
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
  
      expect(mockResolvers.length).toBe(1);
      expect(mockResolvers[0].dispose.mock.calls.length).toBe(0);
      expect(mockResolvers[0].resolve.mock.calls.length).toBe(2);
      expect(mockResolvers[0].resolve.mock.calls[0][0])
        .toEqualQueryNode(fooFragment);
      expect(mockResolvers[0].resolve.mock.calls[0][1])
        .toEqual(mockPointerA.__dataID__);
      expect(mockResolvers[0].resolve.mock.calls[1][0])
        .toEqualQueryNode(fooFragment);
      expect(mockResolvers[0].resolve.mock.calls[1][1])
        .toEqual(mockPointerB.__dataID__);
    });
  
    it('does not create resolvers for null/undefined props', () => {
      RelayTestRenderer.render(
        () => <MockContainer foo={null} bar={undefined} />,
        environment,
        mockRoute
      );
  
      expect(environment.getFragmentResolver.mock.calls.length).toBe(0);
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(0);
      const props = MockContainer.mock.render.mock.calls[0].props;
      expect(props.bar).toBe(undefined);
      expect(props.foo).toBe(null);
    });
  
    it('warns if props are missing fragment pointers', () => {
      const mockData = {};
      RelayTestRenderer.render(
        () => <MockContainer foo={mockData} bar={null} />,
        environment,
        mockRoute
      );
  
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(0);
      const props = MockContainer.mock.render.mock.calls[0].props;
      expect(props.bar).toBe(null);
      expect(props.foo).toBe(mockData);
  
      expect(warning.mock.calls.filter(call =>
        call[0] === false && call[1].indexOf(
          'RelayContainer: component `%s` was rendered with variables ' +
          'that differ from the variables used to fetch fragment ' +
          '`%s`.'
        ) === 0
      ).length).toBe(1);
    });
  
    it('warns if fragment pointer exists on a different prop', () => {
      mockFooPointer = getPointer('42', mockFooFragment);
  
      RelayTestRenderer.render(
        () => <MockContainer baz={mockFooPointer} />,
        environment,
        mockRoute
      );
  
      expect([
        'RelayContainer: Expected record data for prop `%s` on `%s`, ' +
        'but it was instead on prop `%s`. Did you misspell a prop or ' +
        'pass record data into the wrong prop?',
        'foo',
        'MockComponent',
        'baz',
      ]).toBeWarnedNTimes(1);
    });
  
    it('does not warn if fragment hash exists on a different prop', () => {
      const deceptiveArray = [];
      deceptiveArray[Object.keys(mockFooPointer)[0]] = {};
  
      RelayTestRenderer.render(
        () => <MockContainer baz={deceptiveArray} />,
        environment,
        mockRoute
      );
  
      expect([
        'RelayContainer: Expected record data for prop `%s` on `%s`, ' +
        'but it was instead on prop `%s`. Did you misspell a prop or ' +
        'pass record data into the wrong prop?',
        'foo',
        'MockComponent',
        'baz',
      ]).toBeWarnedNTimes(0);
    });
  
    it('warns if a fragment is not passed in', () => {
      RelayTestRenderer.render(
        () => <MockContainer foo={null} />,
        environment,
        mockRoute
      );
  
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(0);
      const props = MockContainer.mock.render.mock.calls[0].props;
      expect(props.bar).toBe(undefined);
      expect(props.foo).toBe(null);
  
      expect([
        'RelayContainer: Expected prop `%s` to be supplied to `%s`, but ' +
        'got `undefined`. Pass an explicit `null` if this is intentional.',
        'bar',
        'MockComponent',
      ]).toBeWarnedNTimes(1);
    });
  
    it('warns if a fragment prop is not an object', () => {
      RelayTestRenderer.render(
        () => <MockContainer foo={''} />,
        environment,
        mockRoute
      );
  
      const mockResolvers = GraphQLStoreQueryResolver.mock.instances;
      expect(mockResolvers.length).toBe(0);
      const props = MockContainer.mock.render.mock.calls[0].props;
      expect(props.bar).toBe(undefined);
      expect(props.foo).toBe('');
  
      expect([
        'RelayContainer: Expected prop `%s` supplied to `%s` to be an ' +
        'object, got `%s`.',
        'foo',
        'MockComponent',
        '',
      ]).toBeWarnedNTimes(1);
    });
  
    it('throws if non-plural fragment receives an array', () => {
      const mockData = [];
      expect(() => {
        RelayTestRenderer.render(
          () => <MockContainer foo={mockData} />,
          environment,
          mockRoute
        );
      }).toFailInvariant(
        'RelayContainer: Invalid prop `foo` supplied to `MockComponent`, ' +
        'expected a single record because the corresponding fragment is not ' +
        'plural (i.e. does not have `@relay(plural: true)`).'
      );
    });
  
    it('throws if plural fragment receives a non-array', () => {
      const mockData = {};
      expect(() => {
        RelayTestRenderer.render(
          () => <MockContainer bar={mockData} />,
          environment,
          mockRoute
        );
      }).toFailInvariant(
        'RelayContainer: Invalid prop `bar` supplied to `MockComponent`, ' +
        'expected an array of records because the corresponding fragment has ' +
        '`@relay(plural: true)`.'
      );
    });
  
    it('warns if plural fragment array item is missing fragment pointers', () => {
      const mockData = [{}];
      RelayTestRenderer.render(
        () => <MockContainer bar={mockData} />,
        environment,
        mockRoute
      );
  
      expect(warning.mock.calls.filter(call =>
        call[0] === false && call[1].indexOf(
          'RelayContainer: component `%s` was rendered with variables ' +
          'that differ from the variables used to fetch fragment ' +
          '`%s`.'
        ) === 0
      ).length).toBe(1);
    });
  
    it('throws if some plural fragment items are null', () => {
      const mockData = [mockBarPointer, null];
      expect(() => {
        RelayTestRenderer.render(
          () => <MockContainer bar={mockData} />,
          environment,
          mockRoute
        );
      }).toFailInvariant(
        'RelayContainer: Invalid prop `bar` supplied to `MockComponent`. Some ' +
        'array items contain data fetched by Relay and some items contain ' +
        'null/mock data.'
      );
    });
  
    it('throws if some but not all plural fragment items are mocked', () => {
      const mockData = [mockBarPointer, {}];
      expect(() => {
        RelayTestRenderer.render(
          () => <MockContainer bar={mockData} />,
          environment,
          mockRoute
        );
      }).toFailInvariant(
        'RelayContainer: Invalid prop `bar` supplied to `MockComponent`. Some ' +
        'array items contain data fetched by Relay and some items contain ' +
        'null/mock data.'
      );
    });
  
    it('passes through empty arrays for plural fragments', () => {
      RelayTestRenderer.render(
        () => <MockContainer bar={[]} />,
        environment,
        mockRoute
      );
      expect(MockContainer.mock.render.mock.calls.length).toBe(1);
      expect(MockContainer.mock.render.mock.calls[0].props.bar).toEqual([]);
      expect(environment.getFragmentResolver).not.toBeCalled();
    });
  
    it('does not re-render if props resolve to the same object', () => {
      const mockData = {__dataID__: '42', id: '42', name: 'Tim'};
  
      GraphQLStoreQueryResolver.mockDefaultResolveImplementation(() => mockData);
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRoute
      );
  
      expect(MockContainer.mock.render.mock.calls.length).toBe(1);
      expect(MockContainer.mock.render.mock.calls[0].props.foo).toEqual(mockData);
  
      GraphQLStoreQueryResolver.mock.instances[0].mock.callback();
  
      expect(MockContainer.mock.render.mock.calls.length).toBe(1);
    });
  
    it('re-renders if props resolve to different objects', () => {
      const mockDataList = [
        {__dataID__: '42', id: '42', name: 'Tim', ...mockFooPointer},
        {__dataID__: '42', id: '42', name: 'Tee', ...mockFooPointer},
      ];
  
      GraphQLStoreQueryResolver.mockResolveImplementation(0, function() {
        return mockDataList[this.resolve.mock.calls.length - 1];
      });
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} />,
        environment,
        mockRoute
      );
  
      expect(MockContainer.mock.render.mock.calls.length).toBe(1);
      expect(MockContainer.mock.render.mock.calls[0].props.foo).toEqual(
        mockDataList[0]
      );
  
      GraphQLStoreQueryResolver.mock.instances[0].mock.callback();
  
      expect(MockContainer.mock.render.mock.calls.length).toBe(2);
      expect(MockContainer.mock.render.mock.calls[1].props.foo).toEqual(
        mockDataList[1]
      );
    });
  
    it('renders with identical props if no data has changed', () => {
      // Non-scalars deoptimize `RelayContainer.shouldComponentUpdate`.
      const nonScalar = {};
  
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} deopt={nonScalar} />,
        environment,
        mockRoute
      );
      RelayTestRenderer.render(
        () => <MockContainer foo={mockFooPointer} deopt={nonScalar} />,
        environment,
        mockRoute
      );
      expect(MockContainer.mock.render.mock.calls.length).toBe(2);
  
      const propsA = MockContainer.mock.render.mock.calls[0].props;
      const propsB = MockContainer.mock.render.mock.calls[1].props;
  
      const propNamesA = Object.keys(propsA);
      const propNamesB = Object.keys(propsB);
  
      // Fix for V8 bug where insertion order isn't preserved (see #10804655)
      expect(propNamesA.sort()).toEqual(propNamesB.sort());
  
      propNamesA.forEach(propName => {
        expect(propsA[propName]).toBe(propsB[propName]);
      });
    });
  
    it('applies `shouldComponentUpdate` properly', () => {
      const mockDataSet = {
        '42': {__dataID__: '42', name: 'Tim'},
        '43': {__dataID__: '43', name: 'Tee'},
        '44': {__dataID__: '44', name: 'Toe'},
      };
      render = jest.fn(() => <div />);
      const shouldComponentUpdate = jest.fn();
  
      const MockFastComponent = React.createClass({render, shouldComponentUpdate});
  
      const MockFastContainer = Relay.createContainer(MockFastComponent, {
        fragments: {
          foo: jest.fn(
            () => Relay.QL`fragment on Node{id,name}`
          ),
        },
      });
  
      GraphQLStoreQueryResolver.mockResolveImplementation(0, (_, dataID) => {
        return mockDataSet[dataID];
      });
      mockFooFragment =
        getNode(MockFastContainer.getFragment('foo').getFragment({}));
      const mockPointerA = getPointer('42', mockFooFragment);
      const mockPointerB = getPointer('43', mockFooFragment);
      const mockPointerC = getPointer('44', mockFooFragment);
  
      RelayTestRenderer.render(
        () => <MockFastContainer foo={mockPointerA} />,
        environment,
        mockRoute
      );
      expect(render.mock.calls.length).toBe(1);
  
      shouldComponentUpdate.mockReturnValue(true);
  
      // Component wants to update, RelayContainer doesn't.
      RelayTestRenderer.render(
        () => <MockFastContainer foo={mockPointerA} />,
        environment,
        mockRoute
      );
      expect(render.mock.calls.length).toBe(1);
  
      // Component wants to update, RelayContainer does too.
      RelayTestRenderer.render(
        () => <MockFastContainer foo={mockPointerB} />,
        environment,
        mockRoute
      );
      expect(render.mock.calls.length).toBe(2);
  
      shouldComponentUpdate.mockReturnValue(false);
  
      // Component doesn't want to update, RelayContainer does.
      RelayTestRenderer.render(
        () => <MockFastContainer foo={mockPointerC} />,
        environment,
        mockRoute
      );
      expect(render.mock.calls.length).toBe(2);
  
      // Component doesn't want to update, RelayContainer doesn't either.
      RelayTestRenderer.render(
        () => <MockFastContainer foo={mockPointerC} />,
        environment,
        mockRoute
      );
      expect(render.mock.calls.length).toBe(2);
  
      shouldComponentUpdate.mockReturnValue(true);
      RelayTestRenderer.render(
        () => <MockFastContainer foo={mockPointerC} thing="scalar" />,
        environment,
        mockRoute
      );
      expect(render.mock.calls.length).toBe(3);
    });
  
    it('applies `shouldComponentUpdate` properly', () => {
      const mockDataSet = {
        '42': {__dataID__: '42', name: 'Tim'},
      };
      render = jest.genMockFunction().mockImplementation(() => <div />);
      const shouldComponentUpdate = jest.fn(() => true);
  
      const MockAlwaysUpdateComponent = Relay.createContainer(
        React.createClass({render, shouldComponentUpdate}),
        {
          shouldComponentUpdate,
          fragments: {
            foo: jest.genMockFunction().mockImplementation(
              () => Relay.QL`fragment on Node{id,name}`
            ),
          },
        }
      );
  
      GraphQLStoreQueryResolver.mockResolveImplementation(0, (_, dataID) => {
        return mockDataSet[dataID];
      });
      mockFooFragment =
        getNode(MockAlwaysUpdateComponent.getFragment('foo').getFragment({}));
      const mockPointerA = getPointer('42', mockFooFragment);
  
      RelayTestRenderer.render(
        () => <MockAlwaysUpdateComponent foo={mockPointerA} />,
        environment,
        mockRoute
      );
      expect(render.mock.calls.length).toBe(1);
  
      RelayTestRenderer.render(
        () => <MockAlwaysUpdateComponent foo={mockPointerA} />,
        environment,
        mockRoute
      );
      expect(shouldComponentUpdate.mock.calls.length).toBe(2);
      expect(shouldComponentUpdate.mock.calls[0].length).toBe(0);
      expect(render.mock.calls.length).toBe(2);
    });
  });
  