diff --git a/packages/relay-runtime/store/RelayReader.js b/packages/relay-runtime/store/RelayReader.js index 9f314bc2e2916..fe5b16c39ac82 100644 --- a/packages/relay-runtime/store/RelayReader.js +++ b/packages/relay-runtime/store/RelayReader.js @@ -218,6 +218,9 @@ class RelayReader { } _markDataAsMissing(): void { + if (this._isWithinUnmatchedTypeRefinement) { + return; + } if (this._errorResponseFields == null) { this._errorResponseFields = []; } diff --git a/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js b/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js index afa5e5d4a6546..ad98487637efb 100644 --- a/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js +++ b/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js @@ -328,14 +328,61 @@ describe('RelayReader error fields', () => { ); expect(isMissingData).toBe(false); + expect(errorResponseFields).toEqual(null); + }); - // FIXME: This should be null + it('does report missing data within an inline fragment that does match', () => { + const source = RelayRecordSource.create({ + 'client:root': { + __id: 'client:root', + __typename: '__Root', + 'node(id:"4")': {__ref: '4'}, + }, + 'client:__type:User': { + __isMaybeNodeInterface: true, + }, + '4': { + __id: '4', + id: '4', + __typename: 'NonNodeNoID', + // NOTE: `name` is missing + }, + }); + + const FooQuery = graphql` + query RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery + @throwOnFieldError { + node(id: "4") { + # GraphQL lets us spread this here as long as there is at least one + # type that overlaps + ... on MaybeNodeInterface { + name + } + } + } + `; + const operation = createOperationDescriptor(FooQuery, {}); + const {errorResponseFields, isMissingData} = read( + source, + operation.fragment, + ); + + expect(isMissingData).toBe(true); expect(errorResponseFields).toEqual([ + // We are missing the metadata bout the interface + { + fieldPath: '', + handled: false, + kind: 'missing_expected_data.throw', + owner: 'RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery', + }, + // We don't know if we should traverse into the inline fragment, but we do + // anyway and find that the field is missing { fieldPath: '', handled: false, kind: 'missing_expected_data.throw', - owner: 'RelayReaderRelayErrorHandlingTestInlineFragmentQuery', + owner: 'RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery', }, ]); }); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery.graphql.js new file mode 100644 index 0000000000000..90cc1b8d90191 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery.graphql.js @@ -0,0 +1,132 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +export type RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery$variables = {||}; +export type RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery$data = {| + +node: ?{| + +name?: ?string, + |}, +|}; +export type RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery = {| + response: RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery$data, + variables: RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "Literal", + "name": "id", + "value": "4" + } +], +v1 = { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "MaybeNodeInterface", + "abstractKey": "__isMaybeNodeInterface" +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "throwOnFieldError": true + }, + "name": "RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/) + ], + "storageKey": "node(id:\"4\")" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": "node(id:\"4\")" + } + ] + }, + "params": { + "cacheID": "0f7c4db114ac13634196313c2e57f4e4", + "id": null, + "metadata": {}, + "name": "RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery", + "operationKind": "query", + "text": "query RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery {\n node(id: \"4\") {\n __typename\n ... on MaybeNodeInterface {\n __isMaybeNodeInterface: __typename\n name\n }\n id\n }\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "5a20c30977bc87aac1a6449bc034610e"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery$variables, + RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery$data, +>*/);