Skip to content

Commit

Permalink
Merge pull request #7224 from beaucollins/relay-incoming-page-cursors
Browse files Browse the repository at this point in the history
Ensure relayStylePagination preserves pageInfo.{start,end}Cursor if edges empty.
  • Loading branch information
benjamn committed Oct 27, 2020
2 parents d4b9fab + dd2a54d commit 5f87003
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 15 deletions.
99 changes: 99 additions & 0 deletions src/utilities/policies/__tests__/relayStylePagination.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { FieldFunctionOptions, InMemoryCache, isReference, makeReference } from '../../../core';
import { relayStylePagination } from '../pagination';

describe('relayStylePagination', () => {
const policy = relayStylePagination();

describe('merge', () => {
const merge = policy.merge;
// The merge function should exist, make TS aware
if (typeof merge !== 'function') {
throw new Error('Expecting merge function');
}

const options: FieldFunctionOptions = {
args: null,
fieldName: 'fake',
storeFieldName: 'fake',
field: null,
isReference: isReference,
toReference: () => undefined,
storage: {},
cache: new InMemoryCache(),
readField: () => undefined,
canRead: () => false,
mergeObjects: (existing, _incoming) => existing,
};

it('should maintain endCursor and startCursor with empty edges', () => {
const incoming: Parameters<typeof merge>[1] = {
pageInfo: {
hasPreviousPage: false,
hasNextPage: true,
startCursor: 'abc',
endCursor: 'xyz',
}
};
const result = merge(undefined, incoming, options);
expect(result).toEqual({
edges: [],
pageInfo: {
hasPreviousPage: false,
hasNextPage: true,
startCursor: 'abc',
endCursor: 'xyz'
}
});
});

it('should maintain existing PageInfo when adding a page', () => {
const existingEdges = [
{ cursor: 'alpha', node: makeReference("fakeAlpha") },
];

const incomingEdges = [
{ cursor: 'omega', node: makeReference("fakeOmega") },
];

const result = merge(
{
edges: existingEdges,
pageInfo: {
hasPreviousPage: false,
hasNextPage: true,
startCursor: 'alpha',
endCursor: 'alpha'
},
},
{
edges: incomingEdges,
pageInfo: {
hasPreviousPage: true,
hasNextPage: true,
startCursor: incomingEdges[0].cursor,
endCursor: incomingEdges[incomingEdges.length - 1].cursor,
},
},
{
...options,
args: {
after: 'alpha',
},
},
);

expect(result).toEqual({
edges: [
...existingEdges,
...incomingEdges,
],
pageInfo: {
hasPreviousPage: false,
hasNextPage: true,
startCursor: 'alpha',
endCursor: 'omega',
}
});
});
})
});
49 changes: 34 additions & 15 deletions src/utilities/policies/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,28 @@ export function relayStylePagination<TNode = Reference>(
}) : [];

if (incoming.pageInfo) {
// In case we did not request the cursor field for edges in this
// query, we can still infer some of those cursors from pageInfo.
const { startCursor, endCursor } = incoming.pageInfo;
const { pageInfo } = incoming;
const { startCursor, endCursor } = pageInfo;
const firstEdge = incomingEdges[0];
const lastEdge = incomingEdges[incomingEdges.length - 1];
// In case we did not request the cursor field for edges in this
// query, we can still infer cursors from pageInfo.
if (firstEdge && startCursor) {
firstEdge.cursor = startCursor;
}
const lastEdge = incomingEdges[incomingEdges.length - 1];
if (lastEdge && endCursor) {
lastEdge.cursor = endCursor;
}
// Cursors can also come from edges, so we default
// pageInfo.{start,end}Cursor to {first,last}Edge.cursor.
const firstCursor = firstEdge && firstEdge.cursor;
if (firstCursor && !startCursor) {
pageInfo.startCursor = firstCursor;
}
const lastCursor = lastEdge && lastEdge.cursor;
if (lastCursor && !endCursor) {
pageInfo.endCursor = lastCursor;
}
}

let prefix = existing.edges;
Expand Down Expand Up @@ -178,27 +189,35 @@ export function relayStylePagination<TNode = Reference>(
...suffix,
];

const firstEdge = edges[0];
const lastEdge = edges[edges.length - 1];

const pageInfo: TPageInfo = {
// The ordering of these two ...spreads may be surprising, but it
// makes sense because we want to combine PageInfo properties with a
// preference for existing values, *unless* the existing values are
// overridden by the logic below, which is permitted only when the
// incoming page falls at the beginning or end of the data.
...incoming.pageInfo,
...existing.pageInfo,
startCursor: firstEdge && firstEdge.cursor || "",
endCursor: lastEdge && lastEdge.cursor || "",
};

if (incoming.pageInfo) {
const { hasPreviousPage, hasNextPage } = incoming.pageInfo;
const {
hasPreviousPage, hasNextPage,
startCursor, endCursor,
} = incoming.pageInfo;
// Keep existing.pageInfo.has{Previous,Next}Page unless the
// placement of the incoming edges means incoming.hasPreviousPage
// or incoming.hasNextPage should become the new values for those
// properties in existing.pageInfo.
if (!prefix.length && hasPreviousPage !== void 0) {
pageInfo.hasPreviousPage = hasPreviousPage;
// properties in existing.pageInfo. Note that these updates are
// only permitted when the beginning or end of the incoming page
// coincides with the beginning or end of the existing data, as
// determined using prefix.length and suffix.length.
if (!prefix.length) {
if (void 0 !== hasPreviousPage) pageInfo.hasPreviousPage = hasPreviousPage;
if (void 0 !== startCursor) pageInfo.startCursor = startCursor;
}
if (!suffix.length && hasNextPage !== void 0) {
pageInfo.hasNextPage = hasNextPage;
if (!suffix.length) {
if (void 0 !== hasNextPage) pageInfo.hasNextPage = hasNextPage;
if (void 0 !== endCursor) pageInfo.endCursor = endCursor;
}
}

Expand Down

0 comments on commit 5f87003

Please sign in to comment.