diff --git a/packages/relay-runtime/store/__tests__/RelayResponseNormalizer-test.js b/packages/relay-runtime/store/__tests__/RelayResponseNormalizer-test.js index bc5f0743544d..2378bae3e841 100644 --- a/packages/relay-runtime/store/__tests__/RelayResponseNormalizer-test.js +++ b/packages/relay-runtime/store/__tests__/RelayResponseNormalizer-test.js @@ -2637,6 +2637,49 @@ describe('RelayResponseNormalizer', () => { }); }); + // Regression test for facebook/relay#5056 — currently demonstrates + // buggy behavior; flip assertion when the underlying bug is fixed. + // The reported schema shape is `[[MyType!]!]!` where the inner type is + // an abstract (interface/union). The test schema does not model nested + // lists, so this test instead sends a doubly-nested payload to a plain + // plural-of-abstract field (`User.actors: [Actor]`). That exercises the + // same `_normalizePluralLink` → `_getRecordType` runtime path described + // in #5056: the inner "item" is itself an array, so `_getRecordType` + // receives an array and fails with an unhelpful Invariant Violation. + it('throws when a plural-of-abstract field receives a nested-array payload (#5056)', () => { + const Query = graphql` + query RelayResponseNormalizerTest5056Query($id: ID) { + node(id: $id) { + id + __typename + ... on User { + actors { + id + __typename + } + } + } + } + `; + const malformedPayload = { + node: { + id: '1', + __typename: 'User', + actors: [[{id: '2', __typename: 'Page'}]], + }, + }; + const recordSource = new RelayRecordSource(); + recordSource.set(ROOT_ID, RelayModernRecord.create(ROOT_ID, ROOT_TYPE)); + expect(() => + normalize( + recordSource, + createNormalizationSelector(Query.operation, ROOT_ID, {id: '1'}), + malformedPayload, + defaultOptions, + ), + ).toThrow(/Expected a typename for record `\[/); + }); + it('warns in __DEV__ if payload data is missing an expected field', () => { const BarQuery = graphql` query RelayResponseNormalizerTest20Query($id: ID) { diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayResponseNormalizerTest5056Query.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayResponseNormalizerTest5056Query.graphql.js new file mode 100644 index 000000000000..c77121ec152c --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayResponseNormalizerTest5056Query.graphql.js @@ -0,0 +1,137 @@ +/** + * 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<<7bef7d5a29221f807fe7b9577c55b148>> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +export type RelayResponseNormalizerTest5056Query$variables = {| + id?: ?string, +|}; +export type RelayResponseNormalizerTest5056Query$data = {| + +node: ?{| + +__typename: string, + +actors?: ?ReadonlyArray, + +id: string, + |}, +|}; +export type RelayResponseNormalizerTest5056Query = {| + response: RelayResponseNormalizerTest5056Query$data, + variables: RelayResponseNormalizerTest5056Query$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } + ], + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*:: as any*/), + (v2/*:: as any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "actors", + "plural": true, + "selections": [ + (v1/*:: as any*/), + (v2/*:: as any*/) + ], + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Fragment", + "metadata": null, + "name": "RelayResponseNormalizerTest5056Query", + "selections": (v3/*:: as any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Operation", + "name": "RelayResponseNormalizerTest5056Query", + "selections": (v3/*:: as any*/) + }, + "params": { + "cacheID": "63c98737e9365e9429d8f0e85b5d0e1f", + "id": null, + "metadata": {}, + "name": "RelayResponseNormalizerTest5056Query", + "operationKind": "query", + "text": "query RelayResponseNormalizerTest5056Query(\n $id: ID\n) {\n node(id: $id) {\n id\n __typename\n ... on User {\n actors {\n id\n __typename\n }\n }\n }\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*:: as any*/).hash = "1364920a1cd7346273dfc3a3cbe8e506"; +} + +module.exports = ((node/*:: as any*/)/*:: as Query< + RelayResponseNormalizerTest5056Query$variables, + RelayResponseNormalizerTest5056Query$data, +>*/);