text: /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @emails oncall+relay
   * @format
   */
  
  'use strict';
  
  jest.mock('../../legacy/store/generateClientID').mock('warning');
  
  require('configureForRelayOSS');
  
  const GraphQLRange = require('../../legacy/store/GraphQLRange');
  const RelayClassic = require('../../RelayPublic');
  const RelayMetaRoute = require('../../route/RelayMetaRoute');
  const RelayQuery = require('../../query/RelayQuery');
  const RelayTestUtils = require('RelayTestUtils');
  
  const {ConnectionInterface} = require('relay-runtime');
  
  describe('writeRelayQueryPayload()', () => {
    let RelayRecordStore;
    let RelayRecordWriter;
  
    const {getNode, writePayload} = RelayTestUtils;
    let END_CURSOR, HAS_NEXT_PAGE, HAS_PREV_PAGE, PAGE_INFO, START_CURSOR;
  
    beforeEach(() => {
      jest.resetModules();
  
      RelayRecordStore = require('../../store/RelayRecordStore');
      RelayRecordWriter = require('../../store/RelayRecordWriter');
  
      ({
        END_CURSOR,
        HAS_NEXT_PAGE,
        HAS_PREV_PAGE,
        PAGE_INFO,
        START_CURSOR,
      } = ConnectionInterface.get());
  
      expect.extend(RelayTestUtils.matchers);
    });
  
    it('creates empty first() connection records', () => {
      const records = {};
      const store = new RelayRecordStore({records});
      const writer = new RelayRecordWriter(records, {}, false);
      const query = getNode(
        RelayClassic.QL`
        query {
          node(id:"123") {
            friends(first: 3) {
              edges {
                cursor
                node {
                  id
                }
                source {
                  id
                }
              }
              pageInfo {
                hasNextPage
                hasPreviousPage
              }
            }
          }
        }
      `,
      );
  
      const payload = {
        node: {
          id: '123',
          friends: {
            edges: [],
            [PAGE_INFO]: {
              [HAS_NEXT_PAGE]: false,
              [HAS_PREV_PAGE]: false,
            },
          },
          __typename: 'User',
        },
      };
  
      const results = writePayload(store, writer, query, payload);
      expect(results).toEqual({
        created: {
          '123': true,
          'client:1': true, // `friends` connection
        },
        updated: {},
      });
      expect(
        store.getRangeMetadata('client:1', [{name: 'first', value: 3}]),
      ).toEqual({
        diffCalls: [],
        filterCalls: [],
        pageInfo: {
          [END_CURSOR]: null,
          [HAS_NEXT_PAGE]: false,
          [HAS_PREV_PAGE]: false,
          [START_CURSOR]: null,
        },
        requestedEdgeIDs: [],
        filteredEdges: [],
      });
    });
  
    it('creates first() connection records', () => {
      const records = {};
      const store = new RelayRecordStore({records});
      const writer = new RelayRecordWriter(records, {}, false);
      const query = getNode(
        RelayClassic.QL`
        query {
          node(id:"123") {
            friends(first: 3) {
              edges {
                cursor
                node {
                  id
                }
                source {
                  id
                }
              }
              pageInfo {
                hasNextPage
                hasPreviousPage
              }
            }
          }
        }
      `,
      );
      const payload = {
        node: {
          id: '123',
          friends: {
            edges: [
              {
                cursor: 'friend1',
                node: {
                  id: 'friend1ID',
                },
                source: {
                  id: '123',
                },
              },
              {
                cursor: 'friend2',
                node: {
                  id: 'friend2ID',
                },
                source: {
                  id: '123',
                },
              },
              {
                cursor: 'friend3',
                node: {
                  id: 'friend3ID',
                },
                source: {
                  id: '123',
                },
              },
            ],
            [PAGE_INFO]: {
              [HAS_NEXT_PAGE]: true,
              [HAS_PREV_PAGE]: false,
            },
          },
          __typename: 'User',
        },
      };
      const results = writePayload(store, writer, query, payload);
      expect(results).toEqual({
        created: {
          '123': true,
          'client:1': true, // `friends` connection
          'client:client:1:friend1ID': true, // edges
          'client:client:1:friend2ID': true,
          'client:client:1:friend3ID': true,
          friend1ID: true, // nodes
          friend2ID: true,
          friend3ID: true,
        },
        updated: {},
      });
      expect(store.getField('friend1ID', 'id')).toBe('friend1ID');
      expect(store.getField('friend2ID', 'id')).toBe('friend2ID');
      expect(store.getField('friend3ID', 'id')).toBe('friend3ID');
      expect(
        store.getRangeMetadata('client:1', [{name: 'first', value: 3}]),
      ).toEqual({
        diffCalls: [],
        filterCalls: [],
        pageInfo: {
          [END_CURSOR]: 'friend3',
          [HAS_NEXT_PAGE]: true,
          [HAS_PREV_PAGE]: false,
          [START_CURSOR]: 'friend1',
        },
        requestedEdgeIDs: [
          'client:client:1:friend1ID',
          'client:client:1:friend2ID',
          'client:client:1:friend3ID',
        ],
        filteredEdges: [
          {edgeID: 'client:client:1:friend1ID', nodeID: 'friend1ID'},
          {edgeID: 'client:client:1:friend2ID', nodeID: 'friend2ID'},
          {edgeID: 'client:client:1:friend3ID', nodeID: 'friend3ID'},
        ],
      });
    });
  
    it('creates first() connection records when connection node is cached', () => {
      const cachedRecords = {
        123: {
          friends: {__dataID__: 'client:customid'},
        },
        'client:customid': {
          __dataID__: 'client:customid',
          __range__: new GraphQLRange(),
        },
      };
      const records = {};
      const store = new RelayRecordStore({records, cachedRecords});
      const writer = new RelayRecordWriter(records, {}, false);
      const query = getNode(
        RelayClassic.QL`
        query {
          node(id:"123") {
            friends(first: 3) {
              edges {
                cursor
                node {
                  id
                }
                source {
                  id
                }
              }
              pageInfo {
                hasNextPage
                hasPreviousPage
              }
            }
          }
        }
      `,
      );
      const payload = {
        node: {
          id: '123',
          friends: {
            edges: [
              {
                cursor: 'friend1',
                node: {
                  id: 'friend1ID',
                },
                source: {
                  id: '123',
                },
              },
              {
                cursor: 'friend2',
                node: {
                  id: 'friend2ID',
                },
                source: {
                  id: '123',
                },
              },
              {
                cursor: 'friend3',
                node: {
                  id: 'friend3ID',
                },
                source: {
                  id: '123',
                },
              },
            ],
            [PAGE_INFO]: {
              [HAS_NEXT_PAGE]: true,
              [HAS_PREV_PAGE]: false,
            },
          },
          __typename: 'User',
        },
      };
      const results = writePayload(store, writer, query, payload);
      expect(results).toEqual({
        created: {
          'client:client:customid:friend1ID': true, // edges
          'client:client:customid:friend2ID': true,
          'client:client:customid:friend3ID': true,
          friend1ID: true, // nodes
          friend2ID: true,
          friend3ID: true,
        },
        updated: {
          '123': true,
          'client:customid': true, // `friends` connection, reusing client id.
        },
      });
      expect(store.getField('friend1ID', 'id')).toBe('friend1ID');
      expect(store.getField('friend2ID', 'id')).toBe('friend2ID');
      expect(store.getField('friend3ID', 'id')).toBe('friend3ID');
      expect(
        store.getRangeMetadata('client:customid', [{name: 'first', value: 3}]),
      ).toEqual({
        diffCalls: [],
        filterCalls: [],
        pageInfo: {
          [END_CURSOR]: 'friend3',
          [HAS_NEXT_PAGE]: true,
          [HAS_PREV_PAGE]: false,
          [START_CURSOR]: 'friend1',
        },
        requestedEdgeIDs: [
          'client:client:customid:friend1ID',
          'client:client:customid:friend2ID',
          'client:client:customid:friend3ID',
        ],
        filteredEdges: [
          {edgeID: 'client:client:customid:friend1ID', nodeID: 'friend1ID'},
          {edgeID: 'client:client:customid:friend2ID', nodeID: 'friend2ID'},
          {edgeID: 'client:client:customid:friend3ID', nodeID: 'friend3ID'},
        ],
      });
    });
  
    it('skips over null edges and nodes', () => {
      const records = {};
      const store = new RelayRecordStore({records});
      const writer = new RelayRecordWriter(records, {}, false);
      const query = getNode(
        RelayClassic.QL`
        query {
          node(id:"123") {
            friends(first: 3) {
              edges {
                cursor
                node {
                  id
                }
              }
              pageInfo {
                hasNextPage
                hasPreviousPage
              }
            }
          }
        }
      `,
      );
      const payload = {
        node: {
          id: '123',
          friends: {
            edges: [
              null,
              {
                cursor: 'friend2',
                node: null,
              },
              {
                cursor: 'friend3',
                node: {
                  id: 'friend3ID',
                },
              },
            ],
            [PAGE_INFO]: {
              [HAS_NEXT_PAGE]: true,
              [HAS_PREV_PAGE]: false,
            },
          },
          __typename: 'User',
        },
      };
      const results = writePayload(store, writer, query, payload);
      expect(results).toEqual({
        created: {
          '123': true,
          'client:1': true, // `friends` connection
          'client:client:1:friend3ID': true, // edges
          friend3ID: true,
        },
        updated: {},
      });
      expect(store.getField('friend3ID', 'id')).toBe('friend3ID');
      expect(
        store.getRangeMetadata('client:1', [{name: 'first', value: 1}]),
      ).toEqual({
        diffCalls: [],
        filterCalls: [],
        pageInfo: {
          [END_CURSOR]: 'friend3',
          [HAS_NEXT_PAGE]: true,
          [HAS_PREV_PAGE]: false,
          [START_CURSOR]: 'friend3',
        },
        requestedEdgeIDs: ['client:client:1:friend3ID'],
        filteredEdges: [
          {edgeID: 'client:client:1:friend3ID', nodeID: 'friend3ID'},
        ],
      });
    });
  
    it('creates range when a connection record already exists', () => {
      const records = {};
      const store = new RelayRecordStore({records});
      const writer = new RelayRecordWriter(records, {}, false);
      let query = getNode(
        RelayClassic.QL`
        query {
          node(id:"123") {
            friends {count}
          }
        }
      `,
      );
      let payload = {
        node: {
          id: '123',
          friends: {count: 5},
          __typename: 'User',
        },
      };
      writePayload(store, writer, query, payload);
  
      query = getNode(
        RelayClassic.QL`
        query {
          node(id:"123") {
            friends(first: 1) {
              edges {
                cursor
                node {
                  id
                }
                source {
                  id
                }
              }
              pageInfo {
                hasNextPage
                hasPreviousPage
              }
            }
          }
        }
      `,
      );
      payload = {
        node: {
          id: '123',
          __typename: 'User',
          friends: {
            edges: [
              {
                cursor: 'friend1',
                node: {
                  id: 'friend1ID',
                },
                source: {
                  id: '123',
                },
              },
            ],
            [PAGE_INFO]: {
              [HAS_NEXT_PAGE]: true,
              [HAS_PREV_PAGE]: false,
            },
          },
        },
      };
      const results = writePayload(store, writer, query, payload);
      expect(results).toEqual({
        created: {
          'client:client:1:friend1ID': true, // edges
          friend1ID: true, // nodes
        },
        updated: {
          'client:1': true,
        },
      });
      expect(store.getField('friend1ID', 'id')).toBe('friend1ID');
      expect(
        store.getRangeMetadata('client:1', [{name: 'first', value: 1}]),
      ).toEqual({
        diffCalls: [],
        filterCalls: [],
        pageInfo: {
          [END_CURSOR]: 'friend1',
          [HAS_NEXT_PAGE]: true,
          [HAS_PREV_PAGE]: false,
          [START_CURSOR]: 'friend1',
        },
        requestedEdgeIDs: ['client:client:1:friend1ID'],
        filteredEdges: [
          {edgeID: 'client:client:1:friend1ID', nodeID: 'friend1ID'},
        ],
      });
    });
  
    it('should throw when connection is missing required calls', () => {
      const records = {};
      const store = new RelayRecordStore({records});
      const writer = new RelayRecordWriter(records, {}, false);
      const edgesFragment = RelayClassic.QL`
        fragment on FriendsConnection {
          edges {
            cursor
            node {
              id
            }
            source {
              id
            }
          }
          pageInfo {
            hasNextPage
            hasPreviousPage
          }
        }
      `;
      const query = getNode(
        RelayClassic.QL`
        query {
          node(id:"123") {
            friends(isViewerFriend:true) {
              ${edgesFragment}
            }
          }
        }
      `,
      );
      const payload = {
        node: {
          id: '123',
          friends: {
            edges: [
              {
                cursor: 'friend1',
                node: {
                  id: 'friend1ID',
                },
                source: {
                  id: '123',
                },
              },
            ],
            [PAGE_INFO]: {
              [HAS_NEXT_PAGE]: true,
              [HAS_PREV_PAGE]: false,
            },
          },
          __typename: 'User',
        },
      };
      expect(() => writePayload(store, writer, query, payload)).toFailInvariant(
        'RelayQueryWriter: Cannot write edges for connection ' +
          'on record `client:1` without `first`, `last`, or `find` argument.',
      );
    });
  
    describe('first() connections with existing data', () => {
      let store, writer;
  
      beforeEach(() => {
        const query = getNode(
          RelayClassic.QL`
          query {
            node(id:"123") {
              friends(first: 1) {
                edges {
                  node {
                    id
                  }
                }
              }
            }
          }
        `,
        );
        const payload = {
          node: {
            id: '123',
            friends: {
              edges: [
                {
                  node: {
                    id: 'node1',
                  },
                  cursor: 'cursor1',
                },
              ],
              [PAGE_INFO]: {
                [HAS_NEXT_PAGE]: true,
                [HAS_PREV_PAGE]: false,
              },
            },
            __typename: 'User',
          },
        };
        const records = {};
        store = new RelayRecordStore({records});
        writer = new RelayRecordWriter(records, {}, false);
        writePayload(store, writer, query, payload);
      });
  
      it('appends new edges', () => {
        const query = getNode(
          RelayClassic.QL`
          query {
            node(id:"123") {
              friends(first: 1,after:"cursor1") {
                edges {
                  node {
                    id
                  }
                }
              }
            }
          }
        `,
        );
        const payload = {
          node: {
            id: '123',
            friends: {
              edges: [
                {
                  node: {
                    id: 'node2',
                  },
                  cursor: 'cursor2',
                },
              ],
              [PAGE_INFO]: {
                [HAS_NEXT_PAGE]: true,
                [HAS_PREV_PAGE]: true,
              },
            },
            __typename: 'User',
          },
        };
        const results = writePayload(store, writer, query, payload);
        expect(results).toEqual({
          created: {
            node2: true,
            'client:client:1:node2': true, // 2nd edge
          },
          updated: {
            'client:1': true, // range updated
          },
        });
        expect(
          store.getRangeMetadata('client:1', [{name: 'first', value: 2}]),
        ).toEqual({
          diffCalls: [],
          filterCalls: [],
          pageInfo: {
            [END_CURSOR]: 'cursor2',
            [HAS_NEXT_PAGE]: true,
            [HAS_PREV_PAGE]: false,
            [START_CURSOR]: 'cursor1',
          },
          requestedEdgeIDs: ['client:client:1:node1', 'client:client:1:node2'],
          filteredEdges: [
            {edgeID: 'client:client:1:node1', nodeID: 'node1'},
            {edgeID: 'client:client:1:node2', nodeID: 'node2'},
          ],
        });
      });
  
      it('updates existing edges when ids match', () => {
        const query = getNode(
          RelayClassic.QL`
          query {
            node(id:"123") {
              friends(first: 1) {
                edges {
                  node {
                    id
                    name
                  }
                }
              }
            }
          }
        `,
        );
        const payload = {
          node: {
            id: '123',
            friends: {
              edges: [
                {
                  node: {
                    id: 'node1',
                    name: 'Tim', // added field
                  },
                  cursor: 'cursor1',
                },
              ],
              [PAGE_INFO]: {
                [HAS_NEXT_PAGE]: true,
                [HAS_PREV_PAGE]: true,
              },
            },
            __typename: 'User',
          },
        };
        const results = writePayload(store, writer, query, payload);
        expect(results).toEqual({
          created: {},
          updated: {
            'client:1': true, // connection updated w/ new nodes
            node1: true, // `name` added
            // range not updated, only the node changed
          },
        });
        expect(store.getField('node1', 'name')).toBe('Tim');
        expect(
          store.getRangeMetadata('client:1', [{name: 'first', value: 1}]),
        ).toEqual({
          diffCalls: [],
          filterCalls: [],
          pageInfo: {
            [END_CURSOR]: 'cursor1',
            [HAS_NEXT_PAGE]: true,
            [HAS_PREV_PAGE]: false,
            [START_CURSOR]: 'cursor1',
          },
          requestedEdgeIDs: ['client:client:1:node1'],
          filteredEdges: [{edgeID: 'client:client:1:node1', nodeID: 'node1'}],
        });
      });
  
      it('updates the range when edge data changes', () => {
        // NOTE: Hack to preserve `source{id}` in all environments for now.
        const query = RelayQuery.Root.create(
          RelayClassic.QL`
          query {
            node(id:"123") {
              friends(find:"node1") {
                edges {
                  node {
                    id
                  }
                  source {
                    id
                  }
                }
              }
            }
          }
        `,
          RelayMetaRoute.get('$RelayTest'),
          {},
        );
        const payload = {
          node: {
            id: '123',
            friends: {
              edges: [
                {
                  node: {
                    id: 'node1',
                  },
                  source: {
                    // new edge field
                    id: '456',
                  },
                  cursor: 'cursor1',
                },
              ],
              [PAGE_INFO]: {
                [HAS_NEXT_PAGE]: true,
                [HAS_PREV_PAGE]: true,
              },
            },
            __typename: 'User',
          },
        };
        const results = writePayload(store, writer, query, payload);
        expect(results).toEqual({
          created: {
            '456': true, // `source` added
          },
          updated: {
            'client:1': true, // range updated because an edge had a change
            'client:client:1:node1': true, // `source` added to edge
          },
        });
        expect(
          store.getRangeMetadata('client:1', [{name: 'first', value: 1}]),
        ).toEqual({
          diffCalls: [],
          filterCalls: [],
          pageInfo: {
            [END_CURSOR]: 'cursor1',
            [HAS_NEXT_PAGE]: true,
            [HAS_PREV_PAGE]: false,
            [START_CURSOR]: 'cursor1',
          },
          requestedEdgeIDs: ['client:client:1:node1'],
          filteredEdges: [{edgeID: 'client:client:1:node1', nodeID: 'node1'}],
        });
        const sourceID = store.getLinkedRecordID(
          'client:client:1:node1',
          'source',
        );
        expect(sourceID).toBe('456');
        expect(store.getField(sourceID, 'id')).toBe('456');
      });
  
      it('does not overwrite edges when ids conflict', () => {
        const query = getNode(
          RelayClassic.QL`
          query {
            node(id:"123") {
              friends(first: 1) {
                edges {
                  node {
                    id
                  }
                }
              }
            }
          }
        `,
        );
        const payload = {
          node: {
            id: '123',
            friends: {
              edges: [
                {
                  node: {
                    id: 'node1b',
                  },
                  cursor: 'cursor1b',
                },
              ],
              [PAGE_INFO]: {
                [HAS_NEXT_PAGE]: true,
                [HAS_PREV_PAGE]: false,
              },
            },
            __typename: 'User',
          },
        };
        const results = writePayload(store, writer, query, payload);
        const edgeID = 'client:client:1:node1b';
        expect(results).toEqual({
          created: {
            node1b: true,
            [edgeID]: true, // edge added but never referenced
          },
          updated: {
            'client:1': true, // range updated
          },
        });
        expect(store.getRecordState(edgeID)).toBe('EXISTENT');
        expect(store.getLinkedRecordID(edgeID, 'node')).toBe('node1b');
        expect(store.getField('node1b', 'id')).toBe('node1b');
        expect(
          store.getRangeMetadata('client:1', [{name: 'first', value: 1}]),
        ).toEqual({
          diffCalls: [],
          filterCalls: [],
          pageInfo: {
            [END_CURSOR]: 'cursor1',
            [HAS_NEXT_PAGE]: true,
            [HAS_PREV_PAGE]: false,
            [START_CURSOR]: 'cursor1',
          },
          requestedEdgeIDs: ['client:client:1:node1'],
          filteredEdges: [{edgeID: 'client:client:1:node1', nodeID: 'node1'}],
        });
      });
  
      it('overwrites ranges when force index is set', () => {
        const query = getNode(
          RelayClassic.QL`
          query {
            node(id:"123") {
              friends(first: 1) {
                edges {
                  node {
                    id
                  }
                }
              }
            }
          }
        `,
        );
        const payload = {
          node: {
            id: '123',
            friends: {
              edges: [
                {
                  node: {
                    id: 'node1b',
                  },
                  cursor: 'cursor1b',
                },
              ],
              [PAGE_INFO]: {
                [HAS_NEXT_PAGE]: true,
                [HAS_PREV_PAGE]: false,
              },
            },
            __typename: 'User',
          },
        };
        const results = writePayload(store, writer, query, payload, null, {
          forceIndex: 1,
        });
        expect(results).toEqual({
          created: {
            node1b: true,
            'client:client:1:node1b': true,
          },
          updated: {
            'client:1': true, // range updated
          },
        });
        expect(store.getField('node1b', 'id')).toBe('node1b');
        expect(
          store.getRangeMetadata('client:1', [{name: 'first', value: 1}]),
        ).toEqual({
          diffCalls: [],
          filterCalls: [],
          pageInfo: {
            [END_CURSOR]: 'cursor1b',
            [HAS_NEXT_PAGE]: true,
            [HAS_PREV_PAGE]: false,
            [START_CURSOR]: 'cursor1b',
          },
          requestedEdgeIDs: ['client:client:1:node1b'],
          filteredEdges: [{edgeID: 'client:client:1:node1b', nodeID: 'node1b'}],
        });
      });
  
      it('updates page info for empty edges', () => {
        const query = getNode(
          RelayClassic.QL`
          query {
            node(id:"123") {
              friends(after:"cursor1", first: 1) {
                edges {
                  node {
                    id
                  }
                }
              }
            }
          }
        `,
        );
        const payload = {
          node: {
            id: '123',
            friends: {
              edges: [],
              [PAGE_INFO]: {
                [HAS_NEXT_PAGE]: false,
                [HAS_PREV_PAGE]: false,
              },
            },
            __typename: 'User',
          },
        };
        const results = writePayload(store, writer, query, payload);
        expect(results).toEqual({
          created: {},
          updated: {
            'client:1': true, // page_info updated
          },
        });
        expect(
          store.getRangeMetadata('client:1', [{name: 'first', value: 1}]),
        ).toEqual({
          diffCalls: [],
          filterCalls: [],
          pageInfo: {
            [END_CURSOR]: 'cursor1',
            [HAS_NEXT_PAGE]: false,
            [HAS_PREV_PAGE]: false,
            [START_CURSOR]: 'cursor1',
          },
          requestedEdgeIDs: ['client:client:1:node1'],
          filteredEdges: [{edgeID: 'client:client:1:node1', nodeID: 'node1'}],
        });
      });
    });
  });
  