diff --git a/.eslintrc b/.eslintrc index 6029447ab00..24fadda116e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,7 +6,7 @@ parserOptions: settings: react: - version: "16.8" + version: "18.2" rules: no-prototype-builtins: 0 @@ -36,19 +36,6 @@ overrides: react/default-props-match-prop-types: 1 react/no-unused-prop-types: 1 require-jsdoc: 1 - - files: - - "packages/replacement-variable-editor/**/*.js" - rules: - react/jsx-no-bind: 1 - react/require-default-props: 1 - - files: - - "packages/search-metadata-previews/**/*.js" - rules: - react/jsx-no-bind: 1 - react/no-unused-state: 1 - react/no-access-state-in-setstate: 1 - react/require-default-props: 1 - react/default-props-match-prop-types: 1 - files: - "packages/yoastseo/**/*.js" settings: diff --git a/package.json b/package.json index eeea6317174..8ce4e5ff053 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,10 @@ "prestart": "grunt build:css && grunt copy:js-dependencies", "start": "wp-scripts start --config config/webpack/webpack.config.js" }, - "dependencies": {}, + "dependencies": { + "@draft-js-plugins/mention": "^5.0.0", + "draft-js": "^0.11.7" + }, "devDependencies": { "@babel/core": "^7.18.5", "@slack/webhook": "^5.0.2", @@ -56,9 +59,6 @@ "core-js": "^2.6.12", "cross-env": "^7.0.3", "dotenv": "^8.2.0", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.6", - "enzyme-to-json": "^3.6.1", "eslint": "^7.32.0", "eslint-config-yoast": "^6.0.0", "eslint-plugin-jsx-a11y": "^6.4.1", @@ -74,9 +74,6 @@ "node-fetch": "^2.6.1", "postcss": "^8.4.14", "postcss-cli": "^9.1.0", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-test-renderer": "^16.14.0", "readline-sync": "^1.4.9", "shusher": "^0.1.1", "tmp": "^0.1.0", diff --git a/packages/analysis-report/package.json b/packages/analysis-report/package.json index 930a7e657fd..33f04cfa74d 100644 --- a/packages/analysis-report/package.json +++ b/packages/analysis-report/package.json @@ -25,7 +25,9 @@ "@yoast/style-guide": "^0.13.0", "lodash": "^4.17.11", "prop-types": "^15.6.0", - "styled-components": "^5.3.6" + "styled-components": "^5.3.6", + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@babel/core": "^7.17.10", @@ -39,11 +41,7 @@ "eslint-plugin-react": "^7.29.4", "jest": "^27.5.1", "jest-styled-components": "^7.0.8", - "react-test-renderer": "^16.14.0" - }, - "peerDependencies": { - "react": "^16.12.0", - "react-dom": "^16.12.0" + "react-test-renderer": "^18.2.0" }, "publishConfig": { "access": "public" diff --git a/packages/components/jest/setupTests.js b/packages/components/jest/setupTests.js index ae2c5706e15..c2212b57c11 100644 --- a/packages/components/jest/setupTests.js +++ b/packages/components/jest/setupTests.js @@ -1,6 +1,2 @@ import "raf/polyfill"; import "jest-styled-components"; -import Enzyme from "enzyme"; -import EnzymeAdapter from "enzyme-adapter-react-16"; - -Enzyme.configure( { adapter: new EnzymeAdapter() } ); diff --git a/packages/components/package.json b/packages/components/package.json index f0083d1ae21..e6f41eb824d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,6 +36,8 @@ "interpolate-components": "^1.1.1", "lodash": "^4.17.11", "prop-types": "^15.7.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-modal": "^3.8.1", "react-select": "^3.1.0", "react-tabs": "^2.3.0", @@ -44,15 +46,9 @@ "devDependencies": { "@yoast/browserslist-config": "^1.2.2", "browserslist": "^4.7.3", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.6", "jest-styled-components": "^7.0.3", "raf": "^3.4.0", - "react-test-renderer": "^16.14.0" - }, - "peerDependencies": { - "react": "^16.14.0", - "react-dom": "^16.14.0" + "react-test-renderer": "^18.2.0" }, "publishConfig": { "access": "public" diff --git a/packages/components/tests/ScreenReaderShortcutTest.js b/packages/components/tests/ScreenReaderShortcutTest.js deleted file mode 100644 index 36fe38a237e..00000000000 --- a/packages/components/tests/ScreenReaderShortcutTest.js +++ /dev/null @@ -1,70 +0,0 @@ -jest.unmock( "../src/a11y/ScreenReaderShortcut" ); -jest.unmock( "prop-types" ); - -import React from "react"; -import ReactShallowRenderer from "react-test-renderer/shallow"; -import ScreenReaderShortcut from "../src/a11y/ScreenReaderShortcut"; -import Styles from "../src/a11y/Styles"; -import { shallow } from "enzyme"; - -describe( "ScreenReaderShortcut", () => { - const renderer = new ReactShallowRenderer(); - - it( "generates a ScreenReaderShortcut div based on the props", () => { - renderer.render( example text ); - - const result = renderer.getRenderOutput(); - - expect( result.type ).toBe( "a" ); - expect( result.props.children ).toBe( "example text" ); - expect( result.props.style ).toBe( Styles.ScreenReaderText.default ); - expect( result.props.href ).toBe( "#example" ); - expect( result.props.className ).toBe( "screen-reader-shortcut" ); - } ); - - it( "has the default styling when it's blurred and the focused styling when focused", () => { - const result = shallow( example text ); - expect( result.prop( "style" ) ).toBe( Styles.ScreenReaderText.default ); - result.find( "a" ).simulate( "focus" ); - expect( result.prop( "style" ) ).toBe( Styles.ScreenReaderText.focused ); - result.find( "a" ).simulate( "blur" ); - expect( result.prop( "style" ) ).toBe( Styles.ScreenReaderText.default ); - } ); - - it( "generates a warning when props.children is not a string.", () => { - console.error = jest.fn(); - renderer.render(
); - - expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `children` of type `object` supplied to " + - "`ScreenReaderShortcut`, expected `string`." ); - } ); - - it( "generates a warning when props.anchor is not a string.", () => { - console.error = jest.fn(); - renderer.render( example text ); - - expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `anchor` of type `number` supplied to `ScreenReaderShortcut`, expected `string`." ); - } ); - - it( "generates a warning when no children are passed in.", () => { - console.error = jest.fn(); - renderer.render( ); - - expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type" ); - } ); - - it( "generates a warning when no anchor prop is passed in.", () => { - console.error = jest.fn(); - renderer.render( example text ); - - expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type" ); - } ); -} ); diff --git a/packages/components/tests/ScreenReaderTextTest.js b/packages/components/tests/ScreenReaderTextTest.js index 65e8f5556fd..4e939c5727a 100644 --- a/packages/components/tests/ScreenReaderTextTest.js +++ b/packages/components/tests/ScreenReaderTextTest.js @@ -25,8 +25,10 @@ describe( "ScreenReaderText", () => { renderer.render(
); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `children` of type `object` supplied to `ScreenReaderText`, expected `string`." ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ) + .toBe( "Invalid prop `children` of type `object` supplied to `ScreenReaderText`, expected `string`." ); } ); it( "generates a warning when no children are passed in.", () => { @@ -34,7 +36,9 @@ describe( "ScreenReaderText", () => { renderer.render( ); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type" ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ) + .toBe( "The prop `children` is marked as required in `ScreenReaderText`, but its value is `undefined`." ); } ); } ); diff --git a/packages/components/tests/YoastModalTest.js b/packages/components/tests/YoastModalTest.js deleted file mode 100644 index 78b755c29cf..00000000000 --- a/packages/components/tests/YoastModalTest.js +++ /dev/null @@ -1,77 +0,0 @@ -import React from "react"; -import { mount } from "enzyme"; - -import Modal from "../src/Modal"; - -describe( "Modal", () => { - test( "the Modal renders its children when open", () => { - const hook = document.createElement( "div" ); - - const wrapper = mount( - {} } - modalAriaLabel="Some label" - > - Hi! - - ); - - // Get the real generated HTML excluding the wrapper components. - const modal = wrapper.find( "div.yoast-modal__overlay" ); - expect( modal.find( ".yoast-modal__inside" ).text() ).toBe( "Hi!" ); - } ); - - test( "the Modal has a role dialog and aria label", () => { - const hook = document.createElement( "div" ); - - const wrapper = mount( - {} } - modalAriaLabel="Some label" - > - Hi! - - ); - - // Get the real generated HTML excluding the wrapper components. - const modalContent = wrapper.find( "div.yoast-modal__overlay .yoast-modal__content" ); - expect( modalContent.getDOMNode().getAttribute( "role" ) ).toBe( "dialog" ); - expect( modalContent.getDOMNode().getAttribute( "aria-label" ) ).toBe( "Some label" ); - } ); - - test( "the Modal renders its default elements", () => { - const hook = document.createElement( "div" ); - - const wrapper = mount( - {} } - modalAriaLabel="Some label" - heading="title" - closeIconButton="Close X" - closeButton="Close" - className="my-modal" - > - Hello! - - ); - - // Get the real generated HTML excluding the wrapper components. - const modal = wrapper.find( "div.yoast-modal__overlay" ); - - /* - * Note: `styled-components` wraps the DOM nodes and also passes class names e.g. - * - *

- * so we need to get the real DOM element. - */ - expect( modal.find( "h1.yoast-modal__title" ).text() ).toBe( "title" ); - expect( modal.find( "button.yoast-modal__button-close-icon" ).getDOMNode().getAttribute( "aria-label" ) ).toBe( "Close X" ); - expect( modal.find( "button.yoast-modal__button-close" ).text() ).toBe( "Close" ); - } ); -} ); diff --git a/packages/components/tests/__snapshots__/WordListTest.js.snap b/packages/components/tests/__snapshots__/WordListTest.js.snap index 69a41b1c7aa..547b289d860 100644 --- a/packages/components/tests/__snapshots__/WordListTest.js.snap +++ b/packages/components/tests/__snapshots__/WordListTest.js.snap @@ -9,11 +9,9 @@ exports[`WordList renders correctly without items 1`] = ` Wordlist

-
    -

`; @@ -26,7 +24,6 @@ exports[`WordList renders wordlist as list items 1`] = ` Wordlist

-
    @@ -46,6 +43,5 @@ exports[`WordList renders wordlist as list items 1`] = ` word3
-
`; diff --git a/packages/components/tests/button/ButtonsTest.js b/packages/components/tests/button/ButtonsTest.js index 0598cc66ac2..1f88ef30da7 100644 --- a/packages/components/tests/button/ButtonsTest.js +++ b/packages/components/tests/button/ButtonsTest.js @@ -75,8 +75,9 @@ describe( "Button", () => { ); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `variant` of value `[object Object]` supplied to `Button`" ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ).toContain( "Invalid prop `variant` of value `[object Object]` supplied to `Button`" ); } ); } ); diff --git a/packages/components/tests/inputTest.js b/packages/components/tests/inputTest.js index 66d84714a4c..e5d1801332f 100644 --- a/packages/components/tests/inputTest.js +++ b/packages/components/tests/inputTest.js @@ -39,8 +39,9 @@ describe( "Input", () => { renderer.render( ); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `type` of value `invalidType` supplied to `Input`" ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ).toContain( "Invalid prop `type` of value `invalidType` supplied to `Input`" ); } ); it( "generates an input based on the defaults and additional, optional attributes", () => { @@ -72,7 +73,8 @@ describe( "Input", () => { renderer.render( ); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `onChange` of type `number` supplied to `Input`, expected `function`." ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ).toBe( "Invalid prop `onChange` of type `number` supplied to `Input`, expected `function`." ); } ); } ); diff --git a/packages/components/tests/inputs/TextAreaTest.js b/packages/components/tests/inputs/TextAreaTest.js index 94f9b7721ad..de45d654863 100644 --- a/packages/components/tests/inputs/TextAreaTest.js +++ b/packages/components/tests/inputs/TextAreaTest.js @@ -42,7 +42,8 @@ describe( "TextArea", () => { ); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `id` of type `boolean` supplied to `TextArea`, expected `string`." ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ).toBe( "Invalid prop `id` of type `boolean` supplied to `TextArea`, expected `string`." ); } ); } ); diff --git a/packages/components/tests/inputs/TextInputTest.js b/packages/components/tests/inputs/TextInputTest.js index f9ce842cef3..4f629d5c991 100644 --- a/packages/components/tests/inputs/TextInputTest.js +++ b/packages/components/tests/inputs/TextInputTest.js @@ -57,8 +57,9 @@ describe( "TextInput", () => { renderer.render( ); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `type` of value `invalidType` supplied to `TextInput`" ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ).toContain( "Invalid prop `type` of value `invalidType` supplied to `TextInput`" ); } ); it( "generates an input based on the defaults and additional, optional attributes", () => { @@ -88,7 +89,9 @@ describe( "TextInput", () => { renderer.render( ); expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type: Invalid prop `onChange` of type `number` supplied to `TextInput`, expected `function`." ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ) + .toBe( "Invalid prop `onChange` of type `number` supplied to `TextInput`, expected `function`." ); } ); } ); diff --git a/packages/components/tests/labelTest.js b/packages/components/tests/labelTest.js index 407475ae764..64734f49ebe 100644 --- a/packages/components/tests/labelTest.js +++ b/packages/components/tests/labelTest.js @@ -21,8 +21,9 @@ describe( "A Label component", () => { expect( console.error ).toBeCalled(); - expect( console.error.mock.calls[ 0 ][ 0 ] ) - .toContain( "Warning: Failed prop type" ); + expect( console.error.mock.calls[ 0 ][ 0 ] ).toBe( "Warning: Failed %s type: %s%s" ); + expect( console.error.mock.calls[ 0 ][ 1 ] ).toBe( "prop" ); + expect( console.error.mock.calls[ 0 ][ 2 ] ).toBe( "The prop `for` is marked as required in `Label`, but its value is `undefined`." ); } ); it( "generates a warning when a faulty htmlFor prop is passed", () => { @@ -31,8 +32,9 @@ describe( "A Label component", () => { renderer.render(