Skip to content

Commit 79b595e

Browse files
committed
Move JS-native version check to its own module + unit tests + prefix Obj-C macro w/RCT
- The version check that ensures the JS and native versions match is now in its own module for two reasons: it is easier to test and it allows react-native-windows to override just this module to implement its own version check (ex: more advanced checks for RNW-specific code). - Added unit tests for the version checking to specify its behavior more clearly, including parity between dev and prod to avoid prod-only behavior and mitigate SEVs. - Prefixed the Obj-C `#define` with `RCT_` to conform with other RN globals. Test Plan: Ran `yarn jest` to run all tests. Compiled iOS app and launched it, which runs the version check.
1 parent d980632 commit 79b595e

File tree

6 files changed

+164
-22
lines changed

6 files changed

+164
-22
lines changed

Libraries/Core/InitializeCore.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,9 @@ if (!global.__fbDisableExceptionsManager) {
116116
ErrorUtils.setGlobalHandler(handleError);
117117
}
118118

119-
const {PlatformConstants} = require('NativeModules');
120-
if (PlatformConstants) {
121-
const formatVersion = version =>
122-
`${version.major}.${version.minor}.${version.patch}` +
123-
(version.prerelease !== null ? `-${version.prerelease}` : '');
124-
125-
const ReactNativeVersion = require('ReactNativeVersion');
126-
const nativeVersion = PlatformConstants.reactNativeVersion;
127-
if (ReactNativeVersion.version.major !== nativeVersion.major ||
128-
ReactNativeVersion.version.minor !== nativeVersion.minor) {
129-
throw new Error(
130-
`React Native version mismatch.\n\nJavaScript version: ${formatVersion(ReactNativeVersion.version)}\n` +
131-
`Native version: ${formatVersion(nativeVersion)}\n\n` +
132-
'Make sure that you have rebuilt the native code. If the problem persists ' +
133-
'try clearing the watchman and packager caches with `watchman watch-del-all ' +
134-
'&& react-native start --reset-cache`.'
135-
);
136-
}
137-
}
119+
// Check for compatibility between the JS and native code
120+
const ReactNativeVersionCheck = require('ReactNativeVersionCheck');
121+
ReactNativeVersionCheck.checkVersions();
138122

139123
// Set up collections
140124
const _shouldPolyfillCollection = require('_shouldPolyfillES6Collection');
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright (c) 2017-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule ReactNativeVersionCheck
10+
* @flow
11+
*/
12+
'use strict';
13+
14+
const {PlatformConstants} = require('NativeModules');
15+
const ReactNativeVersion = require('ReactNativeVersion');
16+
17+
/**
18+
* Checks that the version of this React Native JS is compatible with the native
19+
* code, throwing an error if it isn't.
20+
*
21+
* The existence of this module is part of the public interface of React Native
22+
* even though it is used only internally within React Native. React Native
23+
* implementations for other platforms (ex: Windows) may override this module
24+
* and rely on its existence as a separate module.
25+
*/
26+
exports.checkVersions = function checkVersions(): void {
27+
if (!PlatformConstants) {
28+
return;
29+
}
30+
31+
const nativeVersion = PlatformConstants.reactNativeVersion;
32+
if (
33+
ReactNativeVersion.version.major !== nativeVersion.major ||
34+
ReactNativeVersion.version.minor !== nativeVersion.minor
35+
) {
36+
throw new Error(
37+
`React Native version mismatch.\n\nJavaScript version: ${_formatVersion(
38+
ReactNativeVersion.version
39+
)}\n` +
40+
`Native version: ${_formatVersion(nativeVersion)}\n\n` +
41+
'Make sure that you have rebuilt the native code. If the problem ' +
42+
'persists try clearing the Watchman and packager caches with ' +
43+
'`watchman watch-del-all && react-native start --reset-cache`.'
44+
);
45+
}
46+
};
47+
48+
function _formatVersion(version): string {
49+
return (
50+
`${version.major}.${version.minor}.${version.patch}` +
51+
(version.prerelease !== null ? `-${version.prerelease}` : '')
52+
);
53+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Copyright (c) 2017-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
'use strict';
10+
11+
describe('checkVersion', () => {
12+
describe('in development', () => {
13+
_setDevelopmentModeForTests(true);
14+
_defineCheckVersionTests();
15+
});
16+
17+
describe('in production', () => {
18+
_setDevelopmentModeForTests(false);
19+
_defineCheckVersionTests();
20+
});
21+
});
22+
23+
function _setDevelopmentModeForTests(dev) {
24+
let originalDev;
25+
26+
beforeAll(() => {
27+
originalDev = global.__DEV__;
28+
global.__DEV__ = dev;
29+
});
30+
31+
afterAll(() => {
32+
global.__DEV__ = originalDev;
33+
});
34+
}
35+
36+
function _defineCheckVersionTests() {
37+
afterEach(() => {
38+
jest.resetModules();
39+
});
40+
41+
it('passes when all the versions are zero', () => {
42+
jest.dontMock('ReactNativeVersion');
43+
_mockNativeVersion(0, 0, 0);
44+
45+
const ReactNativeVersion = require('ReactNativeVersion');
46+
const ReactNativeVersionCheck = require('ReactNativeVersionCheck');
47+
expect(ReactNativeVersion).toMatchObject({
48+
version: {major: 0, minor: 0, patch: 0, prerelease: null},
49+
});
50+
expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow();
51+
});
52+
53+
it('passes when the minor matches when the major is zero', () => {
54+
_mockJsVersion(0, 1, 0);
55+
_mockNativeVersion(0, 1, 0);
56+
57+
const ReactNativeVersionCheck = require('ReactNativeVersionCheck');
58+
expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow();
59+
});
60+
61+
it("throws when the minor doesn't match when the major is zero", () => {
62+
_mockJsVersion(0, 1, 0);
63+
_mockNativeVersion(0, 2, 0);
64+
65+
const ReactNativeVersionCheck = require('ReactNativeVersionCheck');
66+
expect(() => ReactNativeVersionCheck.checkVersions()).toThrowError(
67+
/React Native version mismatch/
68+
);
69+
});
70+
71+
it("doesn't throw if the patch doesn't match", () => {
72+
_mockJsVersion(0, 1, 0);
73+
_mockNativeVersion(0, 1, 2);
74+
75+
const ReactNativeVersionCheck = require('ReactNativeVersionCheck');
76+
expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow();
77+
});
78+
79+
it("doesn't throw if the prerelease doesn't match", () => {
80+
_mockJsVersion(0, 1, 0, 'beta.0');
81+
_mockNativeVersion(0, 1, 0, 'alpha.1');
82+
83+
const ReactNativeVersionCheck = require('ReactNativeVersionCheck');
84+
expect(() => ReactNativeVersionCheck.checkVersions()).not.toThrow();
85+
});
86+
}
87+
88+
function _mockJsVersion(major = 0, minor = 0, patch = 0, prerelease = null) {
89+
jest.doMock('ReactNativeVersion', () => ({
90+
version: {major, minor, patch, prerelease},
91+
}));
92+
}
93+
94+
function _mockNativeVersion(
95+
major = 0,
96+
minor = 0,
97+
patch = 0,
98+
prerelease = null
99+
) {
100+
jest.doMock('NativeModules', () => ({
101+
PlatformConstants: {
102+
reactNativeVersion: {major, minor, patch, prerelease},
103+
},
104+
}));
105+
}

React/Base/RCTPlatform.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ + (BOOL)requiresMainQueueSetup
4747
@"systemName": [device systemName],
4848
@"interfaceIdiom": interfaceIdiom([device userInterfaceIdiom]),
4949
@"isTesting": @(RCTRunningInTestEnvironment()),
50-
@"reactNativeVersion": REACT_NATIVE_VERSION,
50+
@"reactNativeVersion": RCT_REACT_NATIVE_VERSION,
5151
};
5252
}
5353

React/Base/RCTVersion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* of patent rights can be found in the PATENTS file in the same directory.
1010
*/
1111

12-
#define REACT_NATIVE_VERSION @{ \
12+
#define RCT_REACT_NATIVE_VERSION @{ \
1313
@"major": @(0), \
1414
@"minor": @(0), \
1515
@"patch": @(0), \

scripts/versiontemplates/RCTVersion.h.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* of patent rights can be found in the PATENTS file in the same directory.
1010
*/
1111

12-
#define REACT_NATIVE_VERSION @{ \
12+
#define RCT_REACT_NATIVE_VERSION @{ \
1313
@"major": ${major}, \
1414
@"minor": ${minor}, \
1515
@"patch": ${patch}, \

0 commit comments

Comments
 (0)