diff --git a/.babelrc.js b/.babelrc.js index a351546b033..a9f5b16ff00 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -11,8 +11,8 @@ module.exports = { "corejs": !process.env.NO_COREJS_POLYFILL ? '3.6' : undefined, "modules": process.env.BABEL_MODULES ? process.env.BABEL_MODULES === 'false' ? false : process.env.BABEL_MODULES : "commonjs" // babel's default is commonjs }], - ["@babel/typescript", { isTSX: true, allExtensions: true }], - "@babel/react", + ["@babel/react", { runtime: 'classic' }], + ["@babel/typescript", { isTSX: true, allExtensions: true, allowDeclareFields: true }], [ "@emotion/babel-preset-css-prop", { diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index b2d2eb20f2b..120cbb74de4 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -1,8 +1,7 @@ -import type { ReactNode } from 'react'; -import { mount } from 'cypress/react'; import { ContextObject, Result, RunOptions } from 'axe-core'; import { realPress } from 'cypress-real-events/commands/realPress'; -import type { EuiProviderProps } from '../../src/components/provider'; +import type { mountCommand } from './setup/mount'; +import type { realMountCommand } from './setup/realMount'; type KeyOrShortcut = Parameters[0]; type RealPressOptions = Parameters[1]; @@ -32,16 +31,13 @@ declare global { /** * Mounts components with a basic `EuiProvider` wrapper */ - mount: ( - children: ReactNode, - options?: { providerProps?: Partial> } - ) => ReturnType; + mount: mountCommand; /** * This ensures the correct testing window has focus when using Cypress Real Events. * @see https://github.com/dmtrKovalenko/cypress-real-events/issues/196 */ - realMount: typeof mount; + realMount: realMountCommand; /** * Repeat the Real Events `realPress()` method 2 or more times diff --git a/cypress/support/setup/mount.js b/cypress/support/setup/mount.js deleted file mode 100644 index c0f7edf3e3f..00000000000 --- a/cypress/support/setup/mount.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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. - */ - -import React from 'react'; -import { mount as cypressMount } from 'cypress/react'; -import { EuiProvider } from '../../../src'; - -Cypress.Commands.add('mount', (children, options = {}) => { - const { providerProps } = options; - return cypressMount({children}); -}); diff --git a/cypress/support/setup/mount.tsx b/cypress/support/setup/mount.tsx new file mode 100644 index 00000000000..a37ef2555af --- /dev/null +++ b/cypress/support/setup/mount.tsx @@ -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. + */ + +import React, { ReactNode } from 'react'; +import { EuiProvider, EuiProviderProps } from '../../../src'; +import type { mount } from '@cypress/react18'; + +// Pick cypress mount function 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 and not throw an error because of a missing import. +let cypressMount: typeof mount; +if (process.env.REACT_VERSION === '18') { + cypressMount = require('@cypress/react18').mount; +} else { + cypressMount = require('@cypress/react').mount; +} + +export interface MountOptions { + providerProps?: Partial>; +} + +const mountCommand = ( + children: ReactNode, + options: MountOptions = {} +): ReturnType => { + const { providerProps } = options; + return cypressMount({children}); +}; + +// Export only the type to not confuse code-completion tools +export type mountCommand = typeof mountCommand; + +Cypress.Commands.add('mount', mountCommand); diff --git a/cypress/support/setup/realMount.js b/cypress/support/setup/realMount.tsx similarity index 69% rename from cypress/support/setup/realMount.js rename to cypress/support/setup/realMount.tsx index 0324084d970..cb114cb5dcf 100644 --- a/cypress/support/setup/realMount.js +++ b/cypress/support/setup/realMount.tsx @@ -6,21 +6,26 @@ * Side Public License, v 1. */ -import { React, Fragment } from 'react'; +import React, { ReactNode } from 'react'; import './mount'; -Cypress.Commands.add('realMount', (children) => { +const realMountCommand = (children: ReactNode) => { cy.mount( - + <>
{children} - + ).then(() => { cy.get('[data-test-subj="cypress-real-event-target"]').realClick({ position: 'topLeft', }); }); -}); +}; + +// Export only the type to not confuse code-completion tools +export type realMountCommand = typeof realMountCommand; + +Cypress.Commands.add('realMount', realMountCommand); diff --git a/cypress/webpack.config.js b/cypress/webpack.config.js index b026d3515ef..c7704a93e3f 100644 --- a/cypress/webpack.config.js +++ b/cypress/webpack.config.js @@ -12,6 +12,15 @@ const { ProvidePlugin, DefinePlugin } = require('webpack'); const THEME_IMPORT = `'../../dist/eui_theme_${process.env.THEME}.css'`; +const alias = {}; +const reactVersion = process.env.REACT_VERSION || '18'; + +// Setup module aliasing when we're testing an older React version +if (['16', '17'].includes(reactVersion)) { + alias.react = `react-${reactVersion}`; + alias['react-dom'] = `react-dom-${reactVersion}`; +} + module.exports = { mode: 'development', @@ -26,6 +35,7 @@ module.exports = { os: false, process: require.resolve('process/browser'), }, + alias, }, module: { @@ -46,9 +56,9 @@ module.exports = { loader: 'style-loader', options: { insert: 'meta[name="css-styles"]', - } + }, }, - 'css-loader' + 'css-loader', ], exclude: /node_modules/, }, @@ -62,7 +72,8 @@ module.exports = { }), new DefinePlugin({ - THEME_IMPORT, // allow cypress/suport/index.js to require the correct css file + THEME_IMPORT, // allow cypress/support/component.tsx to require the correct css file + 'process.env.REACT_VERSION': JSON.stringify(reactVersion), }), ], }; diff --git a/package.json b/package.json index a44cae8822e..eab9ac31b22 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", @@ -56,17 +56,18 @@ }, "resolutions": { "**/prismjs": "1.27.0", - "**/react": "^17.0.0", + "**/react": "^18", + "**/@types/react": "^18", "react-focus-lock": "^2.9.5" }, "pre-commit": [ "test-staged" ], "dependencies": { + "@hello-pangea/dnd": "^16.2.0", "@types/chroma-js": "^2.0.0", "@types/lodash": "^4.14.194", "@types/numeral": "^0.0.28", - "@types/react-beautiful-dnd": "^13.1.2", "@types/react-input-autosize": "^2.2.1", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", @@ -79,7 +80,6 @@ "mdast-util-to-hast": "^10.0.0", "numeral": "^2.0.6", "prop-types": "^15.6.0", - "react-beautiful-dnd": "^13.1.0", "react-dropzone": "^11.5.3", "react-element-to-jsx-string": "^14.3.4", "react-focus-on": "^3.9.1", @@ -116,8 +116,10 @@ "@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": "^5.10.3", + "@cypress/react": "^7.0.3", + "@cypress/react18": "^2.0.0", "@cypress/webpack-dev-server": "^1.7.0", "@elastic/charts": "^53.1.1", "@elastic/datemath": "^5.0.3", @@ -139,17 +141,18 @@ "@svgr/core": "8.0.0", "@svgr/plugin-jsx": "^8.0.1", "@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/cheerio": "^0.22.31", "@types/classnames": "^2.2.10", "@types/enzyme": "^3.10.5", "@types/jest": "^24.0.6", "@types/node": "^10.17.5", - "@types/react": "^17.0.38", - "@types/react-dom": "^17.0.11", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", "@types/react-is": "^17.0.3", "@types/react-router-dom": "^5.3.3", "@types/tabbable": "^3.1.2", @@ -225,9 +228,13 @@ "prop-types": "^15.6.0", "puppeteer": "^5.5.0", "raw-loader": "^4.0.1", - "react": "^17.0.2", + "react": "^18.2.0", + "react-16": "npm:react@^16.14.0", + "react-17": "npm:react@^17.0.2", "react-docgen-typescript": "^2.2.2", - "react-dom": "^17.0.2", + "react-dom": "^18.2.0", + "react-dom-16": "npm:react-dom@^16.14.0", + "react-dom-17": "npm:react-dom@^17.0.2", "react-helmet": "^6.1.0", "react-redux": "^7.2.1", "react-refresh": "^0.11.0", @@ -261,12 +268,12 @@ "@elastic/datemath": "^5.0.2", "@emotion/css": "11.x", "@emotion/react": "11.x", - "@types/react": "^16.9 || ^17.0", - "@types/react-dom": "^16.9 || ^17.0", + "@types/react": "^16.9 || ^17.0 || ^18.0", + "@types/react-dom": "^16.9 || ^17.0 || ^18.0", "moment": "^2.13.0", "prop-types": "^15.5.0", - "react": "^16.12 || ^17.0", - "react-dom": "^16.12 || ^17.0", + "react": "^16.12 || ^17.0 || ^18.0", + "react-dom": "^16.12 || ^17.0 || ^18.0", "typescript": "~4.5.3" }, "files": [ diff --git a/scripts/babel/proptypes-from-ts-props/index.js b/scripts/babel/proptypes-from-ts-props/index.js index eae3c76871a..442670e3cd2 100644 --- a/scripts/babel/proptypes-from-ts-props/index.js +++ b/scripts/babel/proptypes-from-ts-props/index.js @@ -10,7 +10,7 @@ function stripTypeScript(filename, ast) { return babelCore.transform(babelCore.transformFromAst(ast).code, { filename: filename, babelrc: false, - presets: ['@babel/typescript'], + presets: [['@babel/typescript', { allowDeclareFields: true }]], }).code; } diff --git a/scripts/cypress.js b/scripts/cypress.js index 714e322b7ba..04ba81e7673 100644 --- a/scripts/cypress.js +++ b/scripts/cypress.js @@ -21,12 +21,18 @@ const argv = yargs(hideBin(process.argv)) dev: { type: 'boolean' }, theme: { type: 'string', default: 'light', choices: ['light', 'dark'] }, a11y: { type: 'boolean' }, + 'react-version': { + type: 'number', + default: 18, + choices: [16, 17, 18], + }, }).argv; const isDev = argv.hasOwnProperty('dev'); const isA11y = argv.hasOwnProperty('a11y'); const skipScss = argv.hasOwnProperty('skip-css'); const theme = argv.theme; +const reactVersion = argv['react-version']; const info = chalk.white; const log = chalk.grey; @@ -54,11 +60,13 @@ const cypressCommandParts = [ `THEME=${theme}`, // pass the theme 'BABEL_MODULES=false', // let webpack receive ES Module code 'NODE_ENV=cypress_test', // enable code coverage checks + `REACT_VERSION=${reactVersion}`, // set react version to test `cypress ${testParams}`, ...argv._, // pass any extra options given to this script ]; const cypressCommand = cypressCommandParts.join(' '); +console.log(info(`Running tests on React v${reactVersion}`)); console.log(info(`${isDev ? 'Opening' : 'Running'} cypress`)); console.log(log(cypressCommand)); execSync(cypressCommand, { diff --git a/scripts/jest/config.js b/scripts/jest/config.js new file mode 100644 index 00000000000..3174d93b6cb --- /dev/null +++ b/scripts/jest/config.js @@ -0,0 +1,70 @@ +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/src-docs/src/components/codesandbox/link.js b/src-docs/src/components/codesandbox/link.js index b3a2c9bb7bf..2623b99227c 100644 --- a/src-docs/src/components/codesandbox/link.js +++ b/src-docs/src/components/codesandbox/link.js @@ -166,8 +166,8 @@ import '@elastic/charts/dist/theme_only_${colorMode}.css';` }, 'index.js': { content: `import '${cssFile}'; -import ReactDOM from 'react-dom'; import React from 'react'; +import { createRoot } from 'react-dom/client'; import createCache from '@emotion/cache'; import { EuiProvider } from '@elastic/eui'; @@ -179,11 +179,11 @@ const cache = createCache({ }); cache.compat = true; -ReactDOM.render( +const root = createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + );`, }, /* 4 */ diff --git a/src-docs/src/components/guide_tabbed_page/guide_tabbed_page.tsx b/src-docs/src/components/guide_tabbed_page/guide_tabbed_page.tsx index fbdcbf40e2a..78c9fce25ed 100644 --- a/src-docs/src/components/guide_tabbed_page/guide_tabbed_page.tsx +++ b/src-docs/src/components/guide_tabbed_page/guide_tabbed_page.tsx @@ -1,4 +1,9 @@ -import React, { FunctionComponent, ReactNode, useContext } from 'react'; +import React, { + FunctionComponent, + PropsWithChildren, + ReactNode, + useContext, +} from 'react'; import { Switch, Route, @@ -17,19 +22,20 @@ import { import { LanguageSelector, ThemeContext } from '../with_theme'; import { GuideSection } from '../guide_section/guide_section'; -export type GuideTabbedPageProps = CommonProps & { - description?: ReactNode; - guidelines?: ReactNode; - intro?: ReactNode; - isBeta?: boolean; - isNew?: boolean; - notice?: ReactNode; - pages?: any; - rightSideItems?: ReactNode[]; - showThemeLanguageToggle?: boolean; - tabs?: any; - title: string; -}; +export type GuideTabbedPageProps = PropsWithChildren & + CommonProps & { + description?: ReactNode; + guidelines?: ReactNode; + intro?: ReactNode; + isBeta?: boolean; + isNew?: boolean; + notice?: ReactNode; + pages?: any; + rightSideItems?: ReactNode[]; + showThemeLanguageToggle?: boolean; + tabs?: any; + title: string; + }; export const GuideTabbedPage: FunctionComponent = ({ description, diff --git a/src-docs/src/components/with_theme/theme_context.tsx b/src-docs/src/components/with_theme/theme_context.tsx index 66ce79b8fec..fc16b9a5358 100644 --- a/src-docs/src/components/with_theme/theme_context.tsx +++ b/src-docs/src/components/with_theme/theme_context.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { EUI_THEMES, EUI_THEME } from '../../../../src/themes'; // @ts-ignore importing from a JS file import { applyTheme } from '../../services'; @@ -45,7 +45,7 @@ interface State { export const ThemeContext = React.createContext(defaultState); -export class ThemeProvider extends React.Component { +export class ThemeProvider extends React.Component { constructor(props: object) { super(props); diff --git a/src-docs/src/index.js b/src-docs/src/index.js index 4f869e44d9b..ed987c28327 100644 --- a/src-docs/src/index.js +++ b/src-docs/src/index.js @@ -1,5 +1,5 @@ -import React, { createElement } from 'react'; -import ReactDOM from 'react-dom'; +import React, { createElement, Fragment, StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; import { Helmet } from 'react-helmet'; @@ -22,6 +22,10 @@ registerTheme('dark', [themeDark]); // Set up app +// Whether the docs app should be wrapped in +const strictModeEnabled = false; +const StrictModeWrapper = strictModeEnabled ? StrictMode : Fragment; + const store = configureStore(); const childRoutes = [].concat(Routes.getAppRoutes()); @@ -40,130 +44,133 @@ const routes = [ ...childRoutes, ]; -ReactDOM.render( - - - - - - {routes.map( - ({ - name, - path, - sections, - isBeta, - isNew, - component, - from, - to, - }) => { - const meta = ( - - {`${name} - Elastic UI Framework`} - - ); - const mainComponent = ( - { - const { location } = props; - // prevents encoded urls with a section id to fail - if (location.pathname.includes('%23')) { - const url = decodeURIComponent(location.pathname); - return ; - } else { - return ( - - {({ theme }) => ( - <> - {meta} - {createElement(component, { - selectedTheme: theme, - title: name, - })} - - )} - - ); - } - }} - /> - ); +const root = createRoot(document.getElementById('guide')); - const standaloneSections = []; - (sections || []).forEach( - ({ id, fullScreen, sections: subSections }) => { - if (fullScreen) { - const { slug, demo } = fullScreen; - standaloneSections.push( - ( - + + + + + + {routes.map( + ({ + name, + path, + sections, + isBeta, + isNew, + component, + from, + to, + }) => { + const meta = ( + + {`${name} - Elastic UI Framework`} + + ); + const mainComponent = ( + { + const { location } = props; + // prevents encoded urls with a section id to fail + if (location.pathname.includes('%23')) { + const url = decodeURIComponent(location.pathname); + return ; + } else { + return ( + - {meta} - {demo} - - )} - /> - ); - } - if (subSections) { - subSections.forEach(({ fullScreen, id: sectionId }) => { - if (fullScreen) { - const { slug, demo } = fullScreen; - standaloneSections.push( - ( - + {({ theme }) => ( + <> {meta} - {demo} - + {createElement(component, { + selectedTheme: theme, + title: name, + })} + )} - /> + ); } - }); + }} + /> + ); + + const standaloneSections = []; + (sections || []).forEach( + ({ id, fullScreen, sections: subSections }) => { + if (fullScreen) { + const { slug, demo } = fullScreen; + standaloneSections.push( + ( + + {meta} + {demo} + + )} + /> + ); + } + if (subSections) { + subSections.forEach(({ fullScreen, id: sectionId }) => { + if (fullScreen) { + const { slug, demo } = fullScreen; + standaloneSections.push( + ( + + {meta} + {demo} + + )} + /> + ); + } + }); + } } - } - ); - standaloneSections.filter((x) => !!x); + ); + standaloneSections.filter((x) => !!x); - // place standaloneSections before mainComponent so their routes take precedent - const routes = [...standaloneSections, mainComponent]; + // place standaloneSections before mainComponent so their routes take precedent + const routes = [...standaloneSections, mainComponent]; - if (from) - return [ - ...routes, - - - , - ]; - else if (component) return routes; - return null; - } - )} - - - - - , - document.getElementById('guide') + if (from) + return [ + ...routes, + + + , + ]; + else if (component) return routes; + return null; + } + )} + + + + + + ); diff --git a/src-docs/src/views/datagrid/advanced/custom_renderer.tsx b/src-docs/src/views/datagrid/advanced/custom_renderer.tsx index 64a04678e73..142df63ec57 100644 --- a/src-docs/src/views/datagrid/advanced/custom_renderer.tsx +++ b/src-docs/src/views/datagrid/advanced/custom_renderer.tsx @@ -16,6 +16,9 @@ import { EuiSpacer, useEuiTheme, logicalCSS, + EuiDataGridPaginationProps, + EuiDataGridSorting, + EuiDataGridColumnSortingConfig, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -166,16 +169,23 @@ export default () => { // Pagination const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); - const onChangePage = useCallback((pageIndex) => { - setPagination((pagination) => ({ ...pagination, pageIndex })); - }, []); - const onChangePageSize = useCallback((pageSize) => { + const onChangePage = useCallback( + (pageIndex) => { + setPagination((pagination) => ({ ...pagination, pageIndex })); + }, + [] + ); + const onChangePageSize = useCallback< + EuiDataGridPaginationProps['onChangeItemsPerPage'] + >((pageSize) => { setPagination((pagination) => ({ ...pagination, pageSize })); }, []); // Sorting - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback((sortingColumns) => { + const [sortingColumns, setSortingColumns] = useState< + EuiDataGridColumnSortingConfig[] + >([]); + const onSort = useCallback((sortingColumns) => { setSortingColumns(sortingColumns); }, []); diff --git a/src-docs/src/views/datagrid/advanced/ref.tsx b/src-docs/src/views/datagrid/advanced/ref.tsx index b2102e89303..658e60e7ba6 100644 --- a/src-docs/src/views/datagrid/advanced/ref.tsx +++ b/src-docs/src/views/datagrid/advanced/ref.tsx @@ -16,6 +16,10 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiText, + EuiDataGridColumnCellAction, + EuiDataGridColumnSortingConfig, + EuiDataGridPaginationProps, + EuiDataGridSorting, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -44,13 +48,16 @@ export default () => { dataGridRef.current!.setFocusedCell(lastFocusedCell); // Set the data grid focus back to the cell that opened the modal }, [lastFocusedCell]); - const showModal = useCallback(({ rowIndex, colIndex }) => { - setIsModalVisible(true); - dataGridRef.current!.closeCellPopover(); // Close any open cell popovers - setLastFocusedCell({ rowIndex, colIndex }); // Store the cell that opened this modal - }, []); + const showModal = useCallback( + ({ rowIndex, colIndex }: { rowIndex: number; colIndex: number }) => { + setIsModalVisible(true); + dataGridRef.current!.closeCellPopover(); // Close any open cell popovers + setLastFocusedCell({ rowIndex, colIndex }); // Store the cell that opened this modal + }, + [] + ); - const openModalAction = useCallback( + const openModalAction = useCallback( ({ Component, rowIndex, colIndex }) => { return ( { // Pagination const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 }); - const onChangePage = useCallback((pageIndex) => { - setPagination((pagination) => ({ ...pagination, pageIndex })); - }, []); - const onChangePageSize = useCallback((pageSize) => { + const onChangePage = useCallback( + (pageIndex) => { + setPagination((pagination) => ({ ...pagination, pageIndex })); + }, + [] + ); + const onChangePageSize = useCallback< + EuiDataGridPaginationProps['onChangeItemsPerPage'] + >((pageSize) => { setPagination((pagination) => ({ ...pagination, pageSize })); }, []); // Sorting - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback((sortingColumns) => { + const [sortingColumns, setSortingColumns] = useState< + EuiDataGridColumnSortingConfig[] + >([]); + const onSort = useCallback((sortingColumns) => { setSortingColumns(sortingColumns); }, []); diff --git a/src-docs/src/views/datagrid/styling/row_height_auto.tsx b/src-docs/src/views/datagrid/styling/row_height_auto.tsx index fe7c0c055b9..b963efcdfc3 100644 --- a/src-docs/src/views/datagrid/styling/row_height_auto.tsx +++ b/src-docs/src/views/datagrid/styling/row_height_auto.tsx @@ -18,6 +18,9 @@ import { EuiText, EuiSpacer, formatDate, + EuiDataGridSorting, + EuiDataGridColumnSortingConfig, + EuiDataGridPaginationProps, } from '../../../../../src'; interface DataShape { @@ -146,15 +149,19 @@ export default () => { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 }); // Sorting - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback( + const [sortingColumns, setSortingColumns] = useState< + EuiDataGridColumnSortingConfig[] + >([]); + const onSort = useCallback( (sortingColumns) => { setSortingColumns(sortingColumns); }, [setSortingColumns] ); - const onChangeItemsPerPage = useCallback( + const onChangeItemsPerPage = useCallback< + EuiDataGridPaginationProps['onChangeItemsPerPage'] + >( (pageSize) => setPagination((pagination) => ({ ...pagination, @@ -164,7 +171,7 @@ export default () => { [setPagination] ); - const onChangePage = useCallback( + const onChangePage = useCallback( (pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })), [setPagination] diff --git a/src-docs/src/views/datagrid/styling/row_height_fixed.tsx b/src-docs/src/views/datagrid/styling/row_height_fixed.tsx index e46dfb452dc..ba96749e2d0 100644 --- a/src-docs/src/views/datagrid/styling/row_height_fixed.tsx +++ b/src-docs/src/views/datagrid/styling/row_height_fixed.tsx @@ -18,6 +18,9 @@ import { EuiText, EuiSpacer, formatDate, + EuiDataGridSorting, + EuiDataGridColumnSortingConfig, + EuiDataGridPaginationProps, } from '../../../../../src'; interface DataShape { @@ -146,15 +149,19 @@ export default () => { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 }); // Sorting - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback( + const [sortingColumns, setSortingColumns] = useState< + EuiDataGridColumnSortingConfig[] + >([]); + const onSort = useCallback( (sortingColumns) => { setSortingColumns(sortingColumns); }, [setSortingColumns] ); - const onChangeItemsPerPage = useCallback( + const onChangeItemsPerPage = useCallback< + EuiDataGridPaginationProps['onChangeItemsPerPage'] + >( (pageSize) => setPagination((pagination) => ({ ...pagination, @@ -164,7 +171,7 @@ export default () => { [setPagination] ); - const onChangePage = useCallback( + const onChangePage = useCallback( (pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })), [setPagination] diff --git a/src-docs/src/views/datagrid/styling/row_line_height.tsx b/src-docs/src/views/datagrid/styling/row_line_height.tsx index 636c21e42df..b2f87a92ef2 100644 --- a/src-docs/src/views/datagrid/styling/row_line_height.tsx +++ b/src-docs/src/views/datagrid/styling/row_line_height.tsx @@ -8,7 +8,14 @@ import React, { } from 'react'; import githubData from '../_row_auto_height_data.json'; -import { EuiDataGrid, EuiDataGridProps, formatDate } from '../../../../../src'; +import { + EuiDataGrid, + EuiDataGridColumnSortingConfig, + EuiDataGridPaginationProps, + EuiDataGridProps, + EuiDataGridSorting, + formatDate, +} from '../../../../../src'; interface DataShape { html_url: string; @@ -96,15 +103,19 @@ export default () => { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 }); // Sorting - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback( + const [sortingColumns, setSortingColumns] = useState< + EuiDataGridColumnSortingConfig[] + >([]); + const onSort = useCallback( (sortingColumns) => { setSortingColumns(sortingColumns); }, [setSortingColumns] ); - const onChangeItemsPerPage = useCallback( + const onChangeItemsPerPage = useCallback< + EuiDataGridPaginationProps['onChangeItemsPerPage'] + >( (pageSize) => setPagination((pagination) => ({ ...pagination, @@ -114,7 +125,7 @@ export default () => { [setPagination] ); - const onChangePage = useCallback( + const onChangePage = useCallback( (pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })), [setPagination] diff --git a/src-docs/src/views/datagrid/toolbar/additional_controls.tsx b/src-docs/src/views/datagrid/toolbar/additional_controls.tsx index 78debdc0f25..fa98c19745a 100644 --- a/src-docs/src/views/datagrid/toolbar/additional_controls.tsx +++ b/src-docs/src/views/datagrid/toolbar/additional_controls.tsx @@ -16,6 +16,7 @@ import { EuiContextMenuItem, EuiContextMenuPanel, EuiPopover, + EuiDataGridPaginationProps, } from '../../../../../src'; const columns = [ @@ -94,12 +95,14 @@ export default () => { columns.map(({ id }) => id) ); - const setPageIndex = useCallback( + const setPageIndex = useCallback( (pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })), [] ); - const setPageSize = useCallback( + const setPageSize = useCallback< + EuiDataGridPaginationProps['onChangeItemsPerPage'] + >( (pageSize) => setPagination((pagination) => ({ ...pagination, diff --git a/src-docs/src/views/drag_and_drop/drag_and_drop_example.js b/src-docs/src/views/drag_and_drop/drag_and_drop_example.js index 0a74ac9c829..96b5d177fdb 100644 --- a/src-docs/src/views/drag_and_drop/drag_and_drop_example.js +++ b/src-docs/src/views/drag_and_drop/drag_and_drop_example.js @@ -42,10 +42,14 @@ export const DragAndDropExample = {

An extension of{' '} + + @hello-pangea/dnd + {' '} + (which is an actively maintained fork of{' '} react-beautiful-dnd - {' '} - with a compatible API and built-in style opinions. Functionality + + ) with a compatible API and built-in style opinions. Functionality results from 3 components working together:

    @@ -72,14 +76,15 @@ export const DragAndDropExample = { Drag and drop interfaces are not well-adapted to many cases, and may be less suitable than other form types for data operations. For instance, drag and drop interaction relies heavily on spatial - orientation that may not be entirelty valid to all users (e.g., screen + orientation that may not be entirely valid to all users (e.g., screen readers as the sole source of information). Similarly, users navigating by keyboard may not be afforded nuanced, dual-axis drag item manipulation.

    - {`EUI (largely due to the great work already in react-beautiful-dnd) has and will continue to ensure accessibility where possible. - With that in mind, keep your users' working context in mind.`} + EUI (largely due to the great work already in @hello-pangea/dnd) has + and will continue to ensure accessibility where possible. With that in + mind, keep your users' working context in mind.

    diff --git a/src-docs/src/views/elastic_charts/metric/metric_chart_grid.tsx b/src-docs/src/views/elastic_charts/metric/metric_chart_grid.tsx index e8fb53581fd..4508de3f285 100644 --- a/src-docs/src/views/elastic_charts/metric/metric_chart_grid.tsx +++ b/src-docs/src/views/elastic_charts/metric/metric_chart_grid.tsx @@ -149,6 +149,7 @@ export default () => { const chartBaseTheme = isDarkTheme ? DARK_THEME : LIGHT_THEME; return ( + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} diff --git a/src-docs/src/views/elastic_charts/metric/metric_chart_grid_column.tsx b/src-docs/src/views/elastic_charts/metric/metric_chart_grid_column.tsx index d5c6bff2679..5e1810534fa 100644 --- a/src-docs/src/views/elastic_charts/metric/metric_chart_grid_column.tsx +++ b/src-docs/src/views/elastic_charts/metric/metric_chart_grid_column.tsx @@ -22,6 +22,7 @@ export default () => { const chartBaseTheme = isDarkTheme ? DARK_THEME : LIGHT_THEME; return ( + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { const chartBaseTheme = isDarkTheme ? DARK_THEME : LIGHT_THEME; return ( + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { No Data + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { Filtered Out + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { })); return ( + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { paddingSize="none" style={{ overflow: 'hidden', width: '200px' }} > + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { paddingSize="none" style={{ overflow: 'hidden', width: '200px' }} > + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { paddingSize="none" style={{ overflow: 'hidden', height: '100%', width: '100%' }} > + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { return ( + {/* @ts-ignore @elastic/charts typings are not yet compatible with React 18 */} { @@ -23,19 +23,19 @@ export default () => { { value: 'errorPages', text: 'Page not found', - component: errorPages, + component: , source: errorPagesSource, }, { value: 'noPrivileges', text: 'No permission', - component: noPrivileges, + component: , source: noPrivilegesSource, }, { value: 'licenseUpgrade', text: 'License upgrade', - component: licenseUpgrade, + component: , source: licenseUpgradeSource, }, ]; diff --git a/src-docs/src/views/flex/wrapper_styles.tsx b/src-docs/src/views/flex/wrapper_styles.tsx index c5ca6efc815..f02828fdaab 100644 --- a/src-docs/src/views/flex/wrapper_styles.tsx +++ b/src-docs/src/views/flex/wrapper_styles.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { css } from '@emotion/react'; import { useEuiTheme, @@ -18,7 +18,9 @@ export const flexItemHiglightStyles = ({ euiTheme }: UseEuiTheme) => { `; }; -export const FlexItemHighlightWrapper: React.FC = ({ children }) => { +export const FlexItemHighlightWrapper: React.FC = ({ + children, +}) => { const euiTheme = useEuiTheme(); return
    {children}
    ; }; diff --git a/src-docs/src/views/page_components/page.tsx b/src-docs/src/views/page_components/page.tsx index e26cb30003b..64e2b8a13e4 100644 --- a/src-docs/src/views/page_components/page.tsx +++ b/src-docs/src/views/page_components/page.tsx @@ -12,7 +12,7 @@ export default ({ content = <>, sideBar, ...rest -}: EuiPageProps & { +}: Omit & { content: ReactElement; sideBar?: ReactElement; }) => ( diff --git a/src-docs/src/views/popover/popover_htmlelement_anchor.js b/src-docs/src/views/popover/popover_htmlelement_anchor.js index fa699b47244..adc8c5e355d 100644 --- a/src-docs/src/views/popover/popover_htmlelement_anchor.js +++ b/src-docs/src/views/popover/popover_htmlelement_anchor.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { EuiWrappingPopover } from '../../../../src/components'; @@ -35,10 +35,11 @@ export default () => { // the popover DOM is positioned independently of where the container exists const container = document.createElement('div'); document.body.appendChild(container); + const root = createRoot(container); + root.render(); - render(, container); - - return () => unmountComponentAtNode(container); + // Without the setTimeout, React will error about attempting to synchronously unmount + return () => setTimeout(() => root.unmount()); }, []); return ( diff --git a/src-docs/src/views/popover/trap_focus.tsx b/src-docs/src/views/popover/trap_focus.tsx index fbfd37a19f4..3304dbcf4f6 100644 --- a/src-docs/src/views/popover/trap_focus.tsx +++ b/src-docs/src/views/popover/trap_focus.tsx @@ -28,7 +28,10 @@ export default () => { const focusId = useGeneratedHtmlId(); useEffect(() => { if (isPopoverOpen) { - document.getElementById(focusId)!.focus({ preventScroll: true }); + // Wait a tick for element to finish rendering + setTimeout(() => { + document.getElementById(focusId)!.focus({ preventScroll: true }); + }); } }, [isPopoverOpen, focusId]); diff --git a/src-docs/src/views/resizable_container/resizable_container_reset_values.tsx b/src-docs/src/views/resizable_container/resizable_container_reset_values.tsx index 7d5ce5ece77..824a19e82bc 100644 --- a/src-docs/src/views/resizable_container/resizable_container_reset_values.tsx +++ b/src-docs/src/views/resizable_container/resizable_container_reset_values.tsx @@ -30,7 +30,7 @@ const defaultSizes = storedSizes || { export default () => { const [savedSizes, setSavedSizes] = useState(storedSizes); const [sizes, setSizes] = useState(defaultSizes); - const onPanelWidthChange = useCallback((newSizes) => { + const onPanelWidthChange = useCallback((newSizes: Record) => { setSizes((prevSizes: Record) => ({ ...prevSizes, ...newSizes, diff --git a/src-docs/src/views/tables/custom/custom.tsx b/src-docs/src/views/tables/custom/custom.tsx index 8f47e74a2a5..aad9499ad03 100644 --- a/src-docs/src/views/tables/custom/custom.tsx +++ b/src-docs/src/views/tables/custom/custom.tsx @@ -296,7 +296,7 @@ export default class extends Component<{}, State> { size="m" style={{ verticalAlign: 'text-top' }} />{' '} - {title} + {title as ReactNode} ), }, @@ -674,6 +674,7 @@ export default class extends Component<{}, State> { mobileOptions={{ header: column.label, ...column.mobileOptions, + render: column.mobileOptions?.render?.(item), }} > {child} diff --git a/src-docs/src/views/theme/color_mode/inverse.tsx b/src-docs/src/views/theme/color_mode/inverse.tsx index 5d90be2da5d..30c6fb7992e 100644 --- a/src-docs/src/views/theme/color_mode/inverse.tsx +++ b/src-docs/src/views/theme/color_mode/inverse.tsx @@ -1,4 +1,4 @@ -import React, { useState, FC } from 'react'; +import React, { useState, FC, PropsWithChildren } from 'react'; import { EuiThemeProvider, useEuiTheme, @@ -66,7 +66,7 @@ export default () => { ); }; -const ThemedChildren: FC = ({ children, ...rest }) => { +const ThemedChildren: FC = ({ children, ...rest }) => { const { colorMode: _colorMode } = useEuiTheme(); const colorMode = _colorMode.toLowerCase(); return ( @@ -101,7 +101,7 @@ const ThemedChildren: FC = ({ children, ...rest }) => { ); }; -const Popover: FC = ({ children }) => { +const Popover: FC = ({ children }) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); return ( { ); }; -const Modal: FC = ({ children }) => { +const Modal: FC = ({ children }) => { const [isModalOpen, setIsModalOpen] = useState(false); return ( <> @@ -151,7 +151,7 @@ const Modal: FC = ({ children }) => { ); }; -const Flyout: FC = ({ children }) => { +const Flyout: FC = ({ children }) => { const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); return ( <> diff --git a/src-docs/src/views/theme/typography/_typography_js.tsx b/src-docs/src/views/theme/typography/_typography_js.tsx index 0ae87c008e0..c54793b46a2 100644 --- a/src-docs/src/views/theme/typography/_typography_js.tsx +++ b/src-docs/src/views/theme/typography/_typography_js.tsx @@ -217,6 +217,14 @@ export const FontScaleJS = () => { ); }; +interface FontScaleDetails { + id: (typeof EuiThemeFontScales)[number]; + value: string; + size: string; + lineHeight: string; + index: number; +} + export const FontScaleValuesJS = () => { const euiThemeContext = useEuiTheme(); const scaleKeys = EuiThemeFontScales; @@ -261,7 +269,7 @@ export const FontScaleValuesJS = () => {
    - tableLayout="auto" items={scaleKeys.map((scale, index) => { return { diff --git a/src-docs/src/views/theme/typography/_typography_sass.tsx b/src-docs/src/views/theme/typography/_typography_sass.tsx index 16b1a9b2c4f..2926628c860 100644 --- a/src-docs/src/views/theme/typography/_typography_sass.tsx +++ b/src-docs/src/views/theme/typography/_typography_sass.tsx @@ -165,12 +165,18 @@ export const FontWeightSass: FunctionComponent = ({ ); }; +interface FontWeightDetails { + id: (typeof euiFontWeights)[number]; + token: string; + value: number; +} + export const FontWeightValuesSass = () => { const values = useJsonVars(); return ( <> - items={euiFontWeights.map(function (weight) { return { id: weight, @@ -221,7 +227,7 @@ const euiFontSizes = [ 'euiFontSizeL', 'euiFontSizeXL', 'euiFontSizeXXL', -]; +] as const; export const FontScaleSass = () => { return ( @@ -243,12 +249,20 @@ export const FontScaleSass = () => { ); }; +interface FontSizesDetails { + id: (typeof euiFontSizes)[number]; + token: string; + mixin: string; + value: string; + index: number; +} + export const FontScaleValuesSass = () => { const values = useJsonVars(); return ( <> - items={euiFontSizes.map(function (size, index) { return { id: size, diff --git a/src/components/accessibility/skip_link/skip_link.test.tsx b/src/components/accessibility/skip_link/skip_link.test.tsx index 8b6fcf1e2ca..9cc00a8a66b 100644 --- a/src/components/accessibility/skip_link/skip_link.test.tsx +++ b/src/components/accessibility/skip_link/skip_link.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { render } from '../../../test/rtl'; import { requiredProps } from '../../../test'; diff --git a/src/components/basic_table/in_memory_table.tsx b/src/components/basic_table/in_memory_table.tsx index 9c959c265f7..f03dacd5fef 100644 --- a/src/components/basic_table/in_memory_table.tsx +++ b/src/components/basic_table/in_memory_table.tsx @@ -423,7 +423,10 @@ export class EuiInMemoryTable extends Component< // EuiBasicTable returns the column's `field` instead of `name` on sort // and the column's `name` instead of `field` on pagination if (sortName) { - const sortColumn = findColumnByFieldOrName(this.props.columns, sortName); + const sortColumn = findColumnByFieldOrName( + this.props.columns, + sortName as ReactNode + ); if (sortColumn) { // Ensure sortName uses `name` sortName = sortColumn.name as keyof T; @@ -460,7 +463,7 @@ export class EuiInMemoryTable extends Component< this.setState({ pageIndex, pageSize, - sortName, + sortName: sortName as ReactNode, sortDirection, }); }; diff --git a/src/components/breadcrumbs/breadcrumb.tsx b/src/components/breadcrumbs/breadcrumb.tsx index 2cfc5a8dbba..c54172e336b 100644 --- a/src/components/breadcrumbs/breadcrumb.tsx +++ b/src/components/breadcrumbs/breadcrumb.tsx @@ -13,6 +13,7 @@ import React, { MouseEventHandler, ReactNode, useState, + PropsWithChildren, } from 'react'; import classNames from 'classnames'; @@ -57,14 +58,15 @@ export type EuiBreadcrumbProps = Omit< }; // Used internally only by the parent EuiBreadcrumbs -type _EuiBreadcrumbProps = { - type: 'page' | 'application'; - isFirstBreadcrumb?: boolean; - isLastBreadcrumb?: boolean; - isOnlyBreadcrumb?: boolean; - highlightLastBreadcrumb?: boolean; - truncateLastBreadcrumb?: boolean; -} & Pick; +type _EuiBreadcrumbProps = PropsWithChildren & + Pick & { + type: 'page' | 'application'; + isFirstBreadcrumb?: boolean; + isLastBreadcrumb?: boolean; + isOnlyBreadcrumb?: boolean; + highlightLastBreadcrumb?: boolean; + truncateLastBreadcrumb?: boolean; + }; export const EuiBreadcrumb: FunctionComponent< HTMLAttributes & _EuiBreadcrumbProps diff --git a/src/components/button/button_empty/button_empty.test.tsx b/src/components/button/button_empty/button_empty.test.tsx index 7480db3d4de..602e4e4334f 100644 --- a/src/components/button/button_empty/button_empty.test.tsx +++ b/src/components/button/button_empty/button_empty.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'; import { shouldRenderCustomStyles } from '../../../test/internal'; diff --git a/src/components/button/button_icon/button_icon.test.tsx b/src/components/button/button_icon/button_icon.test.tsx index 30424343b1f..f5e50258e50 100644 --- a/src/components/button/button_icon/button_icon.test.tsx +++ b/src/components/button/button_icon/button_icon.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'; import { shouldRenderCustomStyles } from '../../../test/internal'; diff --git a/src/components/code/code_block_annotations.test.tsx b/src/components/code/code_block_annotations.test.tsx index e4427fd3840..267476a36a2 100644 --- a/src/components/code/code_block_annotations.test.tsx +++ b/src/components/code/code_block_annotations.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { render, waitForEuiPopoverOpen } from '../../test/rtl'; import { shouldRenderCustomStyles } from '../../test/internal'; import { requiredProps } from '../../test'; diff --git a/src/components/code/code_block_annotations.tsx b/src/components/code/code_block_annotations.tsx index b39c88a6e1f..7c20a7d6fea 100644 --- a/src/components/code/code_block_annotations.tsx +++ b/src/components/code/code_block_annotations.tsx @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, ReactNode, useState } from 'react'; +import React, { + FunctionComponent, + PropsWithChildren, + ReactNode, + useState, +} from 'react'; import { useEuiTheme } from '../../services'; import { CommonProps } from '../common'; @@ -19,9 +24,10 @@ import { euiCodeBlockAnnotationsStyles } from './code_block_annotations.style'; export type LineAnnotationMap = Record; -type EuiCodeBlockAnnotationProps = CommonProps & { - lineNumber: number; -}; +type EuiCodeBlockAnnotationProps = PropsWithChildren & + CommonProps & { + lineNumber: number; + }; export const EuiCodeBlockAnnotation: FunctionComponent< EuiCodeBlockAnnotationProps diff --git a/src/components/code/code_block_full_screen.tsx b/src/components/code/code_block_full_screen.tsx index a2dc4fabcec..96465e78096 100644 --- a/src/components/code/code_block_full_screen.tsx +++ b/src/components/code/code_block_full_screen.tsx @@ -12,6 +12,7 @@ import React, { useState, useCallback, useMemo, + PropsWithChildren, } from 'react'; import { keys, useEuiTheme } from '../../services'; import { useEuiI18n } from '../i18n'; @@ -87,9 +88,9 @@ export const useFullScreen = ({ /** * Portalled full screen wrapper */ -export const EuiCodeBlockFullScreenWrapper: FunctionComponent = ({ - children, -}) => { +export const EuiCodeBlockFullScreenWrapper: FunctionComponent< + PropsWithChildren +> = ({ children }) => { const euiThemeContext = useEuiTheme(); const styles = euiCodeBlockStyles(euiThemeContext); diff --git a/src/components/code/utils.test.tsx b/src/components/code/utils.test.tsx index 8cbd01f4f82..02afbf3cce3 100644 --- a/src/components/code/utils.test.tsx +++ b/src/components/code/utils.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '../../test/rtl'; import { useEuiTheme } from '../../services'; import { diff --git a/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx b/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx index a68c1ad8c9f..f8b4b366dc7 100644 --- a/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx +++ b/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, PropsWithChildren } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { EuiFlyoutBody, EuiFlyoutFooter } from '../flyout'; @@ -22,7 +22,9 @@ export default meta; type Story = StoryObj<{}>; // TODO: Make this a stateful component in upcoming EuiCollapsibleNavBeta work -const OpenCollapsibleNav: FunctionComponent<{}> = ({ children }) => { +const OpenCollapsibleNav: FunctionComponent = ({ + children, +}) => { return ( {}}> {children} diff --git a/src/components/color_picker/color_picker.test.tsx b/src/components/color_picker/color_picker.test.tsx index cca583fcd1b..8a1e66d3b06 100644 --- a/src/components/color_picker/color_picker.test.tsx +++ b/src/components/color_picker/color_picker.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { render } from '../../test/rtl'; +import { act } from '@testing-library/react'; import { EuiColorPicker } from './color_picker'; import { VISUALIZATION_COLORS, keys } from '../../services'; @@ -191,15 +192,29 @@ test('popover color selector is hidden when the ESC key pressed', async () => { /> ); - findTestSubject(colorPicker, 'euiColorPickerAnchor').simulate('click'); - await sleep(); - findTestSubject(colorPicker, 'euiColorPickerAnchor').simulate('keydown', { - key: keys.ENTER, + await act( + async () => + await findTestSubject(colorPicker, 'euiColorPickerAnchor').simulate( + 'click' + ) + ); + + await act(() => sleep()); + + act(() => { + findTestSubject(colorPicker, 'euiColorPickerAnchor').simulate('keydown', { + key: keys.ENTER, + }); }); - await sleep(); - findTestSubject(colorPicker, 'euiColorPickerPopover').simulate('keydown', { - key: keys.ESCAPE, + + await act(() => sleep()); + + act(() => { + findTestSubject(colorPicker, 'euiColorPickerPopover').simulate('keydown', { + key: keys.ESCAPE, + }); }); + // Portal removal not working with Jest. The blur handler is called just before the portal would be removed. expect(onBlurHandler).toBeCalled(); }); diff --git a/src/components/combo_box/combo_box.test.tsx b/src/components/combo_box/combo_box.test.tsx index 0eea6ea06b1..eb2bf1624a2 100644 --- a/src/components/combo_box/combo_box.test.tsx +++ b/src/components/combo_box/combo_box.test.tsx @@ -7,7 +7,7 @@ */ import React, { ReactNode } from 'react'; -import { fireEvent } from '@testing-library/react'; +import { act, fireEvent } from '@testing-library/react'; import { shallow, mount } from 'enzyme'; import { render } from '../../test/rtl'; import { @@ -91,7 +91,9 @@ describe('props', () => { /> ); - component.setState({ isListOpen: true }); + act(() => { + component.setState({ isListOpen: true }); + }); expect(takeMountedSnapshot(component)).toMatchSnapshot(); }); @@ -136,7 +138,10 @@ describe('props', () => { test('renders in the options dropdown', () => { const component = mount(); - component.setState({ isListOpen: true }); + + act(() => { + component.setState({ isListOpen: true }); + }); const dropdown = component.find( 'div[data-test-subj="comboBoxOptionsList"]' @@ -329,10 +334,16 @@ describe('behavior', () => { /> ); - component.setState({ searchValue: 'foo' }); - const searchInput = findTestSubject(component, 'comboBoxSearchInput'); - searchInput.simulate('focus'); - searchInput.simulate('keyDown', { key: comboBoxKeys.ENTER }); + act(() => { + component.setState({ searchValue: 'foo' }); + }); + + act(() => { + const searchInput = findTestSubject(component, 'comboBoxSearchInput'); + searchInput.simulate('focus'); + searchInput.simulate('keyDown', { key: comboBoxKeys.ENTER }); + }); + expect(onCreateOptionHandler).toHaveBeenCalledTimes(1); expect(onCreateOptionHandler).toHaveBeenNthCalledWith(1, 'foo', options); }); @@ -391,15 +402,20 @@ describe('behavior', () => { /> ); - component.setState({ searchValue: 'foo' }); const searchInput = findTestSubject(component, 'comboBoxSearchInput'); - searchInput.simulate('focus'); - const searchInputNode = searchInput.getDOMNode(); - // React doesn't support `focusout` so we have to manually trigger it - searchInputNode.dispatchEvent( - new FocusEvent('focusout', { bubbles: true }) - ); + act(() => { + component.setState({ searchValue: 'foo' }); + searchInput.simulate('focus'); + }); + + act(() => { + const searchInputNode = searchInput.getDOMNode(); + // React doesn't support `focusout` so we have to manually trigger it + searchInputNode.dispatchEvent( + new FocusEvent('focusout', { bubbles: true }) + ); + }); expect(onCreateOptionHandler).toHaveBeenCalledTimes(1); expect(onCreateOptionHandler).toHaveBeenNthCalledWith(1, 'foo', options); diff --git a/src/components/combo_box/combo_box_options_list/combo_box_title.tsx b/src/components/combo_box/combo_box_options_list/combo_box_title.tsx index 88960afe4ac..5316585d749 100644 --- a/src/components/combo_box/combo_box_options_list/combo_box_title.tsx +++ b/src/components/combo_box/combo_box_options_list/combo_box_title.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, PropsWithChildren } from 'react'; -export const EuiComboBoxTitle: FunctionComponent<{}> = ({ children }) => ( -
    {children}
    -); +export const EuiComboBoxTitle: FunctionComponent = ({ + children, +}) =>
    {children}
    ; diff --git a/src/components/common.ts b/src/components/common.ts index 8817c1c6852..351b03a7979 100644 --- a/src/components/common.ts +++ b/src/components/common.ts @@ -14,7 +14,6 @@ import { FunctionComponent, JSXElementConstructor, MouseEventHandler, - SFC, } from 'react'; import { Interpolation, Theme } from '@emotion/react'; @@ -58,7 +57,7 @@ export function keysOf(obj: T): K[] { */ export type ValueOf = T[keyof T]; -export type PropsOf = C extends SFC +export type PropsOf = C extends FunctionComponent ? SFCProps : C extends FunctionComponent ? FunctionProps diff --git a/src/components/context_menu/context_menu.test.tsx b/src/components/context_menu/context_menu.test.tsx index d91bf59f8c6..23cd068304d 100644 --- a/src/components/context_menu/context_menu.test.tsx +++ b/src/components/context_menu/context_menu.test.tsx @@ -8,11 +8,11 @@ import React from 'react'; import { mount } from 'enzyme'; +import { act } from '@testing-library/react'; import { requiredProps, takeMountedSnapshot } from '../../test'; import { render } from '../../test/rtl'; import { EuiContextMenu, SIZES } from './context_menu'; -import { setTimeout } from 'timers'; const panel3 = { id: 3, @@ -65,9 +65,7 @@ const panel0 = { const panels = [panel0, panel1, panel2, panel3]; export const tick = (ms = 0) => - new Promise((resolve) => { - setTimeout(resolve, ms); - }); + act(() => new Promise((resolve) => setTimeout(resolve, ms))); describe('EuiContextMenu', () => { test('is rendered', () => { diff --git a/src/components/context_menu/context_menu_panel.spec.tsx b/src/components/context_menu/context_menu_panel.spec.tsx index 143d7516f5a..f51a5e18dc2 100644 --- a/src/components/context_menu/context_menu_panel.spec.tsx +++ b/src/components/context_menu/context_menu_panel.spec.tsx @@ -79,6 +79,7 @@ describe('EuiContextMenuPanel', () => { ]} /> ); + cy.get('.euiContextMenuPanel').should('be.focused'); cy.realPress('{downarrow}'); cy.focused().should('have.attr', 'data-test-subj', 'itemA'); }); @@ -100,6 +101,7 @@ describe('EuiContextMenuPanel', () => { ); }; cy.mount(); + cy.get('.euiContextMenuPanel').should('be.focused'); cy.realPress('{downarrow}'); cy.focused().should('have.attr', 'data-test-subj', 'itemA'); cy.realPress('{downarrow}'); @@ -144,6 +146,7 @@ describe('EuiContextMenuPanel', () => { it('focuses the back button panel title by default when no initialFocusedItemIndex is passed', () => { cy.mount(); + cy.get('.euiContextMenuPanel').should('be.focused'); cy.realPress('{downarrow}'); cy.realPress('{downarrow}'); cy.focused().should('have.attr', 'data-test-subj', 'panelA'); @@ -155,6 +158,7 @@ describe('EuiContextMenuPanel', () => { it('focuses the correct toggling item when using the left arrow key to navigate to the previous panel', () => { cy.mount(); + cy.get('[data-test-subj="panelB"]').should('be.focused'); cy.realPress('{leftarrow}'); cy.focused().should('have.attr', 'data-test-subj', 'panelA'); }); @@ -332,10 +336,11 @@ describe('EuiContextMenuPanel', () => { }, ]; - const FLAKY_WAIT = 100; // For some reason CI is flaking on these two tests in way that is hard to repro locally + const FLAKY_WAIT = 200; // For some reason CI is flaking on these two tests in way that is hard to repro locally it('does not lose focus while using left/right arrow navigation between panels', () => { cy.mount(); + cy.wait(FLAKY_WAIT); cy.realPress('{downarrow}'); cy.focused().should('have.attr', 'data-test-subj', 'itemA'); cy.realPress('{rightarrow}'); diff --git a/src/components/datagrid/body/data_grid_body_custom.test.tsx b/src/components/datagrid/body/data_grid_body_custom.test.tsx index 53e6a66d0d1..43012117206 100644 --- a/src/components/datagrid/body/data_grid_body_custom.test.tsx +++ b/src/components/datagrid/body/data_grid_body_custom.test.tsx @@ -7,7 +7,7 @@ */ import React, { useEffect } from 'react'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { render } from '../../../test/rtl'; import { EuiDataGridProps } from '../data_grid_types'; diff --git a/src/components/datagrid/body/data_grid_body_custom.tsx b/src/components/datagrid/body/data_grid_body_custom.tsx index d87b5009fe0..881deb3f642 100644 --- a/src/components/datagrid/body/data_grid_body_custom.tsx +++ b/src/components/datagrid/body/data_grid_body_custom.tsx @@ -19,6 +19,7 @@ import { useRowHeightUtils, useDefaultRowHeight } from '../utils/row_heights'; import { EuiDataGridBodyProps, + EuiDataGridCustomBodyProps, EuiDataGridSetCustomGridBodyProps, } from '../data_grid_types'; import { useDataGridHeader } from './header'; @@ -140,7 +141,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< rowHeightUtils, }; - const _Cell = useCallback( + const _Cell = useCallback( ({ colIndex, visibleRowIndex, ...rest }) => { const style = { height: rowHeightUtils.isAutoHeight(visibleRowIndex, rowHeightsOptions) diff --git a/src/components/datagrid/body/data_grid_body_virtualized.tsx b/src/components/datagrid/body/data_grid_body_virtualized.tsx index 08e28cb4bab..c85c7c52cc8 100644 --- a/src/components/datagrid/body/data_grid_body_virtualized.tsx +++ b/src/components/datagrid/body/data_grid_body_virtualized.tsx @@ -14,6 +14,7 @@ import React, { useContext, useEffect, useRef, + PropsWithChildren, } from 'react'; import { GridChildComponentProps, @@ -68,9 +69,15 @@ export const DataGridWrapperRowsContext = footerRow: null, }); +type InnerElementProps = PropsWithChildren & { + style: { + height: number; + }; +}; + const InnerElement: VariableSizeGridProps['innerElementType'] = forwardRef< HTMLDivElement, - { style: { height: number } } + InnerElementProps >(({ children, style, ...rest }, ref) => { const { headerRowHeight, headerRow, footerRow } = useContext( DataGridWrapperRowsContext diff --git a/src/components/datagrid/body/data_grid_cell.test.tsx b/src/components/datagrid/body/data_grid_cell.test.tsx index c305decd085..190195d77eb 100644 --- a/src/components/datagrid/body/data_grid_cell.test.tsx +++ b/src/components/datagrid/body/data_grid_cell.test.tsx @@ -8,6 +8,7 @@ import React, { useEffect } from 'react'; import { mount, ReactWrapper } from 'enzyme'; +import { act } from '@testing-library/react'; import { keys } from '../../../services'; import { render } from '../../../test/rtl'; import { RowHeightUtils } from '../utils/__mocks__/row_heights'; @@ -80,7 +81,9 @@ describe('EuiDataGridCell', () => { }} /> ); - component.setState({ enableInteractions: true }); + act(() => { + component.setState({ enableInteractions: true }); + }); const getCellActions = () => component.find('EuiDataGridCellActions'); expect(getCellActions()).toHaveLength(1); @@ -175,19 +178,29 @@ describe('EuiDataGridCell', () => { describe('when state changes:', () => { it('cellProps', () => { - component.setState({ cellProps: {} }); + act(() => { + component.setState({ cellProps: {} }); + }); }); it('isEntered', () => { - component.setState({ isEntered: true }); + act(() => { + component.setState({ isEntered: true }); + }); }); it('isFocused', () => { - component.setState({ isFocused: true }); + act(() => { + component.setState({ isFocused: true }); + }); }); it('enableInteractions', () => { - component.setState({ enableInteractions: true }); + act(() => { + component.setState({ enableInteractions: true }); + }); }); it('disableCellTabIndex', () => { - component.setState({ disableCellTabIndex: true }); + act(() => { + component.setState({ disableCellTabIndex: true }); + }); }); }); }); diff --git a/src/components/datagrid/body/data_grid_cell.tsx b/src/components/datagrid/body/data_grid_cell.tsx index 12af15fa800..0cdb33969fa 100644 --- a/src/components/datagrid/body/data_grid_cell.tsx +++ b/src/components/datagrid/body/data_grid_cell.tsx @@ -9,6 +9,7 @@ import classNames from 'classnames'; import React, { Component, + ContextType, createRef, FocusEvent, FunctionComponent, @@ -143,6 +144,7 @@ export class EuiDataGridCell extends Component< style = null; static contextType = DataGridFocusContext; + declare context: ContextType; getInteractables = () => { const tabbingRef = this.cellContentsRef; diff --git a/src/components/datagrid/body/data_grid_cell_popover.spec.tsx b/src/components/datagrid/body/data_grid_cell_popover.spec.tsx index 414560900d7..deca8599bc1 100644 --- a/src/components/datagrid/body/data_grid_cell_popover.spec.tsx +++ b/src/components/datagrid/body/data_grid_cell_popover.spec.tsx @@ -61,9 +61,10 @@ describe('EuiDataGridCellPopover', () => { ); cy.realPress('Escape'); - cy.focused() - .should('have.attr', 'data-gridcell-column-index', '0') - .should('have.attr', 'data-gridcell-row-index', '0'); + + cy.get( + '[data-gridcell-column-index="0"][data-gridcell-row-index="0"]' + ).should('be.focused'); }); it('when the expand button is clicked and then F2 key is pressed', () => { diff --git a/src/components/datagrid/body/data_grid_cell_popover.test.tsx b/src/components/datagrid/body/data_grid_cell_popover.test.tsx index def6ced9387..f9272409669 100644 --- a/src/components/datagrid/body/data_grid_cell_popover.test.tsx +++ b/src/components/datagrid/body/data_grid_cell_popover.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, renderHookAct } from '../../../test/rtl'; import { shallow } from 'enzyme'; import { keys } from '../../../services'; @@ -21,7 +21,7 @@ describe('useCellPopover', () => { const { result } = renderHook(useCellPopover); expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(false); - act(() => + renderHookAct(() => result.current.cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0, @@ -34,7 +34,7 @@ describe('useCellPopover', () => { const { result } = renderHook(useCellPopover); expect(result.current.cellPopover).toBeFalsy(); - act(() => { + renderHookAct(() => { result.current.cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0, @@ -45,7 +45,7 @@ describe('useCellPopover', () => { }); expect(result.current.cellPopover).not.toBeFalsy(); - act(() => { + renderHookAct(() => { result.current.cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0, @@ -59,7 +59,7 @@ describe('useCellPopover', () => { it('sets popoverIsOpen state to false', () => { const { result } = renderHook(useCellPopover); - act(() => + renderHookAct(() => result.current.cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0, @@ -67,7 +67,7 @@ describe('useCellPopover', () => { ); expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(true); - act(() => result.current.cellPopoverContext.closeCellPopover()); + renderHookAct(() => result.current.cellPopoverContext.closeCellPopover()); expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(false); }); }); @@ -80,7 +80,7 @@ describe('useCellPopover', () => { const populateCellPopover = ( cellPopoverContext: DataGridCellPopoverContextShape ) => { - act(() => { + renderHookAct(() => { cellPopoverContext.openCellPopover({ colIndex: 0, rowIndex: 0 }); cellPopoverContext.setPopoverAnchor(mockPopoverAnchor); cellPopoverContext.setPopoverContent(mockPopoverContent); @@ -149,7 +149,7 @@ describe('useCellPopover', () => { preventDefault: jest.fn(), stopPropagation: jest.fn(), }; - act(() => { + renderHookAct(() => { component.find('EuiWrappingPopover').simulate('keyDown', event); }); expect(event.preventDefault).toHaveBeenCalled(); @@ -168,7 +168,7 @@ describe('useCellPopover', () => { preventDefault: jest.fn(), stopPropagation: jest.fn(), }; - act(() => { + renderHookAct(() => { component.find('EuiWrappingPopover').simulate('keyDown', event); }); expect(event.preventDefault).toHaveBeenCalled(); @@ -187,7 +187,7 @@ describe('useCellPopover', () => { preventDefault: jest.fn(), stopPropagation: jest.fn(), }; - act(() => { + renderHookAct(() => { component.find('EuiWrappingPopover').simulate('keyDown', event); }); expect(event.preventDefault).not.toHaveBeenCalled(); diff --git a/src/components/datagrid/body/data_grid_cell_popover.tsx b/src/components/datagrid/body/data_grid_cell_popover.tsx index 060ec7d630d..a64a894e121 100644 --- a/src/components/datagrid/body/data_grid_cell_popover.tsx +++ b/src/components/datagrid/body/data_grid_cell_popover.tsx @@ -46,7 +46,9 @@ export const useCellPopover = (): { >({}); const closeCellPopover = useCallback(() => setPopoverIsOpen(false), []); - const openCellPopover = useCallback( + const openCellPopover = useCallback< + DataGridCellPopoverContextShape['openCellPopover'] + >( ({ rowIndex, colIndex }) => { // Prevent popover DOM issues when re-opening the same popover if ( diff --git a/src/components/datagrid/body/data_grid_row_manager.ts b/src/components/datagrid/body/data_grid_row_manager.ts index 5a19b3f2940..090f4b49365 100644 --- a/src/components/datagrid/body/data_grid_row_manager.ts +++ b/src/components/datagrid/body/data_grid_row_manager.ts @@ -18,7 +18,7 @@ export const useRowManager = ({ }): EuiDataGridRowManager => { const rowIdToElements = useRef(new Map()); - const getRow = useCallback( + const getRow = useCallback( ({ rowIndex, visibleRowIndex, top, height }) => { let rowElement = rowIdToElements.current.get(rowIndex); diff --git a/src/components/datagrid/body/header/data_grid_header_cell.tsx b/src/components/datagrid/body/header/data_grid_header_cell.tsx index 01802e4bd2a..45ae091eb50 100644 --- a/src/components/datagrid/body/header/data_grid_header_cell.tsx +++ b/src/components/datagrid/body/header/data_grid_header_cell.tsx @@ -314,7 +314,7 @@ export const useSortingUtils = ({ export const usePopoverArrowNavigation = () => { const popoverPanelRef = useRef(null); const actionsRef = useRef(undefined); - const panelRef = useCallback((ref) => { + const panelRef = useCallback((ref: HTMLElement | null) => { popoverPanelRef.current = ref; actionsRef.current = ref ? tabbable(ref) : undefined; }, []); diff --git a/src/components/datagrid/body/header/data_grid_header_cell_wrapper.test.tsx b/src/components/datagrid/body/header/data_grid_header_cell_wrapper.test.tsx index 58f95c04e41..97db5f8578e 100644 --- a/src/components/datagrid/body/header/data_grid_header_cell_wrapper.test.tsx +++ b/src/components/datagrid/body/header/data_grid_header_cell_wrapper.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { act } from 'react-dom/test-utils'; +import { act } from '@testing-library/react'; import { mount } from 'enzyme'; import { keys } from '../../../../services'; diff --git a/src/components/datagrid/body/header/header_is_interactive.test.ts b/src/components/datagrid/body/header/header_is_interactive.test.ts index 2949cf487ff..835c3688f5d 100644 --- a/src/components/datagrid/body/header/header_is_interactive.test.ts +++ b/src/components/datagrid/body/header/header_is_interactive.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { act } from 'react-dom/test-utils'; +import { act } from '@testing-library/react'; import { testCustomHook } from '../../../../test/internal'; import { useHeaderIsInteractive } from './header_is_interactive'; diff --git a/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap b/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap index 89da1992b43..7bcf150efde 100644 --- a/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap +++ b/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`useDataGridColumnSelector columnSelector renders a toolbar button/popover allowing users to set column visibility and order 1`] = ` +exports[`useDataGridColumnSelector columnSelector [React 17] renders a toolbar button/popover allowing users to set column visibility and order 1`] = `
    `; -exports[`useDataGridColumnSelector columnSelector renders a toolbar button/popover allowing users to set column visibility and order 2`] = ` +exports[`useDataGridColumnSelector columnSelector [React 17] renders a toolbar button/popover allowing users to set column visibility and order 2`] = `
    @@ -129,11 +129,11 @@ exports[`useDataGridColumnSelector columnSelector renders a toolbar button/popov
    @@ -206,11 +206,11 @@ exports[`useDataGridColumnSelector columnSelector renders a toolbar button/popov
    + +
    +
    +
    +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + +`; + +exports[`useDataGridColumnSelector columnSelector [React 18] renders a toolbar button/popover allowing users to set column visibility and order 1`] = ` +
+
+ +
+
+`; + +exports[`useDataGridColumnSelector columnSelector [React 18] renders a toolbar button/popover allowing users to set column visibility and order 2`] = ` +