From 32f8fd56511f3a101ca518cd3d450d2a435eb9bf Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 30 Mar 2022 10:11:01 +0100 Subject: [PATCH] Fix some unit tests --- package-lock.json | 132 +++---------- package.json | 2 +- .../src/components/url-input/test/button.js | 20 -- packages/components/src/button/test/index.js | 10 +- .../components/src/disabled/test/index.js | 180 ++++++------------ .../src/form-token-field/test/index.js | 139 +++++++++----- .../higher-order/with-filters/test/index.js | 75 +++----- .../with-focus-outside/test/index.js | 44 ++--- .../components/src/scroll-lock/test/index.js | 24 ++- .../components/src/tab-panel/test/index.js | 71 ++----- .../src/text-highlight/test/index.js | 35 ++-- .../src/higher-order/pure/test/index.js | 64 ++++--- .../src/higher-order/with-state/test/index.js | 25 +-- .../src/hooks/use-focus-outside/test/index.js | 35 +--- .../src/hooks/use-merge-refs/test/index.js | 85 +++++---- packages/element/src/react-platform.js | 2 +- packages/element/src/react.js | 1 - 17 files changed, 378 insertions(+), 566 deletions(-) diff --git a/package-lock.json b/package-lock.json index ddbc4481580e32..51c497707eb385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15489,125 +15489,55 @@ "integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==" }, "@testing-library/dom": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.0.tgz", - "integrity": "sha512-0hhuJSmw/zLc6ewR9cVm84TehuTd7tbqBX9pRNSp8znJ9gTmSgesdbiGZtt8R6dL+2rgaPFp9Yjr7IU1HWm49w==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.12.0.tgz", + "integrity": "sha512-rBrJk5WjI02X1edtiUcZhgyhgBhiut96r5Jp8J5qktKdcvLcZpKDW8i2hkGMMItxrghjXuQ5AM6aE0imnFawaw==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", + "aria-query": "^5.0.0", "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.4", + "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" + "pretty-format": "^27.0.2" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", "dev": true }, "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -15756,13 +15686,13 @@ } }, "@testing-library/react": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.2.tgz", - "integrity": "sha512-jaxm0hwUjv+hzC+UFEywic7buDC9JQ1q3cDsrWVSDAPmLotfA6E6kUHlYm/zOeGCac6g48DR36tFHxl7Zb+N5A==", + "version": "13.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.0.0-alpha.6.tgz", + "integrity": "sha512-AVJTnwLlnjvXDNe91P6Nt9pN2fMS4csAzTmIbOewja+LVKzhlr53EONhv3ck0J3GzSZ5MIN5qL3BfISX/Wf1Jg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.28.1" + "@testing-library/dom": "^8.5.0" } }, "@testing-library/react-native": { @@ -31781,9 +31711,9 @@ } }, "dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz", + "integrity": "sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw==", "dev": true }, "dom-converter": { diff --git a/package.json b/package.json index 3bd45a39f8faa5..8ddfe99083e0d0 100755 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "@storybook/manager-webpack5": "6.4.9", "@storybook/react": "6.4.9", "@testing-library/jest-dom": "5.16.1", - "@testing-library/react": "11.2.2", + "@testing-library/react": "13.0.0-alpha.6", "@testing-library/react-native": "9.0.0", "@testing-library/user-event": "^14.0.0-beta.13", "@types/classnames": "2.3.1", diff --git a/packages/block-editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js index 04601dfea51c4c..87414916780cc3 100644 --- a/packages/block-editor/src/components/url-input/test/button.js +++ b/packages/block-editor/src/components/url-input/test/button.js @@ -2,8 +2,6 @@ * External dependencies */ import { shallow } from 'enzyme'; -import TestUtils from 'react-dom/test-utils'; -import ReactDOM from 'react-dom'; /** * Internal dependencies @@ -70,22 +68,4 @@ describe( 'URLInputButton', () => { wrapper.find( '.block-editor-url-input__back' ).simulate( 'click' ); expect( wrapper.state().expanded ).toBe( false ); } ); - it( 'should close the form when user submits it', () => { - const wrapper = TestUtils.renderIntoDocument( ); - const buttonElement = () => - TestUtils.scryRenderedDOMComponentsWithClass( - wrapper, - 'components-toolbar__control' - ); - const formElement = () => - TestUtils.scryRenderedDOMComponentsWithTag( wrapper, 'form' ); - TestUtils.Simulate.click( buttonElement().shift() ); - expect( wrapper.state.expanded ).toBe( true ); - TestUtils.Simulate.submit( formElement().shift() ); - expect( wrapper.state.expanded ).toBe( false ); - ReactDOM.unmountComponentAtNode( - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.findDOMNode( wrapper ).parentNode - ); - } ); } ); diff --git a/packages/components/src/button/test/index.js b/packages/components/src/button/test/index.js index b4dc89eaaf092b..7201a7d09148fc 100644 --- a/packages/components/src/button/test/index.js +++ b/packages/components/src/button/test/index.js @@ -2,11 +2,10 @@ * External dependencies */ import { shallow } from 'enzyme'; -import TestUtils from 'react-dom/test-utils'; /** * WordPress dependencies */ -import { createRef } from '@wordpress/element'; +import { createRef, createRoot } from '@wordpress/element'; import { plusCircle } from '@wordpress/icons'; /** @@ -245,10 +244,11 @@ describe( 'Button', () => { describe( 'ref forwarding', () => { it( 'should enable access to DOM element', () => { const ref = createRef(); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); - TestUtils.renderIntoDocument( - - ); expect( ref.current.type ).toBe( 'button' ); } ); } ); diff --git a/packages/components/src/disabled/test/index.js b/packages/components/src/disabled/test/index.js index 1369c737dcfd27..0333a98b4f063d 100644 --- a/packages/components/src/disabled/test/index.js +++ b/packages/components/src/disabled/test/index.js @@ -1,12 +1,7 @@ -/** - * External dependencies - */ -import TestUtils from 'react-dom/test-utils'; - /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { createRoot } from '@wordpress/element'; /** * Internal dependencies @@ -65,30 +60,18 @@ describe( 'Disabled', () => { ); - // This is needed because TestUtils does not accept a stateless component. - class DisabledComponent extends Component { - render() { - const { children, isDisabled } = this.props; - - return { children }; - } - } - it( 'will disable all fields', () => { - const wrapper = TestUtils.renderIntoDocument( - + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( +
- + ); + jest.runAllTimers(); - const input = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'input' - ); - const div = TestUtils.scryRenderedDOMComponentsWithTag( - wrapper, - 'div' - )[ 1 ]; + const input = container.querySelector( 'input' ); + const div = container.querySelectorAll( 'div' )[ 1 ]; expect( input.hasAttribute( 'disabled' ) ).toBe( true ); expect( div.getAttribute( 'contenteditable' ) ).toBe( 'false' ); @@ -96,83 +79,30 @@ describe( 'Disabled', () => { expect( div.hasAttribute( 'disabled' ) ).toBe( false ); } ); - it( 'should cleanly un-disable via reconciliation', () => { - // If this test suddenly starts failing, it means React has become - // smarter about reusing children into grandfather element when the - // parent is dropped, so we'd need to find another way to restore - // original form state. - // Using state for this test for easier manipulation of the child props. - class MaybeDisable extends Component { - constructor() { - super( ...arguments ); - this.state = { isDisabled: true }; - } - - render() { - return this.state.isDisabled ? ( - - - - ) : ( - - ); - } - } - - const wrapper = TestUtils.renderIntoDocument( ); - wrapper.setState( { isDisabled: false } ); - - const input = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'input' - ); - const div = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'div' ); - - expect( input.hasAttribute( 'disabled' ) ).toBe( false ); - expect( div.getAttribute( 'contenteditable' ) ).toBe( 'true' ); - expect( div.hasAttribute( 'tabindex' ) ).toBe( true ); - } ); - it( 'will disable or enable descendant fields based on the isDisabled prop value', () => { - class MaybeDisable extends Component { - constructor() { - super( ...arguments ); - this.state = { isDisabled: true }; - } - - render() { - return ( - - - - ); - } + function MaybeDisable( props ) { + return ( + + + + ); } - const wrapper = TestUtils.renderIntoDocument( ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); - const input = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'input' - ); - const div = TestUtils.scryRenderedDOMComponentsWithTag( - wrapper, - 'div' - )[ 1 ]; + const input = container.querySelector( 'input' ); + const div = container.querySelectorAll( 'div' )[ 1 ]; expect( input.hasAttribute( 'disabled' ) ).toBe( true ); expect( div.getAttribute( 'contenteditable' ) ).toBe( 'false' ); + root.render( ); + jest.runAllTimers(); - wrapper.setState( { isDisabled: false } ); - - const input2 = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'input' - ); - const div2 = TestUtils.scryRenderedDOMComponentsWithTag( - wrapper, - 'div' - )[ 0 ]; + const input2 = container.querySelector( 'input' ); + const div2 = container.querySelector( 'div' ); expect( input2.hasAttribute( 'disabled' ) ).toBe( false ); expect( div2.getAttribute( 'contenteditable' ) ).not.toBe( 'false' ); @@ -188,52 +118,50 @@ describe( 'Disabled', () => { // https://github.com/jsdom/jsdom/issues/639 describe( 'Consumer', () => { - class DisabledStatus extends Component { - render() { - return ( -

- - { ( isDisabled ) => - isDisabled ? 'Disabled' : 'Not disabled' - } - -

- ); - } + function DisabledStatus() { + return ( +

+ + { ( isDisabled ) => + isDisabled ? 'Disabled' : 'Not disabled' + } + +

+ ); } test( "lets components know that they're disabled via context", () => { - const wrapper = TestUtils.renderIntoDocument( - + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( + - - ); - const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'p' + ); + jest.runAllTimers(); + const wrapperElement = container.querySelector( 'p' ); expect( wrapperElement.textContent ).toBe( 'Disabled' ); } ); test( "lets components know that they're not disabled via context when isDisabled is false", () => { - const wrapper = TestUtils.renderIntoDocument( - + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( + - - ); - const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'p' + ); + jest.runAllTimers(); + const wrapperElement = container.querySelector( 'p' ); expect( wrapperElement.textContent ).toBe( 'Not disabled' ); } ); test( "lets components know that they're not disabled via context", () => { - const wrapper = TestUtils.renderIntoDocument( ); - const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'p' - ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); + const wrapperElement = container.querySelector( 'p' ); expect( wrapperElement.textContent ).toBe( 'Not disabled' ); } ); } ); diff --git a/packages/components/src/form-token-field/test/index.js b/packages/components/src/form-token-field/test/index.js index 61fe4ccc2fea24..399a60d765eae0 100644 --- a/packages/components/src/form-token-field/test/index.js +++ b/packages/components/src/form-token-field/test/index.js @@ -3,14 +3,17 @@ */ import { filter, map } from 'lodash'; import TestUtils, { act } from 'react-dom/test-utils'; -import ReactDOM from 'react-dom'; + +/** + * WordPress dependencies + */ +import { createRoot } from '@wordpress/element'; /** * Internal dependencies */ import fixtures from './lib/fixtures'; import TokenFieldWrapper from './lib/token-field-wrapper'; -import TokenInput from '../token-input'; /** * Module variables @@ -32,7 +35,9 @@ const charCodes = { }; describe( 'FormTokenField', () => { - let wrapper, wrapperElement, textInputElement, textInputComponent; + let textInputElement; + let wrapperElement; + let wrapperRef; function setText( text ) { TestUtils.Simulate.change( textInputElement(), { @@ -40,6 +45,7 @@ describe( 'FormTokenField', () => { value: text, }, } ); + jest.runAllTimers(); } function sendKeyDown( keyCode, shiftKey ) { @@ -47,10 +53,12 @@ describe( 'FormTokenField', () => { keyCode, shiftKey: !! shiftKey, } ); + jest.runAllTimers(); } function sendKeyPress( charCode ) { TestUtils.Simulate.keyPress( wrapperElement(), { charCode } ); + jest.runAllTimers(); } function getTokensHTML() { @@ -96,37 +104,38 @@ describe( 'FormTokenField', () => { } function setUp( props ) { - wrapper = TestUtils.renderIntoDocument( - - ); - /* eslint-disable react/no-find-dom-node */ - wrapperElement = () => ReactDOM.findDOMNode( wrapper ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + wrapperRef = { current: null }; + root.render( ); + jest.runAllTimers(); + wrapperElement = () => container.firstChild; textInputElement = () => - TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-form-token-field__input' - ); - textInputComponent = () => - TestUtils.findRenderedComponentWithType( wrapper, TokenInput ); - /* eslint-enable react/no-find-dom-node */ + container.querySelector( '.components-form-token-field__input' ); TestUtils.Simulate.focus( textInputElement() ); + jest.runAllTimers(); } describe( 'displaying tokens', () => { it( 'should render default tokens', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { isExpanded: true, } ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] ); + jest.runAllTimers(); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + ] ); } ); it( 'should display tokens with escaped special characters properly', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { tokens: fixtures.specialTokens.textEscaped, isExpanded: true, } ); + jest.runAllTimers(); expect( getTokensHTML() ).toEqual( fixtures.specialTokens.htmlEscaped ); @@ -140,10 +149,11 @@ describe( 'FormTokenField', () => { // worth testing, so we can be sure that token values with // dangerous characters in them don't have these characters carried // through unescaped to the HTML. - wrapper.setState( { + wrapperRef.current.setState( { tokens: fixtures.specialTokens.textUnescaped, isExpanded: true, } ); + jest.runAllTimers(); expect( getTokensHTML() ).toEqual( fixtures.specialTokens.htmlUnescaped ); @@ -153,9 +163,10 @@ describe( 'FormTokenField', () => { describe( 'suggestions', () => { it( 'should not render suggestions unless we type at least two characters', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { isExpanded: true, } ); + jest.runAllTimers(); expect( getSuggestionsText() ).toEqual( [] ); setText( 'th' ); expect( getSuggestionsText() ).toEqual( @@ -165,25 +176,28 @@ describe( 'FormTokenField', () => { it( 'should show suggestions when when input is empty if expandOnFocus is set to true', () => { setUp( { __experimentalExpandOnFocus: true } ); - wrapper.setState( { + wrapperRef.current.setState( { isExpanded: true, } ); + jest.runAllTimers(); expect( getSuggestionsText() ).not.toEqual( [] ); } ); it( 'should remove already added tags from suggestions', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { tokens: Object.freeze( [ 'of', 'and' ] ), } ); + jest.runAllTimers(); expect( getSuggestionsText() ).not.toEqual( getTokensHTML() ); } ); it( 'suggestions that begin with match are boosted', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { isExpanded: true, } ); + jest.runAllTimers(); setText( 'so' ); expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.so @@ -192,10 +206,11 @@ describe( 'FormTokenField', () => { it( 'should match against the unescaped values of suggestions with special characters', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { tokenSuggestions: fixtures.specialSuggestions.textUnescaped, isExpanded: true, } ); + jest.runAllTimers(); setText( '& S' ); expect( getSuggestionsText() ).toEqual( fixtures.specialSuggestions.matchAmpersandUnescaped @@ -204,10 +219,11 @@ describe( 'FormTokenField', () => { it( 'should match against the unescaped values of suggestions with special characters (including spaces)', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { tokenSuggestions: fixtures.specialSuggestions.textUnescaped, isExpanded: true, } ); + jest.runAllTimers(); setText( 's &' ); expect( getSuggestionsText() ).toEqual( fixtures.specialSuggestions.matchAmpersandSequence @@ -217,10 +233,11 @@ describe( 'FormTokenField', () => { it( 'should not match against the escaped values of suggestions with special characters', () => { setUp(); setText( 'amp' ); - wrapper.setState( { + wrapperRef.current.setState( { tokenSuggestions: fixtures.specialSuggestions.textUnescaped, isExpanded: true, } ); + jest.runAllTimers(); expect( getSuggestionsText() ).toEqual( fixtures.specialSuggestions.matchAmpersandEscaped ); @@ -228,9 +245,10 @@ describe( 'FormTokenField', () => { it( 'should match suggestions even with trailing spaces', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { isExpanded: true, } ); + jest.runAllTimers(); setText( ' at ' ); expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.at @@ -239,9 +257,10 @@ describe( 'FormTokenField', () => { it( 'should manage the selected suggestion based on both keyboard and mouse events', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { isExpanded: true, } ); + jest.runAllTimers(); setText( 'th' ); expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.th @@ -267,36 +286,41 @@ describe( 'FormTokenField', () => { } ); TestUtils.Simulate.mouseEnter( hoverSuggestion ); + jest.runAllTimers(); expect( getSelectedSuggestion() ).toEqual( [ 'wi', 'th' ] ); sendKeyDown( keyCodes.upArrow ); expect( getSelectedSuggestion() ).toEqual( [ 'th', 'is' ] ); sendKeyDown( keyCodes.upArrow ); expect( getSelectedSuggestion() ).toEqual( [ 'th', 'at' ] ); TestUtils.Simulate.click( hoverSuggestion ); + jest.runAllTimers(); expect( getSelectedSuggestion() ).toBe( null ); expect( getTokensHTML() ).toEqual( [ 'foo', 'bar', 'with' ] ); } ); it( 'should re-render when suggestions prop has changed', () => { setUp(); - wrapper.setState( { + wrapperRef.current.setState( { tokenSuggestions: [], isExpanded: true, } ); + jest.runAllTimers(); expect( getSuggestionsText() ).toEqual( [] ); setText( 'so' ); expect( getSuggestionsText() ).toEqual( [] ); - wrapper.setState( { + wrapperRef.current.setState( { tokenSuggestions: fixtures.specialSuggestions.default, } ); + jest.runAllTimers(); expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.so ); - wrapper.setState( { + wrapperRef.current.setState( { tokenSuggestions: [], } ); + jest.runAllTimers(); expect( getSuggestionsText() ).toEqual( [] ); } ); } ); @@ -305,57 +329,82 @@ describe( 'FormTokenField', () => { it( 'should not allow adding blank tokens with Tab', () => { setUp(); sendKeyDown( keyCodes.tab ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + ] ); } ); it( 'should not allow adding whitespace tokens with Tab', () => { setUp(); setText( ' ' ); sendKeyDown( keyCodes.tab ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + ] ); } ); it( 'should add a token when Enter pressed', () => { setUp(); setText( 'baz' ); sendKeyDown( keyCodes.enter ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar', 'baz' ] ); - const textNode = textInputComponent(); - expect( textNode.props.value ).toBe( '' ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + 'baz', + ] ); } ); it( 'should not allow adding blank tokens with Enter', () => { setUp(); sendKeyDown( keyCodes.enter ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + ] ); } ); it( 'should not allow adding whitespace tokens with Enter', () => { setUp(); setText( ' ' ); sendKeyDown( keyCodes.enter ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + ] ); } ); it( 'should not allow adding whitespace tokens with comma', () => { setUp(); setText( ' ' ); sendKeyPress( charCodes.comma ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + ] ); } ); it( 'should add a token when comma pressed', () => { setUp(); setText( 'baz' ); sendKeyPress( charCodes.comma ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar', 'baz' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + 'baz', + ] ); } ); it( 'should trim token values when adding', () => { setUp(); setText( ' baz ' ); sendKeyDown( keyCodes.enter ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar', 'baz' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + 'baz', + ] ); } ); it( "should not add values that don't pass the validation", () => { @@ -364,7 +413,10 @@ describe( 'FormTokenField', () => { } ); setText( 'baz' ); sendKeyDown( keyCodes.enter ); - expect( wrapper.state.tokens ).toEqual( [ 'foo', 'bar' ] ); + expect( wrapperRef.current.state.tokens ).toEqual( [ + 'foo', + 'bar', + ] ); } ); } ); @@ -375,7 +427,8 @@ describe( 'FormTokenField', () => { '.components-form-token-field__remove-token' ).firstChild; TestUtils.Simulate.click( forClickNode ); - expect( wrapper.state.tokens ).toEqual( [ 'bar' ] ); + jest.runAllTimers(); + expect( wrapperRef.current.state.tokens ).toEqual( [ 'bar' ] ); } ); } ); } ); diff --git a/packages/components/src/higher-order/with-filters/test/index.js b/packages/components/src/higher-order/with-filters/test/index.js index 46360927347b0c..c7630736d18346 100644 --- a/packages/components/src/higher-order/with-filters/test/index.js +++ b/packages/components/src/higher-order/with-filters/test/index.js @@ -2,50 +2,24 @@ * External dependencies */ import { shallow } from 'enzyme'; -import TestUtils from 'react-dom/test-utils'; -import ReactDOM from 'react-dom'; /** * WordPress dependencies */ import { addFilter, removeAllFilters, removeFilter } from '@wordpress/hooks'; -import { Component } from '@wordpress/element'; +import { createRoot } from '@wordpress/element'; /** * Internal dependencies */ import withFilters from '..'; -const assertExpectedHtml = ( wrapper, expectedHTML ) => { - // eslint-disable-next-line react/no-find-dom-node - const element = ReactDOM.findDOMNode( wrapper ); - expect( element.outerHTML ).toBe( expectedHTML ); -}; - -// This is needed because TestUtils does not accept a stateless component. -// anything run through a HOC ends up as a stateless component. -const getTestComponent = ( WrappedComponent ) => { - class TestComponent extends Component { - render() { - return ; - } - } - return ; -}; - describe( 'withFilters', () => { - let shallowWrapper, wrapper; - + let shallowWrapper; const hookName = 'EnhancedComponent'; const MyComponent = () =>
My component
; afterEach( () => { - if ( wrapper ) { - ReactDOM.unmountComponentAtNode( - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.findDOMNode( wrapper ).parentNode - ); - } if ( shallowWrapper ) { shallowWrapper.unmount(); } @@ -114,15 +88,15 @@ describe( 'withFilters', () => { ); const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( - getTestComponent( EnhancedComponent ) - ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); jest.runAllTimers(); expect( spy ).toHaveBeenCalledTimes( 1 ); - assertExpectedHtml( - wrapper, + + expect( container.innerHTML ).toBe( '
Spied component
' ); } ); @@ -135,9 +109,9 @@ describe( 'withFilters', () => { }; const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( - getTestComponent( EnhancedComponent ) - ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); spy.mockClear(); addFilter( @@ -152,8 +126,7 @@ describe( 'withFilters', () => { jest.runAllTimers(); expect( spy ).toHaveBeenCalledTimes( 1 ); - assertExpectedHtml( - wrapper, + expect( container.innerHTML ).toBe( '
Spied component
' ); } ); @@ -165,9 +138,9 @@ describe( 'withFilters', () => { return
Spied component
; }; const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( - getTestComponent( EnhancedComponent ) - ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); spy.mockClear(); @@ -192,8 +165,7 @@ describe( 'withFilters', () => { jest.runAllTimers(); expect( spy ).toHaveBeenCalledTimes( 1 ); - assertExpectedHtml( - wrapper, + expect( container.innerHTML ).toBe( '
Spied component
' ); } ); @@ -205,9 +177,9 @@ describe( 'withFilters', () => { return
Spied component
; }; const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( - getTestComponent( EnhancedComponent ) - ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); spy.mockClear(); addFilter( @@ -225,7 +197,7 @@ describe( 'withFilters', () => { jest.runAllTimers(); expect( spy ).toHaveBeenCalledTimes( 2 ); - assertExpectedHtml( wrapper, '
Spied component
' ); + expect( container.innerHTML ).toBe( '
Spied component
' ); } ); it( 'should re-render both components once each when one filter added', () => { @@ -241,9 +213,9 @@ describe( 'withFilters', () => { ); - wrapper = TestUtils.renderIntoDocument( - getTestComponent( CombinedComponents ) - ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); spy.mockClear(); addFilter( @@ -258,8 +230,7 @@ describe( 'withFilters', () => { jest.runAllTimers(); expect( spy ).toHaveBeenCalledTimes( 2 ); - assertExpectedHtml( - wrapper, + expect( container.innerHTML ).toBe( '
Spied component
Spied component
' ); } ); diff --git a/packages/components/src/higher-order/with-focus-outside/test/index.js b/packages/components/src/higher-order/with-focus-outside/test/index.js index f310506f2d9dc4..99f084d7017faa 100644 --- a/packages/components/src/higher-order/with-focus-outside/test/index.js +++ b/packages/components/src/higher-order/with-focus-outside/test/index.js @@ -6,18 +6,17 @@ import TestUtils from 'react-dom/test-utils'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, createRoot } from '@wordpress/element'; /** * Internal dependencies */ import withFocusOutside from '../'; -import ReactDOM from 'react-dom'; -let wrapper, onFocusOutside; +let onFocusOutside; describe( 'withFocusOutside', () => { - let origHasFocus; + let origHasFocus, container, root; const EnhancedComponent = withFocusOutside( class extends Component { @@ -36,22 +35,8 @@ describe( 'withFocusOutside', () => { } ); - // This is needed because TestUtils does not accept a stateless component. - // anything run through a HOC ends up as a stateless component. - const getTestComponent = ( WrappedComponent, props ) => { - class TestComponent extends Component { - render() { - return ; - } - } - return ; - }; - const simulateEvent = ( event, index = 0 ) => { - const element = TestUtils.scryRenderedDOMComponentsWithTag( - wrapper, - 'input' - ); + const element = container.querySelectorAll( 'input' ); TestUtils.Simulate[ event ]( element[ index ] ); }; @@ -62,9 +47,11 @@ describe( 'withFocusOutside', () => { document.hasFocus = () => true; onFocusOutside = jest.fn(); - wrapper = TestUtils.renderIntoDocument( - getTestComponent( EnhancedComponent, { onFocusOutside } ) - ); + + container = document.createElement( 'div' ); + root = createRoot( container ); + root.render( ); + jest.runAllTimers(); } ); afterEach( () => { @@ -75,7 +62,6 @@ describe( 'withFocusOutside', () => { simulateEvent( 'focus' ); simulateEvent( 'blur' ); simulateEvent( 'focus', 1 ); - jest.runAllTimers(); expect( onFocusOutside ).not.toHaveBeenCalled(); @@ -85,21 +71,19 @@ describe( 'withFocusOutside', () => { simulateEvent( 'focus' ); simulateEvent( 'mouseDown', 1 ); simulateEvent( 'blur' ); + jest.runAllTimers(); // In most browsers, the input at index 1 would receive a focus event // at this point, but this is not guaranteed, which is the intention of // the normalization behavior tested here. simulateEvent( 'mouseUp', 1 ); - jest.runAllTimers(); - expect( onFocusOutside ).not.toHaveBeenCalled(); } ); it( 'should call handler if focus doesn’t shift to element within component', () => { simulateEvent( 'focus' ); simulateEvent( 'blur' ); - jest.runAllTimers(); expect( onFocusOutside ).toHaveBeenCalled(); @@ -112,7 +96,6 @@ describe( 'withFocusOutside', () => { simulateEvent( 'focus' ); simulateEvent( 'blur' ); - jest.runAllTimers(); expect( onFocusOutside ).not.toHaveBeenCalled(); @@ -121,13 +104,8 @@ describe( 'withFocusOutside', () => { it( 'should cancel check when unmounting while queued', () => { simulateEvent( 'focus' ); simulateEvent( 'input' ); - - ReactDOM.unmountComponentAtNode( - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.findDOMNode( wrapper ).parentNode - ); - jest.runAllTimers(); + root.unmount(); expect( onFocusOutside ).not.toHaveBeenCalled(); } ); diff --git a/packages/components/src/scroll-lock/test/index.js b/packages/components/src/scroll-lock/test/index.js index 4579806908c97f..d6ea8eb0a18b74 100644 --- a/packages/components/src/scroll-lock/test/index.js +++ b/packages/components/src/scroll-lock/test/index.js @@ -1,7 +1,7 @@ /** - * External dependencies + * WordPress dependencies */ -import { mount } from 'enzyme'; +import { createRoot } from '@wordpress/element'; /** * Internal dependencies @@ -12,7 +12,7 @@ describe( 'scroll-lock', () => { const lockingClassName = 'lockscroll'; // Use a separate document to reduce the risk of test side-effects. - let wrapper = null; + let root = null; function expectLocked( locked ) { expect( @@ -25,22 +25,28 @@ describe( 'scroll-lock', () => { } afterEach( () => { - if ( wrapper && wrapper.length ) { - wrapper.unmount(); - wrapper = null; + if ( root ) { + root.unmount(); } } ); it( 'locks when mounted', () => { expectLocked( false ); - wrapper = mount( ); + const container = document.createElement( 'div' ); + root = createRoot( container ); + root.render( ); + jest.runAllTimers(); expectLocked( true ); } ); it( 'unlocks when unmounted', () => { - wrapper = mount( ); + const container = document.createElement( 'div' ); + root = createRoot( container ); + root.render( ); + jest.runAllTimers(); expectLocked( true ); - wrapper.unmount(); + root.unmount(); + jest.runAllTimers(); // Running cleanup functions now works asynchronously. the unofficial // enzyme adapter for react 17 we're currently using does not account diff --git a/packages/components/src/tab-panel/test/index.js b/packages/components/src/tab-panel/test/index.js index b3d9bd0477685b..319cf168b768e2 100644 --- a/packages/components/src/tab-panel/test/index.js +++ b/packages/components/src/tab-panel/test/index.js @@ -11,36 +11,12 @@ import TabPanel from '../'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { createRoot } from '@wordpress/element'; describe( 'TabPanel', () => { - const getElementByClass = ( wrapper, className ) => { - return TestUtils.findRenderedDOMComponentWithClass( - wrapper, - className - ); - }; - - const getElementsByClass = ( wrapper, className ) => { - return TestUtils.scryRenderedDOMComponentsWithClass( - wrapper, - className - ); - }; - const elementClick = ( element ) => { TestUtils.Simulate.click( element ); - }; - - // This is needed because TestUtils does not accept a stateless component. - // anything run through a HOC ends up as a stateless component. - const getTestComponent = ( WrappedComponent, props ) => { - class TestComponent extends Component { - render() { - return ; - } - } - return ; + jest.runAllTimers(); }; describe( 'basic rendering', () => { @@ -74,31 +50,26 @@ describe( 'TabPanel', () => { }, }; - let wrapper; - TestUtils.act( () => { - wrapper = TestUtils.renderIntoDocument( - getTestComponent( TabPanel, props ) - ); - } ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); - const alphaTab = getElementByClass( wrapper, 'alpha' ); - const betaTab = getElementByClass( wrapper, 'beta' ); - const gammaTab = getElementByClass( wrapper, 'gamma' ); + const alphaTab = container.querySelector( '.alpha' ); + const betaTab = container.querySelector( '.beta' ); + const gammaTab = container.querySelector( '.gamma' ); const getAlphaViews = () => - getElementsByClass( wrapper, 'alpha-view' ); + container.querySelectorAll( '.alpha-view' ); const getBetaViews = () => - getElementsByClass( wrapper, 'beta-view' ); + container.querySelectorAll( '.beta-view' ); const getGammaViews = () => - getElementsByClass( wrapper, 'gamma-view' ); + container.querySelectorAll( '.gamma-view' ); - const getActiveTab = () => - getElementByClass( wrapper, 'active-tab' ); + const getActiveTab = () => container.querySelector( '.active-tab' ); const getActiveView = () => - getElementByClass( - wrapper, - 'components-tab-panel__tab-content' - ).firstChild.textContent; + container.querySelector( '.components-tab-panel__tab-content' ) + .firstChild.textContent; expect( getActiveTab().innerHTML ).toBe( 'Alpha' ); expect( getAlphaViews() ).toHaveLength( 1 ); @@ -166,14 +137,12 @@ describe( 'TabPanel', () => { }, }; - let wrapper; - TestUtils.act( () => { - wrapper = TestUtils.renderIntoDocument( - getTestComponent( TabPanel, props ) - ); - } ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); - const getActiveTab = () => getElementByClass( wrapper, 'active-tab' ); + const getActiveTab = () => container.querySelector( '.active-tab' ); expect( getActiveTab().innerHTML ).toBe( 'Beta' ); } ); } ); diff --git a/packages/components/src/text-highlight/test/index.js b/packages/components/src/text-highlight/test/index.js index 342a2e85950524..89e758593afbf6 100644 --- a/packages/components/src/text-highlight/test/index.js +++ b/packages/components/src/text-highlight/test/index.js @@ -1,25 +1,36 @@ /** * External dependencies */ -import { render, unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; +/** + * WordPress dependencies + */ +import { createRoot } from '@wordpress/element'; + /** * Internal dependencies */ import TextHighlight from '../index'; let container = null; +let root = null; beforeEach( () => { // Setup a DOM element as a render target. container = document.createElement( 'div' ); document.body.appendChild( container ); + root = createRoot( container ); + + // This is needed due to some kind of bug in JSDOM that conflicts with React 18. + global.IS_REACT_ACT_ENVIRONMENT = true; } ); afterEach( () => { // Cleanup on exiting. - unmountComponentAtNode( container ); + act( () => { + root.unmount(); + } ); container.remove(); container = null; } ); @@ -32,12 +43,11 @@ describe( 'Basic rendering', () => { 'should highlight the singular occurance of the text "%s" in the text if it exists', ( highlight ) => { act( () => { - render( + root.render( , - container + /> ); } ); @@ -57,9 +67,8 @@ describe( 'Basic rendering', () => { const highlight = 'edit'; act( () => { - render( - , - container + root.render( + ); } ); @@ -80,9 +89,8 @@ describe( 'Basic rendering', () => { const highlight = 'The'; // Note this occurs in both sentance of lowercase forms. act( () => { - render( - , - container + root.render( + ); } ); @@ -105,9 +113,8 @@ describe( 'Basic rendering', () => { const highlight = 'Antidisestablishmentarianism'; act( () => { - render( - , - container + root.render( + ); } ); diff --git a/packages/compose/src/higher-order/pure/test/index.js b/packages/compose/src/higher-order/pure/test/index.js index b3bcf24c4e5bc1..8facc2c4ca15d8 100644 --- a/packages/compose/src/higher-order/pure/test/index.js +++ b/packages/compose/src/higher-order/pure/test/index.js @@ -1,12 +1,7 @@ -/** - * External dependencies - */ -import { mount } from 'enzyme'; - /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, createRoot } from '@wordpress/element'; /** * Internal dependencies @@ -19,15 +14,20 @@ describe( 'pure', () => { const MyComp = pure( () => { return

{ ++i }

; } ); - const wrapper = mount( ); - wrapper.update(); // Updating with same props doesn't rerender. - expect( wrapper.html() ).toBe( '

1

' ); - wrapper.setProps( { prop: 'a' } ); // New prop should trigger a rerender. - expect( wrapper.html() ).toBe( '

2

' ); - wrapper.setProps( { prop: 'a' } ); // Keeping the same prop value should not rerender. - expect( wrapper.html() ).toBe( '

2

' ); - wrapper.setProps( { prop: 'b' } ); // Changing the prop value should rerender. - expect( wrapper.html() ).toBe( '

3

' ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

1

' ); + root.render( ); // New prop should trigger a rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

2

' ); + root.render( ); // Keeping the same prop value should not rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

2

' ); + root.render( ); // Changing the prop value should rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

3

' ); } ); it( 'class component should rerender if the props or state change', () => { @@ -43,18 +43,26 @@ describe( 'pure', () => { } } ); - const wrapper = mount( ); - wrapper.update(); // Updating with same props doesn't rerender. - expect( wrapper.html() ).toBe( '

1

' ); - wrapper.setProps( { prop: 'a' } ); // New prop should trigger a rerender. - expect( wrapper.html() ).toBe( '

2

' ); - wrapper.setProps( { prop: 'a' } ); // Keeping the same prop value should not rerender. - expect( wrapper.html() ).toBe( '

2

' ); - wrapper.setProps( { prop: 'b' } ); // Changing the prop value should rerender. - expect( wrapper.html() ).toBe( '

3

' ); - wrapper.setState( { state: 'a' } ); // New state value should trigger a rerender. - expect( wrapper.html() ).toBe( '

4

' ); - wrapper.setState( { state: 'a' } ); // Keeping the same state value should not trigger a rerender. - expect( wrapper.html() ).toBe( '

4

' ); + const ref = { current: null }; + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

1

' ); + root.render( ); // New prop should trigger a rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

2

' ); + root.render( ); // Keeping the same prop value should not rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

2

' ); + root.render( ); // Changing the prop value should rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

3

' ); + ref.current.setState( { state: 'a' } ); // New state value should trigger a rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

4

' ); + ref.current.setState( { state: 'a' } ); // Keeping the same state value should not trigger a rerender. + jest.runAllTimers(); + expect( container.innerHTML ).toBe( '

4

' ); } ); } ); diff --git a/packages/compose/src/higher-order/with-state/test/index.js b/packages/compose/src/higher-order/with-state/test/index.js index 16728b09d21a1b..9d1cf3fc6a3f2d 100644 --- a/packages/compose/src/higher-order/with-state/test/index.js +++ b/packages/compose/src/higher-order/with-state/test/index.js @@ -11,18 +11,7 @@ import withState from '../'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; - -// This is needed because TestUtils does not accept a stateless component. -// anything run through a HOC ends up as a stateless component. -const getTestComponent = ( WrappedComponent ) => { - class TestComponent extends Component { - render() { - return ; - } - } - return ; -}; +import { createRoot } from '@wordpress/element'; describe( 'withState', () => { it( 'should pass initial state and allow updates', () => { @@ -38,16 +27,16 @@ describe( 'withState', () => { ) ); - const wrapper = TestUtils.renderIntoDocument( - getTestComponent( EnhancedComponent ) - ); - - const buttonElement = () => - TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' ); + const container = document.createElement( 'div' ); + const root = createRoot( container ); + root.render( ); + jest.runAllTimers(); + const buttonElement = () => container.querySelector( 'button' ); expect( console ).toHaveWarned(); expect( buttonElement().outerHTML ).toBe( '' ); TestUtils.Simulate.click( buttonElement() ); + jest.runAllTimers(); expect( buttonElement().outerHTML ).toBe( '' ); } ); } ); diff --git a/packages/compose/src/hooks/use-focus-outside/test/index.js b/packages/compose/src/hooks/use-focus-outside/test/index.js index 23c7c36cf43e36..205327eb5e8e41 100644 --- a/packages/compose/src/hooks/use-focus-outside/test/index.js +++ b/packages/compose/src/hooks/use-focus-outside/test/index.js @@ -6,15 +6,14 @@ import TestUtils from 'react-dom/test-utils'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { createRoot } from '@wordpress/element'; /** * Internal dependencies */ import useFocusOutside from '../'; -import ReactDOM from 'react-dom'; -let wrapper, onFocusOutside; +let container, onFocusOutside, root; describe( 'useFocusOutside', () => { let origHasFocus; @@ -26,22 +25,8 @@ describe( 'useFocusOutside', () => { ); - // This is needed because TestUtils does not accept a stateless component. - // anything run through a HOC ends up as a stateless component. - const getTestComponent = ( WrappedComponent, props ) => { - class TestComponent extends Component { - render() { - return ; - } - } - return ; - }; - const simulateEvent = ( event, index = 0 ) => { - const element = TestUtils.scryRenderedDOMComponentsWithTag( - wrapper, - 'input' - ); + const element = container.querySelectorAll( 'input' ); TestUtils.Simulate[ event ]( element[ index ] ); }; @@ -52,9 +37,13 @@ describe( 'useFocusOutside', () => { document.hasFocus = () => true; onFocusOutside = jest.fn(); - wrapper = TestUtils.renderIntoDocument( - getTestComponent( FocusOutsideComponent, { onFocusOutside } ) + + container = document.createElement( 'div' ); + root = createRoot( container ); + root.render( + ); + jest.runAllTimers(); } ); afterEach( () => { @@ -112,11 +101,7 @@ describe( 'useFocusOutside', () => { simulateEvent( 'focus' ); simulateEvent( 'input' ); - ReactDOM.unmountComponentAtNode( - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.findDOMNode( wrapper ).parentNode - ); - + root.unmount(); jest.runAllTimers(); expect( onFocusOutside ).not.toHaveBeenCalled(); diff --git a/packages/compose/src/hooks/use-merge-refs/test/index.js b/packages/compose/src/hooks/use-merge-refs/test/index.js index 6893148462583a..24347316b74d7c 100644 --- a/packages/compose/src/hooks/use-merge-refs/test/index.js +++ b/packages/compose/src/hooks/use-merge-refs/test/index.js @@ -1,12 +1,7 @@ -/** - * External dependencies - */ -import ReactDOM from 'react-dom'; - /** * WordPress dependencies */ -import { useCallback } from '@wordpress/element'; +import { useCallback, createRoot } from '@wordpress/element'; /** * Internal dependencies @@ -80,8 +75,9 @@ describe( 'useMergeRefs', () => { it( 'should work', () => { const rootElement = document.getElementById( 'root' ); - - ReactDOM.render( , rootElement ); + const root = createRoot( rootElement ); + root.render( ); + jest.runAllTimers(); const originalElement = rootElement.firstElementChild; @@ -90,7 +86,8 @@ describe( 'useMergeRefs', () => { [ [ originalElement ], [ originalElement ] ], ] ); - ReactDOM.render( , rootElement ); + root.render( ); + jest.runAllTimers(); // Render 2: the new callback functions should not be called! There has // been no dependency change. @@ -99,7 +96,8 @@ describe( 'useMergeRefs', () => { [ [], [] ], ] ); - ReactDOM.render( null, rootElement ); + root.render( null ); + jest.runAllTimers(); // Unmount: the initial callback functions should receive null. expect( renderCallback.history ).toEqual( [ @@ -113,12 +111,14 @@ describe( 'useMergeRefs', () => { it( 'should work for node change', () => { const rootElement = document.getElementById( 'root' ); - - ReactDOM.render( , rootElement ); + const root = createRoot( rootElement ); + root.render( ); + jest.runAllTimers(); const originalElement = rootElement.firstElementChild; - ReactDOM.render( , rootElement ); + root.render( ); + jest.runAllTimers(); const newElement = rootElement.firstElementChild; @@ -135,7 +135,8 @@ describe( 'useMergeRefs', () => { [ [], [] ], ] ); - ReactDOM.render( null, rootElement ); + root.render( null ); + jest.runAllTimers(); // Unmount: the initial callback functions should receive null. expect( renderCallback.history ).toEqual( [ @@ -149,8 +150,9 @@ describe( 'useMergeRefs', () => { it( 'should work with dependencies', () => { const rootElement = document.getElementById( 'root' ); - - ReactDOM.render( , rootElement ); + const root = createRoot( rootElement ); + root.render( ); + jest.runAllTimers(); const originalElement = rootElement.firstElementChild; @@ -158,7 +160,8 @@ describe( 'useMergeRefs', () => { [ [ originalElement ], [ originalElement ] ], ] ); - ReactDOM.render( , rootElement ); + root.render( ); + jest.runAllTimers(); // After a second render with a dependency change, expect the inital // callback function to be called with null and the new callback @@ -169,7 +172,8 @@ describe( 'useMergeRefs', () => { [ [], [ originalElement ] ], ] ); - ReactDOM.render( null, rootElement ); + root.render( null ); + jest.runAllTimers(); // Unmount: current callback functions should be called with null. expect( renderCallback.history ).toEqual( [ @@ -183,8 +187,9 @@ describe( 'useMergeRefs', () => { it( 'should simultaneously update node and dependencies', () => { const rootElement = document.getElementById( 'root' ); - - ReactDOM.render( , rootElement ); + const root = createRoot( rootElement ); + root.render( ); + jest.runAllTimers(); const originalElement = rootElement.firstElementChild; @@ -192,10 +197,8 @@ describe( 'useMergeRefs', () => { [ [ originalElement ], [ originalElement ] ], ] ); - ReactDOM.render( - , - rootElement - ); + root.render( ); + jest.runAllTimers(); const newElement = rootElement.firstElementChild; @@ -212,7 +215,8 @@ describe( 'useMergeRefs', () => { [ [], [ newElement ] ], ] ); - ReactDOM.render( null, rootElement ); + root.render( null ); + jest.runAllTimers(); // Unmount: current callback functions should be called with null. expect( renderCallback.history ).toEqual( [ @@ -226,12 +230,14 @@ describe( 'useMergeRefs', () => { it( 'should work for dependency change after node change', () => { const rootElement = document.getElementById( 'root' ); - - ReactDOM.render( , rootElement ); + const root = createRoot( rootElement ); + root.render( ); + jest.runAllTimers(); const originalElement = rootElement.firstElementChild; - ReactDOM.render( , rootElement ); + root.render( ); + jest.runAllTimers(); const newElement = rootElement.firstElementChild; @@ -248,10 +254,8 @@ describe( 'useMergeRefs', () => { [ [], [] ], ] ); - ReactDOM.render( - , - rootElement - ); + root.render( ); + jest.runAllTimers(); // After a third render with a dependency change, expect the inital // callback function to be called with null and the new callback @@ -266,7 +270,8 @@ describe( 'useMergeRefs', () => { [ [], [ newElement ] ], ] ); - ReactDOM.render( null, rootElement ); + root.render( null ); + jest.runAllTimers(); // Unmount: current callback functions should be called with null. expect( renderCallback.history ).toEqual( [ @@ -281,8 +286,9 @@ describe( 'useMergeRefs', () => { it( 'should allow disabling a ref', () => { const rootElement = document.getElementById( 'root' ); - - ReactDOM.render( , rootElement ); + const root = createRoot( rootElement ); + root.render( ); + jest.runAllTimers(); const originalElement = rootElement.firstElementChild; @@ -291,7 +297,8 @@ describe( 'useMergeRefs', () => { [ [], [ originalElement ] ], ] ); - ReactDOM.render( , rootElement ); + root.render( ); + jest.runAllTimers(); // Render 2: ref 1 should be enabled and receive the ref. Note that the // callback hasn't changed, so the original callback function will be @@ -301,7 +308,8 @@ describe( 'useMergeRefs', () => { [ [], [] ], ] ); - ReactDOM.render( , rootElement ); + root.render( ); + jest.runAllTimers(); // Render 3: ref 1 should again be disabled. Ref 2 to should receive a // ref with the new callback function because the count has been @@ -315,7 +323,8 @@ describe( 'useMergeRefs', () => { [ [], [ originalElement ] ], ] ); - ReactDOM.render( null, rootElement ); + root.render( null ); + jest.runAllTimers(); // Unmount: current callback functions should receive null. expect( renderCallback.history ).toEqual( [ diff --git a/packages/element/src/react-platform.js b/packages/element/src/react-platform.js index 2c03e96b321070..08fb75b59abc32 100644 --- a/packages/element/src/react-platform.js +++ b/packages/element/src/react-platform.js @@ -6,8 +6,8 @@ import { findDOMNode, render, unmountComponentAtNode, - createRoot, } from 'react-dom'; +import { createRoot } from 'react-dom/client'; /** * Creates a portal into which a component can be rendered. diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 48ff95a4ce0e70..a658228b9a7281 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -26,7 +26,6 @@ import { useDebugValue, lazy, Suspense, - createRoot, } from 'react'; import { isString } from 'lodash';