/**
* Adds Expo-related mocks to the Jest environment. Jest runs this setup module after it runs the
* React Native setup module.
*/
'use strict';
const mockNativeModules = require('react-native/Libraries/BatchedBridge/NativeModules');
const createMockConstants = require('./createMockConstants');
// window isn't defined as of react-native 0.45+ it seems
if (typeof window !== 'object') {
global.window = global;
global.window.navigator = {};
}
const mockImageLoader = {
configurable: true,
enumerable: true,
get: () => ({
prefetchImage: jest.fn(),
getSize: jest.fn((uri, success) => process.nextTick(() => success(320, 240))),
}),
};
Object.defineProperty(mockNativeModules, 'ImageLoader', mockImageLoader);
Object.defineProperty(mockNativeModules, 'ImageViewManager', mockImageLoader);
Object.defineProperty(mockNativeModules, 'LinkingManager', {
configurable: true,
enumerable: true,
get: () => mockNativeModules.Linking,
});
const mockPlatformConstants = {
configurable: true,
enumerable: true,
get: () => ({
forceTouchAvailable: true,
}),
};
Object.defineProperty(mockNativeModules, 'PlatformConstants', mockPlatformConstants);
const publicExpoModules = require('./expoModules');
const internalExpoModules = require('./internalExpoModules');
const expoModules = {
...publicExpoModules,
...internalExpoModules,
};
function mock(property, customMock) {
const propertyType = property.type;
let mockValue;
if (customMock !== undefined) {
mockValue = customMock;
} else if (propertyType === 'function') {
if (property.functionType === 'promise') {
mockValue = jest.fn(() => Promise.resolve());
} else {
mockValue = jest.fn();
}
} else if (propertyType === 'number') {
mockValue = 1;
} else if (propertyType === 'string') {
mockValue = 'mock';
} else if (propertyType === 'array') {
mockValue = [];
} else if (propertyType === 'mock') {
mockValue = mockByMockDefinition(property.mockDefinition);
} else {
mockValue = {};
}
return mockValue;
}
function mockProperties(moduleProperties, customMocks) {
const mockedProperties = {};
for (let propertyName of Object.keys(moduleProperties)) {
const property = moduleProperties[propertyName];
const customMock =
customMocks && customMocks.hasOwnProperty(propertyName)
? customMocks[propertyName]
: property.mock;
mockedProperties[propertyName] = mock(property, customMock);
}
return mockedProperties;
}
function mockByMockDefinition(definition) {
const mock = {};
for (const key of Object.keys(definition)) {
mock[key] = mockProperties(definition[key]);
}
return mock;
}
for (let moduleName of Object.keys(expoModules)) {
const moduleProperties = expoModules[moduleName];
const mockedProperties = mockProperties(moduleProperties);
Object.defineProperty(mockNativeModules, moduleName, {
configurable: true,
enumerable: true,
get: () => mockedProperties,
});
}
mockNativeModules.NativeUnimoduleProxy.viewManagersNames.forEach(viewManagerName => {
Object.defineProperty(mockNativeModules.UIManager, `ViewManagerAdapter_${viewManagerName}`, {
get: () => ({
NativeProps: {},
directEventTypes: [],
}),
});
});
Object.defineProperty(mockNativeModules.UIManager, 'takeSnapshot', {
configurable: true,
enumerable: true,
writable: true,
value: jest.fn(),
});
const modulesConstants = mockNativeModules.NativeUnimoduleProxy.modulesConstants;
mockNativeModules.NativeUnimoduleProxy.modulesConstants = {
...modulesConstants,
ExponentConstants: {
...modulesConstants.ExponentConstants,
...createMockConstants(),
},
};
try {
jest.mock('expo-file-system', () => ({
downloadAsync: jest.fn(() => Promise.resolve({ md5: 'md5', uri: 'uri' })),
getInfoAsync: jest.fn(() => Promise.resolve({ exists: true, md5: 'md5', uri: 'uri' })),
readAsStringAsync: jest.fn(() => Promise.resolve()),
writeAsStringAsync: jest.fn(() => Promise.resolve()),
deleteAsync: jest.fn(() => Promise.resolve()),
moveAsync: jest.fn(() => Promise.resolve()),
copyAsync: jest.fn(() => Promise.resolve()),
makeDirectoryAsync: jest.fn(() => Promise.resolve()),
readDirectoryAsync: jest.fn(() => Promise.resolve()),
createDownloadResumable: jest.fn(() => Promise.resolve()),
}));
} catch (error) {
// Allow this module to be optional for bare-workflow
if (error.code !== 'MODULE_NOT_FOUND') {
throw error;
}
}
jest.mock('react-native/Libraries/Image/AssetRegistry', () => ({
registerAsset: jest.fn(() => 1),
getAssetByID: jest.fn(() => ({
fileSystemLocation: '/full/path/to/directory',
httpServerLocation: '/assets/full/path/to/directory',
scales: [1],
fileHashes: ['md5'],
name: 'name',
exists: true,
type: 'type',
hash: 'md5',
uri: 'uri',
width: 1,
height: 1,
})),
}));
jest.doMock('react-native/Libraries/BatchedBridge/NativeModules', () => mockNativeModules);
try {
jest.mock('@unimodules/react-native-adapter', () => {
const ReactNativeAdapter = require.requireActual('@unimodules/react-native-adapter');
const { NativeModulesProxy } = ReactNativeAdapter;
// After the NativeModules mock is set up, we can mock NativeModuleProxy's functions that call
// into the native proxy module. We're not really interested in checking whether the underlying
// method is called, just that the proxy method is called, since we have unit tests for the
// adapter and believe it works correctly.
//
// NOTE: The adapter validates the number of arguments, which we don't do in the mocked functions.
// This means the mock functions will not throw validation errors the way they would in an app.
for (const moduleName of Object.keys(NativeModulesProxy)) {
const nativeModule = NativeModulesProxy[moduleName];
for (const propertyName of Object.keys(nativeModule)) {
if (typeof nativeModule[propertyName] === 'function') {
nativeModule[propertyName] = jest.fn(async () => {});
}
}
}
return ReactNativeAdapter;
});
} catch (error) {
// Allow this module to be optional for bare-workflow
if (error.code !== 'MODULE_NOT_FOUND') {
throw error;
}
}
// The UIManager module is not idempotent and causes issues if we load it again after resetting
// the modules with Jest.
let UIManager;
jest.doMock('react-native/Libraries/ReactNative/UIManager', () => {
if (!UIManager) {
UIManager = require.requireActual('react-native/Libraries/ReactNative/UIManager');
}
return UIManager;
});