diff --git a/packages/react-native/Libraries/ReactNative/UIManager.js b/packages/react-native/Libraries/ReactNative/UIManager.js index a97c6a7b3ea..64d433c7597 100644 --- a/packages/react-native/Libraries/ReactNative/UIManager.js +++ b/packages/react-native/Libraries/ReactNative/UIManager.js @@ -23,9 +23,7 @@ const UIManagerImpl: UIManagerJSInterface = ? require('./BridgelessUIManager').default : require('./PaperUIManager').default; -// $FlowFixMe[cannot-spread-interface] -const UIManager: UIManagerJSInterface = { - ...UIManagerImpl, +const UIManagerOverrides = { /** * Determines the location on screen, width, and height of the given view and * returns the values via an async callback. If successful, the callback will @@ -243,4 +241,19 @@ const UIManager: UIManagerJSInterface = { }, }; +// Copy property descriptors instead of spreading UIManagerImpl. In Bridgeless +// mode, UIManagerImpl may define enumerable lazy ViewManager getters. Spreading +// the object would invoke every getter during module initialization. +// $FlowFixMe[incompatible-type] Flow cannot infer properties from descriptors. +const UIManager: UIManagerJSInterface = Object.create( + Object.getPrototypeOf(UIManagerImpl), + { + ...Object.getOwnPropertyDescriptors( + // $FlowFixMe[class-object-subtyping] UIManagerImpl is a runtime object. + UIManagerImpl, + ), + ...Object.getOwnPropertyDescriptors(UIManagerOverrides), + }, +); + export default UIManager; diff --git a/packages/react-native/Libraries/ReactNative/__tests__/UIManager-test.js b/packages/react-native/Libraries/ReactNative/__tests__/UIManager-test.js new file mode 100644 index 00000000000..9e2633eb48b --- /dev/null +++ b/packages/react-native/Libraries/ReactNative/__tests__/UIManager-test.js @@ -0,0 +1,74 @@ +/** + * 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 + */ + +'use strict'; + +jest.mock('../../Utilities/Platform', () => ({ + OS: 'android', + select: spec => spec.android ?? spec.native ?? spec.default, +})); +jest.unmock('../UIManager'); + +describe('UIManager', () => { + it('preserves lazy ViewManager getters when composing the Bridgeless implementation', () => { + const originalBridgelessValue = global.RN$Bridgeless; + const originalGetConstants = global.RN$LegacyInterop_UIManager_getConstants; + const originalGetConstantsForViewManager = + global.RN$LegacyInterop_UIManager_getConstantsForViewManager; + const originalGetDefaultEventTypes = + global.RN$LegacyInterop_UIManager_getDefaultEventTypes; + const viewManagerConfig = {Commands: {focus: 1}}; + const getConstants = jest.fn(() => ({ + LazyViewManagersEnabled: true, + ViewManagerNames: ['RCTTestView'], + })); + const getConstantsForViewManager = jest.fn(() => viewManagerConfig); + + jest.resetModules(); + try { + // $FlowExpectedError[cannot-write] + global.RN$Bridgeless = true; + // $FlowExpectedError[cannot-write] + global.RN$LegacyInterop_UIManager_getConstants = getConstants; + // $FlowExpectedError[cannot-write] + global.RN$LegacyInterop_UIManager_getConstantsForViewManager = + getConstantsForViewManager; + // $FlowExpectedError[cannot-write] + global.RN$LegacyInterop_UIManager_getDefaultEventTypes = jest.fn( + () => ({}), + ); + + const UIManager = require('../UIManager').default; + + expect(getConstants).toHaveBeenCalledTimes(1); + expect(getConstantsForViewManager).not.toHaveBeenCalled(); + expect( + Object.getOwnPropertyDescriptor(UIManager, 'RCTTestView'), + ).toBeDefined(); + expect(Object.keys(UIManager)).toContain('RCTTestView'); + + expect(Reflect.get(UIManager, 'RCTTestView')).toBe(viewManagerConfig); + expect(Reflect.get(UIManager, 'RCTTestView')).toBe(viewManagerConfig); + expect(getConstantsForViewManager).toHaveBeenCalledTimes(1); + } finally { + // $FlowExpectedError[cannot-write] + global.RN$Bridgeless = originalBridgelessValue; + // $FlowExpectedError[cannot-write] + global.RN$LegacyInterop_UIManager_getConstants = originalGetConstants; + // $FlowExpectedError[cannot-write] + global.RN$LegacyInterop_UIManager_getConstantsForViewManager = + originalGetConstantsForViewManager; + // $FlowExpectedError[cannot-write] + global.RN$LegacyInterop_UIManager_getDefaultEventTypes = + originalGetDefaultEventTypes; + jest.resetModules(); + } + }); +});