1/** 2 * Adds Expo-related mocks to the Jest environment. Jest runs this setup module after it runs the 3 * React Native setup module. 4 */ 5'use strict'; 6 7const mockNativeModules = require('react-native/Libraries/BatchedBridge/NativeModules'); 8 9const publicExpoModules = require('./expoModules'); 10const internalExpoModules = require('./internalExpoModules'); 11 12// window isn't defined as of react-native 0.45+ it seems 13if (typeof window !== 'object') { 14 globalThis.window = global; 15 globalThis.window.navigator = {}; 16} 17 18const mockImageLoader = { 19 configurable: true, 20 enumerable: true, 21 get: () => ({ 22 prefetchImage: jest.fn(), 23 getSize: jest.fn((uri, success) => process.nextTick(() => success(320, 240))), 24 }), 25}; 26Object.defineProperty(mockNativeModules, 'ImageLoader', mockImageLoader); 27Object.defineProperty(mockNativeModules, 'ImageViewManager', mockImageLoader); 28 29Object.defineProperty(mockNativeModules, 'LinkingManager', { 30 configurable: true, 31 enumerable: true, 32 get: () => mockNativeModules.Linking, 33}); 34 35const expoModules = { 36 ...publicExpoModules, 37 ...internalExpoModules, 38}; 39 40function mock(property, customMock) { 41 const propertyType = property.type; 42 let mockValue; 43 if (customMock !== undefined) { 44 mockValue = customMock; 45 } else if (propertyType === 'function') { 46 if (property.functionType === 'promise') { 47 mockValue = jest.fn(() => Promise.resolve()); 48 } else { 49 mockValue = jest.fn(); 50 } 51 } else if (propertyType === 'number') { 52 mockValue = 1; 53 } else if (propertyType === 'string') { 54 mockValue = 'mock'; 55 } else if (propertyType === 'array') { 56 mockValue = []; 57 } else if (propertyType === 'mock') { 58 mockValue = mockByMockDefinition(property.mockDefinition); 59 } else { 60 mockValue = {}; 61 } 62 return mockValue; 63} 64 65function mockProperties(moduleProperties, customMocks) { 66 const mockedProperties = {}; 67 for (const propertyName of Object.keys(moduleProperties)) { 68 const property = moduleProperties[propertyName]; 69 const customMock = 70 customMocks && customMocks.hasOwnProperty(propertyName) 71 ? customMocks[propertyName] 72 : property.mock; 73 mockedProperties[propertyName] = mock(property, customMock); 74 } 75 return mockedProperties; 76} 77 78function mockByMockDefinition(definition) { 79 const mock = {}; 80 for (const key of Object.keys(definition)) { 81 mock[key] = mockProperties(definition[key]); 82 } 83 return mock; 84} 85 86for (const moduleName of Object.keys(expoModules)) { 87 const moduleProperties = expoModules[moduleName]; 88 const mockedProperties = mockProperties(moduleProperties); 89 90 Object.defineProperty(mockNativeModules, moduleName, { 91 configurable: true, 92 enumerable: true, 93 get: () => mockedProperties, 94 }); 95} 96 97Object.keys(mockNativeModules.NativeUnimoduleProxy.viewManagersMetadata).forEach( 98 (viewManagerName) => { 99 Object.defineProperty(mockNativeModules.UIManager, `ViewManagerAdapter_${viewManagerName}`, { 100 get: () => ({ 101 NativeProps: {}, 102 directEventTypes: [], 103 }), 104 }); 105 } 106); 107 108try { 109 jest.mock('expo-file-system', () => ({ 110 downloadAsync: jest.fn(() => Promise.resolve({ md5: 'md5', uri: 'uri' })), 111 getInfoAsync: jest.fn(() => Promise.resolve({ exists: true, md5: 'md5', uri: 'uri' })), 112 readAsStringAsync: jest.fn(() => Promise.resolve()), 113 writeAsStringAsync: jest.fn(() => Promise.resolve()), 114 deleteAsync: jest.fn(() => Promise.resolve()), 115 moveAsync: jest.fn(() => Promise.resolve()), 116 copyAsync: jest.fn(() => Promise.resolve()), 117 makeDirectoryAsync: jest.fn(() => Promise.resolve()), 118 readDirectoryAsync: jest.fn(() => Promise.resolve()), 119 createDownloadResumable: jest.fn(() => Promise.resolve()), 120 })); 121} catch (error) { 122 // Allow this module to be optional for bare-workflow 123 if (error.code !== 'MODULE_NOT_FOUND') { 124 throw error; 125 } 126} 127 128jest.mock('react-native/Libraries/Image/AssetRegistry', () => ({ 129 registerAsset: jest.fn(() => 1), 130 getAssetByID: jest.fn(() => ({ 131 fileSystemLocation: '/full/path/to/directory', 132 httpServerLocation: '/assets/full/path/to/directory', 133 scales: [1], 134 fileHashes: ['md5'], 135 name: 'name', 136 exists: true, 137 type: 'type', 138 hash: 'md5', 139 uri: 'uri', 140 width: 1, 141 height: 1, 142 })), 143})); 144 145jest.doMock('react-native/Libraries/BatchedBridge/NativeModules', () => mockNativeModules); 146 147jest.doMock('react-native/Libraries/LogBox/LogBox', () => ({ 148 ignoreLogs: (patterns) => { 149 // Do nothing. 150 }, 151 ignoreAllLogs: (value) => { 152 // Do nothing. 153 }, 154 install: () => { 155 // Do nothing. 156 }, 157 uninstall: () => { 158 // Do nothing. 159 }, 160})); 161 162try { 163 jest.mock('expo-modules-core', () => { 164 const ExpoModulesCore = jest.requireActual('expo-modules-core'); 165 const uuid = jest.requireActual('expo-modules-core/build/uuid/uuid.web'); 166 167 const { NativeModulesProxy } = ExpoModulesCore; 168 169 // After the NativeModules mock is set up, we can mock NativeModuleProxy's functions that call 170 // into the native proxy module. We're not really interested in checking whether the underlying 171 // method is called, just that the proxy method is called, since we have unit tests for the 172 // adapter and believe it works correctly. 173 // 174 // NOTE: The adapter validates the number of arguments, which we don't do in the mocked functions. 175 // This means the mock functions will not throw validation errors the way they would in an app. 176 177 // Mock the `uuid` object with the implementation for web. 178 ExpoModulesCore.uuid.v4 = uuid.default.v4; 179 ExpoModulesCore.uuid.v5 = uuid.default.v5; 180 181 for (const moduleName of Object.keys(NativeModulesProxy)) { 182 const nativeModule = NativeModulesProxy[moduleName]; 183 for (const propertyName of Object.keys(nativeModule)) { 184 if (typeof nativeModule[propertyName] === 'function') { 185 nativeModule[propertyName] = jest.fn(async () => {}); 186 } 187 } 188 } 189 190 return ExpoModulesCore; 191 }); 192} catch (error) { 193 // Allow this module to be optional for bare-workflow 194 if (error.code !== 'MODULE_NOT_FOUND') { 195 throw error; 196 } 197} 198