diff --git a/babel.config.js b/babel.config.js index d4d1e3213c574..f8a28b20cc87d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,7 +3,6 @@ module.exports = { plugins: [ '@babel/plugin-syntax-jsx', - '@babel/plugin-transform-react-jsx', '@babel/plugin-transform-flow-strip-types', ['@babel/plugin-proposal-class-properties', {loose: true}], 'syntax-trailing-function-commas', diff --git a/package.json b/package.json index 1f60dfc00ce59..9a4fdf3cc6625 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@babel/plugin-proposal-object-rest-spread": "^7.11.0", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-jsx": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.23.3", "@babel/plugin-syntax-typescript": "^7.14.5", "@babel/plugin-transform-arrow-functions": "^7.10.4", "@babel/plugin-transform-block-scoped-functions": "^7.10.4", @@ -27,12 +27,13 @@ "@babel/plugin-transform-modules-commonjs": "^7.10.4", "@babel/plugin-transform-object-super": "^7.10.4", "@babel/plugin-transform-parameters": "^7.10.5", - "@babel/plugin-transform-react-jsx-source": "^7.10.5", + "@babel/plugin-transform-react-jsx": "^7.23.4", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", "@babel/plugin-transform-shorthand-properties": "^7.10.4", "@babel/plugin-transform-spread": "^7.11.0", "@babel/plugin-transform-template-literals": "^7.10.5", "@babel/preset-flow": "^7.10.4", - "@babel/preset-react": "^7.10.4", + "@babel/preset-react": "^7.23.3", "@babel/traverse": "^7.11.0", "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-commonjs": "^24.0.1", diff --git a/packages/react-dom/src/__tests__/ReactComponent-test.js b/packages/react-dom/src/__tests__/ReactComponent-test.js index a66f115624e65..5fa2a4f9f2e6e 100644 --- a/packages/react-dom/src/__tests__/ReactComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactComponent-test.js @@ -471,7 +471,7 @@ describe('ReactComponent', () => { root.render(); }); }).toErrorDev( - 'React.createElement: type is invalid -- expected a string (for built-in components) ' + + 'React.jsx: type is invalid -- expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined.', ), ).rejects.toThrowError( @@ -492,7 +492,7 @@ describe('ReactComponent', () => { root.render(); }); }).toErrorDev( - 'React.createElement: type is invalid -- expected a string (for built-in components) ' + + 'React.jsx: type is invalid -- expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: null.', ), ).rejects.toThrowError( @@ -528,7 +528,7 @@ describe('ReactComponent', () => { root.render(); }); }).toErrorDev( - 'React.createElement: type is invalid -- expected a string (for built-in components) ' + + 'React.jsx: type is invalid -- expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined.', ), ).rejects.toThrowError( diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js index 3bbdd379813e1..ea30b45e3d61c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js @@ -1007,7 +1007,7 @@ describe('ReactDOMServerIntegration', () => { expect(() => { EmptyComponent = ; }).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string ' + + 'Warning: React.jsx: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: object. You likely forgot to export your ' + "component from the file it's defined in, or you might have mixed up " + @@ -1031,7 +1031,7 @@ describe('ReactDOMServerIntegration', () => { expect(() => { NullComponent = ; }).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string ' + + 'Warning: React.jsx: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: null.', {withoutStack: true}, @@ -1049,7 +1049,7 @@ describe('ReactDOMServerIntegration', () => { expect(() => { UndefinedComponent = ; }).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string ' + + 'Warning: React.jsx: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: undefined. You likely forgot to export your ' + "component from the file it's defined in, or you might have mixed up " + diff --git a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js index a263c18153644..f8b318b2f995e 100644 --- a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js +++ b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js @@ -95,7 +95,10 @@ describe('ReactDeprecationWarnings', () => { } class Component extends React.Component { render() { - return ; + return React.createElement(RefComponent, { + ref: 'refComponent', + __self: this, + }); } } expect(() => { @@ -114,7 +117,10 @@ describe('ReactDeprecationWarnings', () => { } class Component extends React.Component { render() { - return ; + return React.createElement(RefComponent, { + ref: 'refComponent', + __self: {}, + }); } } diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index 2f6b225f2f60e..d60e0b5426401 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -1244,9 +1244,9 @@ describe('ReactIncrementalErrorHandling', () => { , ); await expect(async () => await waitForAll([])).toErrorDev([ - 'Warning: React.createElement: type is invalid -- expected a string', + 'Warning: React.jsx: type is invalid -- expected a string', // React retries once on error - 'Warning: React.createElement: type is invalid -- expected a string', + 'Warning: React.jsx: type is invalid -- expected a string', ]); expect(ReactNoop).toMatchRenderedOutput( { , ); await expect(async () => await waitForAll([])).toErrorDev([ - 'Warning: React.createElement: type is invalid -- expected a string', + 'Warning: React.jsx: type is invalid -- expected a string', // React retries once on error - 'Warning: React.createElement: type is invalid -- expected a string', + 'Warning: React.jsx: type is invalid -- expected a string', ]); expect(ReactNoop).toMatchRenderedOutput( { it('recovers from uncaught reconciler errors', async () => { const InvalidType = undefined; expect(() => ReactNoop.render()).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string', + 'Warning: React.jsx: type is invalid -- expected a string', {withoutStack: true}, ); await waitForThrow( diff --git a/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js b/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js index aca7ad9e0ed37..ed8c56072a217 100644 --- a/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js +++ b/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js @@ -86,13 +86,22 @@ describe('ReactFreshIntegration', () => { // eslint-disable-next-line no-new-func new Function( 'global', + 'require', 'React', 'Scheduler', 'exports', '$RefreshReg$', '$RefreshSig$', compiled, - )(global, React, Scheduler, exportsObj, $RefreshReg$, $RefreshSig$); + )( + global, + require, + React, + Scheduler, + exportsObj, + $RefreshReg$, + $RefreshSig$, + ); // Module systems will register exports as a fallback. // This is useful for cases when e.g. a class is exported, // and we don't want to propagate the update beyond this module. diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js index c8095b8239627..4d885c9eff3fc 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js @@ -288,8 +288,7 @@ describe('ReactTestRenderer', () => { expect(() => ReactTestRenderer.create()).toErrorDev( 'Warning: Function components cannot be given refs. Attempts ' + 'to access this ref will fail. ' + - 'Did you mean to use React.forwardRef()?\n\n' + - 'Check the render method of `Foo`.\n' + + 'Did you mean to use React.forwardRef()?\n' + ' in Bar (at **)\n' + ' in Foo (at **)', ); diff --git a/packages/react/src/__tests__/ReactElement-test.js b/packages/react/src/__tests__/ReactCreateElement-test.js similarity index 90% rename from packages/react/src/__tests__/ReactElement-test.js rename to packages/react/src/__tests__/ReactCreateElement-test.js index 05aab3c326db7..6d1a16e3bb145 100644 --- a/packages/react/src/__tests__/ReactElement-test.js +++ b/packages/react/src/__tests__/ReactCreateElement-test.js @@ -14,7 +14,9 @@ let act; let React; let ReactDOMClient; -describe('ReactElement', () => { +// NOTE: This module tests the old, "classic" JSX runtime, React.createElement. +// Do not use JSX syntax in this module; call React.createElement directly. +describe('ReactCreateElement', () => { let ComponentClass; beforeEach(() => { @@ -24,8 +26,6 @@ describe('ReactElement', () => { React = require('react'); ReactDOMClient = require('react-dom/client'); - // NOTE: We're explicitly not using JSX here. This is intended to test - // classic JS without JSX. ComponentClass = class extends React.Component { render() { return React.createElement('div'); @@ -48,24 +48,24 @@ describe('ReactElement', () => { it('should warn when `key` is being accessed on composite element', async () => { class Child extends React.Component { render() { - return
{this.props.key}
; + return React.createElement('div', null, this.props.key); } } class Parent extends React.Component { render() { - return ( -
- - - -
+ return React.createElement( + 'div', + null, + React.createElement(Child, {key: '0'}), + React.createElement(Child, {key: '1'}), + React.createElement(Child, {key: '2'}), ); } } const root = ReactDOMClient.createRoot(document.createElement('div')); await expect(async () => { await act(() => { - root.render(); + root.render(React.createElement(Parent)); }); }).toErrorDev( 'Child: `key` is not a prop. Trying to access it will result ' + @@ -76,7 +76,7 @@ describe('ReactElement', () => { }); it('should warn when `key` is being accessed on a host element', () => { - const element =
; + const element = React.createElement('div', {key: '3'}); expect(() => void element.props.key).toErrorDev( 'div: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + @@ -89,15 +89,15 @@ describe('ReactElement', () => { it('should warn when `ref` is being accessed', async () => { class Child extends React.Component { render() { - return
{this.props.ref}
; + return React.createElement('div', null, this.props.ref); } } class Parent extends React.Component { render() { - return ( -
- -
+ return React.createElement( + 'div', + null, + React.createElement(Child, {ref: React.createRef()}), ); } } @@ -105,7 +105,7 @@ describe('ReactElement', () => { await expect(async () => { await act(() => { - root.render(); + root.render(React.createElement(Parent)); }); }).toErrorDev( 'Child: `ref` is not a prop. Trying to access it will result ' + @@ -277,8 +277,6 @@ describe('ReactElement', () => { expect(element.props.children).toEqual([1, 2, 3]); }); - // NOTE: We're explicitly not using JSX here. This is intended to test - // classic JS without JSX. it('allows static methods to be called using the type property', () => { class StaticMethodComponentClass extends React.Component { render() { @@ -291,16 +289,12 @@ describe('ReactElement', () => { expect(element.type.someStaticMethod()).toBe('someReturnValue'); }); - // NOTE: We're explicitly not using JSX here. This is intended to test - // classic JS without JSX. it('is indistinguishable from a plain object', () => { const element = React.createElement('div', {className: 'foo'}); const object = {}; expect(element.constructor).toBe(object.constructor); }); - // NOTE: We're explicitly not using JSX here. This is intended to test - // classic JS without JSX. it('should use default prop value when removing a prop', async () => { class Component extends React.Component { render() { @@ -325,8 +319,6 @@ describe('ReactElement', () => { expect(instance.props.fruit).toBe('persimmon'); }); - // NOTE: We're explicitly not using JSX here. This is intended to test - // classic JS without JSX. it('should normalize props with default values', async () => { let instance; class Component extends React.Component { @@ -354,7 +346,7 @@ describe('ReactElement', () => { it('throws when changing a prop (in dev) after element creation', async () => { class Outer extends React.Component { render() { - const el =
; + const el = React.createElement('div', {className: 'moo'}); if (__DEV__) { expect(function () { @@ -374,7 +366,7 @@ describe('ReactElement', () => { const root = ReactDOMClient.createRoot(container); await act(() => { - root.render(); + root.render(React.createElement(Outer, {color: 'orange'})); }); if (__DEV__) { expect(container.firstChild.className).toBe('moo'); @@ -387,7 +379,7 @@ describe('ReactElement', () => { const container = document.createElement('div'); class Outer extends React.Component { render() { - const el =
{this.props.sound}
; + const el = React.createElement('div', null, this.props.sound); if (__DEV__) { expect(function () { @@ -405,7 +397,7 @@ describe('ReactElement', () => { Outer.defaultProps = {sound: 'meow'}; const root = ReactDOMClient.createRoot(container); await act(() => { - root.render(); + root.render(React.createElement(Outer)); }); expect(container.firstChild.textContent).toBe('meow'); if (__DEV__) { @@ -422,12 +414,12 @@ describe('ReactElement', () => { test = this; } render() { - return
; + return React.createElement('div'); } } const root = ReactDOMClient.createRoot(document.createElement('div')); await act(() => { - root.render(); + root.render(React.createElement(Test, {value: +undefined})); }); expect(test.props.value).toBeNaN(); }); diff --git a/packages/react/src/__tests__/ReactElementValidator-test.internal.js b/packages/react/src/__tests__/ReactElementValidator-test.internal.js index dccee54a35f62..cd1259de19c02 100644 --- a/packages/react/src/__tests__/ReactElementValidator-test.internal.js +++ b/packages/react/src/__tests__/ReactElementValidator-test.internal.js @@ -10,7 +10,10 @@ 'use strict'; // NOTE: We're explicitly not using JSX in this file. This is intended to test -// classic JS without JSX. +// classic React.createElement without JSX. +// TODO: ^ the above note is a bit stale because there are tests in this file +// that do use JSX syntax. We should port them to React.createElement, and also +// confirm there's a corresponding test that uses JSX syntax. let PropTypes; let React; @@ -548,7 +551,7 @@ describe('ReactElementValidator', () => { expect(() => { void ({[
]}); }).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string ' + + 'Warning: React.jsx: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: undefined. You likely forgot to export your ' + "component from the file it's defined in, or you might have mixed up " + diff --git a/packages/react/src/__tests__/ReactJSXElementValidator-test.js b/packages/react/src/__tests__/ReactJSXElementValidator-test.js index 62d29ee68fefb..b7cdff0ce0aa1 100644 --- a/packages/react/src/__tests__/ReactJSXElementValidator-test.js +++ b/packages/react/src/__tests__/ReactJSXElementValidator-test.js @@ -220,7 +220,7 @@ describe('ReactJSXElementValidator', () => { const True = true; const Div = 'div'; expect(() => void ()).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string ' + + 'Warning: React.jsx: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: undefined. You likely forgot to export your ' + "component from the file it's defined in, or you might have mixed up " + @@ -229,14 +229,14 @@ describe('ReactJSXElementValidator', () => { {withoutStack: true}, ); expect(() => void ()).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string ' + + 'Warning: React.jsx: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: null.' + '\n\nCheck your code at **.', {withoutStack: true}, ); expect(() => void ()).toErrorDev( - 'Warning: React.createElement: type is invalid -- expected a string ' + + 'Warning: React.jsx: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: boolean.' + '\n\nCheck your code at **.', diff --git a/packages/react/src/__tests__/ReactElementJSX-test.js b/packages/react/src/__tests__/ReactJSXRuntime-test.js similarity index 97% rename from packages/react/src/__tests__/ReactElementJSX-test.js rename to packages/react/src/__tests__/ReactJSXRuntime-test.js index fcb1a1d9b10ce..713430fa5ead9 100644 --- a/packages/react/src/__tests__/ReactElementJSX-test.js +++ b/packages/react/src/__tests__/ReactJSXRuntime-test.js @@ -17,11 +17,10 @@ let JSXRuntime; let JSXDEVRuntime; let act; -// NOTE: We're explicitly not using JSX here. This is intended to test -// a new React.jsx api which does not have a JSX transformer yet. -// A lot of these tests are pulled from ReactElement-test because -// this api is meant to be backwards compatible. -describe('ReactElement.jsx', () => { +// NOTE: Prefer to call the JSXRuntime directly in these tests so we can be +// certain that we are testing the runtime behavior, as opposed to the Babel +// transform that we use in our tests configuration. +describe('ReactJSXRuntime', () => { beforeEach(() => { jest.resetModules(); diff --git a/packages/react/src/__tests__/ReactJSXElement-test.js b/packages/react/src/__tests__/ReactJSXTransformIntegration-test.js similarity index 86% rename from packages/react/src/__tests__/ReactJSXElement-test.js rename to packages/react/src/__tests__/ReactJSXTransformIntegration-test.js index 39f2eae0dc566..6f764db3b11ce 100644 --- a/packages/react/src/__tests__/ReactJSXElement-test.js +++ b/packages/react/src/__tests__/ReactJSXTransformIntegration-test.js @@ -14,7 +14,15 @@ let ReactDOMClient; let ReactTestUtils; let act; -describe('ReactJSXElement', () => { +// TODO: Historically this module was used to confirm that the JSX transform +// produces the correct output. However, most users (and indeed our own test +// suite) use a tool like Babel or TypeScript to transform JSX; unlike the +// runtime, the transform is not part of React itself. So this is really just an +// integration suite for the Babel transform. We might consider deleting it. We +// should prefer to test the JSX runtime directly, in ReactCreateElement-test +// and ReactJsxRuntime-test. In the meantime, there's lots of overlap between +// those modules and this one. +describe('ReactJSXTransformIntegration', () => { let Component; beforeEach(() => { @@ -32,6 +40,19 @@ describe('ReactJSXElement', () => { }; }); + it('sanity check: test environment is configured to compile JSX to the jsx() runtime', async () => { + function App() { + return
; + } + const source = App.toString(); + if (__DEV__) { + expect(source).toContain('jsxDEV('); + } else { + expect(source).toContain('jsx('); + } + expect(source).not.toContain('React.createElement'); + }); + it('returns a complete element according to spec', () => { const element = ; expect(element.type).toBe(Component); diff --git a/packages/shared/__tests__/describeComponentFrame-test.js b/packages/shared/__tests__/describeComponentFrame-test.js index 37eaafb5ed279..6fc5bef2c9945 100644 --- a/packages/shared/__tests__/describeComponentFrame-test.js +++ b/packages/shared/__tests__/describeComponentFrame-test.js @@ -12,15 +12,18 @@ let React; let ReactDOMClient; let act; +let jsxDEV; describe('Component stack trace displaying', () => { beforeEach(() => { React = require('react'); - ReactDOMClient = require('react-dom'); + ReactDOMClient = require('react-dom/client'); act = require('internal-test-utils').act; + jsxDEV = require('react/jsx-dev-runtime').jsxDEV; }); - // @gate !enableComponentStackLocations || !__DEV__ + // @gate !enableComponentStackLocations + // @gate __DEV__ it('should provide filenames in stack traces', async () => { class Component extends React.Component { render() { @@ -98,7 +101,18 @@ describe('Component stack trace displaying', () => { Component.displayName = 'Component ' + i; await act(() => { - root.render(); + root.render( + // Intentionally inlining a manual jsxDEV() instead of relying on the + // compiler so that we can pass a custom source location. + jsxDEV( + Component, + {}, + undefined, + false, + {fileName, lineNumber: i}, + this, + ), + ); }); i++; diff --git a/scripts/jest/devtools/setupEnv.js b/scripts/jest/devtools/setupEnv.js index b3ef1b9387dcf..beaf9c3044ae2 100644 --- a/scripts/jest/devtools/setupEnv.js +++ b/scripts/jest/devtools/setupEnv.js @@ -57,3 +57,41 @@ global._test_react_version_focus = (range, testName, callback) => { global._test_ignore_for_react_version = (testName, callback) => { test.skip(testName, callback); }; + +// Most of our tests call jest.resetModules in a beforeEach and the +// re-require all the React modules. However, the JSX runtime is injected by +// the compiler, so those bindings don't get updated. This causes warnings +// logged by the JSX runtime to not have a component stack, because component +// stack relies on the the secret internals object that lives on the React +// module, which because of the resetModules call is longer the same one. +// +// To workaround this issue, we use a proxy that re-requires the latest +// JSX Runtime from the require cache on every function invocation. +// +// Longer term we should migrate all our tests away from using require() and +// resetModules, and use import syntax instead so this kind of thing doesn't +// happen. +lazyRequireFunctionExports('react/jsx-dev-runtime'); + +// TODO: We shouldn't need to do this in the production runtime, but until +// we remove string refs they also depend on the shared state object. Remove +// once we remove string refs. +lazyRequireFunctionExports('react/jsx-runtime'); + +function lazyRequireFunctionExports(moduleName) { + jest.mock(moduleName, () => { + return new Proxy(jest.requireActual(moduleName), { + get(originalModule, prop) { + // If this export is a function, return a wrapper function that lazily + // requires the implementation from the current module cache. + if (typeof originalModule[prop] === 'function') { + return function () { + return jest.requireActual(moduleName)[prop].apply(this, arguments); + }; + } else { + return originalModule[prop]; + } + }, + }); + }); +} diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index 7b9f9e9e9abdf..bf9fe7fed3604 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -34,12 +34,6 @@ const babelOptions = { // For Node environment only. For builds, Rollup takes care of ESM. require.resolve('@babel/plugin-transform-modules-commonjs'), - // Keep stacks detailed in tests. - // Don't put this in .babelrc so that we don't embed filenames - // into ReactART builds that include JSX. - // TODO: I have not verified that this actually works. - require.resolve('@babel/plugin-transform-react-jsx-source'), - pathToTransformInfiniteLoops, pathToTransformTestGatePragma, @@ -86,6 +80,16 @@ module.exports = { if (isTestFile && isInDevToolsPackages) { plugins.push(pathToTransformReactVersionPragma); } + + plugins.push([ + process.env.NODE_ENV === 'development' + ? require.resolve('@babel/plugin-transform-react-jsx-development') + : require.resolve('@babel/plugin-transform-react-jsx'), + // The "automatic" runtime corresponds to react/jsx-runtime. "classic" + // would be React.createElement. + {runtime: 'automatic'}, + ]); + let sourceAst = hermesParser.parse(src, {babel: true}); return { code: babel.transformFromAstSync( diff --git a/scripts/jest/setupTests.js b/scripts/jest/setupTests.js index 09712d587f93f..71a13489cf5f1 100644 --- a/scripts/jest/setupTests.js +++ b/scripts/jest/setupTests.js @@ -313,3 +313,41 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) { return fn(flags); }; } + +// Most of our tests call jest.resetModules in a beforeEach and the +// re-require all the React modules. However, the JSX runtime is injected by +// the compiler, so those bindings don't get updated. This causes warnings +// logged by the JSX runtime to not have a component stack, because component +// stack relies on the the secret internals object that lives on the React +// module, which because of the resetModules call is longer the same one. +// +// To workaround this issue, we use a proxy that re-requires the latest +// JSX Runtime from the require cache on every function invocation. +// +// Longer term we should migrate all our tests away from using require() and +// resetModules, and use import syntax instead so this kind of thing doesn't +// happen. +lazyRequireFunctionExports('react/jsx-dev-runtime'); + +// TODO: We shouldn't need to do this in the production runtime, but until +// we remove string refs they also depend on the shared state object. Remove +// once we remove string refs. +lazyRequireFunctionExports('react/jsx-runtime'); + +function lazyRequireFunctionExports(moduleName) { + jest.mock(moduleName, () => { + return new Proxy(jest.requireActual(moduleName), { + get(originalModule, prop) { + // If this export is a function, return a wrapper function that lazily + // requires the implementation from the current module cache. + if (typeof originalModule[prop] === 'function') { + return function () { + return jest.requireActual(moduleName)[prop].apply(this, arguments); + }; + } else { + return originalModule[prop]; + } + }, + }); + }); +} diff --git a/yarn.lock b/yarn.lock index 75b90870c449d..ba16987b85283 100644 --- a/yarn.lock +++ b/yarn.lock @@ -220,6 +220,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" @@ -465,6 +472,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + "@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" @@ -547,6 +561,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== +"@babel/helper-plugin-utils@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + "@babel/helper-regex@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" @@ -673,6 +692,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + "@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.14.0": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" @@ -688,6 +712,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" @@ -698,6 +727,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-option@^7.22.15": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + "@babel/helper-wrap-function@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" @@ -1089,6 +1123,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-jsx@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" + integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1509,6 +1550,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-react-display-name@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz#70529f034dd1e561045ad3c8152a267f0d7b6200" + integrity sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-react-jsx-development@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.4.tgz#6ec90f244394604623880e15ebc3c34c356258ba" @@ -1518,6 +1566,13 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-jsx" "^7.10.4" +"@babel/plugin-transform-react-jsx-development@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" + integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.22.5" + "@babel/plugin-transform-react-jsx-self@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz#cd301a5fed8988c182ed0b9d55e9bd6db0bd9369" @@ -1555,6 +1610,17 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-jsx" "^7.10.4" +"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5", "@babel/plugin-transform-react-jsx@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312" + integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.23.3" + "@babel/types" "^7.23.4" + "@babel/plugin-transform-react-pure-annotations@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz#3eefbb73db94afbc075f097523e445354a1c6501" @@ -1563,6 +1629,14 @@ "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-react-pure-annotations@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz#fabedbdb8ee40edf5da96f3ecfc6958e3783b93c" + integrity sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-regenerator@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" @@ -1775,6 +1849,18 @@ "@babel/plugin-transform-react-jsx-source" "^7.10.4" "@babel/plugin-transform-react-pure-annotations" "^7.10.4" +"@babel/preset-react@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.23.3.tgz#f73ca07e7590f977db07eb54dbe46538cc015709" + integrity sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-transform-react-display-name" "^7.23.3" + "@babel/plugin-transform-react-jsx" "^7.22.15" + "@babel/plugin-transform-react-jsx-development" "^7.22.5" + "@babel/plugin-transform-react-pure-annotations" "^7.23.3" + "@babel/preset-typescript@^7.14.5": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.0.tgz#b0b4f105b855fb3d631ec036cdc9d1ffd1fa5eac" @@ -1979,6 +2065,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.4": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" + integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"