Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer existing.pageInfo cursors in relayStylePagination field policy read functions #8438

Merged
merged 2 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- Fix race condition in `@apollo/client/link/context` that could leak subscriptions if the subscription is cancelled before `operation.setContext` is called. <br/>
[@sofianhn](https://github.com/sofianhn) in [#8399](https://github.com/apollographql/apollo-client/pull/8399)

- Prefer `existing.pageInfo.startCursor` and `endCursor` (if defined) in `read` function of `relayStylePagination` policies. <br/>
[@benjamn](https://github.com/benjamn) in [#8438](https://github.com/apollographql/apollo-client/pull/8438)

## Apollo Client 3.3.20

### Bug fixes
Expand Down
2 changes: 1 addition & 1 deletion src/cache/inmemory/__tests__/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3461,7 +3461,7 @@ describe("type policies", function () {
edges,
pageInfo: {
__typename: "PageInfo",
startCursor: thirdPageInfo.startCursor,
startCursor: fourthPageInfo.startCursor,
endCursor: fifthPageInfo.endCursor,
hasPreviousPage: false,
hasNextPage: true,
Expand Down
109 changes: 108 additions & 1 deletion src/utilities/policies/__tests__/relayStylePagination.test.ts
Original file line number Diff line number Diff line change
@@ -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: string, obj: StoreObject) {
return obj && obj[key];
},
} as any 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
Expand Down
19 changes: 13 additions & 6 deletions src/utilities/policies/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,25 @@ export function relayStylePagination<TNode = Reference>(
if (!existing) return;

const edges: TRelayEdge<TNode>[] = [];
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;
}
}
});

const {
startCursor,
endCursor,
} = existing.pageInfo || {};

return {
// Some implementations return additional Connection fields, such
// as existing.totalCount. These fields are saved by the merge
Expand All @@ -120,8 +125,10 @@ export function relayStylePagination<TNode = Reference>(
edges,
pageInfo: {
...existing.pageInfo,
startCursor,
endCursor,
// If existing.pageInfo.{start,end}Cursor are undefined or "", default
// to firstEdgeCursor and/or lastEdgeCursor.
startCursor: startCursor || firstEdgeCursor,
endCursor: endCursor || lastEdgeCursor,
},
};
},
Expand Down