From 61c7bffac76f63179869d3cab797784ddfa87289 Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Wed, 1 Nov 2023 13:46:01 -0700 Subject: [PATCH] Regression test for mutating complex resolver values Reviewed By: alunyov Differential Revision: D50764867 fbshipit-source-id: ae20dcd9f41b00ea3d5820dc54d79dfa81dbbaf6 --- .../__tests__/RelayResolverModel-test.js | 54 ++++++++++ ...rModelTestGetMutableEntityQuery.graphql.js | 99 +++++++++++++++++++ .../store/__tests__/resolvers/MutableModel.js | 93 +++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 packages/react-relay/__tests__/__generated__/RelayResolverModelTestGetMutableEntityQuery.graphql.js create mode 100644 packages/relay-runtime/store/__tests__/resolvers/MutableModel.js diff --git a/packages/react-relay/__tests__/RelayResolverModel-test.js b/packages/react-relay/__tests__/RelayResolverModel-test.js index 2d7a70e77fa3d..cc0c128fdc1c8 100644 --- a/packages/react-relay/__tests__/RelayResolverModel-test.js +++ b/packages/react-relay/__tests__/RelayResolverModel-test.js @@ -34,6 +34,11 @@ const { completeTodo, resetStore, } = require('relay-runtime/store/__tests__/resolvers/ExampleTodoStore'); +const { + chargeBattery, + resetModels, + setIsHuman, +} = require('relay-runtime/store/__tests__/resolvers/MutableModel'); const LiveResolverStore = require('relay-runtime/store/experimental-live-resolvers/LiveResolverStore.js'); const RelayModernEnvironment = require('relay-runtime/store/RelayModernEnvironment'); const RelayRecordSource = require('relay-runtime/store/RelayRecordSource'); @@ -494,4 +499,53 @@ describe.each([ }, }); }); + + test('should not mutate complex resolver values', () => { + resetModels(); + // Do not deep freeze + jest.mock('relay-runtime/util/deepFreeze'); + + TestRenderer.act(() => { + setIsHuman(true); + }); + function GetMutableEntity() { + const data = useClientQuery( + graphql` + query RelayResolverModelTestGetMutableEntityQuery { + mutable_entity + } + `, + {}, + ); + if (data.mutable_entity == null) { + return null; + } + return `${data.mutable_entity.type}:${data.mutable_entity.props.battery}`; + } + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toEqual('human:0'); + + TestRenderer.act(() => { + setIsHuman(false); + jest.runAllImmediates(); + }); + expect(renderer.toJSON()).toEqual('robot:0'); + + TestRenderer.act(() => { + chargeBattery(); + setIsHuman(true); + jest.runAllImmediates(); + }); + // TODO: Should be 0. Relay should not mutate the value here. + expect(renderer.toJSON()).toEqual('human:100'); + + TestRenderer.act(() => { + renderer.unmount(); + }); + }); }); diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestGetMutableEntityQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestGetMutableEntityQuery.graphql.js new file mode 100644 index 0000000000000..df5db6c912ed3 --- /dev/null +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestGetMutableEntityQuery.graphql.js @@ -0,0 +1,99 @@ +/** + * 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<<66d0b3cabdba7cc580b3e2e2169ef5d8>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ClientRequest, ClientQuery } from 'relay-runtime'; +import type { LiveState } from "relay-runtime/store/experimental-live-resolvers/LiveResolverStore"; +import {mutable_entity as queryMutableEntityResolverType} from "../../../relay-runtime/store/__tests__/resolvers/MutableModel.js"; +// Type assertion validating that `queryMutableEntityResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(queryMutableEntityResolverType: () => LiveState); +export type RelayResolverModelTestGetMutableEntityQuery$variables = {||}; +export type RelayResolverModelTestGetMutableEntityQuery$data = {| + +mutable_entity: ?ReturnType["read"]>, +|}; +export type RelayResolverModelTestGetMutableEntityQuery = {| + response: RelayResolverModelTestGetMutableEntityQuery$data, + variables: RelayResolverModelTestGetMutableEntityQuery$variables, +|}; +*/ + +var node/*: ClientRequest*/ = { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayResolverModelTestGetMutableEntityQuery", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "fragment": null, + "kind": "RelayLiveResolver", + "name": "mutable_entity", + "resolverModule": require('./../../../relay-runtime/store/__tests__/resolvers/MutableModel').mutable_entity, + "path": "mutable_entity" + } + ] + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayResolverModelTestGetMutableEntityQuery", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "name": "mutable_entity", + "args": null, + "fragment": null, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": true + } + ] + } + ] + }, + "params": { + "cacheID": "aa33fbf58d2c2c1640de7da7280d2f2e", + "id": null, + "metadata": {}, + "name": "RelayResolverModelTestGetMutableEntityQuery", + "operationKind": "query", + "text": null + } +}; + +if (__DEV__) { + (node/*: any*/).hash = "bd6186904ff5b69591c6929ee7f72aa4"; +} + +module.exports = ((node/*: any*/)/*: ClientQuery< + RelayResolverModelTestGetMutableEntityQuery$variables, + RelayResolverModelTestGetMutableEntityQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/resolvers/MutableModel.js b/packages/relay-runtime/store/__tests__/resolvers/MutableModel.js new file mode 100644 index 0000000000000..0b8f74868ae4e --- /dev/null +++ b/packages/relay-runtime/store/__tests__/resolvers/MutableModel.js @@ -0,0 +1,93 @@ +/** + * 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 {LiveState} from '../../experimental-live-resolvers/LiveResolverStore'; + +type Entity = { + name: string, + type: string, + props: { + battery: string, + }, +}; + +let HUMAN: Entity = { + name: 'Alice', + type: 'human', + props: { + battery: '0', + }, +}; + +let ROBOT: Entity = { + name: 'Bob', + type: 'robot', + props: { + battery: '0', + }, +}; + +const subscriptions: Array<() => void> = []; +let isHuman: boolean = true; +/** + * @RelayResolver Query.mutable_entity: RelayResolverValue + * @live + + */ +function mutable_entity(): LiveState { + return { + read() { + return isHuman ? HUMAN : ROBOT; + }, + subscribe(cb) { + subscriptions.push(cb); + return () => { + subscriptions.filter(x => x !== cb); + }; + }, + }; +} + +function setIsHuman(val: boolean): void { + isHuman = val; + subscriptions.forEach(x => x()); +} + +function chargeBattery(): void { + ROBOT.props.battery = '100'; + subscriptions.forEach(x => x()); +} + +function resetModels(): void { + HUMAN = { + name: 'Alice', + type: 'human', + props: { + battery: '0', + }, + }; + ROBOT = { + name: 'Bob', + type: 'robot', + props: { + battery: '0', + }, + }; +} + +module.exports = { + mutable_entity, + setIsHuman, + chargeBattery, + resetModels, +};