From 1de2de6755d45a40a5f4d38d744d36e23c706a46 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 26 Aug 2025 14:54:46 -0700 Subject: [PATCH] Regression test for abstract root fragment Differential Revision: D80984351 --- ...Resolvers-abstractTypeRootFragment-test.js | 102 ++++++++++++ ...stractTypeRootFragmentTestQuery.graphql.js | 153 ++++++++++++++++++ .../__tests__/resolvers/NodeResolvers.js | 37 +++++ .../NodeResolversGreeting.graphql.js | 59 +++++++ 4 files changed, 351 insertions(+) create mode 100644 packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js create mode 100644 packages/react-relay/__tests__/__generated__/RelayResolversAbstractTypeRootFragmentTestQuery.graphql.js create mode 100644 packages/relay-runtime/store/__tests__/resolvers/NodeResolvers.js create mode 100644 packages/relay-runtime/store/__tests__/resolvers/__generated__/NodeResolversGreeting.graphql.js diff --git a/packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js b/packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js new file mode 100644 index 0000000000000..87a507857be0e --- /dev/null +++ b/packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js @@ -0,0 +1,102 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @oncall relay + */ + +'use strict'; + +import type {IEnvironment} from 'relay-runtime'; + +const React = require('react'); +const {RelayEnvironmentProvider, useLazyLoadQuery} = require('react-relay'); +const TestRenderer = require('react-test-renderer'); +const {graphql} = require('relay-runtime/query/GraphQLTag'); +const {createMockEnvironment} = require('relay-test-utils'); +const { + disallowConsoleErrors, + disallowWarnings, + injectPromisePolyfill__DEPRECATED, +} = require('relay-test-utils-internal'); + +injectPromisePolyfill__DEPRECATED(); +disallowWarnings(); +disallowConsoleErrors(); + +function EnvironmentWrapper({ + children, + environment, +}: { + children: React.Node, + environment: IEnvironment, +}) { + return ( + + {children} + + ); +} + +function NodeGreeting() { + const data = useLazyLoadQuery( + graphql` + query RelayResolversAbstractTypeRootFragmentTestQuery { + node(id: "4") { + node_greeting + } + } + `, + {}, + ); + return data.node?.node_greeting; +} + +// https://github.com/facebook/relay/issues/4943#issuecomment-3221637018 +test('Can read a resolver with a rootFragment on an abstract type', async () => { + const logEvents = []; + const environment = createMockEnvironment({ + relayFieldLogger(event) { + logEvents.push(event); + }, + }); + let renderer; + TestRenderer.act(() => { + renderer = TestRenderer.create( + + + , + ); + }); + expect(renderer?.toJSON()).toEqual('Loading...'); + expect(logEvents).toEqual([]); + + await TestRenderer.act(() => { + environment.mock.resolveMostRecentOperation({ + data: { + node: { + __typename: 'User', + __isNode: 'User', + id: '4', + }, + }, + }); + jest.runAllImmediates(); + }); + + // Incorrect! We provided `__isNode`! + expect(logEvents).toEqual([ + { + fieldPath: '', + kind: 'missing_expected_data.log', + owner: 'NodeResolversGreeting', + uiContext: undefined, + }, + ]); + // Incorrect! Should be the greeting. + expect(renderer?.toJSON()).toEqual(null); +}); diff --git a/packages/react-relay/__tests__/__generated__/RelayResolversAbstractTypeRootFragmentTestQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolversAbstractTypeRootFragmentTestQuery.graphql.js new file mode 100644 index 0000000000000..326c00bedfac4 --- /dev/null +++ b/packages/react-relay/__tests__/__generated__/RelayResolversAbstractTypeRootFragmentTestQuery.graphql.js @@ -0,0 +1,153 @@ +/** + * 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<<99027ecd32b72e3db29e851998075f28>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { NodeResolversGreeting$key } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/NodeResolversGreeting.graphql"; +import {node_greeting as nodeNodeGreetingResolverType} from "../../../relay-runtime/store/__tests__/resolvers/NodeResolvers.js"; +import type { TestResolverContextType } from "../../../relay-runtime/mutations/__tests__/TestResolverContextType"; +// Type assertion validating that `nodeNodeGreetingResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(nodeNodeGreetingResolverType: ( + rootKey: NodeResolversGreeting$key, + args: void, + context: TestResolverContextType, +) => ?string); +export type RelayResolversAbstractTypeRootFragmentTestQuery$variables = {||}; +export type RelayResolversAbstractTypeRootFragmentTestQuery$data = {| + +node: ?{| + +node_greeting: ?string, + |}, +|}; +export type RelayResolversAbstractTypeRootFragmentTestQuery = {| + response: RelayResolversAbstractTypeRootFragmentTestQuery$data, + variables: RelayResolversAbstractTypeRootFragmentTestQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "Literal", + "name": "id", + "value": "4" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayResolversAbstractTypeRootFragmentTestQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "NodeResolversGreeting" + }, + "kind": "RelayResolver", + "name": "node_greeting", + "resolverModule": require('../../../relay-runtime/store/__tests__/resolvers/NodeResolvers').node_greeting, + "path": "node.node_greeting" + } + ], + "storageKey": "node(id:\"4\")" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayResolversAbstractTypeRootFragmentTestQuery", + "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 + }, + { + "name": "node_greeting", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + (v1/*: any*/) + ], + "type": "Node", + "abstractKey": "__isNode" + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": true + }, + (v1/*: any*/) + ], + "storageKey": "node(id:\"4\")" + } + ] + }, + "params": { + "cacheID": "16a426563480b45f425459f7da9a621f", + "id": null, + "metadata": {}, + "name": "RelayResolversAbstractTypeRootFragmentTestQuery", + "operationKind": "query", + "text": "query RelayResolversAbstractTypeRootFragmentTestQuery {\n node(id: \"4\") {\n __typename\n ...NodeResolversGreeting\n id\n }\n}\n\nfragment NodeResolversGreeting on Node {\n __isNode: __typename\n id\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "38ce80d969e3fdad2e27031ddd534888"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayResolversAbstractTypeRootFragmentTestQuery$variables, + RelayResolversAbstractTypeRootFragmentTestQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/resolvers/NodeResolvers.js b/packages/relay-runtime/store/__tests__/resolvers/NodeResolvers.js new file mode 100644 index 0000000000000..4ea07205e544e --- /dev/null +++ b/packages/relay-runtime/store/__tests__/resolvers/NodeResolvers.js @@ -0,0 +1,37 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @oncall relay + */ + +'use strict'; + +import type {NodeResolversGreeting$key} from './__generated__/NodeResolversGreeting.graphql'; + +const {graphql} = require('relay-runtime'); +const {readFragment} = require('relay-runtime/store/ResolverFragments'); + +/** + * @RelayResolver Node.node_greeting: String + * @rootFragment NodeResolversGreeting + */ +function node_greeting(rootKey: NodeResolversGreeting$key): string { + const node = readFragment( + graphql` + fragment NodeResolversGreeting on Node { + id + } + `, + rootKey, + ); + return `Hello Node with id ${node.id}!`; +} + +module.exports = { + node_greeting, +}; diff --git a/packages/relay-runtime/store/__tests__/resolvers/__generated__/NodeResolversGreeting.graphql.js b/packages/relay-runtime/store/__tests__/resolvers/__generated__/NodeResolversGreeting.graphql.js new file mode 100644 index 0000000000000..2f5988d659606 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/resolvers/__generated__/NodeResolversGreeting.graphql.js @@ -0,0 +1,59 @@ +/** + * 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<<65d3cfa934e7852b8f03a5fab85b131b>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type NodeResolversGreeting$fragmentType: FragmentType; +export type NodeResolversGreeting$data = {| + +id: string, + +$fragmentType: NodeResolversGreeting$fragmentType, +|}; +export type NodeResolversGreeting$key = { + +$data?: NodeResolversGreeting$data, + +$fragmentSpreads: NodeResolversGreeting$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "NodeResolversGreeting", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "type": "Node", + "abstractKey": "__isNode" +}; + +if (__DEV__) { + (node/*: any*/).hash = "d331274a92b58d1de6941541d745b6ae"; +} + +module.exports = ((node/*: any*/)/*: Fragment< + NodeResolversGreeting$fragmentType, + NodeResolversGreeting$data, +>*/);