diff --git a/.eslintrc.js b/.eslintrc.js index eaad9393c5685..a048ec6d247da 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -381,6 +381,13 @@ module.exports = { 'jest/valid-expect-in-promise': ERROR, }, }, + { + // disable no focused tests for test setup helper files even if they are inside __tests__ directory + files: ['**/setupTests.js'], + rules: { + 'jest/no-focused-tests': OFF, + }, + }, { files: [ '**/__tests__/**/*.js', diff --git a/packages/react-devtools-shared/src/__tests__/gate-test.js b/packages/react-devtools-shared/src/__tests__/gate-test.js new file mode 100644 index 0000000000000..73f81ab5f9daf --- /dev/null +++ b/packages/react-devtools-shared/src/__tests__/gate-test.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +describe('gate', () => { + //@gate false + it('should expect an error for this test', () => { + throw new Error('This test should fail'); + }); + + //@gate true + it('should not an error for this test', () => {}); +}); diff --git a/packages/react-devtools-shared/src/__tests__/setupTests.js b/packages/react-devtools-shared/src/__tests__/setupTests.js index 614c3a8ef88c2..e73b655cee1d3 100644 --- a/packages/react-devtools-shared/src/__tests__/setupTests.js +++ b/packages/react-devtools-shared/src/__tests__/setupTests.js @@ -13,6 +13,7 @@ import type { BackendBridge, FrontendBridge, } from 'react-devtools-shared/src/bridge'; +const {getTestFlags} = require('../../../../scripts/jest/TestFlags'); // Argument is serialized when passed from jest-cli script through to setupTests. const compactConsole = process.env.compactConsole === 'true'; @@ -32,6 +33,76 @@ if (compactConsole) { global.console = new CustomConsole(process.stdout, process.stderr, formatter); } +const expectTestToFail = async (callback, error) => { + if (callback.length > 0) { + throw Error( + 'Gated test helpers do not support the `done` callback. Return a ' + + 'promise instead.', + ); + } + try { + const maybePromise = callback(); + if ( + maybePromise !== undefined && + maybePromise !== null && + typeof maybePromise.then === 'function' + ) { + await maybePromise; + } + } catch (testError) { + return; + } + throw error; +}; + +const gatedErrorMessage = 'Gated test was expected to fail, but it passed.'; +global._test_gate = (gateFn, testName, callback) => { + let shouldPass; + try { + const flags = getTestFlags(); + shouldPass = gateFn(flags); + } catch (e) { + test(testName, () => { + throw e; + }); + return; + } + if (shouldPass) { + test(testName, callback); + } else { + const error = new Error(gatedErrorMessage); + Error.captureStackTrace(error, global._test_gate); + test(`[GATED, SHOULD FAIL] ${testName}`, () => + expectTestToFail(callback, error)); + } +}; +global._test_gate_focus = (gateFn, testName, callback) => { + let shouldPass; + try { + const flags = getTestFlags(); + shouldPass = gateFn(flags); + } catch (e) { + test.only(testName, () => { + throw e; + }); + return; + } + if (shouldPass) { + test.only(testName, callback); + } else { + const error = new Error(gatedErrorMessage); + Error.captureStackTrace(error, global._test_gate_focus); + test.only(`[GATED, SHOULD FAIL] ${testName}`, () => + expectTestToFail(callback, error)); + } +}; + +// Dynamic version of @gate pragma +global.gate = fn => { + const flags = getTestFlags(); + return fn(flags); +}; + beforeEach(() => { global.mockClipboardCopy = jest.fn();