From 4c25549bcb1a6c5c3a3168d0401d15dff677af78 Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Fri, 15 Dec 2023 17:31:56 -0800 Subject: [PATCH] More strict selectors equal check Reviewed By: voideanvalue Differential Revision: D52184539 fbshipit-source-id: 2116cb5ba866c763b5a026145910914a17ddf66d --- .../store/RelayModernSelector.js | 43 +++++++++- .../__tests__/RelayModernSelector-test.js | 81 +++++++++++++++++++ .../relay-runtime/util/RelayFeatureFlags.js | 3 + 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/packages/relay-runtime/store/RelayModernSelector.js b/packages/relay-runtime/store/RelayModernSelector.js index 1c65be417cca6..a57709f08fe7e 100644 --- a/packages/relay-runtime/store/RelayModernSelector.js +++ b/packages/relay-runtime/store/RelayModernSelector.js @@ -23,6 +23,7 @@ import type { SingularReaderSelector, } from './RelayStoreTypes'; +const RelayFeatureFlags = require('../util/RelayFeatureFlags'); const {getFragmentVariables} = require('./RelayConcreteVariables'); const { CLIENT_EDGE_TRAVERSAL_PATH, @@ -412,7 +413,14 @@ function areEqualSingularSelectors( thisSelector.dataID === thatSelector.dataID && thisSelector.node === thatSelector.node && areEqual(thisSelector.variables, thatSelector.variables) && - areEqualOwners(thisSelector.owner, thatSelector.owner) + areEqualOwners(thisSelector.owner, thatSelector.owner) && + (!RelayFeatureFlags.ENABLE_STRICT_EQUAL_SELECTORS || + (thisSelector.isWithinUnmatchedTypeRefinement === + thatSelector.isWithinUnmatchedTypeRefinement && + areEqualClientEdgeTraversalPaths( + thisSelector.clientEdgeTraversalPath, + thatSelector.clientEdgeTraversalPath, + ))) ); } @@ -434,6 +442,39 @@ function areEqualOwners( } } +function areEqualClientEdgeTraversalPaths( + thisPath: ClientEdgeTraversalPath | null, + thatPath: ClientEdgeTraversalPath | null, +): boolean { + if (thisPath === thatPath) { + return true; + } + if ( + thisPath == null || + thatPath == null || + thisPath.length !== thatPath.length + ) { + return false; + } + let idx = thisPath.length; + while (idx--) { + const a = thisPath[idx]; + const b = thatPath[idx]; + if (a === b) { + continue; + } + if ( + a == null || + b == null || + a.clientEdgeDestinationID !== b.clientEdgeDestinationID || + a.readerClientEdge !== b.readerClientEdge + ) { + return false; + } + } + return true; +} + /** * @public * diff --git a/packages/relay-runtime/store/__tests__/RelayModernSelector-test.js b/packages/relay-runtime/store/__tests__/RelayModernSelector-test.js index 52101ab2774c0..345b9fc03b2fd 100644 --- a/packages/relay-runtime/store/__tests__/RelayModernSelector-test.js +++ b/packages/relay-runtime/store/__tests__/RelayModernSelector-test.js @@ -15,6 +15,7 @@ import type {OperationDescriptor} from '../RelayStoreTypes'; import type {Variables} from 'relay-runtime/util/RelayRuntimeTypes'; const {graphql} = require('../../query/GraphQLTag'); +const RelayFeatureFlags = require('../../util/RelayFeatureFlags'); const { createOperationDescriptor, createRequestDescriptor, @@ -120,6 +121,7 @@ describe('RelayModernSelector', () => { size: null, cond: false, }; + RelayFeatureFlags.ENABLE_STRICT_EQUAL_SELECTORS = true; }); describe('getSingularSelector()', () => { @@ -700,6 +702,85 @@ describe('RelayModernSelector', () => { expect(areEqualSelectors(selector, differentOwner)).toBe(false); }); + it('returns false for equivalent selectors but with different isWithinUnmatchedTypeRefinement', () => { + const queryNode = UserQuery; + owner = createOperationDescriptor(queryNode, operationVariables); + const selector = createReaderSelector( + UserFragment, + '4', + variables, + owner.request, + ); + const differentOwner = { + ...selector, + isWithinUnmatchedTypeRefinement: true, + }; + expect(areEqualSelectors(selector, differentOwner)).toBe(false); + }); + + it('returns false for equivalent selectors but with different clientEdgeTraversalPath', () => { + const queryNode = UserQuery; + owner = createOperationDescriptor(queryNode, operationVariables); + /*$FlowFixMe[unclear-type]*/ + const fakeAstNode: Object = {}; + const clientEdgeTraversalInfoA = { + readerClientEdge: fakeAstNode, + clientEdgeDestinationID: 'a', + }; + const clientEdgeTraversalInfoB = { + readerClientEdge: fakeAstNode, + clientEdgeDestinationID: 'b', + }; + const clientEdgeTraversalInfoANewNode = { + /*$FlowFixMe[unclear-type]*/ + readerClientEdge: ({}: Object), + clientEdgeDestinationID: 'a', + }; + const baseSelector = createReaderSelector( + UserFragment, + '4', + variables, + owner.request, + false, + null, + ); + const selectors = [ + baseSelector, + { + ...baseSelector, + clientEdgeTraversalPath: [clientEdgeTraversalInfoA], + }, + { + ...baseSelector, + clientEdgeTraversalPath: [clientEdgeTraversalInfoB], + }, + { + ...baseSelector, + clientEdgeTraversalPath: [ + clientEdgeTraversalInfoANewNode, + clientEdgeTraversalInfoANewNode, + ], + }, + { + ...baseSelector, + clientEdgeTraversalPath: [clientEdgeTraversalInfoANewNode], + }, + { + ...baseSelector, + clientEdgeTraversalPath: [null], + }, + { + ...baseSelector, + clientEdgeTraversalPath: [null, clientEdgeTraversalInfoA], + }, + ]; + for (let i = 0; i < selectors.length - 1; i++) { + for (let j = i + 1; j < selectors.length; j++) { + expect(areEqualSelectors(selectors[i], selectors[j])).toBe(false); + } + } + }); + it('returns true for equivalent selectors and equivalent owners', () => { const queryNode = UserQuery; owner = createOperationDescriptor(queryNode, operationVariables); diff --git a/packages/relay-runtime/util/RelayFeatureFlags.js b/packages/relay-runtime/util/RelayFeatureFlags.js index edc01c77e5fa5..d26de0a8c6b5e 100644 --- a/packages/relay-runtime/util/RelayFeatureFlags.js +++ b/packages/relay-runtime/util/RelayFeatureFlags.js @@ -44,6 +44,8 @@ export type FeatureFlags = { // in a partial response. // @see https://spec.graphql.org/October2021/#sec-Handling-Field-Errors ENABLE_FIELD_ERROR_HANDLING: boolean, + + ENABLE_STRICT_EQUAL_SELECTORS: boolean, }; const RelayFeatureFlags: FeatureFlags = { @@ -65,6 +67,7 @@ const RelayFeatureFlags: FeatureFlags = { ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE: false, ENABLE_FIELD_ERROR_HANDLING: false, ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES: true, + ENABLE_STRICT_EQUAL_SELECTORS: false, }; module.exports = RelayFeatureFlags;