[DevTools] Add Pragma to Only Run Tests if Version Requirement Satisfied (#24533)
This PR: Adds a transform-react-version-pragma that transforms // @reactVersion SEMVER_VERSION into _test_react_version(...) and _test_react_version_focus(...) that lets us only run a test if it satisfies the right react version. Adds _test_react_version and _test_react_version_focus to the devtools setupEnv file Add a devtools preprocessor file for devtools specific plugins
Luna Ruan committed
May 11, 2022 at 09:01 UTC
7d9e17a9826595dacbf9e19e8f85b07837adf276
5 files changed
+271
packages/react-devtools-shared/src/__tests__/setupEnv.js
+24
@@ -1,5 +1,8 @@
1
'use strict';
2
3
+const semver = require('semver');
4
+const ReactVersion = require('../../../shared/ReactVersion');
5
+
6
const {
7
DARK_MODE_DIMMED_WARNING_COLOR,
8
DARK_MODE_DIMMED_ERROR_COLOR,
@@ -24,3 +27,24 @@ global.process.env.DARK_MODE_DIMMED_LOG_COLOR = DARK_MODE_DIMMED_LOG_COLOR;
27
global.process.env.LIGHT_MODE_DIMMED_WARNING_COLOR = LIGHT_MODE_DIMMED_WARNING_COLOR;
28
global.process.env.LIGHT_MODE_DIMMED_ERROR_COLOR = LIGHT_MODE_DIMMED_ERROR_COLOR;
29
global.process.env.LIGHT_MODE_DIMMED_LOG_COLOR = LIGHT_MODE_DIMMED_LOG_COLOR;
30
+
31
+global._test_react_version = (range, testName, callback) => {
32
+ const trimmedRange = range.replaceAll(' ', '');
33
+ const reactVersion = process.env.REACT_VERSION || ReactVersion;
34
+ const shouldPass = semver.satisfies(reactVersion, trimmedRange);
35
+
36
+ if (shouldPass) {
37
+ test(testName, callback);
38
+ }
39
+};
40
+
41
+global._test_react_version_focus = (range, testName, callback) => {
42
+ const trimmedRange = range.replaceAll(' ', '');
43
+ const reactVersion = process.env.REACT_VERSION || ReactVersion;
44
+ const shouldPass = semver.satisfies(reactVersion, trimmedRange);
45
+
46
+ if (shouldPass) {
47
+ // eslint-disable-next-line jest/no-focused-tests
48
+ test.only(testName, callback);
49
+ }
50
+};
packages/react-devtools-shared/src/__tests__/transform-react-version-pragma-test.js
new
+127
@@ -0,0 +1,127 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+'use strict';
8
+
9
+const semver = require('semver');
10
+
11
+let shouldPass;
12
+let isFocused;
13
+describe('transform-react-version-pragma', () => {
14
+ // eslint-disable-next-line no-unused-vars
15
+ const _test_react_version = (range, testName, cb) => {
16
+ test(testName, (...args) => {
17
+ shouldPass = semver.satisfies('18.0.0', range);
18
+ return cb(...args);
19
+ });
20
+ };
21
+
22
+ // eslint-disable-next-line no-unused-vars
23
+ const _test_react_version_focus = (range, testName, cb) => {
24
+ test(testName, (...args) => {
25
+ shouldPass = semver.satisfies('18.0.0', range);
26
+ isFocused = true;
27
+ return cb(...args);
28
+ });
29
+ };
30
+
31
+ beforeEach(() => {
32
+ shouldPass = null;
33
+ isFocused = false;
34
+ });
35
+
36
+ // @reactVersion >= 17.9
37
+ test('reactVersion flag is on >=', () => {
38
+ expect(shouldPass).toBe(true);
39
+ });
40
+
41
+ // @reactVersion >= 18.1
42
+ test('reactVersion flag is off >=', () => {
43
+ expect(shouldPass).toBe(false);
44
+ });
45
+
46
+ // @reactVersion <= 18.1
47
+ test('reactVersion flag is on <=', () => {
48
+ expect(shouldPass).toBe(true);
49
+ });
50
+
51
+ // @reactVersion <= 17.9
52
+ test('reactVersion flag is off <=', () => {
53
+ expect(shouldPass).toBe(false);
54
+ });
55
+
56
+ // @reactVersion > 17.9
57
+ test('reactVersion flag is on >', () => {
58
+ expect(shouldPass).toBe(true);
59
+ });
60
+
61
+ // @reactVersion > 18.1
62
+ test('reactVersion flag is off >', () => {
63
+ expect(shouldPass).toBe(false);
64
+ });
65
+
66
+ // @reactVersion < 18.1
67
+ test('reactVersion flag is on <', () => {
68
+ expect(shouldPass).toBe(true);
69
+ });
70
+
71
+ // @reactVersion < 17.0.0
72
+ test('reactVersion flag is off <', () => {
73
+ expect(shouldPass).toBe(false);
74
+ });
75
+
76
+ // @reactVersion = 18.0
77
+ test('reactVersion flag is on =', () => {
78
+ expect(shouldPass).toBe(true);
79
+ });
80
+
81
+ // @reactVersion = 18.1
82
+ test('reactVersion flag is off =', () => {
83
+ expect(shouldPass).toBe(false);
84
+ });
85
+
86
+ /* eslint-disable jest/no-focused-tests */
87
+
88
+ // @reactVersion >= 18.1
89
+ fit('reactVersion fit', () => {
90
+ expect(shouldPass).toBe(false);
91
+ expect(isFocused).toBe(true);
92
+ });
93
+
94
+ // @reactVersion <= 18.1
95
+ test.only('reactVersion test.only', () => {
96
+ expect(shouldPass).toBe(true);
97
+ expect(isFocused).toBe(true);
98
+ });
99
+
100
+ // @reactVersion <= 18.1
101
+ // @reactVersion <= 17.1
102
+ test('reactVersion multiple pragmas fail', () => {
103
+ expect(shouldPass).toBe(false);
104
+ expect(isFocused).toBe(false);
105
+ });
106
+
107
+ // @reactVersion <= 18.1
108
+ // @reactVersion >= 17.1
109
+ test('reactVersion multiple pragmas pass', () => {
110
+ expect(shouldPass).toBe(true);
111
+ expect(isFocused).toBe(false);
112
+ });
113
+
114
+ // @reactVersion <= 18.1
115
+ // @reactVersion <= 17.1
116
+ test.only('reactVersion focused multiple pragmas fail', () => {
117
+ expect(shouldPass).toBe(false);
118
+ expect(isFocused).toBe(true);
119
+ });
120
+
121
+ // @reactVersion <= 18.1
122
+ // @reactVersion >= 17.1
123
+ test.only('reactVersion focused multiple pragmas pass', () => {
124
+ expect(shouldPass).toBe(true);
125
+ expect(isFocused).toBe(true);
126
+ });
127
+});
scripts/babel/transform-react-version-pragma.js
new
+107
@@ -0,0 +1,107 @@
1
+'use strict';
2
+
3
+/* eslint-disable no-for-of-loops/no-for-of-loops */
4
+
5
+const GATE_VERSION_STR = '@reactVersion ';
6
+
7
+function transform(babel) {
8
+ const {types: t} = babel;
9
+
10
+ // Runs tests conditionally based on the version of react (semver range) we are running
11
+ // Input:
12
+ // @reactVersion >= 17.0
13
+ // test('some test', () => {/*...*/})
14
+ //
15
+ // Output:
16
+ // @reactVersion >= 17.0
17
+ // _test_react_version('>= 17.0', 'some test', () => {/*...*/});
18
+ //
19
+ // See info about semver ranges here:
20
+ // https://www.npmjs.com/package/semver
21
+ function buildGateVersionCondition(comments) {
22
+ let conditions = null;
23
+ for (const line of comments) {
24
+ const commentStr = line.value.trim();
25
+ if (commentStr.startsWith(GATE_VERSION_STR)) {
26
+ const condition = t.stringLiteral(
27
+ commentStr.slice(GATE_VERSION_STR.length)
28
+ );
29
+ if (conditions === null) {
30
+ conditions = [condition];
31
+ } else {
32
+ conditions.push(condition);
33
+ }
34
+ }
35
+ }
36
+
37
+ if (conditions !== null) {
38
+ let condition = conditions[0];
39
+ for (let i = 1; i < conditions.length; i++) {
40
+ const right = conditions[i];
41
+ condition = t.logicalExpression('&&', condition, right);
42
+ }
43
+ return condition;
44
+ } else {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ return {
50
+ name: 'transform-react-version-pragma',
51
+ visitor: {
52
+ ExpressionStatement(path) {
53
+ const statement = path.node;
54
+ const expression = statement.expression;
55
+ if (expression.type === 'CallExpression') {
56
+ const callee = expression.callee;
57
+ switch (callee.type) {
58
+ case 'Identifier': {
59
+ if (
60
+ callee.name === 'test' ||
61
+ callee.name === 'it' ||
62
+ callee.name === 'fit'
63
+ ) {
64
+ const comments = statement.leadingComments;
65
+ if (comments !== undefined) {
66
+ const condition = buildGateVersionCondition(comments);
67
+ if (condition !== null) {
68
+ callee.name =
69
+ callee.name === 'fit'
70
+ ? '_test_react_version_focus'
71
+ : '_test_react_version';
72
+ expression.arguments = [condition, ...expression.arguments];
73
+ }
74
+ }
75
+ }
76
+ break;
77
+ }
78
+ case 'MemberExpression': {
79
+ if (
80
+ callee.object.type === 'Identifier' &&
81
+ (callee.object.name === 'test' ||
82
+ callee.object.name === 'it') &&
83
+ callee.property.type === 'Identifier' &&
84
+ callee.property.name === 'only'
85
+ ) {
86
+ const comments = statement.leadingComments;
87
+ if (comments !== undefined) {
88
+ const condition = buildGateVersionCondition(comments);
89
+ if (condition !== null) {
90
+ statement.expression = t.callExpression(
91
+ t.identifier('_test_react_version_focus'),
92
+ [condition, ...expression.arguments]
93
+ );
94
+ }
95
+ }
96
+ }
97
+ break;
98
+ }
99
+ }
100
+ }
101
+ return;
102
+ },
103
+ },
104
+ };
105
+}
106
+
107
+module.exports = transform;
scripts/jest/devtools/preprocessor.js
new
+9
@@ -0,0 +1,9 @@
1
+'use strict';
2
+
3
+const pathToTransformReactVersionPragma = require.resolve(
4
+ '../../babel/transform-react-version-pragma'
5
+);
6
+
7
+module.exports = {
8
+ devtoolsPlugins: [pathToTransformReactVersionPragma],
9
+};
scripts/jest/preprocessor.js
+4
@@ -7,6 +7,7 @@ const coffee = require('coffee-script');
7
8
const tsPreprocessor = require('./typescript/preprocessor');
9
const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction');
10
+const {devtoolsPlugins} = require('./devtools/preprocessor.js');
11
12
const pathToBabel = path.join(
13
require.resolve('@babel/core'),
@@ -82,6 +83,9 @@ module.exports = {
83
const plugins = (isTestFile ? testOnlyPlugins : sourceOnlyPlugins).concat(
84
babelOptions.plugins
85
);
86
+ if (isTestFile && isInDevToolsPackages) {
87
+ plugins.push(...devtoolsPlugins);
88
+ }
89
return babel.transform(
90
src,
91
Object.assign(