diff --git a/package.json b/package.json index 9f9c54615f0..5164499712c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "lint-sass": "yarn stylelint \"**/*.scss\" --quiet-deprecation-warnings", "test": "yarn lint && yarn test-unit", "test-ci": "yarn test && yarn test-cypress", - "test-unit": "cross-env NODE_ENV=test jest --config ./scripts/jest/config.json", + "test-unit": "cross-env NODE_ENV=test jest --config ./scripts/jest/config.js", "test-a11y": "node ./scripts/a11y-testing", "test-staged": "yarn lint && node scripts/test-staged.js", "test-cypress": "node ./scripts/cypress", @@ -116,6 +116,7 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.5", "@babel/template": "^7.21.9", + "@cfaester/enzyme-adapter-react-18": "^0.7.0", "@cypress/code-coverage": "^3.10.0", "@cypress/react": "^7.0.3", "@cypress/react18": "^2.0.0", @@ -142,7 +143,8 @@ "@svgr/plugin-svgo": "^8.0.1", "@testing-library/dom": "^8.12.0", "@testing-library/jest-dom": "^5.16.3", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^14.0.0", + "@testing-library/react-16-17": "npm:@testing-library/react@^12.1.5", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", "@types/classnames": "^2.2.10", diff --git a/scripts/jest/config.js b/scripts/jest/config.js new file mode 100644 index 00000000000..7037b61de39 --- /dev/null +++ b/scripts/jest/config.js @@ -0,0 +1,69 @@ +const getCacheDirectory = + require('jest-config/build/getCacheDirectory').default; + +// Set REACT_VERSION env variable to latest if empty or invalid +if (!['16', '17', '18'].includes(process.env.REACT_VERSION)) { + process.env.REACT_VERSION = '18'; +} + +const reactVersion = process.env.REACT_VERSION; + +console.log(`Running tests on React v${reactVersion}`); + +/** @type {import('jest').Config} */ +const config = { + rootDir: '../../', + roots: [ + '/src/', + '/src-docs/src/components', + '/scripts/babel', + '/scripts/tests', + '/scripts/eslint-plugin', + ], + collectCoverageFrom: [ + 'src/{components,services,global_styling}/**/*.{ts,tsx,js,jsx}', + '!src/{components,services,global_styling}/**/*.{testenv,spec,a11y,stories}.{ts,tsx,js,jsx}', + '!src/{components,services,global_styling}/index.ts', + '!src/{components,services,global_styling}/**/*/index.ts', + '!src/components/date_picker/react-datepicker/**/*.{js,jsx}', + ], + moduleNameMapper: { + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/scripts/jest/mocks/file_mock.js', + '\\.(css|less|scss)$': '/scripts/jest/mocks/style_mock.js', + }, + setupFiles: [ + '/scripts/jest/setup/enzyme.js', + '/scripts/jest/setup/throw_on_console_error.js', + '/scripts/jest/setup/mocks.js', + ], + setupFilesAfterEnv: [ + '/scripts/jest/setup/polyfills.js', + '/scripts/jest/setup/unmount_enzyme.js', + ], + coverageDirectory: '/reports/jest-coverage', + coverageReporters: ['json', 'html'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], + testMatch: ['**/*.test.js', '**/*.test.ts', '**/*.test.tsx'], + transform: { + '^.+\\.(js|tsx?)$': 'babel-jest', + }, + snapshotSerializers: [ + '/node_modules/enzyme-to-json/serializer', + '/scripts/jest/setup/emotion', + ], + // react version and user permissions aware cache directory + cacheDirectory: `${getCacheDirectory()}_react-${reactVersion}`, +}; + +if (['16', '17'].includes(reactVersion)) { + config.moduleNameMapper[ + '^@testing-library/react((\\\\/.*)?)$' + ] = `@testing-library/react-16-17$1`; + config.moduleNameMapper['^react((\\/.*)?)$'] = `react-${reactVersion}$1`; + config.moduleNameMapper[ + '^react-dom((\\/.*)?)$' + ] = `react-dom-${reactVersion}$1`; +} + +module.exports = config; diff --git a/scripts/jest/config.json b/scripts/jest/config.json deleted file mode 100644 index e3b879ffce8..00000000000 --- a/scripts/jest/config.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "rootDir": "../../", - "roots": [ - "/src/", - "/src-docs/src/components", - "/scripts/babel", - "/scripts/tests", - "/scripts/eslint-plugin" - ], - "collectCoverageFrom": [ - "src/{components,services,global_styling}/**/*.{ts,tsx,js,jsx}", - "!src/{components,services,global_styling}/**/*.{testenv,spec,a11y,stories}.{ts,tsx,js,jsx}", - "!src/{components,services,global_styling}/index.ts", - "!src/{components,services,global_styling}/**/*/index.ts", - "!src/components/date_picker/react-datepicker/**/*.{js,jsx}" - ], - "moduleNameMapper": { - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/scripts/jest/mocks/file_mock.js", - "\\.(css|less|scss)$": "/scripts/jest/mocks/style_mock.js" - }, - "setupFiles": [ - "/scripts/jest/setup/enzyme.js", - "/scripts/jest/setup/throw_on_console_error.js", - "/scripts/jest/setup/mocks.js" - ], - "setupFilesAfterEnv": [ - "/scripts/jest/setup/polyfills.js", - "/scripts/jest/setup/unmount_enzyme.js" - ], - "coverageDirectory": "/reports/jest-coverage", - "coverageReporters": [ - "json", - "html" - ], - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "json" - ], - "testMatch": [ - "**/*.test.js", - "**/*.test.ts", - "**/*.test.tsx" - ], - "transform": { - "^.+\\.(js|tsx?)$": "babel-jest" - }, - "snapshotSerializers": [ - "/node_modules/enzyme-to-json/serializer", - "/scripts/jest/setup/emotion" - ] -} diff --git a/scripts/jest/setup/enzyme.js b/scripts/jest/setup/enzyme.js index b60dbe22bfe..334d2c91e74 100644 --- a/scripts/jest/setup/enzyme.js +++ b/scripts/jest/setup/enzyme.js @@ -1,4 +1,18 @@ import { configure } from 'enzyme'; -import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; + +// Pick Enzyme adapter based on which React version is currently being tested. +// It has to be directly compared against process.env.REACT_VERSION +// for tree-shaking to work. +let Adapter; +if (process.env.REACT_VERSION === '18') { + Adapter = require('@cfaester/enzyme-adapter-react-18').default; + + // set IS_REACT_ACT_ENVIRONMENT to silence "The current testing environment + // is not configured to support act()" errors for now + // TODO: Remove when enzyme tests are replaced with RTL + global.IS_REACT_ACT_ENVIRONMENT = true; +} else { + Adapter = require('@wojtekmaj/enzyme-adapter-react-17'); +} configure({ adapter: new Adapter() }); diff --git a/scripts/jest/setup/polyfills.js b/scripts/jest/setup/polyfills.js index 3619912b9a9..048df84a28f 100644 --- a/scripts/jest/setup/polyfills.js +++ b/scripts/jest/setup/polyfills.js @@ -1,9 +1,14 @@ import { MutationObserver, MutationNotifier } from '../polyfills/mutation_observer'; +import util from 'util'; // polyfill window.MutationObserver and intersect jsdom's relevant methods // from https://github.com/aurelia/pal-nodejs // https://github.com/aurelia/pal-nodejs/blob/56396ff7c7693669dbafc8e9e49ee6bc29472f12/src/nodejs-pal-builder.ts#L63 +Object.defineProperty(global, 'TextEncoder', { + value: util.TextEncoder, +}); + const undos = []; afterAll(() => { while (undos.length) { diff --git a/src/components/drag_and_drop/droppable.test.tsx b/src/components/drag_and_drop/droppable.test.tsx index e6117afa5eb..dcbe400a536 100644 --- a/src/components/drag_and_drop/droppable.test.tsx +++ b/src/components/drag_and_drop/droppable.test.tsx @@ -8,9 +8,13 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; +import { resetServerContext } from '@hello-pangea/dnd'; -import { findTestSubject } from '../../test'; -import { requiredProps } from '../../test/required_props'; +import { + findTestSubject, + requiredProps, + invokeOnReactVersion, +} from '../../test'; import { EuiDragDropContext, EuiDroppable } from './'; import { EuiDroppableContext } from './droppable'; @@ -24,6 +28,11 @@ function snapshotDragDropContext(component: ReactWrapper) { } describe('EuiDroppable', () => { + afterEach(() => { + // Resetting DND server context is only required in older versions of React + invokeOnReactVersion(['16', '17'], resetServerContext); + }); + test('is rendered', () => { const handler = jest.fn(); jest.mock('react', () => { diff --git a/src/components/form/form.styles.test.tsx b/src/components/form/form.styles.test.tsx index dc7c08ad8e6..6803be8e9fa 100644 --- a/src/components/form/form.styles.test.tsx +++ b/src/components/form/form.styles.test.tsx @@ -181,7 +181,7 @@ describe('euiCustomControl', () => { expect(result.current).toMatchInlineSnapshot(` " padding: 7px; - + border: 1px solid #f5f7fc; background: #FFF no-repeat center; @@ -200,7 +200,7 @@ describe('euiCustomControl', () => { expect(result.current).toMatchInlineSnapshot(` " padding: 15px; - + border: 1px solid #f5f7fc; background: #FFF no-repeat center; diff --git a/src/components/selectable/selectable_search/selectable_search.test.tsx b/src/components/selectable/selectable_search/selectable_search.test.tsx index 631f936cc95..1964471d86e 100644 --- a/src/components/selectable/selectable_search/selectable_search.test.tsx +++ b/src/components/selectable/selectable_search/selectable_search.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { render } from '../../../test/rtl'; import { requiredProps } from '../../../test/required_props'; diff --git a/src/test/index.ts b/src/test/index.ts index 96d6201dfab..b783039efcd 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -15,3 +15,4 @@ export { } from './react_warnings'; export { sleep } from './sleep'; export * from './emotion-prefix'; +export * from './react_version'; diff --git a/src/test/react_version.ts b/src/test/react_version.ts new file mode 100644 index 00000000000..bae0cb26d62 --- /dev/null +++ b/src/test/react_version.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type ReactVersion = '16' | '17' | '18'; + +/** + * Get major version of React that's currently used. + * + */ +export const getReactVersion = (): ReactVersion => { + const reactVersion = process.env.REACT_VERSION; + if (reactVersion !== undefined && ['16', '17', '18'].includes(reactVersion)) { + return reactVersion as ReactVersion; + } + + return '18'; +}; + +/** + * Invoke passed function when running on specified version(s) of React + */ +export const invokeOnReactVersion = ( + versionOrVersions: ReactVersion | ReactVersion[], + func: Function +) => { + if (!Array.isArray(versionOrVersions)) { + versionOrVersions = [versionOrVersions]; + } + + if (versionOrVersions.includes(getReactVersion())) { + func(); + } +}; diff --git a/yarn.lock b/yarn.lock index eb9fb063cb4..2ffae9ea66e 100755 --- a/yarn.lock +++ b/yarn.lock @@ -2183,6 +2183,15 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cfaester/enzyme-adapter-react-18@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cfaester/enzyme-adapter-react-18/-/enzyme-adapter-react-18-0.7.0.tgz#92d81903855ed542ab54d2241a787c1b95883d0a" + integrity sha512-24rdgq6ncwmuyHizvKpdPUSczx5PjIhSr+LfXH/q5/Y28WbNZB6iGIqQ2iSiXp41qs+lutRIQQgwRJIf/a1N9w== + dependencies: + enzyme-shallow-equal "^1.0.0" + react-is "^18.0.0" + react-test-renderer "^18.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -4292,6 +4301,20 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/dom@^9.0.0": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.1.tgz#8094f560e9389fb973fe957af41bf766937a9ee9" + integrity sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.1.3" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + "@testing-library/jest-dom@^5.16.3": version "5.16.4" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" @@ -4307,6 +4330,15 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-16-17@npm:@testing-library/react@^12.1.5": + version "12.1.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" + integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.0.0" + "@types/react-dom" "<18.0.0" + "@testing-library/react-hooks@^7.0.2": version "7.0.2" resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" @@ -4318,14 +4350,14 @@ "@types/react-test-renderer" ">=16.9.0" react-error-boundary "^3.1.0" -"@testing-library/react@^12.1.5": - version "12.1.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" - integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== +"@testing-library/react@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" + integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "<18.0.0" + "@testing-library/dom" "^9.0.0" + "@types/react-dom" "^18.0.0" "@testing-library/user-event@^13.2.1", "@testing-library/user-event@^13.5.0": version "13.5.0" @@ -4847,7 +4879,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@^18.2.6": +"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.6": version "18.2.6" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.6.tgz#ad621fa71a8db29af7c31b41b2ea3d8a6f4144d1" integrity sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A== @@ -17827,7 +17859,7 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0: +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -17920,7 +17952,7 @@ react-router@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-shallow-renderer@^16.13.1: +react-shallow-renderer@^16.13.1, react-shallow-renderer@^16.15.0: version "16.15.0" resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== @@ -17957,6 +17989,15 @@ react-test-renderer@^17.0.0: react-shallow-renderer "^16.13.1" scheduler "^0.20.2" +react-test-renderer@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.2.0.tgz#1dd912bd908ff26da5b9fca4fd1c489b9523d37e" + integrity sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA== + dependencies: + react-is "^18.2.0" + react-shallow-renderer "^16.15.0" + scheduler "^0.23.0" + react-view@^2.3.2: version "2.3.7" resolved "https://registry.yarnpkg.com/react-view/-/react-view-2.3.7.tgz#86cca759618b73094172416d0c9d276fb632a25c"