From 5a9d42e62c02ac48f37db7ad344b55c9297a6bee Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 4 Apr 2022 11:32:17 -0700 Subject: [PATCH] Fix incorrect type generation with @required on pluarl fragments Reviewed By: kassens Differential Revision: D35344295 fbshipit-source-id: 031a08e98c9d8179f33901d86e54c37dec5a02ba --- compiler/crates/relay-typegen/src/lib.rs | 17 ++- ...d-bubbles-to-plural-fragment-root.expected | 32 +++++ ...ed-bubbles-to-plural-fragment-root.graphql | 7 + .../relay-typegen/tests/generate_flow_test.rs | 9 +- .../RelayReader-RequiredFields-test.js | 42 +++++- ...ReaderRequiredFieldsTest24Query.graphql.js | 135 ++++++++++++++++++ ...aderRequiredFieldsTest6Fragment.graphql.js | 64 +++++++++ 7 files changed, 295 insertions(+), 11 deletions(-) create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.graphql create mode 100644 packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest24Query.graphql.js create mode 100644 packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest6Fragment.graphql.js diff --git a/compiler/crates/relay-typegen/src/lib.rs b/compiler/crates/relay-typegen/src/lib.rs index c62c89fb9b2b7..d63d358d33cdc 100644 --- a/compiler/crates/relay-typegen/src/lib.rs +++ b/compiler/crates/relay-typegen/src/lib.rs @@ -440,25 +440,24 @@ impl<'a> TypeGenerator<'a> { let unmasked = RelayDirective::is_unmasked_fragment_definition(fragment_definition); - let base_type = self.selections_to_babel( + let mut type_ = self.selections_to_babel( selections.into_iter(), unmasked, if unmasked { None } else { Some(fragment_name) }, ); - let type_ = if is_plural_fragment { - AST::ReadOnlyArray(base_type.into()) - } else { - base_type - }; - let type_ = match fragment_definition + if fragment_definition .directives .named(*CHILDREN_CAN_BUBBLE_METADATA_KEY) + .is_some() { - Some(_) => AST::Nullable(type_.into()), - None => type_, + type_ = AST::Nullable(type_.into()); }; + if is_plural_fragment { + type_ = AST::ReadOnlyArray(type_.into()) + } + self.runtime_imports.fragment_reference = true; self.write_import_actor_change_point()?; self.write_fragment_imports()?; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected new file mode 100644 index 0000000000000..106bfa580e54e --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected @@ -0,0 +1,32 @@ +==================================== INPUT ==================================== +fragment SomeFragment on User @relay(plural: true) { + name @required(action: LOG) +} + +fragment SomeOtherFragment on User @relay(plural: true) { + name +} +==================================== OUTPUT =================================== +import type { FragmentType } from "relay-runtime"; +declare export opaque type SomeFragment$fragmentType: FragmentType; +export type SomeFragment$data = $ReadOnlyArray; +export type SomeFragment$key = $ReadOnlyArray<{ + +$data?: SomeFragment$data, + +$fragmentSpreads: SomeFragment$fragmentType, + ... +}>; +------------------------------------------------------------------------------- +import type { FragmentType } from "relay-runtime"; +declare export opaque type SomeOtherFragment$fragmentType: FragmentType; +export type SomeOtherFragment$data = $ReadOnlyArray<{| + +name: ?string, + +$fragmentType: SomeOtherFragment$fragmentType, +|}>; +export type SomeOtherFragment$key = $ReadOnlyArray<{ + +$data?: SomeOtherFragment$data, + +$fragmentSpreads: SomeOtherFragment$fragmentType, + ... +}>; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.graphql new file mode 100644 index 0000000000000..623ce33434d32 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/required-bubbles-to-plural-fragment-root.graphql @@ -0,0 +1,7 @@ +fragment SomeFragment on User @relay(plural: true) { + name @required(action: LOG) +} + +fragment SomeOtherFragment on User @relay(plural: true) { + name +} diff --git a/compiler/crates/relay-typegen/tests/generate_flow_test.rs b/compiler/crates/relay-typegen/tests/generate_flow_test.rs index aedcea6af6c14..9b350bff5d36b 100644 --- a/compiler/crates/relay-typegen/tests/generate_flow_test.rs +++ b/compiler/crates/relay-typegen/tests/generate_flow_test.rs @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<72f4bf167568e5c4a2a2218c8bc29ac6>> + * @generated SignedSource<<0d8c404a6c037950a24e7f8515f31878>> */ mod generate_flow; @@ -348,6 +348,13 @@ fn required_bubbles_to_non_null_plural_linked_field() { test_fixture(transform_fixture, "required-bubbles-to-non-null-plural-linked-field.graphql", "generate_flow/fixtures/required-bubbles-to-non-null-plural-linked-field.expected", input, expected); } +#[test] +fn required_bubbles_to_plural_fragment_root() { + let input = include_str!("generate_flow/fixtures/required-bubbles-to-plural-fragment-root.graphql"); + let expected = include_str!("generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected"); + test_fixture(transform_fixture, "required-bubbles-to-plural-fragment-root.graphql", "generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected", input, expected); +} + #[test] fn required_bubbles_to_query() { let input = include_str!("generate_flow/fixtures/required-bubbles-to-query.graphql"); diff --git a/packages/relay-runtime/store/__tests__/RelayReader-RequiredFields-test.js b/packages/relay-runtime/store/__tests__/RelayReader-RequiredFields-test.js index 5af1b6160db3c..51437b8ee8946 100644 --- a/packages/relay-runtime/store/__tests__/RelayReader-RequiredFields-test.js +++ b/packages/relay-runtime/store/__tests__/RelayReader-RequiredFields-test.js @@ -16,7 +16,7 @@ const { } = require('../RelayModernOperationDescriptor'); const {read} = require('../RelayReader'); const RelayRecordSource = require('../RelayRecordSource'); -const {createReaderSelector} = require('relay-runtime'); +const {createReaderSelector, getPluralSelector} = require('relay-runtime'); describe('RelayReader @required', () => { it('bubbles @required(action: LOG) scalars up to LinkedField', () => { @@ -751,4 +751,44 @@ describe('RelayReader @required', () => { expect(isMissingData).toBe(true); expect(data).toEqual(null); }); + + it('bubbles to list item when used in plural fragment', () => { + const source = RelayRecordSource.create({ + 'client:root': { + __id: 'client:root', + __typename: '__Root', + 'nodes(ids:["1","2"])': {__refs: ['1', '2']}, + }, + '1': { + __id: '1', + id: '1', + __typename: 'User', + username: 'Wendy', + }, + '2': { + __id: '2', + id: '2', + __typename: 'User', + username: null, + }, + }); + const BarFragment = graphql` + fragment RelayReaderRequiredFieldsTest6Fragment on User + @relay(plural: true) { + username @required(action: LOG) + } + `; + const UserQuery = graphql` + query RelayReaderRequiredFieldsTest24Query { + nodes(ids: ["1", "2"]) { + ...RelayReaderRequiredFieldsTest6Fragment + } + } + `; + const owner = createOperationDescriptor(UserQuery); + const {nodes} = read(source, owner.fragment).data; + const pluralSelector = getPluralSelector(BarFragment, nodes); + const data = pluralSelector.selectors.map(s => read(source, s).data); + expect(data).toEqual([{username: 'Wendy'}, null]); + }); }); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest24Query.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest24Query.graphql.js new file mode 100644 index 0000000000000..cf74fb283d8a8 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest24Query.graphql.js @@ -0,0 +1,135 @@ +/** + * 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. + * + * @generated SignedSource<<5e434ad780d5d43394c351cd48edae31>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +type RelayReaderRequiredFieldsTest6Fragment$fragmentType = any; +export type RelayReaderRequiredFieldsTest24Query$variables = {||}; +export type RelayReaderRequiredFieldsTest24Query$data = {| + +nodes: ?$ReadOnlyArray, +|}; +export type RelayReaderRequiredFieldsTest24Query = {| + response: RelayReaderRequiredFieldsTest24Query$data, + variables: RelayReaderRequiredFieldsTest24Query$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "Literal", + "name": "ids", + "value": [ + "1", + "2" + ] + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayReaderRequiredFieldsTest24Query", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "nodes", + "plural": true, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayReaderRequiredFieldsTest6Fragment" + } + ], + "storageKey": "nodes(ids:[\"1\",\"2\"])" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayReaderRequiredFieldsTest24Query", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "nodes", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "username", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": "nodes(ids:[\"1\",\"2\"])" + } + ] + }, + "params": { + "cacheID": "1cf2b88c746e99f6164ed83a5df79a98", + "id": null, + "metadata": {}, + "name": "RelayReaderRequiredFieldsTest24Query", + "operationKind": "query", + "text": "query RelayReaderRequiredFieldsTest24Query {\n nodes(ids: [\"1\", \"2\"]) {\n __typename\n ...RelayReaderRequiredFieldsTest6Fragment\n id\n }\n}\n\nfragment RelayReaderRequiredFieldsTest6Fragment on User {\n username\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "9ac143a6a63180abd74a237cb422c2a8"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayReaderRequiredFieldsTest24Query$variables, + RelayReaderRequiredFieldsTest24Query$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest6Fragment.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest6Fragment.graphql.js new file mode 100644 index 0000000000000..3c85cb743d863 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRequiredFieldsTest6Fragment.graphql.js @@ -0,0 +1,64 @@ +/** + * 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. + * + * @generated SignedSource<<730eabd3329bd7727b1b0bb98a7b6267>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayReaderRequiredFieldsTest6Fragment$fragmentType: FragmentType; +export type RelayReaderRequiredFieldsTest6Fragment$data = $ReadOnlyArray; +export type RelayReaderRequiredFieldsTest6Fragment$key = $ReadOnlyArray<{ + +$data?: RelayReaderRequiredFieldsTest6Fragment$data, + +$fragmentSpreads: RelayReaderRequiredFieldsTest6Fragment$fragmentType, + ... +}>; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "plural": true + }, + "name": "RelayReaderRequiredFieldsTest6Fragment", + "selections": [ + { + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "username", + "storageKey": null + }, + "action": "LOG", + "path": "username" + } + ], + "type": "User", + "abstractKey": null +}; + +if (__DEV__) { + (node/*: any*/).hash = "bdda7f856891c1faba4a6392d5ea209c"; +} + +module.exports = ((node/*: any*/)/*: Fragment< + RelayReaderRequiredFieldsTest6Fragment$fragmentType, + RelayReaderRequiredFieldsTest6Fragment$data, +>*/);