Skip to content

Commit

Permalink
Add more tests of field finalization.
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamn committed Apr 29, 2021
1 parent f3d6fd4 commit 25af660
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 2 deletions.
80 changes: 80 additions & 0 deletions src/cache/inmemory/__tests__/__snapshots__/policies.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,86 @@ Object {
}
`;

exports[`type policies field policies custom finalize functions are called if merge function returns undefined 1`] = `
Object {
"ROOT_QUERY": Object {
"__typename": "Query",
"todoList": Object {
"__ref": "ToDoList:{}",
},
},
"Task:{\\"taskID\\":1}": Object {
"__typename": "Task",
"taskID": 1,
"text": "task #1",
},
"Task:{\\"taskID\\":2}": Object {
"__typename": "Task",
"taskID": 2,
"text": "task #2",
},
"ToDoList:{}": Object {
"__typename": "ToDoList",
"tasks": Array [
Object {
"__ref": "Task:{\\"taskID\\":1}",
},
Object {
"__ref": "Task:{\\"taskID\\":2}",
},
],
},
}
`;

exports[`type policies field policies custom finalize functions are called if merge function returns undefined 2`] = `
Object {
"ROOT_QUERY": Object {
"__typename": "Query",
"todoList": Object {
"__ref": "ToDoList:{}",
},
},
"Task:{\\"taskID\\":1}": Object {
"__typename": "Task",
"taskID": 1,
"text": "task #1",
},
"Task:{\\"taskID\\":2}": Object {
"__typename": "Task",
"taskID": 2,
"text": "task #2",
},
"Task:{\\"taskID\\":3}": Object {
"__typename": "Task",
"taskID": 3,
"text": "task #3",
},
"Task:{\\"taskID\\":4}": Object {
"__typename": "Task",
"taskID": 4,
"text": "task #4",
},
"ToDoList:{}": Object {
"__typename": "ToDoList",
"tasks": Array [
Object {
"__ref": "Task:{\\"taskID\\":1}",
},
Object {
"__ref": "Task:{\\"taskID\\":2}",
},
Object {
"__ref": "Task:{\\"taskID\\":3}",
},
Object {
"__ref": "Task:{\\"taskID\\":4}",
},
],
},
}
`;

exports[`type policies field policies read, merge, and modify functions can access options.storage 1`] = `
Object {
"ROOT_QUERY": Object {
Expand Down
98 changes: 98 additions & 0 deletions src/cache/inmemory/__tests__/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,104 @@ describe("type policies", function () {
},
});
});

itAsync("are called if merge function returns undefined", resolve => {
const cache = new InMemoryCache({
typePolicies: {
ToDoList: {
keyFields: [],
fields: {
tasks: {
keyArgs: false,

merge(existing: number[] | undefined, incoming: number[], { args }) {
if (args && args.deleteOnMerge) return;
return existing ? [
...existing,
...incoming,
] : incoming;
},

finalize(existing) {
expect(existing).toEqual([
{ __ref: 'Task:{"taskID":1}' },
{ __ref: 'Task:{"taskID":2}' },
{ __ref: 'Task:{"taskID":3}' },
{ __ref: 'Task:{"taskID":4}' },
]);
// Finish the test (success).
resolve();
},
},
},
},

Task: {
keyFields: ["taskID"],
},
},
});

const query = gql`
query {
todoList {
tasks {
taskID
text
}
}
}
`;

cache.writeQuery({
query,
data: {
todoList: {
__typename: "ToDoList",
tasks: [
{ __typename: "Task", taskID: 1, text: "task #1" },
{ __typename: "Task", taskID: 2, text: "task #2" },
],
},
},
});

expect(cache.extract()).toMatchSnapshot();

cache.writeQuery({
query,
data: {
todoList: {
__typename: "ToDoList",
tasks: [
{ __typename: "Task", taskID: 3, text: "task #3" },
{ __typename: "Task", taskID: 4, text: "task #4" },
],
},
},
});

expect(cache.extract()).toMatchSnapshot();

cache.writeQuery({
query: gql`
query {
todoList {
tasks(deleteOnMerge: true) {
taskID
text
}
}
}
`,
data: {
todoList: {
__typename: "ToDoList",
tasks: [],
},
},
});
});
});

it("merge functions can deduplicate items using readField", function () {
Expand Down
23 changes: 21 additions & 2 deletions src/cache/inmemory/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,19 @@ export abstract class EntityStore implements NormalizedCache {
// restrict our attention to the incoming fields, since those are the
// top-level fields that might have changed.
Object.keys(incoming).forEach(storeFieldName => {
walk(existing[storeFieldName], incoming[storeFieldName]);
const eChild = existing[storeFieldName];
const iChild = incoming[storeFieldName];

walk(eChild, iChild);

if (iChild === void 0) {
this.policies.finalizeField(
existing.__typename,
existing,
storeFieldName,
context,
);
}
});
}
}
Expand Down Expand Up @@ -608,12 +620,19 @@ class CacheGroup {
}
}

// This WeakMap maps every non-normalized object reference contained by the
// store to the path of that object within the enclosing entity object. This
// information is collected by the assignPaths method after every store.merge,
// so store.data should never contain any un-pathed objects. As a reminder,
// these object references are handled immutably from here on, so the objects
// should not move around in a way that invalidates these paths. This path
// information is useful in the getStorage method, below.
private paths = new WeakMap<object, (string | number)[]>();

public assignPaths(dataId: string, merged: StoreObject) {
const paths = this.paths;
const path: (string | number)[] = [dataId];

// TODO
function assign(this: void, obj: StoreValue) {
if (Array.isArray(obj)) {
obj.forEach(handleChild);
Expand Down

0 comments on commit 25af660

Please sign in to comment.