react-native/jest/setup.js

378 lines
11 KiB
JavaScript
Raw Normal View History

/**
* 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.
*
* @format
*/
'use strict';
const MockNativeMethods = jest.requireActual('./MockNativeMethods');
const mockComponent = jest.requireActual('./mockComponent');
jest.requireActual('@react-native/js-polyfills/Object.es8');
jest.requireActual('@react-native/js-polyfills/error-guard');
global.__DEV__ = true;
global.performance = {
now: jest.fn(Date.now),
};
global.regeneratorRuntime = jest.requireActual('regenerator-runtime/runtime');
Add window to jest setup (#28067) Summary: `window` exists in the React Native runtime, but not inside the test environment. Many libraries use `typeof window === 'undefined'` to check if code is running in SSR. Because of the difference in the real environment and test environment, tests can behave different than the real app, which is an unwanted behavior. ## Background I'm using https://github.com/tannerlinsley/react-query in my React Native Project, which works really well. When writing tests, they wouldn't work: jest started and then seemingly did nothing. While debugging I noticed the render was stuck in an infinite loop. Then I noticed the following line inside `react-query`: ```js const isServer = typeof window === 'undefined' ``` I didn't know that the React Native runtime has a global `window`, and thought it's a bug inside react-query. But it does have a `window`, which is not defined inside the test environment. The infinite loop was caused by react-query thinking it is running on the server, which doesn't fetch any data. If the react-query hook mounts, it re-executes because then it should be mounted inside the client. But `isServer` was still `true`. This repeats forever. ## Changelog [General] [Fixed] - Fix `window` not existing in jest setup Pull Request resolved: https://github.com/facebook/react-native/pull/28067 Test Plan: Are there tests to check if the test environment is setup correctly? � Reviewed By: yungsters Differential Revision: D30317021 Pulled By: charlesbdudley fbshipit-source-id: 837ed952833ef8e70c5132c9b4152b0e0f28b4dd
2021-08-24 16:40:39 +00:00
global.window = global;
global.requestAnimationFrame = function (callback) {
return setTimeout(() => callback(jest.now()), 0);
};
global.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
// there's a __mock__ for it.
jest.setMock(
'../Libraries/vendor/core/ErrorUtils',
require('../Libraries/vendor/core/ErrorUtils'),
);
jest
.mock('../Libraries/Core/InitializeCore', () => {})
.mock('../Libraries/Core/NativeExceptionsManager', () => ({
__esModule: true,
default: {
reportException: jest.fn(),
},
}))
.mock('../Libraries/ReactNative/UIManager', () => ({
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(),
removeSubviewsFromContainerWithID: jest.fn(),
replaceExistingNonRootView: jest.fn(),
setChildren: jest.fn(),
updateView: jest.fn(),
AndroidDrawerLayout: {
Constants: {
DrawerPosition: {
Left: 10,
},
},
},
AndroidTextInput: {
Commands: {},
},
ScrollView: {
Constants: {},
},
View: {
Constants: {},
},
}))
.mock('../Libraries/Image/Image', () =>
mockComponent('../Libraries/Image/Image'),
)
.mock('../Libraries/Text/Text', () =>
mockComponent('../Libraries/Text/Text', MockNativeMethods),
)
.mock('../Libraries/Components/TextInput/TextInput', () =>
mockComponent('../Libraries/Components/TextInput/TextInput', {
...MockNativeMethods,
isFocused: jest.fn(),
clear: jest.fn(),
getNativeRef: jest.fn(),
}),
)
Update Modal's mock to not render its children when it is not visible (#32346) Summary: The Modal's mock always render its children (whether it is visible or not), whereas in reality the Modal renders `null` when the Modal is not visible. This causes troubles when trying to test whether the Modal is visible or not. Instead of testing if the children are rendered (using getByText from React Native Testing Library for instance), we are forced to test the value of the visible prop directly (see https://github.com/callstack/react-native-testing-library/issues/508 and https://github.com/callstack/react-native-testing-library/issues/659). This is not ideal because we are forced to test implementation detail and can't test from the user perspective. I also believe the mock should be closest as possible from reality. I had 2 options: 1. Rendering the Modal without its children 2. Not rendering the Modal at all The latter has the advantage of being closer to the reality, but I chose the former to still be able to test the Modal through the visible prop, so there is no breaking change (only snapshots update will be required). ## Changelog [General] [Changed] - Update Modal's mock to not render its children when it is not visible Pull Request resolved: https://github.com/facebook/react-native/pull/32346 Test Plan: I added a test case when visible is false, then updated the mock so the children are not rendered. The before / after is here: ![image](https://user-images.githubusercontent.com/17070498/136256142-a351d002-8b77-490a-ba65-1e8ad0d6eb55.png) Reviewed By: yungsters Differential Revision: D31445964 Pulled By: lunaleaps fbshipit-source-id: 08501921455728cde6befd0103016c95074cc1df
2021-10-08 05:42:56 +00:00
.mock('../Libraries/Modal/Modal', () => {
const baseComponent = mockComponent('../Libraries/Modal/Modal');
const mockModal = jest.requireActual('./mockModal');
return mockModal(baseComponent);
})
.mock('../Libraries/Components/View/View', () =>
mockComponent('../Libraries/Components/View/View', MockNativeMethods),
)
.mock('../Libraries/Components/AccessibilityInfo/AccessibilityInfo', () => ({
__esModule: true,
default: {
addEventListener: jest.fn(),
announceForAccessibility: jest.fn(),
isAccessibilityServiceEnabled: jest.fn(),
isBoldTextEnabled: jest.fn(),
isGrayscaleEnabled: jest.fn(),
isInvertColorsEnabled: jest.fn(),
isReduceMotionEnabled: jest.fn(),
prefersCrossFadeTransitions: jest.fn(),
isReduceTransparencyEnabled: jest.fn(),
isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)),
setAccessibilityFocus: jest.fn(),
sendAccessibilityEvent: jest.fn(),
getRecommendedTimeoutMillis: jest.fn(),
},
}))
.mock('../Libraries/Components/Clipboard/Clipboard', () => ({
getString: jest.fn(() => ''),
setString: jest.fn(),
}))
.mock('../Libraries/Components/RefreshControl/RefreshControl', () =>
jest.requireActual(
'../Libraries/Components/RefreshControl/__mocks__/RefreshControlMock',
),
)
Make ScrollView use ForwardRef Summary: Have ScrollView use forwardRef so that the host component methods like `measure` and `measureLayout` are available without having to call `getNativeScrollRef`. Instead, you can use `<ScrollView ref={myRef} />` and directly call all methods of ScrollView and host components on `myRef`. Previous usage: ``` const myRef = React.createRef<React.ElementRef<typeof ScrollView>>(); <ScrollView ref={myRef} /> const innerViewRef = myRef.current.getNativeScrollRef(); innerViewRef.measure(); ``` New usage: ``` const myRef = React.createRef<React.ElementRef<typeof View>>(); <ScrollView ref={myRef} /> // now, myRef.current can be used directly as the ref myRef.current.measure(); myRef.current.measureLayout(); // Additionally, myRef still has access to ScrollView methods myRef.current.scrollTo(...); ``` Changes: * Added deprecation warnings to ScrollView methods `getNativeScrollRef`, `getScrollableNode`, and `getScrollResponder` * Added the forwardRef call to create `ForwardedScrollView` - this takes in `ref` and passes it into the class ScrollView as `scrollViewRef`. * Forwarded the ref to the native scroll view using `setAndForwardRef`. * Added statics onto `ForwardedScrollView` so that `ScrollView.Context` can still be accessed. * Added type `ScrollViewImperativeMethods`, which lists the public methods of ScrollView. * Converted all public methods of ScrollView to arrow functions. This is because they need to be bound to the forwarded ref. * Bound all public methods of ScrollView to the forwarded ref in the `setAndForwardRef` call. * Flow typed the final output (ForwardedScrollView) as an abstract component that takes in the props of the `ScrollView` class, and has all methods of both the inner host component (`measure`, `measureLayout`, etc) and the public methods (`scrollTo`, etc). Changes to mockScrollView: * Changed mockScrollView to be able to mock the function component instead of a class component * Updated necessary tests Changelog: [General] [Changed] - Make ScrollView use forwardRef Reviewed By: TheSavior Differential Revision: D19304480 fbshipit-source-id: 6c359897526d9d5ac6bc6ab6d5f9d82bfc0d8af4
2020-03-26 23:47:39 +00:00
.mock('../Libraries/Components/ScrollView/ScrollView', () => {
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(),
},
);
const mockScrollView = jest.requireActual('./mockScrollView');
return mockScrollView(baseComponent);
})
.mock('../Libraries/Components/ActivityIndicator/ActivityIndicator', () => ({
__esModule: true,
default: mockComponent(
'../Libraries/Components/ActivityIndicator/ActivityIndicator',
null,
true,
),
}))
.mock('../Libraries/AppState/AppState', () => ({
addEventListener: jest.fn(() => ({
remove: jest.fn(),
})),
}))
.mock('../Libraries/Linking/Linking', () => ({
openURL: jest.fn(),
canOpenURL: jest.fn(() => Promise.resolve(true)),
openSettings: jest.fn(),
addEventListener: 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', () => ({
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: {
Fix ImageLoader.getSize jest mock (#34653) Summary: `getSize` should resolve with an array of `[width, height]` but this mock resolves with `{ width, height }`. It should be `ReadOnlyArray<number>` instead of `{width: number, height: number}` The native image loader call is [here](https://github.com/facebook/react-native/blob/main/Libraries/Image/NativeImageLoaderIOS.js#L18): ```js +getSize: (uri: string) => Promise<$ReadOnlyArray<number>>; ``` but in the [jest setup file](https://github.com/facebook/react-native/blob/main/jest/setup.js): ```js getSize: jest.fn(url => Promise.resolve({width: 320, height: 240})), ``` My tests were failing on `Image.getSize()` - `TypeError: Invalid attempt to destructure non-iterable instance.` I managed to trace this down to this object being returned by the Jest mock - looks like it's returning a size object instead of a dimensions array. ## Workaround If you are hitting this issue, you can work around this mock by using: ```js ReactNative.NativeModules.ImageLoader.getSize = jest.fn((_) => Promise.resolve([320, 240])); ``` ## Changelog [JavaScript] [Changed]: Changed the mocked return value of `ImageLoader.getSize` to be `[320, 240]` instead of `{ width: 320, height: 240 }` Pull Request resolved: https://github.com/facebook/react-native/pull/34653 Test Plan: TBD? I think a test with `Image.getSize(path)` will cover it. That's where I hit the error with the ios-specific imageLoader's getSize method. Reviewed By: robhogan Differential Revision: D39413522 Pulled By: NickGerleman fbshipit-source-id: 7f18d7acde0cf94da0b4aec8fe2d0cad3fb0cc55
2022-09-12 21:11:28 +00:00
getSize: jest.fn(url => Promise.resolve([320, 240])),
prefetchImage: jest.fn(),
},
ImageViewManager: {
getSize: jest.fn((uri, success) =>
process.nextTick(() => success(320, 240)),
),
prefetchImage: jest.fn(),
},
KeyboardObserver: {
addListener: jest.fn(),
removeListeners: jest.fn(),
},
Networking: {
sendRequest: jest.fn(),
abortRequest: jest.fn(),
addListener: jest.fn(),
removeListeners: jest.fn(),
},
PlatformConstants: {
getConstants() {
return {};
},
},
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) => {
return jest.requireActual('./mockNativeComponent').default(name);
}),
getWithFallback_DEPRECATED: jest.fn((name, viewConfigProvider) => {
return jest.requireActual('./mockNativeComponent').default(name);
}),
setRuntimeConfigProvider: jest.fn(),
};
})
.mock('../Libraries/ReactNative/requireNativeComponent', () => {
return jest.requireActual('./mockNativeComponent');
})
.mock(
'../Libraries/Utilities/verifyComponentAttributeEquivalence',
() => function () {},
)
.mock('../Libraries/Vibration/Vibration', () => ({
vibrate: jest.fn(),
cancel: jest.fn(),
}))
.mock('../Libraries/Components/View/ViewNativeComponent', () => {
const React = require('react');
const Component = class extends React.Component {
render() {
return React.createElement('View', this.props, this.props.children);
}
};
Component.displayName = 'View';
return {
__esModule: true,
default: Component,
};
});