diff --git a/Libraries/Core/InitializeCore.js b/Libraries/Core/InitializeCore.js index 4531b114efd90f..0006ee12ce9453 100644 --- a/Libraries/Core/InitializeCore.js +++ b/Libraries/Core/InitializeCore.js @@ -116,25 +116,9 @@ if (!global.__fbDisableExceptionsManager) { ErrorUtils.setGlobalHandler(handleError); } -const {PlatformConstants} = require('NativeModules'); -if (PlatformConstants) { - const formatVersion = version => - `${version.major}.${version.minor}.${version.patch}` + - (version.prerelease !== null ? `-${version.prerelease}` : ''); - - const ReactNativeVersion = require('ReactNativeVersion'); - const nativeVersion = PlatformConstants.reactNativeVersion; - if (ReactNativeVersion.version.major !== nativeVersion.major || - ReactNativeVersion.version.minor !== nativeVersion.minor) { - throw new Error( - `React Native version mismatch.\n\nJavaScript version: ${formatVersion(ReactNativeVersion.version)}\n` + - `Native version: ${formatVersion(nativeVersion)}\n\n` + - 'Make sure that you have rebuilt the native code. If the problem persists ' + - 'try clearing the watchman and packager caches with `watchman watch-del-all ' + - '&& react-native start --reset-cache`.' - ); - } -} +// Check for compatibility between the JS and native code +const ReactNativeVersionCheck = require('ReactNativeVersionCheck'); +ReactNativeVersionCheck.checkVersions(); // Set up collections const _shouldPolyfillCollection = require('_shouldPolyfillES6Collection'); diff --git a/Libraries/Core/ReactNativeVersionCheck.js b/Libraries/Core/ReactNativeVersionCheck.js new file mode 100644 index 00000000000000..33e494e5887d85 --- /dev/null +++ b/Libraries/Core/ReactNativeVersionCheck.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeVersionCheck + * @flow + * @format + */ +'use strict'; + +const {PlatformConstants} = require('NativeModules'); +const ReactNativeVersion = require('ReactNativeVersion'); + +/** + * Checks that the version of this React Native JS is compatible with the native + * code, throwing an error if it isn't. + * + * The existence of this module is part of the public interface of React Native + * even though it is used only internally within React Native. React Native + * implementations for other platforms (ex: Windows) may override this module + * and rely on its existence as a separate module. + */ +exports.checkVersions = function checkVersions(): void { + if (!PlatformConstants) { + return; + } + + const nativeVersion = PlatformConstants.reactNativeVersion; + if ( + ReactNativeVersion.version.major !== nativeVersion.major || + ReactNativeVersion.version.minor !== nativeVersion.minor + ) { + throw new Error( + `React Native version mismatch.\n\nJavaScript version: ${_formatVersion( + ReactNativeVersion.version, + )}\n` + + `Native version: ${_formatVersion(nativeVersion)}\n\n` + + 'Make sure that you have rebuilt the native code. If the problem ' + + 'persists try clearing the Watchman and packager caches with ' + + '`watchman watch-del-all && react-native start --reset-cache`.', + ); + } +}; + +function _formatVersion(version): string { + return ( + `${version.major}.${version.minor}.${version.patch}` + + (version.prerelease !== null ? `-${version.prerelease}` : '') + ); +} diff --git a/Libraries/Core/__tests__/ReactNativeVersionCheck-test.js b/Libraries/Core/__tests__/ReactNativeVersionCheck-test.js new file mode 100644 index 00000000000000..dbafabcd566bf2 --- /dev/null +++ b/Libraries/Core/__tests__/ReactNativeVersionCheck-test.js @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @format + */ +'use strict'; + +describe('checkVersion', () => { + describe('in development', () => { + _setDevelopmentModeForTests(true); + _defineCheckVersionTests(); + }); + + describe('in production', () => { + _setDevelopmentModeForTests(false); + _defineCheckVersionTests(); + }); +}); + +function _setDevelopmentModeForTests(dev) { + let originalDev; + + beforeAll(() => { + originalDev = global.__DEV__; + global.__DEV__ = dev; + }); + + afterAll(() => { + global.__DEV__ = originalDev; + }); +} + +function _defineCheckVersionTests() { + afterEach(() => { + jest.resetModules(); + }); + + it('passes when all the versions are zero', () => { + jest.dontMock('ReactNativeVersion'); + _mockNativeVersion(0, 0, 0); + + const ReactNativeVersion = require('ReactNativeVersion'); + const ReactNativeVersionCheck = require('ReactNativeVersionCheck'); + expect(ReactNativeVersion).toMatchObject({ + version: {major: 0, minor: 0, patch: 0, prerelease: null}, + }); + expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow(); + }); + + it('passes when the minor matches when the major is zero', () => { + _mockJsVersion(0, 1, 0); + _mockNativeVersion(0, 1, 0); + + const ReactNativeVersionCheck = require('ReactNativeVersionCheck'); + expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow(); + }); + + it("throws when the minor doesn't match when the major is zero", () => { + _mockJsVersion(0, 1, 0); + _mockNativeVersion(0, 2, 0); + + const ReactNativeVersionCheck = require('ReactNativeVersionCheck'); + expect(() => ReactNativeVersionCheck.checkVersions()).toThrowError( + /React Native version mismatch/, + ); + }); + + it("throws when the major doesn't match", () => { + _mockJsVersion(1, 0, 0); + _mockNativeVersion(2, 0, 0); + + const ReactNativeVersionCheck = require('ReactNativeVersionCheck'); + expect(() => ReactNativeVersionCheck.checkVersions()).toThrowError( + /React Native version mismatch/, + ); + }); + + it("doesn't throw if the patch doesn't match", () => { + _mockJsVersion(0, 1, 0); + _mockNativeVersion(0, 1, 2); + + const ReactNativeVersionCheck = require('ReactNativeVersionCheck'); + expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow(); + }); + + it("doesn't throw if the prerelease doesn't match", () => { + _mockJsVersion(0, 1, 0, 'beta.0'); + _mockNativeVersion(0, 1, 0, 'alpha.1'); + + const ReactNativeVersionCheck = require('ReactNativeVersionCheck'); + expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow(); + }); +} + +function _mockJsVersion(major = 0, minor = 0, patch = 0, prerelease = null) { + jest.doMock('ReactNativeVersion', () => ({ + version: {major, minor, patch, prerelease}, + })); +} + +function _mockNativeVersion( + major = 0, + minor = 0, + patch = 0, + prerelease = null, +) { + jest.doMock('NativeModules', () => ({ + PlatformConstants: { + reactNativeVersion: {major, minor, patch, prerelease}, + }, + })); +} diff --git a/React/Base/RCTPlatform.m b/React/Base/RCTPlatform.m index b9ece18b85bb81..82a63167c23e82 100644 --- a/React/Base/RCTPlatform.m +++ b/React/Base/RCTPlatform.m @@ -47,7 +47,7 @@ + (BOOL)requiresMainQueueSetup @"systemName": [device systemName], @"interfaceIdiom": interfaceIdiom([device userInterfaceIdiom]), @"isTesting": @(RCTRunningInTestEnvironment()), - @"reactNativeVersion": REACT_NATIVE_VERSION, + @"reactNativeVersion": RCT_REACT_NATIVE_VERSION, }; } diff --git a/React/Base/RCTVersion.h b/React/Base/RCTVersion.h index 6aa3df5a538508..c741022e5b8537 100644 --- a/React/Base/RCTVersion.h +++ b/React/Base/RCTVersion.h @@ -9,7 +9,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#define REACT_NATIVE_VERSION @{ \ +#define RCT_REACT_NATIVE_VERSION @{ \ @"major": @(0), \ @"minor": @(0), \ @"patch": @(0), \ diff --git a/scripts/versiontemplates/RCTVersion.h.template b/scripts/versiontemplates/RCTVersion.h.template index 52fd1c480506e0..f188ae8a2ba71d 100644 --- a/scripts/versiontemplates/RCTVersion.h.template +++ b/scripts/versiontemplates/RCTVersion.h.template @@ -9,7 +9,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#define REACT_NATIVE_VERSION @{ \ +#define RCT_REACT_NATIVE_VERSION @{ \ @"major": ${major}, \ @"minor": ${minor}, \ @"patch": ${patch}, \