diff --git a/.flowconfig b/.flowconfig index 17c48f1714dc67..ef7f6b6a532a56 100644 --- a/.flowconfig +++ b/.flowconfig @@ -69,6 +69,8 @@ module.name_mapper='^react-native/\(.*\)$' -> '/packages/react-nat module.name_mapper='^@react-native/dev-middleware$' -> '/packages/dev-middleware' module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\)$' -> '/packages/react-native/Libraries/Image/RelativeImageStub' +module.system.haste.module_ref_prefix=m# + react.runtime=automatic suppress_type=$FlowIssue diff --git a/packages/react-native/jest/mock.js b/packages/react-native/jest/mock.js new file mode 100644 index 00000000000000..9c3b433db0a6de --- /dev/null +++ b/packages/react-native/jest/mock.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + * @oncall react_native + */ + +/** + * Mocks the module referenced by `moduleRef` (expected to begin with `m#`) + * while enforcing type safety of mock factories. + * + * If `factoryRef` is provided, it is expected to reference a module that + * exports the same type signature as the module referenced by `moduleRef`. + */ +export default function mock>( + moduleRef: TModuleRef, + factoryRef?: NoInfer, +): void { + // NOTE: Jest's `babel-plugin-jest-hoist` requires that the second argument to + // `jest.mock` be an inline function, so structure this code accordingly. + if (factoryRef === undefined) { + jest.mock(deref(moduleRef)); + } else { + // NOTE: Jest's `babel-plugin-jest-hoist` requires that module factories + // only reference local variables or variables starting with "mock", so be + // careful when renaming this `mockFactory` variable. + const mockFactory = deref(factoryRef); + jest.mock(deref(moduleRef), () => jest.requireActual(mockFactory)); + } +} + +function deref(ref: $Flow$ModuleRef): string { + // $FlowIgnore[incompatible-cast] + return (ref as string).substring(2); +} diff --git a/packages/react-native/jest/mockComponent.js b/packages/react-native/jest/mockComponent.js index 4b63f8c4786796..6ff4f6bd3e248c 100644 --- a/packages/react-native/jest/mockComponent.js +++ b/packages/react-native/jest/mockComponent.js @@ -14,8 +14,12 @@ import {createElement} from 'react'; type Modulish = T | $ReadOnly<{default: T}>; type ModuleDefault = T['default']; -type TComponentType = React.ComponentType<$ReadOnly<{children?: React.Node}>>; +type TComponentType = React.ComponentType<{...}>; +/** + * WARNING: The `moduleName` must be relative to this file's directory, which is + * a major footgun. Be careful when using this function! + */ export default function mockComponent< TComponentModule: Modulish, >( @@ -23,7 +27,7 @@ export default function mockComponent< instanceMethods: ?interface {}, isESModule: boolean, ): typeof isESModule extends true - ? ModuleDefault + ? ModuleDefault : TComponentModule & typeof instanceMethods { const RealComponent: TComponentType = isESModule ? // $FlowIgnore[prop-missing] @@ -71,6 +75,7 @@ export default function mockComponent< } // $FlowIgnore[not-a-function] + // $FlowIgnore[prop-missing] return createElement(nameWithoutPrefix, props, this.props.children); } }; diff --git a/packages/react-native/jest/mockModal.js b/packages/react-native/jest/mockModal.js deleted file mode 100644 index 1b683831e33d8a..00000000000000 --- a/packages/react-native/jest/mockModal.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict - * @format - */ - -import * as React from 'react'; - -export default function mockModal( - BaseComponent: React.ComponentType<{children?: React.Node}>, -): React.ComponentType<{ - ...React.ElementConfig, - visible?: ?boolean, -}> { - // $FlowIgnore[incompatible-use] - return class ModalMock extends BaseComponent { - render(): React.Node { - if (this.props.visible === false) { - return null; - } - - return ( - {this.props.children} - ); - } - }; -} diff --git a/packages/react-native/jest/mockNativeComponent.js b/packages/react-native/jest/mockNativeComponent.js index 0e1d5cdbd90c70..117171da8f30c9 100644 --- a/packages/react-native/jest/mockNativeComponent.js +++ b/packages/react-native/jest/mockNativeComponent.js @@ -15,17 +15,20 @@ import {createElement} from 'react'; let nativeTag = 1; -type MockNativeComponent> = - component(ref?: ?React.RefSetter, ...props: TProps); - -export default function mockNativeComponent< - TProps: $ReadOnly<{children?: React.Node}>, ->(viewName: string): MockNativeComponent { +type MockNativeComponent = component( + ref?: ?React.RefSetter, + ...props: TProps +); + +export default function mockNativeComponent( + viewName: string, +): MockNativeComponent { const Component = class extends React.Component { _nativeTag: number = nativeTag++; render(): React.Node { // $FlowIgnore[not-a-function] + // $FlowIgnore[prop-missing] return createElement(viewName, this.props, this.props.children); } diff --git a/packages/react-native/jest/mockScrollView.js b/packages/react-native/jest/mockScrollView.js deleted file mode 100644 index 6b7fbc0da273cd..00000000000000 --- a/packages/react-native/jest/mockScrollView.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -import type {ScrollViewNativeProps} from '../Libraries/Components/ScrollView/ScrollViewNativeComponentType'; - -import View from '../Libraries/Components/View/View'; -import requireNativeComponent from '../Libraries/ReactNative/requireNativeComponent'; -import * as React from 'react'; - -const RCTScrollView = - requireNativeComponent('RCTScrollView'); - -export default function mockScrollView( - BaseComponent: React.ComponentType<{children?: React.Node}>, -): React.ComponentType<{ - ...React.ElementConfig, - refreshControl?: ?React.MixedElement, -}> { - // $FlowIgnore[incompatible-use] - return class ScrollViewMock extends BaseComponent { - render(): React.Node { - return ( - - {this.props.refreshControl} - {this.props.children} - - ); - } - }; -} diff --git a/packages/react-native/jest/mocks/AccessibilityInfo.js b/packages/react-native/jest/mocks/AccessibilityInfo.js new file mode 100644 index 00000000000000..4dc215bcabd7f4 --- /dev/null +++ b/packages/react-native/jest/mocks/AccessibilityInfo.js @@ -0,0 +1,62 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +const AccessibilityInfo = { + addEventListener: jest.fn(() => ({ + remove: jest.fn(), + })) as JestMockFn<$FlowFixMe, {remove: JestMockFn<[], void>}>, + announceForAccessibility: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + announceForAccessibilityWithOptions: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + isAccessibilityServiceEnabled: jest.fn(() => + Promise.resolve(false), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + isBoldTextEnabled: jest.fn(() => Promise.resolve(false)) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + isGrayscaleEnabled: jest.fn(() => Promise.resolve(false)) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + isInvertColorsEnabled: jest.fn(() => Promise.resolve(false)) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + isReduceMotionEnabled: jest.fn(() => Promise.resolve(false)) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + isHighTextContrastEnabled: jest.fn(() => + Promise.resolve(false), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + isDarkerSystemColorsEnabled: jest.fn(() => + Promise.resolve(false), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + prefersCrossFadeTransitions: jest.fn(() => + Promise.resolve(false), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + isReduceTransparencyEnabled: jest.fn(() => + Promise.resolve(false), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + setAccessibilityFocus: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + sendAccessibilityEvent: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + getRecommendedTimeoutMillis: jest.fn(() => + Promise.resolve(false), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, +}; + +export default AccessibilityInfo; diff --git a/packages/react-native/jest/mocks/ActivityIndicator.js b/packages/react-native/jest/mocks/ActivityIndicator.js new file mode 100644 index 00000000000000..fb53c591a57a2a --- /dev/null +++ b/packages/react-native/jest/mocks/ActivityIndicator.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import typeof TActivityIndicator from '../../Libraries/Components/ActivityIndicator/ActivityIndicator'; +import typeof * as TmockComponent from '../mockComponent'; + +const mockComponent = + jest.requireActual('../mockComponent').default; + +const ActivityIndicator = mockComponent( + '../Libraries/Components/ActivityIndicator/ActivityIndicator', + null, // instanceMethods + true, // isESModule +) as TActivityIndicator; + +export default ActivityIndicator; diff --git a/packages/react-native/jest/mocks/AppState.js b/packages/react-native/jest/mocks/AppState.js new file mode 100644 index 00000000000000..8cb3d6a3cf1553 --- /dev/null +++ b/packages/react-native/jest/mocks/AppState.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +const AppState = { + addEventListener: jest.fn(() => ({ + remove: jest.fn(), + })) as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeEventListener: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + currentState: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, +}; + +export default AppState; diff --git a/packages/react-native/jest/mocks/Clipboard.js b/packages/react-native/jest/mocks/Clipboard.js new file mode 100644 index 00000000000000..035dc076fd62a4 --- /dev/null +++ b/packages/react-native/jest/mocks/Clipboard.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +const Clipboard = { + getString: jest.fn(async () => '') as JestMockFn<[], Promise>, + setString: jest.fn() as JestMockFn<[string], void>, +}; + +export default Clipboard; diff --git a/packages/react-native/jest/mocks/Image.js b/packages/react-native/jest/mocks/Image.js new file mode 100644 index 00000000000000..87febdbcc3264f --- /dev/null +++ b/packages/react-native/jest/mocks/Image.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import typeof TImage from '../../Libraries/Image/Image'; +import typeof * as TmockComponent from '../mockComponent'; + +const mockComponent = + jest.requireActual('../mockComponent').default; + +const Image = mockComponent( + '../Libraries/Image/Image', + null, // instanceMethods + true, // isESModule +) as TImage; + +export default Image; diff --git a/packages/react-native/jest/mocks/InitializeCore.js b/packages/react-native/jest/mocks/InitializeCore.js new file mode 100644 index 00000000000000..3d2794d08ec361 --- /dev/null +++ b/packages/react-native/jest/mocks/InitializeCore.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ diff --git a/packages/react-native/jest/mocks/Linking.js b/packages/react-native/jest/mocks/Linking.js new file mode 100644 index 00000000000000..c558c824a14943 --- /dev/null +++ b/packages/react-native/jest/mocks/Linking.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +const Linking = { + openURL: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + canOpenURL: jest.fn(() => Promise.resolve(true)) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + openSettings: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + addEventListener: jest.fn(() => ({ + remove: jest.fn(), + })) as JestMockFn<$FlowFixMe, $FlowFixMe>, + getInitialURL: jest.fn(() => Promise.resolve()) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + sendIntent: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, +}; + +export default Linking; diff --git a/packages/react-native/jest/mocks/Modal.js b/packages/react-native/jest/mocks/Modal.js new file mode 100644 index 00000000000000..c9000e19d75b06 --- /dev/null +++ b/packages/react-native/jest/mocks/Modal.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type {ModalProps} from '../../Libraries/Modal/Modal'; +import typeof * as TmockComponent from '../mockComponent'; + +const mockComponent = + jest.requireActual('../mockComponent').default; + +type TModal = component(...ModalProps); + +const BaseComponent = mockComponent( + '../Libraries/Modal/Modal', + null, // instanceMethods + true, // isESModule +) as TModal; + +// $FlowIgnore[incompatible-use] +export default class Modal extends BaseComponent { + render(): React.Node { + if (this.props.visible === false) { + return null; + } + return {this.props.children}; + } +} diff --git a/packages/react-native/jest/mocks/NativeComponentRegistry.js b/packages/react-native/jest/mocks/NativeComponentRegistry.js new file mode 100644 index 00000000000000..9efd7f220bcdb0 --- /dev/null +++ b/packages/react-native/jest/mocks/NativeComponentRegistry.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +import typeof * as TmockNativeComponent from '../mockNativeComponent'; + +const mockNativeComponent = jest.requireActual( + '../mockNativeComponent', +).default; + +export const get = jest.fn((name, viewConfigProvider) => { + return mockNativeComponent(name); +}) as JestMockFn<$FlowFixMe, $FlowFixMe>; + +export const getWithFallback_DEPRECATED = jest.fn( + (name, viewConfigProvider) => { + return mockNativeComponent(name); + }, +) as JestMockFn<$FlowFixMe, $FlowFixMe>; + +export const setRuntimeConfigProvider = jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, +>; diff --git a/packages/react-native/jest/mocks/NativeModules.js b/packages/react-native/jest/mocks/NativeModules.js new file mode 100644 index 00000000000000..a3a358fed6647a --- /dev/null +++ b/packages/react-native/jest/mocks/NativeModules.js @@ -0,0 +1,239 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +// TODO: Split this up into separate files. +const NativeModules = { + AlertManager: { + alertWithArgs: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + AsyncLocalStorage: { + multiGet: jest.fn((keys, callback) => + process.nextTick(() => callback(null, [])), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + multiSet: jest.fn((entries, callback) => + process.nextTick(() => callback(null)), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + multiRemove: jest.fn((keys, callback) => + process.nextTick(() => callback(null)), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + multiMerge: jest.fn((entries, callback) => + process.nextTick(() => callback(null)), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + clear: jest.fn(callback => + process.nextTick(() => callback(null)), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + getAllKeys: jest.fn(callback => + process.nextTick(() => callback(null, [])), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + DeviceInfo: { + getConstants(): $FlowFixMe { + return { + Dimensions: { + window: { + fontScale: 2, + height: 1334, + scale: 2, + width: 750, + }, + screen: { + fontScale: 2, + height: 1334, + scale: 2, + width: 750, + }, + }, + }; + }, + }, + DevSettings: { + addMenuItem: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + reload: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + ImageLoader: { + getSize: jest.fn(url => Promise.resolve([320, 240])) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + getSizeWithHeaders: jest.fn((url, headers) => + Promise.resolve({height: 222, width: 333}), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + prefetchImage: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + prefetchImageWithMetadata: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + queryCache: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + ImageViewManager: { + getSize: jest.fn((uri, success) => + process.nextTick(() => success(320, 240)), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + prefetchImage: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + KeyboardObserver: { + addListener: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeListeners: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + NativeAnimatedModule: { + createAnimatedNode: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + updateAnimatedNodeConfig: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + getValue: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + startListeningToAnimatedNodeValue: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + stopListeningToAnimatedNodeValue: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + connectAnimatedNodes: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + disconnectAnimatedNodes: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + startAnimatingNode: jest.fn((animationId, nodeTag, config, endCallback) => { + setTimeout(() => endCallback({finished: true}), 16); + }) as JestMockFn<$FlowFixMe, $FlowFixMe>, + stopAnimation: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setAnimatedNodeValue: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setAnimatedNodeOffset: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + flattenAnimatedNodeOffset: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + extractAnimatedNodeOffset: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + connectAnimatedNodeToView: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + disconnectAnimatedNodeFromView: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + restoreDefaultValues: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + dropAnimatedNode: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + addAnimatedEventToView: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeAnimatedEventFromView: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + addListener: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeListener: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeListeners: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + Networking: { + sendRequest: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + abortRequest: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + addListener: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeListeners: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + PlatformConstants: { + getConstants(): $FlowFixMe { + return { + reactNativeVersion: { + major: 1000, + minor: 0, + patch: 0, + prerelease: undefined, + }, + }; + }, + }, + PushNotificationManager: { + presentLocalNotification: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + scheduleLocalNotification: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + cancelAllLocalNotifications: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + removeAllDeliveredNotifications: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + getDeliveredNotifications: jest.fn(callback => + process.nextTick(() => []), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeDeliveredNotifications: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + setApplicationIconBadgeNumber: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + getApplicationIconBadgeNumber: jest.fn(callback => + process.nextTick(() => callback(0)), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + cancelLocalNotifications: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + getScheduledLocalNotifications: jest.fn(callback => + process.nextTick(() => callback()), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + requestPermissions: jest.fn(() => + Promise.resolve({alert: true, badge: true, sound: true}), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + abandonPermissions: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + checkPermissions: jest.fn(callback => + process.nextTick(() => callback({alert: true, badge: true, sound: true})), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + getInitialNotification: jest.fn(() => Promise.resolve(null)) as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + addListener: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeListeners: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + SourceCode: { + getConstants(): $FlowFixMe { + return { + scriptURL: null, + }; + }, + }, + StatusBarManager: { + setColor: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setStyle: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setHidden: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setNetworkActivityIndicatorVisible: jest.fn() as JestMockFn< + $FlowFixMe, + $FlowFixMe, + >, + setBackgroundColor: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setTranslucent: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + getConstants: (): $FlowFixMe => ({ + HEIGHT: 42, + }), + }, + Timing: { + createTimer: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + deleteTimer: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + UIManager: {}, + BlobModule: { + getConstants: (): $FlowFixMe => ({ + BLOB_URI_SCHEME: 'content', + BLOB_URI_HOST: null, + }), + addNetworkingHandler: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + enableBlobSupport: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + disableBlobSupport: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + createFromParts: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + sendBlob: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + release: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + WebSocketModule: { + connect: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + send: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + sendBinary: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + ping: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + close: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + addListener: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + removeListeners: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + I18nManager: { + allowRTL: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + forceRTL: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + swapLeftAndRightInRTL: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + getConstants: (): $FlowFixMe => ({ + isRTL: false, + doLeftAndRightSwapInRTL: true, + }), + }, +}; + +export default NativeModules; diff --git a/packages/react-native/jest/mocks/RefreshControl.js b/packages/react-native/jest/mocks/RefreshControl.js new file mode 100644 index 00000000000000..bdd76ff6e486c8 --- /dev/null +++ b/packages/react-native/jest/mocks/RefreshControl.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type {RefreshControlProps} from '../../Libraries/Components/RefreshControl/RefreshControl'; +import type {HostComponent} from '../../src/private/types/HostComponent'; + +import requireNativeComponent from '../../Libraries/ReactNative/requireNativeComponent'; +import * as React from 'react'; + +const RCTRefreshControl: HostComponent<{}> = requireNativeComponent<{}>( + 'RCTRefreshControl', +); + +export default class RefreshControlMock extends React.Component { + static latestRef: ?RefreshControlMock; + + render(): React.Node { + return ; + } + + componentDidMount() { + RefreshControlMock.latestRef = this; + } +} diff --git a/packages/react-native/jest/mocks/RendererProxy.js b/packages/react-native/jest/mocks/RendererProxy.js new file mode 100644 index 00000000000000..ed1246f1bc28a9 --- /dev/null +++ b/packages/react-native/jest/mocks/RendererProxy.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +// In tests, we can use the default version without dependency injection. + +import typeof * as TRendererImplementation from '../../Libraries/ReactNative/RendererImplementation'; + +const { + dispatchCommand, + findHostInstance_DEPRECATED, + findNodeHandle, + getNodeFromInternalInstanceHandle, + getPublicInstanceFromInternalInstanceHandle, + getPublicInstanceFromRootTag, + isChildPublicInstance, + isProfilingRenderer, + renderElement, + sendAccessibilityEvent, + unmountComponentAtNodeAndRemoveContainer, + unstable_batchedUpdates, +} = jest.requireActual( + '../../Libraries/ReactNative/RendererImplementation', +) as TRendererImplementation; + +export { + dispatchCommand, + findHostInstance_DEPRECATED, + findNodeHandle, + getNodeFromInternalInstanceHandle, + getPublicInstanceFromInternalInstanceHandle, + getPublicInstanceFromRootTag, + isChildPublicInstance, + isProfilingRenderer, + renderElement, + sendAccessibilityEvent, + unmountComponentAtNodeAndRemoveContainer, + unstable_batchedUpdates, +}; diff --git a/packages/react-native/jest/mocks/ScrollView.js b/packages/react-native/jest/mocks/ScrollView.js new file mode 100644 index 00000000000000..62f50b052d5517 --- /dev/null +++ b/packages/react-native/jest/mocks/ScrollView.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import typeof TScrollView from '../../Libraries/Components/ScrollView/ScrollView'; +import type {ScrollViewNativeProps} from '../../Libraries/Components/ScrollView/ScrollViewNativeComponentType'; +import typeof * as TmockComponent from '../mockComponent'; +import typeof * as TMockNativeMethods from '../MockNativeMethods'; + +import View from '../../Libraries/Components/View/View'; +import requireNativeComponent from '../../Libraries/ReactNative/requireNativeComponent'; +import * as React from 'react'; + +const mockComponent = + jest.requireActual('../mockComponent').default; +const MockNativeMethods = jest.requireActual( + '../MockNativeMethods', +).default; + +const RCTScrollView = + requireNativeComponent('RCTScrollView'); + +const BaseComponent = mockComponent( + '../Libraries/Components/ScrollView/ScrollView', + { + ...MockNativeMethods, + getScrollResponder: jest.fn(), + getScrollableNode: jest.fn(), + getInnerViewNode: jest.fn(), + getInnerViewRef: jest.fn(), + getNativeScrollRef: jest.fn(), + scrollTo: jest.fn(), + scrollToEnd: jest.fn(), + flashScrollIndicators: jest.fn(), + scrollResponderZoomTo: jest.fn(), + scrollResponderScrollNativeHandleToKeyboard: jest.fn(), + }, // instanceMethods + true, // isESModule +) as TScrollView; + +// $FlowFixMe[incompatible-extend] +// $FlowFixMe[invalid-exported-annotation] +export default class ScrollViewMock extends BaseComponent { + render(): React.Node { + return ( + + {this.props.refreshControl} + {this.props.children} + + ); + } +} diff --git a/packages/react-native/jest/mocks/Text.js b/packages/react-native/jest/mocks/Text.js new file mode 100644 index 00000000000000..6dfe039ff290a0 --- /dev/null +++ b/packages/react-native/jest/mocks/Text.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import typeof TText from '../../Libraries/Text/Text'; +import typeof * as TmockComponent from '../mockComponent'; +import typeof * as TMockNativeMethods from '../MockNativeMethods'; + +const mockComponent = + jest.requireActual('../mockComponent').default; +const MockNativeMethods = jest.requireActual( + '../MockNativeMethods', +).default; + +const Text = mockComponent( + '../Libraries/Text/Text', + MockNativeMethods, // instanceMethods + true, // isESModule +) as TText; + +export default Text; diff --git a/packages/react-native/jest/mocks/TextInput.js b/packages/react-native/jest/mocks/TextInput.js new file mode 100644 index 00000000000000..f20514ab34bc48 --- /dev/null +++ b/packages/react-native/jest/mocks/TextInput.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import typeof TTextInput from '../../Libraries/Components/TextInput/TextInput'; +import typeof * as TmockComponent from '../mockComponent'; +import typeof * as TMockNativeMethods from '../MockNativeMethods'; + +const mockComponent = + jest.requireActual('../mockComponent').default; +const MockNativeMethods = jest.requireActual( + '../MockNativeMethods', +).default; + +const TextInput = mockComponent( + '../Libraries/Components/TextInput/TextInput', + { + ...MockNativeMethods, + isFocused: jest.fn(), + clear: jest.fn(), + getNativeRef: jest.fn(), + }, // instanceMethods + true, // isESModule +) as TTextInput; + +export default TextInput; diff --git a/packages/react-native/jest/mocks/UIManager.js b/packages/react-native/jest/mocks/UIManager.js new file mode 100644 index 00000000000000..601134e5575e0c --- /dev/null +++ b/packages/react-native/jest/mocks/UIManager.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +const UIManager = { + AndroidViewPager: { + Commands: { + setPage: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setPageWithoutAnimation: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + }, + }, + blur: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + createView: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + customBubblingEventTypes: {}, + customDirectEventTypes: {}, + dispatchViewManagerCommand: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + focus: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + getViewManagerConfig: jest.fn(name => { + if (name === 'AndroidDrawerLayout') { + return { + Constants: { + DrawerPosition: { + Left: 10, + }, + }, + }; + } + }) as JestMockFn<[string], $FlowFixMe>, + hasViewManagerConfig: jest.fn(name => { + return name === 'AndroidDrawerLayout'; + }) as JestMockFn<[string], boolean>, + measure: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + manageChildren: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + setChildren: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + updateView: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + AndroidDrawerLayout: { + Constants: { + DrawerPosition: { + Left: 10, + }, + }, + }, + AndroidTextInput: { + Commands: {}, + }, + ScrollView: { + Constants: {}, + }, + View: { + Constants: {}, + }, +}; + +export default UIManager; diff --git a/packages/react-native/jest/mocks/Vibration.js b/packages/react-native/jest/mocks/Vibration.js new file mode 100644 index 00000000000000..d68b157d49bfac --- /dev/null +++ b/packages/react-native/jest/mocks/Vibration.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +const Vibration = { + vibrate: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, + cancel: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, +}; + +export default Vibration; diff --git a/packages/react-native/jest/mocks/View.js b/packages/react-native/jest/mocks/View.js new file mode 100644 index 00000000000000..ea9a181dcf5d59 --- /dev/null +++ b/packages/react-native/jest/mocks/View.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import typeof TView from '../../Libraries/Components/View/View'; +import typeof * as TmockComponent from '../mockComponent'; +import typeof * as TMockNativeMethods from '../MockNativeMethods'; + +const mockComponent = + jest.requireActual('../mockComponent').default; +const MockNativeMethods = jest.requireActual( + '../MockNativeMethods', +).default; + +const View = mockComponent( + '../Libraries/Components/View/View', + MockNativeMethods, // instanceMethods + true, // isESModule +) as TView; + +export default View; diff --git a/packages/react-native/jest/mocks/ViewNativeComponent.js b/packages/react-native/jest/mocks/ViewNativeComponent.js new file mode 100644 index 00000000000000..b1c1a940a7af2c --- /dev/null +++ b/packages/react-native/jest/mocks/ViewNativeComponent.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type {ViewProps} from '../../Libraries/Components/View/ViewPropTypes'; + +import * as React from 'react'; +import {createElement} from 'react'; + +export default class View extends React.Component { + render(): React.Node { + // $FlowIgnore[not-a-function] + return createElement('View', this.props, this.props.children); + } +} + +View.displayName = 'View'; diff --git a/packages/react-native/jest/mocks/requireNativeComponent.js b/packages/react-native/jest/mocks/requireNativeComponent.js new file mode 100644 index 00000000000000..eff40ac54e259f --- /dev/null +++ b/packages/react-native/jest/mocks/requireNativeComponent.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +import type {HostComponent} from '../../src/private/types/HostComponent'; +import typeof * as TmockNativeComponent from '../mockNativeComponent'; + +const mockNativeComponent = jest.requireActual( + '../mockNativeComponent', +).default; + +export default function requireNativeComponent( + uiViewClassName: string, +): HostComponent { + return mockNativeComponent(uiViewClassName); +} diff --git a/packages/react-native/jest/mocks/useColorScheme.js b/packages/react-native/jest/mocks/useColorScheme.js new file mode 100644 index 00000000000000..557f659078ce1c --- /dev/null +++ b/packages/react-native/jest/mocks/useColorScheme.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +import type {ColorSchemeName} from '../../Libraries/Utilities/NativeAppearance'; + +const useColorScheme = jest.fn(() => 'light') as JestMockFn< + [], + ColorSchemeName, +>; + +export default useColorScheme; diff --git a/packages/react-native/jest/setup.js b/packages/react-native/jest/setup.js index cb5a8afe2e8cc9..056eb57dd2645d 100644 --- a/packages/react-native/jest/setup.js +++ b/packages/react-native/jest/setup.js @@ -8,33 +8,14 @@ * @format */ -// NOTE: Ideally, these would use `$Exports`, but Flow is struggling to resolve -// these module specifiers. Also, these are prefixed with `mock_` to workaround -// Jest's `babel-plugin-jest-hoist` plugin which validates that mock factories -// only reference local variables. (It is unaware of generic type annotations.) -import typeof * as mock_TScrollView from '../Libraries/Components/ScrollView/ScrollView'; -import type {ViewProps as mock_ViewProps} from '../Libraries/Components/View/ViewPropTypes'; -import typeof * as mock_TModal from '../Libraries/Modal/Modal'; -import typeof * as mock_TMockComponent from './mockComponent'; -import typeof * as mock_TMockModal from './mockModal'; -import typeof * as mock_TMockNativeComponent from './mockNativeComponent'; -import typeof * as mock_TMockNativeMethods from './MockNativeMethods'; -import typeof * as mock_TMockScrollView from './mockScrollView'; -import typeof * as mock_TRefreshControlMock from './RefreshControlMock'; - global.IS_REACT_ACT_ENVIRONMENT = true; // Suppress the `react-test-renderer` warnings until New Architecture and legacy // mode are no longer supported by React Native. global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true; -jest.requireActual('@react-native/js-polyfills/error-guard'); +import '@react-native/js-polyfills/error-guard'; -/** - * @see https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options - */ -function mockESModule(exports: T): {__esModule: true, ...T} { - return {__esModule: true, ...exports}; -} +import mock from './mock'; // $FlowIgnore[cannot-write] Object.defineProperties(global, { @@ -89,505 +70,54 @@ Object.defineProperties(global, { }, }); -jest - .mock('../Libraries/Core/InitializeCore', () => {}) - .mock('../Libraries/Core/NativeExceptionsManager') - .mock('../Libraries/ReactNative/UIManager', () => - mockESModule({ - default: { - AndroidViewPager: { - Commands: { - setPage: jest.fn(), - setPageWithoutAnimation: jest.fn(), - }, - }, - blur: jest.fn(), - createView: jest.fn(), - customBubblingEventTypes: {}, - customDirectEventTypes: {}, - dispatchViewManagerCommand: jest.fn(), - focus: jest.fn(), - getViewManagerConfig: jest.fn(name => { - if (name === 'AndroidDrawerLayout') { - return { - Constants: { - DrawerPosition: { - Left: 10, - }, - }, - }; - } - }), - hasViewManagerConfig: jest.fn(name => { - return name === 'AndroidDrawerLayout'; - }), - measure: jest.fn(), - manageChildren: jest.fn(), - setChildren: jest.fn(), - updateView: jest.fn(), - AndroidDrawerLayout: { - Constants: { - DrawerPosition: { - Left: 10, - }, - }, - }, - AndroidTextInput: { - Commands: {}, - }, - ScrollView: { - Constants: {}, - }, - View: { - Constants: {}, - }, - }, - }), - ) - .mock('../Libraries/Image/Image', () => { - const mockComponent = - jest.requireActual('./mockComponent').default; - return mockESModule({ - default: mockComponent( - '../Libraries/Image/Image', - /* instanceMethods */ null, - /* isESModule */ true, - ), - }); - }) - .mock('../Libraries/Text/Text', () => { - const MockNativeMethods = jest.requireActual( - './MockNativeMethods', - ).default; - const mockComponent = - jest.requireActual('./mockComponent').default; - - return mockESModule({ - default: mockComponent( - '../Libraries/Text/Text', - MockNativeMethods, - /* isESModule */ true, - ), - }); - }) - .mock('../Libraries/Components/TextInput/TextInput', () => { - const MockNativeMethods = jest.requireActual( - './MockNativeMethods', - ).default; - const mockComponent = - jest.requireActual('./mockComponent').default; - - return mockESModule({ - default: mockComponent( - '../Libraries/Components/TextInput/TextInput', - /* instanceMethods */ { - ...MockNativeMethods, - isFocused: jest.fn(), - clear: jest.fn(), - getNativeRef: jest.fn(), - }, - /* isESModule */ true, - ), - }); - }) - .mock('../Libraries/Modal/Modal', () => { - const mockComponent = - jest.requireActual('./mockComponent').default; - const mockModal = - jest.requireActual('./mockModal').default; - - const baseComponent = mockComponent( - '../Libraries/Modal/Modal', - /* instanceMethods */ null, - /* isESModule */ true, - ); - - return mockESModule({ - default: mockModal(baseComponent), - }); - }) - .mock('../Libraries/Components/View/View', () => { - const MockNativeMethods = jest.requireActual( - './MockNativeMethods', - ).default; - const mockComponent = - jest.requireActual('./mockComponent').default; - - return mockESModule({ - default: mockComponent( - '../Libraries/Components/View/View', - /* instanceMethods */ MockNativeMethods, - /* isESModule */ true, - ), - }); - }) - .mock('../Libraries/Components/AccessibilityInfo/AccessibilityInfo', () => - mockESModule({ - default: { - addEventListener: jest.fn(() => ({ - remove: jest.fn(), - })), - announceForAccessibility: jest.fn(), - announceForAccessibilityWithOptions: jest.fn(), - isAccessibilityServiceEnabled: jest.fn(() => Promise.resolve(false)), - isBoldTextEnabled: jest.fn(() => Promise.resolve(false)), - isGrayscaleEnabled: jest.fn(() => Promise.resolve(false)), - isInvertColorsEnabled: jest.fn(() => Promise.resolve(false)), - isReduceMotionEnabled: jest.fn(() => Promise.resolve(false)), - isHighTextContrastEnabled: jest.fn(() => Promise.resolve(false)), - isDarkerSystemColorsEnabled: jest.fn(() => Promise.resolve(false)), - prefersCrossFadeTransitions: jest.fn(() => Promise.resolve(false)), - isReduceTransparencyEnabled: jest.fn(() => Promise.resolve(false)), - isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)), - setAccessibilityFocus: jest.fn(), - sendAccessibilityEvent: jest.fn(), - getRecommendedTimeoutMillis: jest.fn(() => Promise.resolve(false)), - }, - }), - ) - .mock('../Libraries/Components/Clipboard/Clipboard', () => - mockESModule({ - default: { - getString: jest.fn(() => ''), - setString: jest.fn(), - }, - }), - ) - .mock('../Libraries/Components/RefreshControl/RefreshControl', () => - mockESModule({ - default: jest.requireActual( - './RefreshControlMock', - ).default, - }), - ) - .mock('../Libraries/Components/ScrollView/ScrollView', () => { - const MockNativeMethods = jest.requireActual( - './MockNativeMethods', - ).default; - const mockComponent = - jest.requireActual('./mockComponent').default; - const mockScrollView = - jest.requireActual('./mockScrollView').default; - - const baseComponent = mockComponent( - '../Libraries/Components/ScrollView/ScrollView', - { - ...MockNativeMethods, - getScrollResponder: jest.fn(), - getScrollableNode: jest.fn(), - getInnerViewNode: jest.fn(), - getInnerViewRef: jest.fn(), - getNativeScrollRef: jest.fn(), - scrollTo: jest.fn(), - scrollToEnd: jest.fn(), - flashScrollIndicators: jest.fn(), - scrollResponderZoomTo: jest.fn(), - scrollResponderScrollNativeHandleToKeyboard: jest.fn(), - }, - true, // isESModule - ); - - return mockESModule({ - default: mockScrollView(baseComponent), - }); - }) - .mock('../Libraries/Components/ActivityIndicator/ActivityIndicator', () => { - const mockComponent = - jest.requireActual('./mockComponent').default; - return mockESModule({ - default: mockComponent( - '../Libraries/Components/ActivityIndicator/ActivityIndicator', - null, // instanceMethods - true, // isESModule - ), - }); - }) - .mock('../Libraries/AppState/AppState', () => - mockESModule({ - default: { - addEventListener: jest.fn(() => ({ - remove: jest.fn(), - })), - removeEventListener: jest.fn(), - currentState: jest.fn(), - }, - }), - ) - .mock('../Libraries/Linking/Linking', () => - mockESModule({ - default: { - openURL: jest.fn(), - canOpenURL: jest.fn(() => Promise.resolve(true)), - openSettings: jest.fn(), - addEventListener: jest.fn(() => ({ - remove: jest.fn(), - })), - getInitialURL: jest.fn(() => Promise.resolve()), - sendIntent: jest.fn(), - }, - }), - ) - // Mock modules defined by the native layer (ex: Objective-C, Java) - .mock('../Libraries/BatchedBridge/NativeModules', () => - mockESModule({ - default: { - AlertManager: { - alertWithArgs: jest.fn(), - }, - AsyncLocalStorage: { - multiGet: jest.fn((keys, callback) => - process.nextTick(() => callback(null, [])), - ), - multiSet: jest.fn((entries, callback) => - process.nextTick(() => callback(null)), - ), - multiRemove: jest.fn((keys, callback) => - process.nextTick(() => callback(null)), - ), - multiMerge: jest.fn((entries, callback) => - process.nextTick(() => callback(null)), - ), - clear: jest.fn(callback => process.nextTick(() => callback(null))), - getAllKeys: jest.fn(callback => - process.nextTick(() => callback(null, [])), - ), - }, - DeviceInfo: { - getConstants() { - return { - Dimensions: { - window: { - fontScale: 2, - height: 1334, - scale: 2, - width: 750, - }, - screen: { - fontScale: 2, - height: 1334, - scale: 2, - width: 750, - }, - }, - }; - }, - }, - DevSettings: { - addMenuItem: jest.fn(), - reload: jest.fn(), - }, - ImageLoader: { - getSize: jest.fn(url => Promise.resolve([320, 240])), - getSizeWithHeaders: jest.fn((url, headers) => - Promise.resolve({height: 222, width: 333}), - ), - prefetchImage: jest.fn(), - prefetchImageWithMetadata: jest.fn(), - queryCache: jest.fn(), - }, - ImageViewManager: { - getSize: jest.fn((uri, success) => - process.nextTick(() => success(320, 240)), - ), - prefetchImage: jest.fn(), - }, - KeyboardObserver: { - addListener: jest.fn(), - removeListeners: jest.fn(), - }, - NativeAnimatedModule: { - createAnimatedNode: jest.fn(), - updateAnimatedNodeConfig: jest.fn(), - getValue: jest.fn(), - startListeningToAnimatedNodeValue: jest.fn(), - stopListeningToAnimatedNodeValue: jest.fn(), - connectAnimatedNodes: jest.fn(), - disconnectAnimatedNodes: jest.fn(), - startAnimatingNode: jest.fn( - (animationId, nodeTag, config, endCallback) => { - setTimeout(() => endCallback({finished: true}), 16); - }, - ), - stopAnimation: jest.fn(), - setAnimatedNodeValue: jest.fn(), - setAnimatedNodeOffset: jest.fn(), - flattenAnimatedNodeOffset: jest.fn(), - extractAnimatedNodeOffset: jest.fn(), - connectAnimatedNodeToView: jest.fn(), - disconnectAnimatedNodeFromView: jest.fn(), - restoreDefaultValues: jest.fn(), - dropAnimatedNode: jest.fn(), - addAnimatedEventToView: jest.fn(), - removeAnimatedEventFromView: jest.fn(), - addListener: jest.fn(), - removeListener: jest.fn(), - removeListeners: jest.fn(), - }, - Networking: { - sendRequest: jest.fn(), - abortRequest: jest.fn(), - addListener: jest.fn(), - removeListeners: jest.fn(), - }, - PlatformConstants: { - getConstants() { - return { - reactNativeVersion: { - major: 1000, - minor: 0, - patch: 0, - prerelease: undefined, - }, - }; - }, - }, - PushNotificationManager: { - presentLocalNotification: jest.fn(), - scheduleLocalNotification: jest.fn(), - cancelAllLocalNotifications: jest.fn(), - removeAllDeliveredNotifications: jest.fn(), - getDeliveredNotifications: jest.fn(callback => - process.nextTick(() => []), - ), - removeDeliveredNotifications: jest.fn(), - setApplicationIconBadgeNumber: jest.fn(), - getApplicationIconBadgeNumber: jest.fn(callback => - process.nextTick(() => callback(0)), - ), - cancelLocalNotifications: jest.fn(), - getScheduledLocalNotifications: jest.fn(callback => - process.nextTick(() => callback()), - ), - requestPermissions: jest.fn(() => - Promise.resolve({alert: true, badge: true, sound: true}), - ), - abandonPermissions: jest.fn(), - checkPermissions: jest.fn(callback => - process.nextTick(() => - callback({alert: true, badge: true, sound: true}), - ), - ), - getInitialNotification: jest.fn(() => Promise.resolve(null)), - addListener: jest.fn(), - removeListeners: jest.fn(), - }, - SourceCode: { - getConstants() { - return { - scriptURL: null, - }; - }, - }, - StatusBarManager: { - setColor: jest.fn(), - setStyle: jest.fn(), - setHidden: jest.fn(), - setNetworkActivityIndicatorVisible: jest.fn(), - setBackgroundColor: jest.fn(), - setTranslucent: jest.fn(), - getConstants: () => ({ - HEIGHT: 42, - }), - }, - Timing: { - createTimer: jest.fn(), - deleteTimer: jest.fn(), - }, - UIManager: {}, - BlobModule: { - getConstants: () => ({ - BLOB_URI_SCHEME: 'content', - BLOB_URI_HOST: null, - }), - addNetworkingHandler: jest.fn(), - enableBlobSupport: jest.fn(), - disableBlobSupport: jest.fn(), - createFromParts: jest.fn(), - sendBlob: jest.fn(), - release: jest.fn(), - }, - WebSocketModule: { - connect: jest.fn(), - send: jest.fn(), - sendBinary: jest.fn(), - ping: jest.fn(), - close: jest.fn(), - addListener: jest.fn(), - removeListeners: jest.fn(), - }, - I18nManager: { - allowRTL: jest.fn(), - forceRTL: jest.fn(), - swapLeftAndRightInRTL: jest.fn(), - getConstants: () => ({ - isRTL: false, - doLeftAndRightSwapInRTL: true, - }), - }, - }, - }), - ) - .mock('../Libraries/NativeComponent/NativeComponentRegistry', () => { - return { - get: jest.fn((name, viewConfigProvider) => { - const mockNativeComponent = - jest.requireActual( - './mockNativeComponent', - ).default; - return mockNativeComponent(name); - }), - getWithFallback_DEPRECATED: jest.fn((name, viewConfigProvider) => { - const mockNativeComponent = - jest.requireActual( - './mockNativeComponent', - ).default; - return mockNativeComponent(name); - }), - setRuntimeConfigProvider: jest.fn(), - }; - }) - .mock('../Libraries/ReactNative/requireNativeComponent', () => { - const mockNativeComponent = jest.requireActual( - './mockNativeComponent', - ).default; - return mockESModule({ - default: mockNativeComponent, - }); - }) - .mock('../Libraries/Vibration/Vibration', () => - mockESModule({ - default: { - vibrate: jest.fn(), - cancel: jest.fn(), - }, - }), - ) - .mock('../Libraries/Components/View/ViewNativeComponent', () => { - const React = require('react'); - const {createElement} = React; - - const Component = class extends React.Component { - render(): React.Node { - // $FlowIgnore[not-a-function] - return createElement('View', this.props, this.props.children); - } - }; - - Component.displayName = 'View'; - - return mockESModule({ - default: Component, - }); - }) - // In tests, we can use the default version instead of the one using - // dependency injection. - .mock('../Libraries/ReactNative/RendererProxy', () => { - return jest.requireActual( - '../Libraries/ReactNative/RendererImplementation', - ); - }) - .mock('../Libraries/Utilities/useColorScheme', () => - mockESModule({ - default: jest.fn().mockReturnValue('light'), - }), - ); +// $FlowFixMe[incompatible-call] - `./mocks/AppState` is incomplete. +mock('m#../Libraries/AppState/AppState', 'm#./mocks/AppState'); +mock('m#../Libraries/BatchedBridge/NativeModules', 'm#./mocks/NativeModules'); +mock( + 'm#../Libraries/Components/AccessibilityInfo/AccessibilityInfo', + 'm#./mocks/AccessibilityInfo', +); +mock( + 'm#../Libraries/Components/ActivityIndicator/ActivityIndicator', + 'm#./mocks/ActivityIndicator', +); +mock('m#../Libraries/Components/Clipboard/Clipboard', 'm#./mocks/Clipboard'); +mock( + 'm#../Libraries/Components/RefreshControl/RefreshControl', + // $FlowFixMe[incompatible-call] - `../Libraries/Components/RefreshControl/RefreshControl` should export a component type. + 'm#./mocks/RefreshControl', +); +// $FlowFixMe[incompatible-exact] - `../Libraries/Components/ScrollView/ScrollView` is... I don't even. +// $FlowFixMe[prop-missing] +mock('m#../Libraries/Components/ScrollView/ScrollView', 'm#./mocks/ScrollView'); +mock('m#../Libraries/Components/TextInput/TextInput', 'm#./mocks/TextInput'); +mock('m#../Libraries/Components/View/View', 'm#./mocks/View'); +mock( + 'm#../Libraries/Components/View/ViewNativeComponent', + // $FlowFixMe[incompatible-call] - `./mocks/ViewNativeComponent` is incomplete. + // $FlowFixMe[prop-missing] + 'm#./mocks/ViewNativeComponent', +); +mock('m#../Libraries/Core/InitializeCore', 'm#./mocks/InitializeCore'); +mock('m#../Libraries/Core/NativeExceptionsManager'); +mock('m#../Libraries/Image/Image', 'm#./mocks/Image'); +// $FlowFixMe[incompatible-call] - `./mocks/Linking` is incomplete. +mock('m#../Libraries/Linking/Linking', 'm#./mocks/Linking'); +// $FlowFixMe[incompatible-call] - `../Libraries/Modal/Modal` should export a component type. +mock('m#../Libraries/Modal/Modal', 'm#./mocks/Modal'); +mock( + 'm#../Libraries/NativeComponent/NativeComponentRegistry', + // $FlowFixMe[prop-missing] - `./mocks/NativeComponentRegistry` should export named functions. + 'm#./mocks/NativeComponentRegistry', +); +// $FlowFixMe[prop-missing] - `./mocks/RendererProxy` is incomplete. +mock('m#../Libraries/ReactNative/RendererProxy', 'm#./mocks/RendererProxy'); +mock( + 'm#../Libraries/ReactNative/requireNativeComponent', + 'm#./mocks/requireNativeComponent', +); +// $FlowFixMe[prop-missing] - `./mocks/UIManager` is incomplete. +mock('m#../Libraries/ReactNative/UIManager', 'm#./mocks/UIManager'); +mock('m#../Libraries/Text/Text', 'm#./mocks/Text'); +mock('m#../Libraries/Utilities/useColorScheme', 'm#./mocks/useColorScheme'); +mock('m#../Libraries/Vibration/Vibration', 'm#./mocks/Vibration');