From b8fcf2fa73ceabb9c0a194e54a5824eadfbf48f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 5 Aug 2025 09:08:25 -0700 Subject: [PATCH 1/2] Add private function in feature flags to reset internal JS state for testing Differential Revision: D79639889 --- .../ReactNativeFeatureFlagsBase.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlagsBase.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlagsBase.js index 5b3d461ff36a..050b003a2efe 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlagsBase.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlagsBase.js @@ -18,6 +18,10 @@ import NativeReactNativeFeatureFlags from './specs/NativeReactNativeFeatureFlags const accessedFeatureFlags: Set = new Set(); let overrides: ?ReactNativeFeatureFlagsJsOnlyOverrides; +// This is a list of functions to clear the cached value for each feature flag +// getter. This is only used in development. +const clearCachedValuesFns: Array<() => void> = []; + export type Getter = () => T; // This defines the types for the overrides object, whose methods also receive @@ -33,6 +37,12 @@ function createGetter( ): Getter { let cachedValue: ?T; + if (__DEV__) { + clearCachedValuesFns.push(() => { + cachedValue = undefined; + }); + } + return () => { if (cachedValue == null) { cachedValue = customValueGetter() ?? defaultValue; @@ -115,3 +125,12 @@ function maybeLogUnavailableNativeModuleError(configName: string): void { ); } } + +export function dangerouslyResetForTesting(): void { + if (__DEV__) { + overrides = null; + accessedFeatureFlags.clear(); + reportedConfigNames.clear(); + clearCachedValuesFns.forEach(fn => fn()); + } +} From 1e529cd875ef008b7889e422245ea777959a4601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 5 Aug 2025 09:17:29 -0700 Subject: [PATCH 2/2] Add test to ensure setUpDefaultReactNativeEnvironment does not access feature flags (#53057) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53057 Changelog: [internal] This adds a Fantom test to ensure that `setUpDefaultReactNativeEnvironment` doesn't read any feature flags. This prevents catching this as a runtime issue when the feature flag system complains that feature flags were accessed before being overridden, which always would happen if this module read any flags (as it runs before any product code that sets overrides). Reviewed By: rshest Differential Revision: D79639890 --- ...actNativeEnvironment-FeatureFlags-itest.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-FeatureFlags-itest.js diff --git a/packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-FeatureFlags-itest.js b/packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-FeatureFlags-itest.js new file mode 100644 index 000000000000..b6f7acd4be4b --- /dev/null +++ b/packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-FeatureFlags-itest.js @@ -0,0 +1,28 @@ +/** + * 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 + */ + +import * as ReactNativeFeatureFlags from '../../featureflags/ReactNativeFeatureFlags'; +import * as ReactNativeFeatureFlagsBase from '../../featureflags/ReactNativeFeatureFlagsBase'; +import setUpDefaultReactNativeEnvironment from 'react-native/src/private/setup/setUpDefaultReactNativeEnvironment'; + +describe('setUpReactNativeEnvironment', () => { + it('should not read any feature flags', () => { + ReactNativeFeatureFlagsBase.dangerouslyResetForTesting(); + + setUpDefaultReactNativeEnvironment(false); + + expect(() => { + // If any feature flags were read, this call would fail. + ReactNativeFeatureFlags.override({ + jsOnlyTestFlag: () => true, + }); + }).not.toThrow(); + }); +});