diff --git a/fixtures/dom/src/toWarnDev.js b/fixtures/dom/src/toWarnDev.js index b4cd7554d5f8..13b3d928111c 100644 --- a/fixtures/dom/src/toWarnDev.js +++ b/fixtures/dom/src/toWarnDev.js @@ -1,7 +1,7 @@ // copied from scripts/jest/matchers/toWarnDev.js 'use strict'; -const jestDiff = require('jest-diff').default; +const {diff: jestDiff} = require('jest-diff'); const util = require('util'); function shouldIgnoreConsoleError(format, args) { diff --git a/fixtures/legacy-jsx-runtimes/setupTests.js b/fixtures/legacy-jsx-runtimes/setupTests.js index 3e675b3f7c2c..4e2d8699ce55 100644 --- a/fixtures/legacy-jsx-runtimes/setupTests.js +++ b/fixtures/legacy-jsx-runtimes/setupTests.js @@ -5,7 +5,7 @@ const expect = global.expect; -const jestDiff = require('jest-diff').default; +const {diff: jestDiff} = require('jest-diff'); const util = require('util'); function shouldIgnoreConsoleError(format, args) { diff --git a/package.json b/package.json index 05e7f835128a..07ad96c217e9 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@babel/preset-react": "^7.10.4", "@babel/traverse": "^7.11.0", "abort-controller": "^3.0.0", + "abortcontroller-polyfill": "^1.7.3", "art": "0.10.1", "babel-eslint": "^10.0.3", "babel-plugin-syntax-trailing-function-commas": "^6.5.0", @@ -61,7 +62,7 @@ "eslint-plugin-no-function-declare-after-return": "^1.0.0", "eslint-plugin-react": "^6.7.1", "eslint-plugin-react-internal": "link:./scripts/eslint-rules", - "fbjs-scripts": "1.2.0", + "fbjs-scripts": "3.0.1", "filesize": "^6.0.1", "flow-bin": "^0.142.0", "glob": "^7.1.6", @@ -69,9 +70,11 @@ "google-closure-compiler": "^20200517.0.0", "gzip-size": "^5.1.1", "jasmine-check": "^1.0.0-rc.0", - "jest": "^26.6.3", - "jest-cli": "^26.6.3", - "jest-diff": "^26.6.2", + "jest": "^29.1.1", + "jest-cli": "^29.1.1", + "jest-diff": "^29.1.0", + "jest-environment-jsdom": "^29.1.1", + "jest-jasmine2": "^29.1.1", "jest-snapshot-serializer-raw": "^1.1.0", "minimatch": "^3.0.4", "minimist": "^1.2.3", diff --git a/packages/dom-event-testing-library/__tests__/index-test.internal.js b/packages/dom-event-testing-library/__tests__/index-test.internal.js index e383e6485a1b..91bf0b280407 100644 --- a/packages/dom-event-testing-library/__tests__/index-test.internal.js +++ b/packages/dom-event-testing-library/__tests__/index-test.internal.js @@ -344,6 +344,8 @@ describe('createEventTarget', () => { "right": 0, "top": 0, "width": 0, + "x": 0, + "y": 0, } `); target.setBoundingClientRect({x: 10, y: 20, width: 100, height: 200}); diff --git a/packages/jest-mock-scheduler/package.json b/packages/jest-mock-scheduler/package.json index 16761b19d375..349f582fa843 100644 --- a/packages/jest-mock-scheduler/package.json +++ b/packages/jest-mock-scheduler/package.json @@ -19,7 +19,7 @@ }, "homepage": "https://reactjs.org/", "peerDependencies": { - "jest": "^23.0.1 || ^24.0.0 || ^25.1.0", + "jest": "^23.0.1 || ^24.0.0 || ^25.1.0 || ^26.0.0 || ^27.0.1 || ^28.0.0 || ^29.0.0", "scheduler": "^0.20.0" }, "files": [ diff --git a/packages/jest-react/package.json b/packages/jest-react/package.json index 9921c329e941..6ab223ca1dfe 100644 --- a/packages/jest-react/package.json +++ b/packages/jest-react/package.json @@ -19,7 +19,7 @@ }, "homepage": "https://reactjs.org/", "peerDependencies": { - "jest": "^23.0.1 || ^24.0.0 || ^25.1.0", + "jest": "^23.0.1 || ^24.0.0 || ^25.1.0 || ^26.0.0 || ^27.0.1 || ^28.0.0 || ^29.0.0", "react": "^18.2.0", "react-test-renderer": "^18.2.0" }, diff --git a/packages/jest-react/src/internalAct.js b/packages/jest-react/src/internalAct.js index 84ee25b7617e..343d3b7f3b9e 100644 --- a/packages/jest-react/src/internalAct.js +++ b/packages/jest-react/src/internalAct.js @@ -33,7 +33,7 @@ export function act(scope: () => Thenable | T): Thenable { if (setTimeout._isMockFunction !== true) { throw Error( "This version of `act` requires Jest's timer mocks " + - '(i.e. jest.useFakeTimers).', + '(i.e. jest.useFakeTimers({legacyFakeTimers: true})).', ); } diff --git a/packages/react-devtools-shared/src/__tests__/setupTests.js b/packages/react-devtools-shared/src/__tests__/setupTests.js index ca7767845114..a9966793d73e 100644 --- a/packages/react-devtools-shared/src/__tests__/setupTests.js +++ b/packages/react-devtools-shared/src/__tests__/setupTests.js @@ -56,7 +56,7 @@ env.beforeEach(() => { } = require('react-devtools-shared/src/utils'); // Fake timers let us flush Bridge operations between setup and assertions. - jest.useFakeTimers(); + jest.useFakeTimers({legacyFakeTimers: true}); // Use utils.js#withErrorsOrWarningsIgnored instead of directly mutating this array. global._ignoredErrorOrWarningMessages = []; diff --git a/packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js b/packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js index dcbe900f9839..0aef52df48de 100644 --- a/packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js +++ b/packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js @@ -40,7 +40,8 @@ describe(dimmedColor, () => { describe(ColorGenerator, () => { describe(ColorGenerator.prototype.colorForID, () => { it('should generate a color for an ID', () => { - expect(new ColorGenerator().colorForID('123')).toMatchInlineSnapshot(` + const color = new ColorGenerator().colorForID('123'); + expect(color).toMatchInlineSnapshot(` Object { "a": 1, "h": 190, diff --git a/packages/react-dom/src/__tests__/InvalidEventListeners-test.js b/packages/react-dom/src/__tests__/InvalidEventListeners-test.js index 88bb160a513b..66f7e1155bb6 100644 --- a/packages/react-dom/src/__tests__/InvalidEventListeners-test.js +++ b/packages/react-dom/src/__tests__/InvalidEventListeners-test.js @@ -65,9 +65,14 @@ describe('InvalidEventListeners', () => { if (!__DEV__) { expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error.calls.argsFor(0)[0]).toMatch( - 'Expected `onClick` listener to be a function, ' + - 'instead got a value of `string` type.', + expect(console.error.calls.argsFor(0)[0]).toEqual( + expect.objectContaining({ + detail: expect.objectContaining({ + message: + 'Expected `onClick` listener to be a function, instead got a value of `string` type.', + }), + type: 'unhandled exception', + }), ); } }); diff --git a/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js index 1f26e615bb01..1dc9f54a365a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js @@ -98,17 +98,17 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported because we're in a browser click event: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ // This one is jsdom-only. Real browser deduplicates it. // (In DEV, we have a nested event due to guarded callback.) - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], ]); @@ -124,9 +124,9 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported because we're in a browser click event: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], ]); @@ -178,17 +178,17 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ // This is only duplicated with createRoot // because it retries once with a sync render. - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -260,17 +260,17 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported by jsdom due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ // This is only duplicated with createRoot // because it retries once with a sync render. - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -336,9 +336,9 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -406,9 +406,9 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported by jsdom due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -473,10 +473,10 @@ describe('ReactDOMConsoleErrorReporting', () => { ]); expect(console.error.calls.all().map(c => c.args)).toEqual([ [ - // Reported due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), + // Reported by jsdom due to the guarded callback: expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -544,9 +544,9 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported by jsdom due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -630,18 +630,18 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [expect.stringContaining('ReactDOM.render is no longer supported')], [ - // Reported because we're in a browser click event: - expect.stringContaining('Error: Uncaught [Error: Boom]'), + // Reported by jsdom due to the guarded callback: expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ // This one is jsdom-only. Real browser deduplicates it. // (In DEV, we have a nested event due to guarded callback.) - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], ]); @@ -657,9 +657,9 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [ // Reported because we're in a browser click event: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], ]); @@ -705,10 +705,10 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [expect.stringContaining('ReactDOM.render is no longer supported')], [ - // Reported due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), + // Reported by jsdom due to the guarded callback: expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -776,9 +776,9 @@ describe('ReactDOMConsoleErrorReporting', () => { [expect.stringContaining('ReactDOM.render is no longer supported')], [ // Reported by jsdom due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -845,10 +845,10 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [expect.stringContaining('ReactDOM.render is no longer supported')], [ - // Reported due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), + // Reported by jsdom due to the guarded callback: expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -919,9 +919,9 @@ describe('ReactDOMConsoleErrorReporting', () => { [expect.stringContaining('ReactDOM.render is no longer supported')], [ // Reported by jsdom due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -988,10 +988,10 @@ describe('ReactDOMConsoleErrorReporting', () => { expect(console.error.calls.all().map(c => c.args)).toEqual([ [expect.stringContaining('ReactDOM.render is no longer supported')], [ - // Reported due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), + // Reported by jsdom due to the guarded callback: expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ @@ -1062,9 +1062,9 @@ describe('ReactDOMConsoleErrorReporting', () => { [expect.stringContaining('ReactDOM.render is no longer supported')], [ // Reported by jsdom due to the guarded callback: - expect.stringContaining('Error: Uncaught [Error: Boom]'), expect.objectContaining({ - message: 'Boom', + detail: expect.objectContaining({message: 'Boom'}), + type: 'unhandled exception', }), ], [ diff --git a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js index 786a209d4faa..66bd819ef313 100644 --- a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js @@ -7,7 +7,7 @@ 'use strict'; -describe('ReactDOMEventListener', () => { +describe('ReactDOMEventPropagation', () => { let React; let OuterReactDOM; let InnerReactDOM; @@ -16,12 +16,12 @@ describe('ReactDOMEventListener', () => { beforeEach(() => { window.TextEvent = function() {}; jest.resetModules(); - React = require('react'); jest.isolateModules(() => { - OuterReactDOM = require('react-dom'); + InnerReactDOM = require('react-dom'); }); jest.isolateModules(() => { - InnerReactDOM = require('react-dom'); + React = require('react'); + OuterReactDOM = require('react-dom'); }); expect(OuterReactDOM).not.toBe(InnerReactDOM); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 532a8da583f7..2049f574a7ab 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -5,11 +5,13 @@ * LICENSE file in the root directory of this source tree. * * @emails react-core + * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment */ 'use strict'; let JSDOM; +let JSDOMVirtualConsole; let Stream; let Scheduler; let React; @@ -22,58 +24,74 @@ let useSyncExternalStoreWithSelector; let use; let PropTypes; let textCache; -let window; -let document; let writable; let CSPnonce = null; -let container; +let container = null; let buffer = ''; let hasErrored = false; let fatalError = undefined; +let originalDocument; +let originalWindow; + +function resetModules() { + jest.resetModules(); + ({JSDOM, VirtualConsole: JSDOMVirtualConsole} = require('jsdom')); + Scheduler = require('scheduler'); + React = require('react'); + ReactDOMClient = require('react-dom/client'); + ReactDOMFizzServer = require('react-dom/server'); + Stream = require('stream'); + Suspense = React.Suspense; + if (gate(flags => flags.enableSuspenseList)) { + SuspenseList = React.SuspenseList; + use = React.experimental_use; + } -describe('ReactDOMFizzServer', () => { - beforeEach(() => { - jest.resetModules(); - JSDOM = require('jsdom').JSDOM; - Scheduler = require('scheduler'); - React = require('react'); - ReactDOMClient = require('react-dom/client'); - ReactDOMFizzServer = require('react-dom/server'); - Stream = require('stream'); - Suspense = React.Suspense; - if (gate(flags => flags.enableSuspenseList)) { - SuspenseList = React.SuspenseList; - use = React.experimental_use; - } + PropTypes = require('prop-types'); - PropTypes = require('prop-types'); + if (gate(flags => flags.source)) { + // The `with-selector` module composes the main `use-sync-external-store` + // entrypoint. In the compiled artifacts, this is resolved to the `shim` + // implementation by our build config, but when running the tests against + // the source files, we need to tell Jest how to resolve it. Because this + // is a source module, this mock has no affect on the build tests. + jest.mock('use-sync-external-store/src/useSyncExternalStore', () => + jest.requireActual('react'), + ); + } + useSyncExternalStore = React.useSyncExternalStore; + useSyncExternalStoreWithSelector = require('use-sync-external-store/with-selector') + .useSyncExternalStoreWithSelector; +} + +function resetJSDOM(markup) { + // Test Environment + const virtualConsole = new JSDOMVirtualConsole(); + virtualConsole.sendTo(console, { + omitJSDOMErrors: true, + }); + virtualConsole.on('jsdomError', error => { + console.error(error); + }); + const jsdom = new JSDOM(markup, { + runScripts: 'dangerously', + virtualConsole, + }); + global.window = jsdom.window; + global.document = jsdom.window.document; + resetModules(); +} - if (gate(flags => flags.source)) { - // The `with-selector` module composes the main `use-sync-external-store` - // entrypoint. In the compiled artifacts, this is resolved to the `shim` - // implementation by our build config, but when running the tests against - // the source files, we need to tell Jest how to resolve it. Because this - // is a source module, this mock has no affect on the build tests. - jest.mock('use-sync-external-store/src/useSyncExternalStore', () => - jest.requireActual('react'), - ); - } - useSyncExternalStore = React.useSyncExternalStore; - useSyncExternalStoreWithSelector = require('use-sync-external-store/with-selector') - .useSyncExternalStoreWithSelector; +describe('ReactDOMFizzServer', () => { + beforeEach(() => { + originalDocument = global.document; + originalWindow = global.window; + resetModules(); textCache = new Map(); - // Test Environment - const jsdom = new JSDOM( - '
', - { - runScripts: 'dangerously', - }, - ); - window = jsdom.window; - document = jsdom.window.document; - container = document.getElementById('container'); + container = document.createElement('div'); + document.body.appendChild(container); buffer = ''; hasErrored = false; @@ -89,6 +107,15 @@ describe('ReactDOMFizzServer', () => { }); }); + afterEach(() => { + // restore actIntoEmptyDocument() effects + global.window = originalWindow; + global.document = originalDocument; + + container?.remove?.(); + container = null; + }); + function expectErrors(errorsArr, toBeDevArr, toBeProdArr) { const mappedErrows = errorsArr.map(({error, errorInfo}) => { const stack = errorInfo && errorInfo.componentStack; @@ -163,11 +190,8 @@ describe('ReactDOMFizzServer', () => { // We assume that we have now received a proper fragment of HTML. const bufferedContent = buffer; // Test Environment - const jsdom = new JSDOM(bufferedContent, { - runScripts: 'dangerously', - }); - window = jsdom.window; - document = jsdom.window.document; + resetJSDOM(bufferedContent); + container = document; buffer = ''; } @@ -1466,7 +1490,7 @@ describe('ReactDOMFizzServer', () => { function normalizeCodeLocInfo(str) { return ( str && - str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) { + String(str).replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) { return '\n in ' + name + ' (at **)'; }) ); @@ -3494,7 +3518,7 @@ describe('ReactDOMFizzServer', () => { window.__test_outlet = ''; const stringWithScriptsInIt = 'prescription pre