Separate unit tests for ReactFabricHostComponent (#21969)

This commit is contained in:
Timothy Yung
2021-07-26 23:45:26 -07:00
committed by GitHub
parent d4d7864934
commit 4cc8ec64c2
2 changed files with 220 additions and 354 deletions
@@ -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},
@@ -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();
});
});