Skip to content

Commit 7d9e17a

Browse files
authored
[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
1 parent 8197c73 commit 7d9e17a

File tree

5 files changed

+271
-0
lines changed

5 files changed

+271
-0
lines changed

packages/react-devtools-shared/src/__tests__/setupEnv.js

+24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
'use strict';
22

3+
const semver = require('semver');
4+
const ReactVersion = require('../../../shared/ReactVersion');
5+
36
const {
47
DARK_MODE_DIMMED_WARNING_COLOR,
58
DARK_MODE_DIMMED_ERROR_COLOR,
@@ -24,3 +27,24 @@ global.process.env.DARK_MODE_DIMMED_LOG_COLOR = DARK_MODE_DIMMED_LOG_COLOR;
2427
global.process.env.LIGHT_MODE_DIMMED_WARNING_COLOR = LIGHT_MODE_DIMMED_WARNING_COLOR;
2528
global.process.env.LIGHT_MODE_DIMMED_ERROR_COLOR = LIGHT_MODE_DIMMED_ERROR_COLOR;
2629
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+
};
Original file line numberDiff line numberDiff line change
@@ -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+
});
Original file line numberDiff line numberDiff line change
@@ -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

+9
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const coffee = require('coffee-script');
77

88
const tsPreprocessor = require('./typescript/preprocessor');
99
const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction');
10+
const {devtoolsPlugins} = require('./devtools/preprocessor.js');
1011

1112
const pathToBabel = path.join(
1213
require.resolve('@babel/core'),
@@ -82,6 +83,9 @@ module.exports = {
8283
const plugins = (isTestFile ? testOnlyPlugins : sourceOnlyPlugins).concat(
8384
babelOptions.plugins
8485
);
86+
if (isTestFile && isInDevToolsPackages) {
87+
plugins.push(...devtoolsPlugins);
88+
}
8589
return babel.transform(
8690
src,
8791
Object.assign(

0 commit comments

Comments
 (0)