Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix(apollo-forest-run): apollo-compatible behavior for corrupt writes",
"packageName": "@graphitation/apollo-forest-run",
"email": "vrazuvaev@microsoft.com_msteamsmdb",
"dependentChangeType": "patch"
}
161 changes: 161 additions & 0 deletions packages/apollo-forest-run/src/__tests__/regression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,167 @@ test("treats incorrect list items as empty objects", () => {
expect(data.complete).toBe(false);
});

test("does not crash when object diff is applied to list field with key collision", () => {
// Schema (valid):
// type ParentInfo { id: ID! items: [ChildItem!] note: String }
// type ChildItem { id: ID! details: ParentInfo }
// type Query { parentInfo: ParentInfo childItem: ChildItem }
const listQuery = gql`
{
parentInfo {
id
items {
id
}
}
}
`;
const objectQuery = gql`
{
childItem {
id
details {
# id
note
}
}
}
`;

const cache = new ForestRun({
dataIdFromObject: (object: any) => object.id,
});

cache.write({
query: listQuery,
result: {
parentInfo: {
__typename: "ParentInfo",
id: "1",
items: [
{
__typename: "ChildItem",
id: "1",
},
],
},
},
});

cache.write({
query: objectQuery,
result: {
childItem: {
__typename: "ChildItem",
id: "1",
details: {
// id: "2",
note: "old",
},
},
},
});

// This currently throws inside updateObject (assert on non-object base).
const run = () =>
cache.write({
query: objectQuery,
result: {
childItem: {
__typename: "ChildItem",
id: "1",
details: {
// id: "2",
note: "new",
},
},
},
});

expect(run).not.toThrow();
});

test("matches apollo InMemoryCache behavior on incorrect cache overwrites", () => {
const listQuery = gql`
query ListQuery {
container {
__typename
id
entries {
__typename
note
}
}
}
`;
const objectQuery = gql`
query ObjectQuery {
container {
__typename
id
entries {
__typename
note
}
}
}
`;

const forestRun = new ForestRun();

const op1 = {
container: {
__typename: "Container",
id: "1",
entries: [
{
__typename: "Entry",
note: "old",
},
],
},
};
const op2 = {
container: {
__typename: "Container",
id: "1",
entries: {
__typename: "Entry",
note: "old",
},
},
};
const op3 = {
container: {
__typename: "Container",
id: "1",
entries: {
__typename: "Entry",
note: "new",
},
},
};

forestRun.write({ query: listQuery, result: op1 });
forestRun.write({ query: objectQuery, result: op2 });
forestRun.write({ query: objectQuery, result: op3 });

const forestList = forestRun.read({ query: listQuery, optimistic: true });
const forestObj = forestRun.read({ query: objectQuery, optimistic: true });

// This is what InMemoryCache produces for both queries
const apolloCompatibleResult = {
container: {
__typename: "Container",
id: "1",
entries: { __typename: "Entry", note: "new" },
},
};

expect(forestList).toEqual(apolloCompatibleResult);
expect(forestObj).toEqual(apolloCompatibleResult);
});

test("consistent root-level __typename in optimistic response 1", () => {
const query = gql`
{
Expand Down
9 changes: 9 additions & 0 deletions packages/apollo-forest-run/src/diff/diffObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,15 @@ function diffValueInternal(
? undefined
: Difference.createReplacement(base, model);
}
// ApolloCompat: wrong structural writes are still acceptable by Apollo client (instead of throwing errors)
if (Value.isCompositeValue(base) || Value.isCompositeValue(model)) {
if (!Value.isCompositeValue(base) || !Value.isCompositeValue(model)) {
return Difference.createReplacement(base, model);
}
if (base.kind !== model.kind) {
return Difference.createReplacement(base, model);
}
}
switch (base.kind) {
case ValueKind.CompositeNull: {
assert(!currentDiff);
Expand Down