@samitouri / QOS-React-2 / commits / 7d9e17a982

[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(