Repository: facebook/react
Pull request: Separate unit tests for ReactFabricHostComponent

## Summary

Organizes the unit tests for `ReactFabricHostComponent` into a separate file and refactors them to reduce duplication.

## Test Plan

Ran `yarn test ReactFabric` successfully.

Changed files:
filename: packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
patch: @@ -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(
-        <View
-          foo="bar"
-          ref={ref => {
-            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(
-        <View
-          ref={ref => {
-            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(
-        <View
-          ref={ref => {
-            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(
-        <View
-          ref={ref => {
-            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(
-        <View
-          ref={ref => {
-            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(
-        <View>
-          <View
-            foo="bar"
-            ref={ref => {
-              viewRef = ref;
-            }}
-          />
-          <View
-            ref={ref => {
-              otherRef = ref;
-            }}
-          />
-        </View>,
-        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(
-        <View>
-          <View
-            foo="bar"
-            ref={ref => {
-              viewRef = ref;
-            }}
-          />
-          <View
-            ref={ref => {
-              otherRef = ref;
-            }}
-          />
-        </View>,
-        11,
-      );
-    });
-    const dangerouslyRetainedOtherRef = otherRef;
-    act(() => {
-      ReactFabric.render(
-        <View>
-          <View
-            foo="bar"
-            ref={ref => {
-              viewRef = ref;
-            }}
-          />
-          {null}
-        </View>,
-        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(
-        <View>
-          <View
-            foo="bar"
-            ref={ref => {
-              viewRef = ref;
-            }}
-          />
-          <View
-            ref={ref => {
-              otherRef = ref;
-            }}
-          />
-        </View>,
-        11,
-      );
-    });
-    const dangerouslyRetainedViewRef = viewRef;
-    act(() => {
-      ReactFabric.render(
-        <View>
-          {null}
-          <View
-            ref={ref => {
-              otherRef = ref;
-            }}
-          />
-        </View>,
-        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(<View ref={viewRef} />, 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(<View ref={viewRef} />, 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},

filename: packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js
patch: @@ -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(
+          <MockView>
+            {keyList.map((key, index) => (
+              <MockView
+                key={key}
+                ref={ref => {
+                  refs[index] = ref;
+                }}
+              />
+            ))}
+          </MockView>,
+          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();
+  });
+});