Skip to content

Commit 4f709f7

Browse files
committed
Move JS-native version check to its own module + unit tests + prefix Obj-C macro w/RCT
Summary: - 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. Closes #16403 Differential Revision: D6068491 Pulled By: hramos fbshipit-source-id: 2b255b93982fb9d1b655fc62cb17b126bd5a939a
1 parent 63238f6 commit 4f709f7

6 files changed

Lines changed: 177 additions & 22 deletions

File tree

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

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": @(50), \
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)