diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index 8e8b806ad2981..bc673062307f4 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -119,13 +119,36 @@ class ReactFabricHostComponent { } measureLayout( - relativeToNativeNode: number, + relativeToNativeNode: number | Object, onSuccess: MeasureLayoutOnSuccessCallback, onFail: () => void /* currently unused */, ) { + let relativeNode; + + if (typeof relativeToNativeNode === 'number') { + // Already a node handle + relativeNode = relativeToNativeNode; + } else if (relativeToNativeNode._nativeTag) { + relativeNode = relativeToNativeNode._nativeTag; + } else if ( + relativeToNativeNode.canonical && + relativeToNativeNode.canonical._nativeTag + ) { + relativeNode = relativeToNativeNode.canonical._nativeTag; + } + + if (relativeNode == null) { + warningWithoutStack( + false, + 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.', + ); + + return; + } + UIManager.measureLayout( this._nativeTag, - relativeToNativeNode, + relativeNode, mountSafeCallback_NOT_REALLY_SAFE(this, onFail), mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess), ); diff --git a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js index af75b088d5af0..629816fef1c53 100644 --- a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js +++ b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js @@ -70,13 +70,36 @@ class ReactNativeFiberHostComponent { } measureLayout( - relativeToNativeNode: number, + relativeToNativeNode: number | Object, onSuccess: MeasureLayoutOnSuccessCallback, onFail: () => void /* currently unused */, ) { + let relativeNode; + + if (typeof relativeToNativeNode === 'number') { + // Already a node handle + relativeNode = relativeToNativeNode; + } else if (relativeToNativeNode._nativeTag) { + relativeNode = relativeToNativeNode._nativeTag; + } else if ( + relativeToNativeNode.canonical && + relativeToNativeNode.canonical._nativeTag + ) { + relativeNode = relativeToNativeNode.canonical._nativeTag; + } + + if (relativeNode == null) { + warningWithoutStack( + false, + 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.', + ); + + return; + } + UIManager.measureLayout( this._nativeTag, - relativeToNativeNode, + relativeNode, mountSafeCallback_NOT_REALLY_SAFE(this, onFail), mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess), ); diff --git a/packages/react-native-renderer/src/__mocks__/UIManager.js b/packages/react-native-renderer/src/__mocks__/UIManager.js index afc6908d5585e..2905761af0560 100644 --- a/packages/react-native-renderer/src/__mocks__/UIManager.js +++ b/packages/react-native-renderer/src/__mocks__/UIManager.js @@ -153,6 +153,7 @@ const RCTUIManager = { views.get(parentTag).children.forEach(tag => removeChild(parentTag, tag)); }), replaceExistingNonRootView: jest.fn(), + measureLayout: jest.fn(), __takeSnapshot: jest.fn(), }; diff --git a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js index 4244b7c51988f..9ec320ad74e6b 100644 --- a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js @@ -302,6 +302,61 @@ describe('ReactFabric', () => { }); }); + it('should support ref in ref.measureLayout', () => { + const View = createReactNativeComponentClass('RCTView', () => ({ + validAttributes: {foo: true}, + uiViewClassName: 'RCTView', + })); + + [View].forEach(Component => { + UIManager.measureLayout.mockReset(); + + let viewRef; + let otherRef; + ReactFabric.render( + + { + viewRef = ref; + }} + /> + { + otherRef = ref; + }} + /> + , + 11, + ); + + expect(UIManager.measureLayout).not.toBeCalled(); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + viewRef.measureLayout(otherRef, successCallback, failureCallback); + + expect(UIManager.measureLayout).toHaveBeenCalledTimes(1); + expect(UIManager.measureLayout).toHaveBeenCalledWith( + expect.any(Number), + expect.any(Number), + expect.any(Function), + expect.any(Function), + ); + + const args = UIManager.measureLayout.mock.calls[0]; + expect(args[0]).not.toEqual(args[1]); + expect(successCallback).not.toBeCalled(); + expect(failureCallback).not.toBeCalled(); + args[2]('fail'); + expect(failureCallback).toBeCalledWith('fail'); + + expect(successCallback).not.toBeCalled(); + args[3]('success'); + expect(successCallback).toBeCalledWith('success'); + }); + }); + it('returns the correct instance and calls it in the callback', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js index 47065e794d320..fed124ed8afa0 100644 --- a/packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js @@ -249,6 +249,133 @@ describe('ReactNative', () => { }); }); + it('should support reactTag in ref.measureLayout', () => { + const View = createReactNativeComponentClass('RCTView', () => ({ + validAttributes: {foo: true}, + uiViewClassName: 'RCTView', + })); + + class Subclass extends ReactNative.NativeComponent { + render() { + return {this.props.children}; + } + } + + const CreateClass = createReactClass({ + mixins: [NativeMethodsMixin], + render() { + return {this.props.children}; + }, + }); + + [View, Subclass, CreateClass].forEach(Component => { + UIManager.measureLayout.mockReset(); + + let viewRef; + let otherRef; + ReactNative.render( + + { + viewRef = ref; + }} + /> + { + otherRef = ref; + }} + /> + , + 11, + ); + + expect(UIManager.measureLayout).not.toBeCalled(); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + viewRef.measureLayout( + ReactNative.findNodeHandle(otherRef), + successCallback, + failureCallback, + ); + + expect(UIManager.measureLayout).toHaveBeenCalledTimes(1); + expect(UIManager.measureLayout).toHaveBeenCalledWith( + expect.any(Number), + expect.any(Number), + expect.any(Function), + expect.any(Function), + ); + + const args = UIManager.measureLayout.mock.calls[0]; + expect(args[0]).not.toEqual(args[1]); + expect(successCallback).not.toBeCalled(); + expect(failureCallback).not.toBeCalled(); + args[2]('fail'); + expect(failureCallback).toBeCalledWith('fail'); + + expect(successCallback).not.toBeCalled(); + args[3]('success'); + expect(successCallback).toBeCalledWith('success'); + }); + }); + + it('should support ref in ref.measureLayout of host components', () => { + const View = createReactNativeComponentClass('RCTView', () => ({ + validAttributes: {foo: true}, + uiViewClassName: 'RCTView', + })); + + [View].forEach(Component => { + UIManager.measureLayout.mockReset(); + + let viewRef; + let otherRef; + ReactNative.render( + + { + viewRef = ref; + }} + /> + { + otherRef = ref; + }} + /> + , + 11, + ); + + expect(UIManager.measureLayout).not.toBeCalled(); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + viewRef.measureLayout(otherRef, successCallback, failureCallback); + + expect(UIManager.measureLayout).toHaveBeenCalledTimes(1); + expect(UIManager.measureLayout).toHaveBeenCalledWith( + expect.any(Number), + expect.any(Number), + expect.any(Function), + expect.any(Function), + ); + + const args = UIManager.measureLayout.mock.calls[0]; + expect(args[0]).not.toEqual(args[1]); + expect(successCallback).not.toBeCalled(); + expect(failureCallback).not.toBeCalled(); + args[2]('fail'); + expect(failureCallback).toBeCalledWith('fail'); + + expect(successCallback).not.toBeCalled(); + args[3]('success'); + expect(successCallback).toBeCalledWith('success'); + }); + }); + it('returns the correct instance and calls it in the callback', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true},