From 9d1527c5ffc94b59c73b6a8bda873a5dd2668feb Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 31 May 2019 03:12:46 -0700 Subject: [PATCH] Explicitly separate mocked native modules from mocked JS modules (#24809) Summary: This commit more clearly defines the mocks RN sets up and uses paths instead of Haste names to define the mocks. The Jest setup script defined mocks for native modules (Obj-C, Java) and mocks for JS modules in the same data structure. This meant that some non-native modules (that is, JS modules) were in the `mockNativeModules` map -- this commit splits them out and mocks them in typical `jest.mock` fashion. Additionally, the setup script used to mock the modules using the Haste names. As one of the steps toward migrating to standard path-based imports, the setup script now mocks JS modules using paths (native modules don't need a Haste name nor path since they are just entries in `NativeModules`). This gets us closer to being able to remove `hasteImpl`. (Tracking in https://github.com/facebook/react-native/issues/24772.) Also, this commit removes mocks that are not referenced anywhere in the RN and React repositories (grepped for the names and found no entries outside of the Jest setup scripts). [General] [Changed] - Explicitly separate mocked native modules from mocked JS modules Pull Request resolved: https://github.com/facebook/react-native/pull/24809 Differential Revision: D15316882 Pulled By: cpojer fbshipit-source-id: 039e4e320121bea9580196fe0a091b8b1e8b41bf (cherry picked from commit 1ed352517ad387b4b7e48ce2b0ba3b9f7a8508e8) --- .../__tests__/resolveAssetSource-test.js | 27 +- ...erifyComponentAttributeEquivalence-test.js | 1 + jest/setup.js | 478 ++++++++---------- 3 files changed, 233 insertions(+), 273 deletions(-) diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index c5afbdf8705587..d2e3a93f8f75c3 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -10,19 +10,19 @@ 'use strict'; -const AssetRegistry = require('../AssetRegistry'); -const Platform = require('../../Utilities/Platform'); -const NativeModules = require('../../BatchedBridge/NativeModules'); -const resolveAssetSource = require('../resolveAssetSource'); - -function expectResolvesAsset(input, expectedSource) { - const assetId = AssetRegistry.registerAsset(input); - expect(resolveAssetSource(assetId)).toEqual(expectedSource); -} - describe('resolveAssetSource', () => { + let AssetRegistry; + let resolveAssetSource; + let NativeModules; + let Platform; + beforeEach(() => { jest.resetModules(); + + AssetRegistry = require('../AssetRegistry'); + resolveAssetSource = require('../resolveAssetSource'); + NativeModules = require('../../BatchedBridge/NativeModules'); + Platform = require('../../Utilities/Platform'); }); it('returns same source for simple static and network images', () => { @@ -295,9 +295,16 @@ describe('resolveAssetSource', () => { ); }); }); + + function expectResolvesAsset(input, expectedSource) { + const assetId = AssetRegistry.registerAsset(input); + expect(resolveAssetSource(assetId)).toEqual(expectedSource); + } }); describe('resolveAssetSource.pickScale', () => { + const resolveAssetSource = require('../resolveAssetSource'); + it('picks matching scale', () => { expect(resolveAssetSource.pickScale([1], 2)).toBe(1); expect(resolveAssetSource.pickScale([1, 2, 3], 2)).toBe(2); diff --git a/Libraries/Utilities/__tests__/verifyComponentAttributeEquivalence-test.js b/Libraries/Utilities/__tests__/verifyComponentAttributeEquivalence-test.js index 57e0d00e8a1fa0..4a754218a17587 100644 --- a/Libraries/Utilities/__tests__/verifyComponentAttributeEquivalence-test.js +++ b/Libraries/Utilities/__tests__/verifyComponentAttributeEquivalence-test.js @@ -13,6 +13,7 @@ const getNativeComponentAttributes = require('../../ReactNative/getNativeComponentAttributes'); const verifyComponentAttributeEquivalence = require('../verifyComponentAttributeEquivalence'); +jest.dontMock('../verifyComponentAttributeEquivalence') jest.mock('../../ReactNative/getNativeComponentAttributes', () => () => ({ NativeProps: { value: 'BOOL', diff --git a/jest/setup.js b/jest/setup.js index fd1f12ec48da50..da98e25e7c77f3 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -29,14 +29,61 @@ global.cancelAnimationFrame = function(id) { jest.mock('../Libraries/Core/Devtools/setupDevtools'); -// there's a __mock__ for it. -jest.setMock( - '../Libraries/vendor/core/ErrorUtils', - require('../Libraries/vendor/core/ErrorUtils'), -); +// // there's a __mock__ for it. +// jest.setMock( +// '../Libraries/vendor/core/ErrorUtils', +// require('../Libraries/Core/__mocks__/ErrorUtils'), +// ); jest .mock('../Libraries/Core/InitializeCore', () => {}) + .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, + }, + }, + }; + } + }), + 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'), ) @@ -52,6 +99,19 @@ jest .mock('../Libraries/Components/View/View', () => mockComponent('../Libraries/Components/View/View', MockNativeMethods), ) + .mock('../Libraries/Components/AccessibilityInfo/AccessibilityInfo', () => ({ + addEventListener: jest.fn(), + announceForAccessibility: jest.fn(), + fetch: jest.fn(), + isBoldTextEnabled: jest.fn(), + isGrayscaleEnabled: jest.fn(), + isInvertColorsEnabled: jest.fn(), + isReduceMotionEnabled: jest.fn(), + isReduceTransparencyEnabled: jest.fn(), + isScreenReaderEnabled: jest.fn(), + removeEventListener: jest.fn(), + setAccessibilityFocus: jest.fn(), + })) .mock('../Libraries/Components/RefreshControl/RefreshControl', () => jest.requireActual( '../Libraries/Components/RefreshControl/__mocks__/RefreshControlMock', @@ -82,6 +142,19 @@ jest }; return AnimatedImplementation; }) + .mock('../Libraries/AppState/AppState', () => ({ + addEventListener: jest.fn(), + removeEventListener: 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()), + removeEventListener: jest.fn(), + sendIntent: jest.fn(), + })) .mock('../Libraries/Renderer/shims/ReactNative', () => { const ReactNative = jest.requireActual( '../Libraries/Renderer/shims/ReactNative', @@ -97,273 +170,152 @@ jest }) .mock('../Libraries/Components/Touchable/ensureComponentIsNative', () => () => true, - ); - -const mockNativeModules = { - AccessibilityInfo: { - addEventListener: jest.fn(), - announceForAccessibility: jest.fn(), - fetch: jest.fn(), - isBoldTextEnabled: jest.fn(), - isGrayscaleEnabled: jest.fn(), - isInvertColorsEnabled: jest.fn(), - isReduceMotionEnabled: jest.fn(), - isReduceTransparencyEnabled: jest.fn(), - isScreenReaderEnabled: jest.fn(), - removeEventListener: jest.fn(), - setAccessibilityFocus: jest.fn(), - }, - AlertManager: { - alertWithArgs: jest.fn(), - }, - AppState: { - addEventListener: jest.fn(), - removeEventListener: 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, []))), - }, - BuildInfo: { - appVersion: '0', - buildVersion: '0', - getConstants() { - return { - appVersion: '0', - buildVersion: '0', - }; + ) + // Mock modules defined by the native layer (ex: Objective-C, Java) + .mock('../Libraries/BatchedBridge/NativeModules', () => ({ + AlertManager: { + alertWithArgs: jest.fn(), }, - }, - Clipboard: { - setString: jest.fn(), - }, - DataManager: { - queryData: jest.fn(), - }, - DeviceInfo: { - Dimensions: { - window: { - fontScale: 2, - height: 1334, - scale: 2, - width: 750, - }, - screen: { - fontScale: 2, - height: 1334, - scale: 2, - width: 750, - }, + 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, [])), + ), }, - }, - FacebookSDK: { - login: jest.fn(), - logout: jest.fn(), - queryGraphPath: jest.fn((path, method, params, callback) => callback()), - }, - GraphPhotoUpload: { - upload: jest.fn(), - }, - I18n: { - translationsDictionary: JSON.stringify({ - 'Good bye, {name}!|Bye message': '\u{00A1}Adi\u{00F3}s {name}!', - }), - }, - ImageLoader: { - getSize: jest.fn(url => Promise.resolve({width: 320, height: 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(), - }, - Linking: { - openURL: jest.fn(), - canOpenURL: jest.fn(() => Promise.resolve(true)), - openSettings: jest.fn(), - addEventListener: jest.fn(), - getInitialURL: jest.fn(() => Promise.resolve()), - removeEventListener: jest.fn(), - sendIntent: jest.fn(), - }, - LocationObserver: { - addListener: jest.fn(), - getCurrentPosition: jest.fn(), - removeListeners: jest.fn(), - requestAuthorization: jest.fn(), - setConfiguration: jest.fn(), - startObserving: jest.fn(), - stopObserving: jest.fn(), - }, - ModalFullscreenViewManager: {}, - NetInfo: { - fetch: jest.fn(() => Promise.resolve()), - getConnectionInfo: jest.fn(() => Promise.resolve()), - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - isConnected: { - fetch: jest.fn(() => Promise.resolve()), - addEventListener: jest.fn(), - removeEventListener: jest.fn(), + Clipboard: { + getString: jest.fn(() => ''), + setString: jest.fn(), }, - isConnectionExpensive: jest.fn(() => Promise.resolve()), - }, - Networking: { - sendRequest: jest.fn(), - abortRequest: jest.fn(), - addListener: jest.fn(), - removeListeners: jest.fn(), - }, - 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: { - scriptURL: null, - }, - StatusBarManager: { - HEIGHT: 42, - setColor: jest.fn(), - setStyle: jest.fn(), - setHidden: jest.fn(), - setNetworkActivityIndicatorVisible: jest.fn(), - setBackgroundColor: jest.fn(), - setTranslucent: jest.fn(), - }, - Timing: { - createTimer: jest.fn(), - deleteTimer: jest.fn(), - }, - UIManager: { - AndroidViewPager: { - Commands: { - setPage: jest.fn(), - setPageWithoutAnimation: jest.fn(), + DeviceInfo: { + Dimensions: { + window: { + fontScale: 2, + height: 1334, + scale: 2, + width: 750, + }, + screen: { + fontScale: 2, + height: 1334, + scale: 2, + width: 750, + }, }, }, - blur: jest.fn(), - createView: jest.fn(), - dispatchViewManagerCommand: jest.fn(), - focus: jest.fn(), - getViewManagerConfig: jest.fn(name => { - if (name === 'AndroidDrawerLayout') { - return { - Constants: { - DrawerPosition: { - Left: 10, - }, - }, - }; - } - }), - setChildren: jest.fn(), - manageChildren: jest.fn(), - updateView: jest.fn(), - removeSubviewsFromContainerWithID: jest.fn(), - replaceExistingNonRootView: jest.fn(), - customBubblingEventTypes: {}, - customDirectEventTypes: {}, - AndroidDrawerLayout: { - Constants: { - DrawerPosition: { - Left: 10, - }, + ImageLoader: { + getSize: jest.fn(url => Promise.resolve({width: 320, height: 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 {}; }, }, - AndroidTextInput: { - Commands: {}, + 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(), }, - ModalFullscreenView: { - Constants: {}, + SourceCode: { + scriptURL: null, }, - ScrollView: { - Constants: {}, + StatusBarManager: { + HEIGHT: 42, + setColor: jest.fn(), + setStyle: jest.fn(), + setHidden: jest.fn(), + setNetworkActivityIndicatorVisible: jest.fn(), + setBackgroundColor: jest.fn(), + setTranslucent: jest.fn(), }, - View: { - Constants: {}, + Timing: { + createTimer: jest.fn(), + deleteTimer: jest.fn(), }, - }, - BlobModule: { - 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(), - }, -}; - -Object.keys(mockNativeModules).forEach(module => { - try { - jest.doMock(module, () => mockNativeModules[module]); // needed by FacebookSDK-test - } catch (e) { - jest.doMock(module, () => mockNativeModules[module], {virtual: true}); - } -}); - -jest.doMock( - '../Libraries/BatchedBridge/NativeModules', - () => mockNativeModules, -); - -jest.doMock('../Libraries/ReactNative/requireNativeComponent', () => { - const React = require('react'); + 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(), + }, + })) + .mock('../Libraries/ReactNative/requireNativeComponent', () => { + const React = require('react'); - return viewName => - class extends React.Component { - render() { - return React.createElement(viewName, this.props, this.props.children); - } - }; -}); + return viewName => + class extends React.Component { + render() { + return React.createElement(viewName, this.props, this.props.children); + } + }; + }) + .mock( + '../Libraries/Utilities/verifyComponentAttributeEquivalence', + () => function() {}, + );