From 25fed77a6ad0db185a732af1c0f0f417ca024805 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 28 Jun 2021 19:15:26 -0400 Subject: [PATCH] Prefer existing.pageInfo cursors in relayStylePagination read function. --- src/cache/inmemory/__tests__/policies.ts | 2 +- .../__tests__/relayStylePagination.test.ts | 109 +++++++++++++++++- src/utilities/policies/pagination.ts | 17 ++- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/cache/inmemory/__tests__/policies.ts b/src/cache/inmemory/__tests__/policies.ts index 58fc8032af1..3fe2c070e51 100644 --- a/src/cache/inmemory/__tests__/policies.ts +++ b/src/cache/inmemory/__tests__/policies.ts @@ -3462,7 +3462,7 @@ describe("type policies", function () { edges, pageInfo: { __typename: "PageInfo", - startCursor: thirdPageInfo.startCursor, + startCursor: fourthPageInfo.startCursor, endCursor: fifthPageInfo.endCursor, hasPreviousPage: false, hasNextPage: true, diff --git a/src/utilities/policies/__tests__/relayStylePagination.test.ts b/src/utilities/policies/__tests__/relayStylePagination.test.ts index a2d81ab5736..59f42be940c 100644 --- a/src/utilities/policies/__tests__/relayStylePagination.test.ts +++ b/src/utilities/policies/__tests__/relayStylePagination.test.ts @@ -1,9 +1,116 @@ -import { FieldFunctionOptions, InMemoryCache, isReference, makeReference } from '../../../core'; +import { FieldFunctionOptions, InMemoryCache, isReference, makeReference, StoreObject } from '../../../cache'; import { relayStylePagination, TRelayPageInfo } from '../pagination'; describe('relayStylePagination', () => { const policy = relayStylePagination(); + describe('read', () => { + const fakeEdges = [ + { node: { __ref: "A" }, cursor: "cursorA" }, + { node: { __ref: "B" }, cursor: "cursorB" }, + { node: { __ref: "C" }, cursor: "cursorC" }, + ]; + + const fakeReadOptions = { + canRead() { return true }, + readField(key, obj: StoreObject) { + return obj && obj[key]; + }, + } as FieldFunctionOptions; + + it("should prefer existing.pageInfo.startCursor", () => { + const resultWithStartCursor = policy.read!({ + edges: fakeEdges, + pageInfo: { + startCursor: "preferredStartCursor", + hasPreviousPage: false, + hasNextPage: true, + } as TRelayPageInfo, + }, fakeReadOptions); + + expect( + resultWithStartCursor && + resultWithStartCursor.pageInfo + ).toEqual({ + startCursor: "preferredStartCursor", + endCursor: "cursorC", + hasPreviousPage: false, + hasNextPage: true, + }); + }); + + it("should prefer existing.pageInfo.endCursor", () => { + const resultWithEndCursor = policy.read!({ + edges: fakeEdges, + pageInfo: { + endCursor: "preferredEndCursor", + hasPreviousPage: false, + hasNextPage: true, + } as TRelayPageInfo, + }, fakeReadOptions); + + expect( + resultWithEndCursor && + resultWithEndCursor.pageInfo + ).toEqual({ + startCursor: "cursorA", + endCursor: "preferredEndCursor", + hasPreviousPage: false, + hasNextPage: true, + }); + }); + + it("should prefer existing.pageInfo.{start,end}Cursor", () => { + const resultWithEndCursor = policy.read!({ + edges: fakeEdges, + pageInfo: { + startCursor: "preferredStartCursor", + endCursor: "preferredEndCursor", + hasPreviousPage: false, + hasNextPage: true, + }, + }, fakeReadOptions); + + expect( + resultWithEndCursor && + resultWithEndCursor.pageInfo + ).toEqual({ + startCursor: "preferredStartCursor", + endCursor: "preferredEndCursor", + hasPreviousPage: false, + hasNextPage: true, + }); + }); + + it("should override pageInfo.{start,end}Cursor if empty strings", () => { + const resultWithEndCursor = policy.read!({ + edges: [ + { node: { __ref: "A" }, cursor: "" }, + { node: { __ref: "B" }, cursor: "cursorB" }, + { node: { __ref: "C" }, cursor: "" }, + { node: { __ref: "D" }, cursor: "cursorD" }, + { node: { __ref: "E" } }, + ], + pageInfo: { + startCursor: "", + endCursor: "", + hasPreviousPage: false, + hasNextPage: true, + }, + }, fakeReadOptions); + + expect( + resultWithEndCursor && + resultWithEndCursor.pageInfo + ).toEqual({ + startCursor: "cursorB", + endCursor: "cursorD", + hasPreviousPage: false, + hasNextPage: true, + }); + }); + }); + describe('merge', () => { const merge = policy.merge; // The merge function should exist, make TS aware diff --git a/src/utilities/policies/pagination.ts b/src/utilities/policies/pagination.ts index fc885888905..525a2f5a7de 100644 --- a/src/utilities/policies/pagination.ts +++ b/src/utilities/policies/pagination.ts @@ -98,20 +98,29 @@ export function relayStylePagination( if (!existing) return; const edges: TRelayEdge[] = []; - let startCursor = ""; - let endCursor = ""; + let firstEdgeCursor = ""; + let lastEdgeCursor = ""; existing.edges.forEach(edge => { // Edges themselves could be Reference objects, so it's important // to use readField to access the edge.edge.node property. if (canRead(readField("node", edge))) { edges.push(edge); if (edge.cursor) { - startCursor = startCursor || edge.cursor; - endCursor = edge.cursor; + firstEdgeCursor = firstEdgeCursor || edge.cursor || ""; + lastEdgeCursor = edge.cursor || lastEdgeCursor; } } }); + let { + startCursor, + endCursor, + } = existing.pageInfo || {}; + // If existing.pageInfo.{start,end}Cursor are undefined or "", default to + // firstEdgeCursor and/or lastEdgeCursor. + startCursor = startCursor || firstEdgeCursor; + endCursor = endCursor || lastEdgeCursor; + return { // Some implementations return additional Connection fields, such // as existing.totalCount. These fields are saved by the merge