From 00aabd00be72858934b06d25ef26d353fb9c71cd Mon Sep 17 00:00:00 2001 From: Monica Tang Date: Tue, 2 Jan 2024 14:50:49 -0800 Subject: [PATCH] Fix client->client nullable model issue for PluralConcrete object output types Reviewed By: captbaritone Differential Revision: D51870132 fbshipit-source-id: ffa35fb3c16128651457e638cc754c929f074d22 --- ...layResolverNullableModelClientEdge-test.js | 92 ++++++++ ..._PluralLiveModelNoneExist_Query.graphql.js | 198 ++++++++++++++++++ ...tEdgeTest_PluralLiveModel_Query.graphql.js | 198 ++++++++++++++++++ packages/relay-runtime/store/RelayReader.js | 10 +- 4 files changed, 497 insertions(+), 1 deletion(-) create mode 100644 packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query.graphql.js create mode 100644 packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query.graphql.js diff --git a/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js b/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js index ac16fecbe050a..b41686ebb7b23 100644 --- a/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js +++ b/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js @@ -29,9 +29,34 @@ const { RelayFeatureFlags, graphql, } = require('relay-runtime'); +const { + addTodo, +} = require('relay-runtime/store/__tests__/resolvers/ExampleTodoStore'); const LiveResolverStore = require('relay-runtime/store/experimental-live-resolvers/LiveResolverStore'); const {createMockEnvironment} = require('relay-test-utils'); +/** + * CLIENT EDGE TO PLURAL LIVE STRONG CLIENT OBJECT + */ + +/** + * @RelayResolver Query.edge_to_plural_live_objects_some_exist: [TodoModel] + */ +export function edge_to_plural_live_objects_some_exist(): $ReadOnlyArray<{ + id: DataID, +}> { + return [{id: 'todo-1'}, {id: 'THERE_IS_NO_TODO_WITH_THIS_ID'}]; +} + +/** + * @RelayResolver Query.edge_to_plural_live_objects_none_exist: [TodoModel] + */ +export function edge_to_plural_live_objects_none_exist(): $ReadOnlyArray<{ + id: DataID, +}> { + return [{id: 'NO_TODO_1'}, {id: 'NO_TODO_2'}]; +} + /** * CLIENT EDGE TO LIVE STRONG CLIENT OBJECT */ @@ -158,6 +183,73 @@ describe.each([ beforeEach(() => { environment = createEnvironment(); }); + test('client edge to plural IDs, none have corresponding live object', () => { + function TodoNullComponent() { + const data = useClientQuery( + graphql` + query RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query { + edge_to_plural_live_objects_none_exist { + id + description + } + } + `, + {}, + ); + + invariant(data != null, 'Query response should be nonnull'); + expect(data.edge_to_plural_live_objects_none_exist).toHaveLength(2); + return data.edge_to_plural_live_objects_none_exist + ?.map(item => + item + ? `${item.id ?? 'unknown'} - ${item.description ?? 'unknown'}` + : 'unknown', + ) + .join(','); + } + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toEqual('unknown,unknown'); + }); + + test('client edge to plural IDs, some with no corresponding live object', () => { + function TodoNullComponent() { + const data = useClientQuery( + graphql` + query RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query { + edge_to_plural_live_objects_some_exist { + id + description + } + } + `, + {}, + ); + + invariant(data != null, 'Query response should be nonnull'); + expect(data.edge_to_plural_live_objects_some_exist).toHaveLength(2); + return data.edge_to_plural_live_objects_some_exist + ?.map(item => + item + ? `${item.id ?? 'unknown'} - ${item.description ?? 'unknown'}` + : 'unknown', + ) + .join(','); + } + + addTodo('Test todo'); + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toEqual('todo-1 - Test todo,unknown'); + }); + test('client edge to ID with no corresponding live object', () => { function TodoNullComponent() { const data = useClientQuery( diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query.graphql.js new file mode 100644 index 0000000000000..746b929f3284d --- /dev/null +++ b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query.graphql.js @@ -0,0 +1,198 @@ +/** + * 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<<64c14c21fc90aa6c3436149d7a959674>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ClientRequest, ClientQuery } from 'relay-runtime'; +import type { DataID } from "relay-runtime"; +import type { TodoModel____relay_model_instance$data } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel____relay_model_instance.graphql"; +import {edge_to_plural_live_objects_none_exist as queryEdgeToPluralLiveObjectsNoneExistResolverType} from "../RelayResolverNullableModelClientEdge-test.js"; +// Type assertion validating that `queryEdgeToPluralLiveObjectsNoneExistResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(queryEdgeToPluralLiveObjectsNoneExistResolverType: () => ?$ReadOnlyArray); +import {description as todoModelDescriptionResolverType} from "../../../relay-runtime/store/__tests__/resolvers/TodoModel.js"; +// Type assertion validating that `todoModelDescriptionResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(todoModelDescriptionResolverType: ( + __relay_model_instance: TodoModel____relay_model_instance$data['__relay_model_instance'], +) => ?string); +export type RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query$variables = {||}; +export type RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query$data = {| + +edge_to_plural_live_objects_none_exist: ?$ReadOnlyArray, +|}; +export type RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query = {| + response: RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query$data, + variables: RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query$variables, +|}; +*/ + +var node/*: ClientRequest*/ = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "hasClientEdges": true + }, + "name": "RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "edge_to_plural_live_objects_none_exist", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "edge_to_plural_live_objects_none_exist.__relay_model_instance" + }, + "backingField": { + "alias": null, + "args": null, + "fragment": null, + "kind": "RelayResolver", + "name": "edge_to_plural_live_objects_none_exist", + "resolverModule": require('./../RelayResolverNullableModelClientEdge-test').edge_to_plural_live_objects_none_exist, + "path": "edge_to_plural_live_objects_none_exist" + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "TodoModel", + "kind": "LinkedField", + "name": "edge_to_plural_live_objects_none_exist", + "plural": true, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel____relay_model_instance" + }, + "kind": "RelayResolver", + "name": "description", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel____relay_model_instance.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').description, '__relay_model_instance', true), + "path": "edge_to_plural_live_objects_none_exist.description" + } + ], + "storageKey": null + } + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "backingField": { + "name": "edge_to_plural_live_objects_none_exist", + "args": null, + "fragment": null, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": false + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "TodoModel", + "kind": "LinkedField", + "name": "edge_to_plural_live_objects_none_exist", + "plural": true, + "selections": [ + (v0/*: any*/), + { + "name": "description", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + { + "name": "__relay_model_instance", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + (v0/*: any*/) + ], + "type": "TodoModel", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": false + } + ], + "type": "TodoModel", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": true + } + ], + "storageKey": null + } + } + ] + }, + "params": { + "cacheID": "2cf8306cf86529bf2fddf425e1816af4", + "id": null, + "metadata": {}, + "name": "RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query", + "operationKind": "query", + "text": null + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "99ff4eeb2e8eb3dfaed38852f3d2c70f"; +} + +module.exports = ((node/*: any*/)/*: ClientQuery< + RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query$variables, + RelayResolverNullableModelClientEdgeTest_PluralLiveModelNoneExist_Query$data, +>*/); diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query.graphql.js new file mode 100644 index 0000000000000..5b75ca25b8fcc --- /dev/null +++ b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query.graphql.js @@ -0,0 +1,198 @@ +/** + * 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<<068bc2521f50334bee6b072b269cdb55>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ClientRequest, ClientQuery } from 'relay-runtime'; +import type { DataID } from "relay-runtime"; +import type { TodoModel____relay_model_instance$data } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel____relay_model_instance.graphql"; +import {edge_to_plural_live_objects_some_exist as queryEdgeToPluralLiveObjectsSomeExistResolverType} from "../RelayResolverNullableModelClientEdge-test.js"; +// Type assertion validating that `queryEdgeToPluralLiveObjectsSomeExistResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(queryEdgeToPluralLiveObjectsSomeExistResolverType: () => ?$ReadOnlyArray); +import {description as todoModelDescriptionResolverType} from "../../../relay-runtime/store/__tests__/resolvers/TodoModel.js"; +// Type assertion validating that `todoModelDescriptionResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(todoModelDescriptionResolverType: ( + __relay_model_instance: TodoModel____relay_model_instance$data['__relay_model_instance'], +) => ?string); +export type RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query$variables = {||}; +export type RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query$data = {| + +edge_to_plural_live_objects_some_exist: ?$ReadOnlyArray, +|}; +export type RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query = {| + response: RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query$data, + variables: RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query$variables, +|}; +*/ + +var node/*: ClientRequest*/ = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "hasClientEdges": true + }, + "name": "RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "edge_to_plural_live_objects_some_exist", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "edge_to_plural_live_objects_some_exist.__relay_model_instance" + }, + "backingField": { + "alias": null, + "args": null, + "fragment": null, + "kind": "RelayResolver", + "name": "edge_to_plural_live_objects_some_exist", + "resolverModule": require('./../RelayResolverNullableModelClientEdge-test').edge_to_plural_live_objects_some_exist, + "path": "edge_to_plural_live_objects_some_exist" + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "TodoModel", + "kind": "LinkedField", + "name": "edge_to_plural_live_objects_some_exist", + "plural": true, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel____relay_model_instance" + }, + "kind": "RelayResolver", + "name": "description", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel____relay_model_instance.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').description, '__relay_model_instance', true), + "path": "edge_to_plural_live_objects_some_exist.description" + } + ], + "storageKey": null + } + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "backingField": { + "name": "edge_to_plural_live_objects_some_exist", + "args": null, + "fragment": null, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": false + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "TodoModel", + "kind": "LinkedField", + "name": "edge_to_plural_live_objects_some_exist", + "plural": true, + "selections": [ + (v0/*: any*/), + { + "name": "description", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + { + "name": "__relay_model_instance", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + (v0/*: any*/) + ], + "type": "TodoModel", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": false + } + ], + "type": "TodoModel", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": true + } + ], + "storageKey": null + } + } + ] + }, + "params": { + "cacheID": "21b0ec4e0d6526708b4a0de91391b7d7", + "id": null, + "metadata": {}, + "name": "RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query", + "operationKind": "query", + "text": null + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "abbb7292c9ca7ffab83aec05b278406b"; +} + +module.exports = ((node/*: any*/)/*: ClientQuery< + RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query$variables, + RelayResolverNullableModelClientEdgeTest_PluralLiveModel_Query$data, +>*/); diff --git a/packages/relay-runtime/store/RelayReader.js b/packages/relay-runtime/store/RelayReader.js index 0414ca6868aeb..9387f2ab0d3aa 100644 --- a/packages/relay-runtime/store/RelayReader.js +++ b/packages/relay-runtime/store/RelayReader.js @@ -740,10 +740,18 @@ class RelayReader { validClientEdgeResolverResponse.ids, this._resolverCache, ); + let validStoreIDs: $ReadOnlyArray = storeIDs; + if (field.modelResolver != null) { + const modelResolver = field.modelResolver; + validStoreIDs = storeIDs.map(storeID => { + const model = this._readResolverFieldImpl(modelResolver, storeID); + return model != null ? storeID : null; + }); + } this._clientEdgeTraversalPath.push(null); const edgeValues = this._readLinkedIds( field.linkedField, - storeIDs, + validStoreIDs, record, data, );