/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core * @jest-environment node */ 'use strict'; let React; let StrictMode; let ReactNative; let createReactNativeComponentClass; let UIManager; let TextInputState; 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'; describe('ReactNative', () => { beforeEach(() => { jest.resetModules(); React = require('react'); StrictMode = React.StrictMode; ReactNative = require('react-native-renderer'); 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; }); it('should be able to create and render a native component', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); ReactNative.render(, 1); expect(UIManager.createView).toBeCalled(); expect(UIManager.setChildren).toBeCalled(); expect(UIManager.manageChildren).not.toBeCalled(); expect(UIManager.updateView).not.toBeCalled(); }); it('should be able to create and update a native component', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); ReactNative.render(, 11); expect(UIManager.createView).toHaveBeenCalledTimes(1); expect(UIManager.setChildren).toHaveBeenCalledTimes(1); expect(UIManager.manageChildren).not.toBeCalled(); expect(UIManager.updateView).not.toBeCalled(); ReactNative.render(, 11); expect(UIManager.createView).toHaveBeenCalledTimes(1); expect(UIManager.setChildren).toHaveBeenCalledTimes(1); expect(UIManager.manageChildren).not.toBeCalled(); expect(UIManager.updateView).toBeCalledWith(3, 'RCTView', {foo: 'bar'}); }); it('should not call UIManager.updateView after render for properties that have not changed', () => { const Text = createReactNativeComponentClass('RCTText', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTText', })); ReactNative.render(1, 11); expect(UIManager.updateView).not.toBeCalled(); // If no properties have changed, we shouldn't call updateView. ReactNative.render(1, 11); expect(UIManager.updateView).not.toBeCalled(); // Only call updateView for the changed property (and not for text). ReactNative.render(1, 11); expect(UIManager.updateView).toHaveBeenCalledTimes(1); // Only call updateView for the changed text (and no other properties). ReactNative.render(2, 11); expect(UIManager.updateView).toHaveBeenCalledTimes(2); // Call updateView for both changed text and properties. ReactNative.render(3, 11); expect(UIManager.updateView).toHaveBeenCalledTimes(4); }); it('should call dispatchCommand for native refs', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); UIManager.dispatchViewManagerCommand.mockClear(); let viewRef; ReactNative.render( { viewRef = ref; }} />, 11, ); expect(UIManager.dispatchViewManagerCommand).not.toBeCalled(); ReactNative.dispatchCommand(viewRef, 'updateCommand', [10, 20]); expect(UIManager.dispatchViewManagerCommand).toHaveBeenCalledTimes(1); expect( UIManager.dispatchViewManagerCommand, ).toHaveBeenCalledWith(expect.any(Number), 'updateCommand', [10, 20]); }); it('should warn and no-op if calling dispatchCommand on non native refs', () => { class BasicClass extends React.Component { render() { return ; } } UIManager.dispatchViewManagerCommand.mockReset(); let viewRef; ReactNative.render( { viewRef = ref; }} />, 11, ); expect(UIManager.dispatchViewManagerCommand).not.toBeCalled(); expect(() => { ReactNative.dispatchCommand(viewRef, 'updateCommand', [10, 20]); }).toErrorDev([DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT], { withoutStack: true, }); expect(UIManager.dispatchViewManagerCommand).not.toBeCalled(); }); it('should not call UIManager.updateView from ref.setNativeProps for properties that have not changed', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); UIManager.updateView.mockReset(); let viewRef; ReactNative.render( { viewRef = ref; }} />, 11, ); expect(UIManager.updateView).not.toBeCalled(); viewRef.setNativeProps({}); expect(UIManager.updateView).not.toBeCalled(); viewRef.setNativeProps({foo: 'baz'}); expect(UIManager.updateView).toHaveBeenCalledTimes(1); expect(UIManager.updateView).toHaveBeenCalledWith( expect.any(Number), 'RCTView', {foo: 'baz'}, ); }); it('should call UIManager.measure on ref.measure', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); UIManager.measure.mockClear(); let viewRef; ReactNative.render( { viewRef = ref; }} />, 11, ); expect(UIManager.measure).not.toBeCalled(); const successCallback = jest.fn(); viewRef.measure(successCallback); expect(UIManager.measure).toHaveBeenCalledTimes(1); expect(successCallback).toHaveBeenCalledTimes(1); expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100, 0, 0); }); it('should call UIManager.measureInWindow on ref.measureInWindow', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); UIManager.measureInWindow.mockClear(); let viewRef; ReactNative.render( { viewRef = ref; }} />, 11, ); expect(UIManager.measureInWindow).not.toBeCalled(); const successCallback = jest.fn(); viewRef.measureInWindow(successCallback); expect(UIManager.measureInWindow).toHaveBeenCalledTimes(1); expect(successCallback).toHaveBeenCalledTimes(1); expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100); }); it('should support reactTag in ref.measureLayout', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); UIManager.measureLayout.mockClear(); 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(successCallback).toHaveBeenCalledTimes(1); expect(successCallback).toHaveBeenCalledWith(1, 1, 100, 100); }); it('should support ref in ref.measureLayout of host components', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); UIManager.measureLayout.mockClear(); 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(successCallback).toHaveBeenCalledTimes(1); expect(successCallback).toHaveBeenCalledWith(1, 1, 100, 100); }); it('returns the correct instance and calls it in the callback', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); let a; let b; const c = ReactNative.render( (a = v)} />, 11, function() { b = this; }, ); expect(a).toBeTruthy(); expect(a).toBe(b); expect(a).toBe(c); }); it('renders and reorders children', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {title: true}, uiViewClassName: 'RCTView', })); class Component extends React.Component { render() { const chars = this.props.chars.split(''); return ( {chars.map(text => ( ))} ); } } // Mini multi-child stress test: lots of reorders, some adds, some removes. const before = 'abcdefghijklmnopqrst'; const after = 'mxhpgwfralkeoivcstzy'; ReactNative.render(, 11); expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot(); ReactNative.render(, 11); expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot(); }); it('calls setState with no arguments', () => { let mockArgs; class Component extends React.Component { componentDidMount() { this.setState({}, (...args) => (mockArgs = args)); } render() { return false; } } ReactNative.render(, 11); expect(mockArgs.length).toEqual(0); }); it('should not throw when is used inside of a ancestor', () => { const Image = createReactNativeComponentClass('RCTImage', () => ({ validAttributes: {}, uiViewClassName: 'RCTImage', })); const Text = createReactNativeComponentClass('RCTText', () => ({ validAttributes: {}, uiViewClassName: 'RCTText', })); const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {}, uiViewClassName: 'RCTView', })); ReactNative.render( , 11, ); // Non-View things (e.g. Image) are fine ReactNative.render( , 11, ); }); it('should throw for text not inside of a ancestor', () => { const ScrollView = createReactNativeComponentClass('RCTScrollView', () => ({ validAttributes: {}, uiViewClassName: 'RCTScrollView', })); const Text = createReactNativeComponentClass('RCTText', () => ({ validAttributes: {}, uiViewClassName: 'RCTText', })); const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {}, uiViewClassName: 'RCTView', })); expect(() => ReactNative.render(this should warn, 11)).toThrow( 'Text strings must be rendered within a component.', ); expect(() => ReactNative.render( hi hello hi , 11, ), ).toThrow('Text strings must be rendered within a component.'); }); it('should not throw for text inside of an indirect ancestor', () => { const Text = createReactNativeComponentClass('RCTText', () => ({ validAttributes: {}, uiViewClassName: 'RCTText', })); const Indirection = () => 'Hi'; ReactNative.render( , 11, ); }); it('findHostInstance_DEPRECATED should warn if used to find a host component inside StrictMode', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); let parent = undefined; let child = undefined; class ContainsStrictModeChild extends React.Component { render() { return ( (child = n)} /> ); } } ReactNative.render( (parent = n)} />, 11); let match; expect( () => (match = ReactNative.findHostInstance_DEPRECATED(parent)), ).toErrorDev([ 'Warning: findHostInstance_DEPRECATED is deprecated in StrictMode. ' + 'findHostInstance_DEPRECATED was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' + 'Instead, add a ref directly to the element you want to reference. ' + 'Learn more about using refs safely here: ' + 'https://fb.me/react-strict-mode-find-node' + '\n in RCTView (at **)' + '\n in StrictMode (at **)' + '\n in ContainsStrictModeChild (at **)', ]); expect(match).toBe(child); }); it('findHostInstance_DEPRECATED should warn if passed a component that is inside StrictMode', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); let parent = undefined; let child = undefined; class IsInStrictMode extends React.Component { render() { return (child = n)} />; } } ReactNative.render( (parent = n)} /> , 11, ); let match; expect( () => (match = ReactNative.findHostInstance_DEPRECATED(parent)), ).toErrorDev([ 'Warning: findHostInstance_DEPRECATED is deprecated in StrictMode. ' + 'findHostInstance_DEPRECATED was passed an instance of IsInStrictMode which is inside StrictMode. ' + 'Instead, add a ref directly to the element you want to reference. ' + 'Learn more about using refs safely here: ' + 'https://fb.me/react-strict-mode-find-node' + '\n in RCTView (at **)' + '\n in IsInStrictMode (at **)' + '\n in StrictMode (at **)', ]); expect(match).toBe(child); }); it('findNodeHandle should warn if used to find a host component inside StrictMode', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); let parent = undefined; let child = undefined; class ContainsStrictModeChild extends React.Component { render() { return ( (child = n)} /> ); } } ReactNative.render( (parent = n)} />, 11); let match; expect(() => (match = ReactNative.findNodeHandle(parent))).toErrorDev([ 'Warning: findNodeHandle is deprecated in StrictMode. ' + 'findNodeHandle was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' + 'Instead, add a ref directly to the element you want to reference. ' + 'Learn more about using refs safely here: ' + 'https://fb.me/react-strict-mode-find-node' + '\n in RCTView (at **)' + '\n in StrictMode (at **)' + '\n in ContainsStrictModeChild (at **)', ]); expect(match).toBe(child._nativeTag); }); it('findNodeHandle should warn if passed a component that is inside StrictMode', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); let parent = undefined; let child = undefined; class IsInStrictMode extends React.Component { render() { return (child = n)} />; } } ReactNative.render( (parent = n)} /> , 11, ); let match; expect(() => (match = ReactNative.findNodeHandle(parent))).toErrorDev([ 'Warning: findNodeHandle is deprecated in StrictMode. ' + 'findNodeHandle was passed an instance of IsInStrictMode which is inside StrictMode. ' + 'Instead, add a ref directly to the element you want to reference. ' + 'Learn more about using refs safely here: ' + 'https://fb.me/react-strict-mode-find-node' + '\n in RCTView (at **)' + '\n in IsInStrictMode (at **)' + '\n in StrictMode (at **)', ]); expect(match).toBe(child._nativeTag); }); it('blur on host component calls TextInputState', () => { const View = createReactNativeComponentClass('RCTView', () => ({ validAttributes: {foo: true}, uiViewClassName: 'RCTView', })); let viewRef = React.createRef(); ReactNative.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', })); let viewRef = React.createRef(); ReactNative.render(, 11); expect(TextInputState.focusTextInput).not.toBeCalled(); viewRef.current.focus(); expect(TextInputState.focusTextInput).toHaveBeenCalledTimes(1); expect(TextInputState.focusTextInput).toHaveBeenCalledWith(viewRef.current); }); });