From 4cc8ec64c2c853146f35ee3c7d072fe23847a006 Mon Sep 17 00:00:00 2001 From: Timothy Yung Date: Mon, 26 Jul 2021 23:45:26 -0700 Subject: [PATCH] Separate unit tests for ReactFabricHostComponent (#21969) --- .../__tests__/ReactFabric-test.internal.js | 354 ------------------ .../ReactFabricHostComponent-test.internal.js | 220 +++++++++++ 2 files changed, 220 insertions(+), 354 deletions(-) create mode 100644 packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js 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 ebe763af02d06..c7e4e3dee1b51 100644 --- a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js @@ -13,14 +13,9 @@ let React; let ReactFabric; let createReactNativeComponentClass; -let UIManager; let StrictMode; -let TextInputState; let act; -const SET_NATIVE_PROPS_NOT_SUPPORTED_MESSAGE = - 'Warning: setNativeProps is not currently supported in Fabric'; - const DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT = "Warning: dispatchCommand was called with a ref that isn't a " + 'native component. Use React.forwardRef to get access to the underlying native component'; @@ -42,12 +37,8 @@ describe('ReactFabric', () => { React = require('react'); StrictMode = React.StrictMode; ReactFabric = require('react-native-renderer/fabric'); - UIManager = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface') - .UIManager; createReactNativeComponentClass = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface') .ReactNativeViewConfigRegistry.register; - TextInputState = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface') - .TextInputState; act = require('jest-react').act; }); @@ -227,44 +218,6 @@ describe('ReactFabric', () => { ).toMatchSnapshot(); }); - it('should not call UIManager.updateView from ref.setNativeProps', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - UIManager.updateView.mockReset(); - - let viewRef; - act(() => { - ReactFabric.render( - { - viewRef = ref; - }} - />, - 11, - ); - }); - expect(UIManager.updateView).not.toBeCalled(); - - expect(() => { - viewRef.setNativeProps({}); - }).toErrorDev([SET_NATIVE_PROPS_NOT_SUPPORTED_MESSAGE], { - withoutStack: true, - }); - - expect(UIManager.updateView).not.toBeCalled(); - - expect(() => { - viewRef.setNativeProps({foo: 'baz'}); - }).toErrorDev([SET_NATIVE_PROPS_NOT_SUPPORTED_MESSAGE], { - withoutStack: true, - }); - expect(UIManager.updateView).not.toBeCalled(); - }); - it('should call dispatchCommand for native refs', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, @@ -386,275 +339,6 @@ describe('ReactFabric', () => { expect(nativeFabricUIManager.sendAccessibilityEvent).not.toBeCalled(); }); - it('should call FabricUIManager.measure on ref.measure', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - nativeFabricUIManager.measure.mockClear(); - - let viewRef; - act(() => { - ReactFabric.render( - { - viewRef = ref; - }} - />, - 11, - ); - }); - - expect(nativeFabricUIManager.measure).not.toBeCalled(); - const successCallback = jest.fn(); - viewRef.measure(successCallback); - expect(nativeFabricUIManager.measure).toHaveBeenCalledTimes(1); - expect(successCallback).toHaveBeenCalledTimes(1); - expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100, 0, 0); - }); - - it('should no-op if calling measure on unmounted refs', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - nativeFabricUIManager.measure.mockClear(); - - let viewRef; - act(() => { - ReactFabric.render( - { - viewRef = ref; - }} - />, - 11, - ); - }); - const dangerouslyRetainedViewRef = viewRef; - act(() => { - ReactFabric.stopSurface(11); - }); - - expect(nativeFabricUIManager.measure).not.toBeCalled(); - const successCallback = jest.fn(); - dangerouslyRetainedViewRef.measure(successCallback); - expect(nativeFabricUIManager.measure).not.toBeCalled(); - expect(successCallback).not.toBeCalled(); - }); - - it('should call FabricUIManager.measureInWindow on ref.measureInWindow', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - nativeFabricUIManager.measureInWindow.mockClear(); - - let viewRef; - act(() => { - ReactFabric.render( - { - viewRef = ref; - }} - />, - 11, - ); - }); - - expect(nativeFabricUIManager.measureInWindow).not.toBeCalled(); - const successCallback = jest.fn(); - viewRef.measureInWindow(successCallback); - expect(nativeFabricUIManager.measureInWindow).toHaveBeenCalledTimes(1); - expect(successCallback).toHaveBeenCalledTimes(1); - expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100); - }); - - it('should no-op if calling measureInWindow on unmounted refs', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - nativeFabricUIManager.measureInWindow.mockClear(); - - let viewRef; - act(() => { - ReactFabric.render( - { - viewRef = ref; - }} - />, - 11, - ); - }); - const dangerouslyRetainedViewRef = viewRef; - act(() => { - ReactFabric.stopSurface(11); - }); - - expect(nativeFabricUIManager.measureInWindow).not.toBeCalled(); - const successCallback = jest.fn(); - dangerouslyRetainedViewRef.measureInWindow(successCallback); - expect(nativeFabricUIManager.measureInWindow).not.toBeCalled(); - expect(successCallback).not.toBeCalled(); - }); - - it('should support ref in ref.measureLayout', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - nativeFabricUIManager.measureLayout.mockClear(); - - let viewRef; - let otherRef; - act(() => { - ReactFabric.render( - - { - viewRef = ref; - }} - /> - { - otherRef = ref; - }} - /> - , - 11, - ); - }); - - expect(nativeFabricUIManager.measureLayout).not.toBeCalled(); - const successCallback = jest.fn(); - const failureCallback = jest.fn(); - viewRef.measureLayout(otherRef, successCallback, failureCallback); - expect(nativeFabricUIManager.measureLayout).toHaveBeenCalledTimes(1); - expect(successCallback).toHaveBeenCalledTimes(1); - expect(successCallback).toHaveBeenCalledWith(1, 1, 100, 100); - }); - - it('should no-op if calling measureLayout on unmounted "from" ref', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - nativeFabricUIManager.measureLayout.mockClear(); - - let viewRef; - let otherRef; - act(() => { - ReactFabric.render( - - { - viewRef = ref; - }} - /> - { - otherRef = ref; - }} - /> - , - 11, - ); - }); - const dangerouslyRetainedOtherRef = otherRef; - act(() => { - ReactFabric.render( - - { - viewRef = ref; - }} - /> - {null} - , - 11, - ); - }); - - expect(nativeFabricUIManager.measureLayout).not.toBeCalled(); - const successCallback = jest.fn(); - const failureCallback = jest.fn(); - viewRef.measureLayout( - dangerouslyRetainedOtherRef, - successCallback, - failureCallback, - ); - expect(nativeFabricUIManager.measureLayout).not.toBeCalled(); - expect(successCallback).not.toBeCalled(); - expect(failureCallback).not.toBeCalled(); - }); - - it('should no-op if calling measureLayout on unmounted "to" ref', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - nativeFabricUIManager.measureLayout.mockClear(); - - let viewRef; - let otherRef; - act(() => { - ReactFabric.render( - - { - viewRef = ref; - }} - /> - { - otherRef = ref; - }} - /> - , - 11, - ); - }); - const dangerouslyRetainedViewRef = viewRef; - act(() => { - ReactFabric.render( - - {null} - { - otherRef = ref; - }} - /> - , - 11, - ); - }); - - expect(nativeFabricUIManager.measureLayout).not.toBeCalled(); - const successCallback = jest.fn(); - const failureCallback = jest.fn(); - dangerouslyRetainedViewRef.measureLayout( - otherRef, - successCallback, - failureCallback, - ); - expect(nativeFabricUIManager.measureLayout).not.toBeCalled(); - expect(successCallback).not.toBeCalled(); - expect(failureCallback).not.toBeCalled(); - }); - it('returns the correct instance and calls it in the callback', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, @@ -1202,44 +886,6 @@ describe('ReactFabric', () => { expect(match).toBe(child._nativeTag); }); - it('blur on host component calls TextInputState', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - const viewRef = React.createRef(); - act(() => { - ReactFabric.render(, 11); - }); - - expect(TextInputState.blurTextInput).not.toBeCalled(); - - viewRef.current.blur(); - - expect(TextInputState.blurTextInput).toHaveBeenCalledTimes(1); - expect(TextInputState.blurTextInput).toHaveBeenCalledWith(viewRef.current); - }); - - it('focus on host component calls TextInputState', () => { - const View = createReactNativeComponentClass('RCTView', () => ({ - validAttributes: {foo: true}, - uiViewClassName: 'RCTView', - })); - - const viewRef = React.createRef(); - act(() => { - ReactFabric.render(, 11); - }); - - expect(TextInputState.focusTextInput).not.toBeCalled(); - - viewRef.current.focus(); - - expect(TextInputState.focusTextInput).toHaveBeenCalledTimes(1); - expect(TextInputState.focusTextInput).toHaveBeenCalledWith(viewRef.current); - }); - it('should no-op if calling sendAccessibilityEvent on unmounted refs', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, diff --git a/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js new file mode 100644 index 0000000000000..de19bf7fe81e1 --- /dev/null +++ b/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js @@ -0,0 +1,220 @@ +/** + * (c) Facebook, Inc. and its affiliates. Confidential and proprietary. + * + * @emails react-core + * @jest-environment node + */ + +'use strict'; + +import * as React from 'react'; + +beforeEach(() => { + jest.resetModules(); + jest.restoreAllMocks(); + + require('react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager'); +}); + +/** + * Renders a sequence of mock views as dictated by `keyLists`. The `keyLists` + * argument is an array of arrays which determines the number of render passes, + * how many views will be rendered in each pass, and what the keys are for each + * of the views. + * + * If an element in `keyLists` is null, the entire root will be unmounted. + * + * The return value is an array of arrays with the resulting refs from rendering + * each corresponding array of keys. + * + * If the corresponding array of keys is null, the returned element at that + * index will also be null. + */ +function mockRenderKeys(keyLists) { + const ReactFabric = require('react-native-renderer/fabric'); + const createReactNativeComponentClass = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface') + .ReactNativeViewConfigRegistry.register; + const {act} = require('jest-react'); + + const mockContainerTag = 11; + const MockView = createReactNativeComponentClass('RCTMockView', () => ({ + validAttributes: {}, + uiViewClassName: 'RCTMockView', + })); + + return keyLists.map(keyList => { + if (Array.isArray(keyList)) { + const refs = keyList.map(key => undefined); + act(() => { + ReactFabric.render( + + {keyList.map((key, index) => ( + { + refs[index] = ref; + }} + /> + ))} + , + mockContainerTag, + ); + }); + // Clone `refs` to ignore future passes. + return [...refs]; + } + if (keyList == null) { + act(() => { + ReactFabric.stopSurface(mockContainerTag); + }); + return null; + } + throw new TypeError( + `Invalid 'keyLists' element of type ${typeof keyList}.`, + ); + }); +} + +describe('blur', () => { + test('blur() invokes TextInputState', () => { + const { + TextInputState, + } = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'); + + const [[fooRef]] = mockRenderKeys([['foo']]); + + fooRef.blur(); + + expect(TextInputState.blurTextInput.mock.calls).toEqual([[fooRef]]); + }); +}); + +describe('focus', () => { + test('focus() invokes TextInputState', () => { + const { + TextInputState, + } = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'); + + const [[fooRef]] = mockRenderKeys([['foo']]); + + fooRef.focus(); + + expect(TextInputState.focusTextInput.mock.calls).toEqual([[fooRef]]); + }); +}); + +describe('measure', () => { + test('component.measure(...) invokes callback', () => { + const [[fooRef]] = mockRenderKeys([['foo']]); + + const callback = jest.fn(); + fooRef.measure(callback); + + expect(nativeFabricUIManager.measure).toHaveBeenCalledTimes(1); + expect(callback.mock.calls).toEqual([[10, 10, 100, 100, 0, 0]]); + }); + + test('unmounted.measure(...) does nothing', () => { + const [[fooRef]] = mockRenderKeys([['foo'], null]); + + const callback = jest.fn(); + fooRef.measure(callback); + + expect(nativeFabricUIManager.measure).not.toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); + }); +}); + +describe('measureInWindow', () => { + test('component.measureInWindow(...) invokes callback', () => { + const [[fooRef]] = mockRenderKeys([['foo']]); + + const callback = jest.fn(); + fooRef.measureInWindow(callback); + + expect(nativeFabricUIManager.measureInWindow).toHaveBeenCalledTimes(1); + expect(callback.mock.calls).toEqual([[10, 10, 100, 100]]); + }); + + test('unmounted.measureInWindow(...) does nothing', () => { + const [[fooRef]] = mockRenderKeys([['foo'], null]); + + const callback = jest.fn(); + fooRef.measureInWindow(callback); + + expect(nativeFabricUIManager.measureInWindow).not.toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); + }); +}); + +describe('measureLayout', () => { + test('component.measureLayout(component, ...) invokes callback', () => { + const [[fooRef, barRef]] = mockRenderKeys([['foo', 'bar']]); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); + + expect(nativeFabricUIManager.measureLayout).toHaveBeenCalledTimes(1); + expect(successCallback.mock.calls).toEqual([[1, 1, 100, 100]]); + }); + + test('unmounted.measureLayout(component, ...) does nothing', () => { + const [[fooRef, barRef]] = mockRenderKeys([ + ['foo', 'bar'], + ['foo', null], + ]); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); + + expect(nativeFabricUIManager.measureLayout).not.toHaveBeenCalled(); + expect(successCallback).not.toHaveBeenCalled(); + }); + + test('component.measureLayout(unmounted, ...) does nothing', () => { + const [[fooRef, barRef]] = mockRenderKeys([ + ['foo', 'bar'], + [null, 'bar'], + ]); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); + + expect(nativeFabricUIManager.measureLayout).not.toHaveBeenCalled(); + expect(successCallback).not.toHaveBeenCalled(); + }); + + test('unmounted.measureLayout(unmounted, ...) does nothing', () => { + const [[fooRef, barRef]] = mockRenderKeys([['foo', 'bar'], null]); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); + + expect(nativeFabricUIManager.measureLayout).not.toHaveBeenCalled(); + expect(successCallback).not.toHaveBeenCalled(); + }); +}); + +describe('setNativeProps', () => { + test('setNativeProps(...) emits a warning', () => { + const { + UIManager, + } = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'); + + const [[fooRef]] = mockRenderKeys([['foo']]); + + expect(() => { + fooRef.setNativeProps({}); + }).toErrorDev( + ['Warning: setNativeProps is not currently supported in Fabric'], + { + withoutStack: true, + }, + ); + expect(UIManager.updateView).not.toBeCalled(); + }); +});