diff --git a/src/core/__tests__/masking.test.ts b/src/core/__tests__/masking.test.ts index 1186da58143..69735a2867a 100644 --- a/src/core/__tests__/masking.test.ts +++ b/src/core/__tests__/masking.test.ts @@ -106,6 +106,47 @@ describe("maskOperation", () => { expect(data).toEqual({ user: { __typename: "User", id: 1 } }); }); + test("masks fragments from nested objects when query gets fields from same object", () => { + const query = gql` + query { + user { + __typename + profile { + __typename + id + } + ...UserFields + } + } + + fragment UserFields on User { + profile { + __typename + id + fullName + } + } + `; + + const data = maskOperation( + deepFreeze({ + user: { + __typename: "User", + profile: { __typename: "Profile", id: "1", fullName: "Test User" }, + }, + }), + query, + createFragmentMatcher(new InMemoryCache()) + ); + + expect(data).toEqual({ + user: { + __typename: "User", + profile: { __typename: "Profile", id: "1" }, + }, + }); + }); + test("deep freezes the masked result if the original data is frozen", () => { const query = gql` query { diff --git a/src/core/masking.ts b/src/core/masking.ts index 8303215edd6..0891b2be985 100644 --- a/src/core/masking.ts +++ b/src/core/masking.ts @@ -149,13 +149,23 @@ function maskSelectionSet( memo[keyName] = data[keyName]; if (childSelectionSet) { - const [masked, childChanged] = maskSelectionSet( + let [masked, childChanged] = maskSelectionSet( data[keyName], childSelectionSet, context, __DEV__ ? `${path || ""}.${keyName}` : void 0 ); + // This check prevents cases where masked fields may accidentally be + // returned as part of this object when the fragment also selects + // additional fields from the same child selection. + if ( + !childChanged && + Object.keys(masked).length !== Object.keys(data[keyName]).length + ) { + childChanged = true; + } + if (childChanged) { memo[keyName] = masked; changed = true;