mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[Lint] Officially deprecate Enzyme (#221779)
## Summary Please see for [context](https://docs.google.com/document/d/1J9mGfmGukgFS-6jqD1zopH8wIRvsqNhDhQbjQZ9hjEY/edit?tab=t.hzzf0a72f9gd) This PR tries to discourage usage of enzyme: - Mark shared test utils that use enzyme as `@deprecated` - Add eslint warning for imports of `enzyme` - Add ci stat counter of imports of `enzyme`
This commit is contained in:
parent
b4b49acb68
commit
0dbbf482a5
15 changed files with 175 additions and 11 deletions
14
.eslintrc.js
14
.eslintrc.js
|
@ -391,6 +391,19 @@ const RESTRICTED_IMPORTS = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports that are deprecated and should be phased out
|
||||||
|
* They are not restricted until fully removed,
|
||||||
|
* but will log a warning
|
||||||
|
**/
|
||||||
|
const DEPRECATED_IMPORTS = [
|
||||||
|
{
|
||||||
|
name: 'enzyme',
|
||||||
|
message:
|
||||||
|
'Enzyme is deprecated and no longer maintained. Please use @testing-library/react instead.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
|
|
||||||
|
@ -918,6 +931,7 @@ module.exports = {
|
||||||
files: ['**/*.{js,mjs,ts,tsx}'],
|
files: ['**/*.{js,mjs,ts,tsx}'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-imports': ['error', ...RESTRICTED_IMPORTS],
|
'no-restricted-imports': ['error', ...RESTRICTED_IMPORTS],
|
||||||
|
'@kbn/eslint/no_deprecated_imports': ['warn', ...DEPRECATED_IMPORTS],
|
||||||
'no-restricted-modules': [
|
'no-restricted-modules': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { writeDeprecationDueByTeam } from './mdx/write_deprecations_due_by_team'
|
||||||
import { trimDeletedDocsFromNav } from './trim_deleted_docs_from_nav';
|
import { trimDeletedDocsFromNav } from './trim_deleted_docs_from_nav';
|
||||||
import { getAllDocFileIds } from './mdx/get_all_doc_file_ids';
|
import { getAllDocFileIds } from './mdx/get_all_doc_file_ids';
|
||||||
import { getPathsByPackage } from './get_paths_by_package';
|
import { getPathsByPackage } from './get_paths_by_package';
|
||||||
|
import { countEnzymeImports, EnzymeImportCounts } from './count_enzyme_imports';
|
||||||
|
|
||||||
function isStringArray(arr: unknown | string[]): arr is string[] {
|
function isStringArray(arr: unknown | string[]): arr is string[] {
|
||||||
return Array.isArray(arr) && arr.every((p) => typeof p === 'string');
|
return Array.isArray(arr) && arr.every((p) => typeof p === 'string');
|
||||||
|
@ -144,7 +145,9 @@ export function runBuildApiDocsCli() {
|
||||||
|
|
||||||
const reporter = CiStatsReporter.fromEnv(log);
|
const reporter = CiStatsReporter.fromEnv(log);
|
||||||
|
|
||||||
const allPluginStats: { [key: string]: PluginMetaInfo & ApiStats & EslintDisableCounts } = {};
|
const allPluginStats: {
|
||||||
|
[key: string]: PluginMetaInfo & ApiStats & EslintDisableCounts & EnzymeImportCounts;
|
||||||
|
} = {};
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
const id = plugin.id;
|
const id = plugin.id;
|
||||||
|
|
||||||
|
@ -162,6 +165,7 @@ export function runBuildApiDocsCli() {
|
||||||
|
|
||||||
allPluginStats[id] = {
|
allPluginStats[id] = {
|
||||||
...(await countEslintDisableLines(paths)),
|
...(await countEslintDisableLines(paths)),
|
||||||
|
...(await countEnzymeImports(paths)),
|
||||||
...collectApiStatsForPlugin(
|
...collectApiStatsForPlugin(
|
||||||
pluginApi,
|
pluginApi,
|
||||||
missingApiItems,
|
missingApiItems,
|
||||||
|
@ -283,6 +287,12 @@ export function runBuildApiDocsCli() {
|
||||||
group: 'Total ESLint disabled count',
|
group: 'Total ESLint disabled count',
|
||||||
value: pluginStats.eslintDisableFileCount + pluginStats.eslintDisableLineCount,
|
value: pluginStats.eslintDisableFileCount + pluginStats.eslintDisableLineCount,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
meta: { pluginTeam },
|
||||||
|
group: 'Enzyme imports',
|
||||||
|
value: pluginStats.enzymeImportCount,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const getLink = (d: ApiDeclaration) =>
|
const getLink = (d: ApiDeclaration) =>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Path from 'path';
|
||||||
|
import { getRepoFiles } from '@kbn/get-repo-files';
|
||||||
|
import { countEnzymeImports } from './count_enzyme_imports';
|
||||||
|
|
||||||
|
describe('count', () => {
|
||||||
|
test('number of "enzyme" imports in this file', async () => {
|
||||||
|
const { enzymeImportCount } = await countEnzymeImports([Path.resolve(__dirname, __filename)]);
|
||||||
|
expect(enzymeImportCount).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('number of "enzyme" imports in this directory', async () => {
|
||||||
|
const allFiles = await getRepoFiles([__dirname]);
|
||||||
|
const { enzymeImportCount } = await countEnzymeImports(Array.from(allFiles, (f) => f.abs));
|
||||||
|
expect(enzymeImportCount).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { asyncForEachWithLimit } from '@kbn/std';
|
||||||
|
import Fs from 'fs';
|
||||||
|
|
||||||
|
export interface EnzymeImportCounts {
|
||||||
|
enzymeImportCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = (s: string, r: RegExp) => Array.from(s.matchAll(r)).length;
|
||||||
|
|
||||||
|
export async function countEnzymeImports(paths: string[]): Promise<EnzymeImportCounts> {
|
||||||
|
let enzymeImportCount = 0;
|
||||||
|
|
||||||
|
await asyncForEachWithLimit(paths, 100, async (path) => {
|
||||||
|
const content = await Fs.promises.readFile(path, 'utf8');
|
||||||
|
enzymeImportCount +=
|
||||||
|
count(content, /import\s+[^;]*?from\s+['"]enzyme['"]/g) +
|
||||||
|
count(content, /require\(['"]enzyme['"]\)/g);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { enzymeImportCount };
|
||||||
|
}
|
10
packages/kbn-docs-utils/src/count_enzyme_imports/index.ts
Normal file
10
packages/kbn-docs-utils/src/count_enzyme_imports/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { countEnzymeImports, type EnzymeImportCounts } from './count_enzyme_imports';
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
describe('test_enzyme_import', () => {
|
||||||
|
it('renders a div', () => {
|
||||||
|
const wrapper = shallow(React.createElement('div', null, 'Hello World'));
|
||||||
|
expect(wrapper.is('div')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,5 +21,6 @@ module.exports = {
|
||||||
no_unsafe_console: require('./rules/no_unsafe_console'),
|
no_unsafe_console: require('./rules/no_unsafe_console'),
|
||||||
no_unsafe_hash: require('./rules/no_unsafe_hash'),
|
no_unsafe_hash: require('./rules/no_unsafe_hash'),
|
||||||
require_kibana_feature_privileges_naming: require('./rules/require_kibana_feature_privileges_naming'),
|
require_kibana_feature_privileges_naming: require('./rules/require_kibana_feature_privileges_naming'),
|
||||||
|
no_deprecated_imports: require('./rules/no_deprecated_imports'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Linter = require('eslint').Linter;
|
||||||
|
|
||||||
|
const coreRule = new Linter().getRules().get('no-restricted-imports');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This rule is used to prevent the use of deprecated imports in Kibana code.
|
||||||
|
* It is a wrapper around the core ESLint rule `no-restricted-imports` with
|
||||||
|
* a different id to avoid conflicts with the core rule.
|
||||||
|
*/
|
||||||
|
module.exports = coreRule;
|
|
@ -53,6 +53,8 @@ function getOptions(context = {}, props = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated - use `renderWithKibanaRenderContext` or `renderWithI18n` with mocked nested components instead to switch from Enzyme to Testing Library
|
||||||
|
*
|
||||||
* Creates the wrapper instance using shallow with provided intl object into context
|
* Creates the wrapper instance using shallow with provided intl object into context
|
||||||
*
|
*
|
||||||
* @param node The React element or cheerio wrapper
|
* @param node The React element or cheerio wrapper
|
||||||
|
@ -74,6 +76,8 @@ export function shallowWithIntl(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated - use `renderWithKibanaRenderContext` or `renderWithI18n` instead to switch from Enzyme to Testing Library
|
||||||
|
*
|
||||||
* Creates the wrapper instance using mount with provided intl object into context
|
* Creates the wrapper instance using mount with provided intl object into context
|
||||||
*
|
*
|
||||||
* @param node The React element or cheerio wrapper
|
* @param node The React element or cheerio wrapper
|
||||||
|
@ -92,7 +96,9 @@ export function mountWithIntl(node: React.ReactElement, options?: MountRendererP
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the wrapper instance using render with provided intl object into context
|
* @deprecated - use `renderWithKibanaRenderContext` or `renderWithI18n` instead to switch from Enzyme to Testing Library
|
||||||
|
*
|
||||||
|
* Creates the wrapper instance using render with provided intl object into context
|
||||||
*
|
*
|
||||||
* @param node The React element or cheerio wrapper
|
* @param node The React element or cheerio wrapper
|
||||||
* @param options properties to pass into render wrapper
|
* @param options properties to pass into render wrapper
|
||||||
|
@ -129,6 +135,8 @@ interface ReactHookWrapper<Args, HookValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated - use `renderHook` from testing-library/react instead to switch from Enzyme to Testing Library
|
||||||
|
*
|
||||||
* Allows for execution of hooks inside of a test component which records the
|
* Allows for execution of hooks inside of a test component which records the
|
||||||
* returned values.
|
* returned values.
|
||||||
*
|
*
|
||||||
|
@ -186,6 +194,9 @@ export const mountHook = <Args extends {}, HookValue extends any>(
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated - use `renderWithKibanaRenderContext` or `renderWithI18n` instead to switch from Enzyme to Testing Library
|
||||||
|
*/
|
||||||
export function shallowWithI18nProvider<T>(
|
export function shallowWithI18nProvider<T>(
|
||||||
child: ReactElement<T>,
|
child: ReactElement<T>,
|
||||||
options?: Omit<ShallowRendererProps, 'wrappingComponent'> & {
|
options?: Omit<ShallowRendererProps, 'wrappingComponent'> & {
|
||||||
|
@ -196,6 +207,9 @@ export function shallowWithI18nProvider<T>(
|
||||||
return wrapped.children().dive();
|
return wrapped.children().dive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated - use `renderWithKibanaRenderContext` or `renderWithI18n` instead to switch from Enzyme to Testing Library
|
||||||
|
*/
|
||||||
export function mountWithI18nProvider<T>(
|
export function mountWithI18nProvider<T>(
|
||||||
child: ReactElement<T>,
|
child: ReactElement<T>,
|
||||||
options?: Omit<MountRendererProps, 'wrappingComponent'> & {
|
options?: Omit<MountRendererProps, 'wrappingComponent'> & {
|
||||||
|
@ -206,6 +220,9 @@ export function mountWithI18nProvider<T>(
|
||||||
return wrapped.children().childAt(0);
|
return wrapped.children().childAt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated - use `renderWithKibanaRenderContext` or `renderWithI18n` instead to switch from Enzyme to Testing Library
|
||||||
|
*/
|
||||||
export function renderWithI18nProvider<T>(
|
export function renderWithI18nProvider<T>(
|
||||||
child: ReactElement<T>,
|
child: ReactElement<T>,
|
||||||
options?: Omit<MountRendererProps, 'wrappingComponent'> & {
|
options?: Omit<MountRendererProps, 'wrappingComponent'> & {
|
||||||
|
|
|
@ -21,6 +21,8 @@ const MATCHERS: Matcher[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated - use '@testing-library/react' and byId query instead.
|
||||||
|
*
|
||||||
* Find node which matches a specific test subject selector. Returns ReactWrappers around DOM element,
|
* Find node which matches a specific test subject selector. Returns ReactWrappers around DOM element,
|
||||||
* https://github.com/airbnb/enzyme/tree/master/docs/api/ReactWrapper.
|
* https://github.com/airbnb/enzyme/tree/master/docs/api/ReactWrapper.
|
||||||
* Common use cases include calling simulate or getDOMNode on the returned ReactWrapper.
|
* Common use cases include calling simulate or getDOMNode on the returned ReactWrapper.
|
||||||
|
|
|
@ -48,6 +48,9 @@ function getCompFromConfig<T extends object = Record<string, any>>({
|
||||||
return Comp;
|
return Comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated - use @testing-library/react instead
|
||||||
|
*/
|
||||||
export function mountComponentSync<T extends object = Record<string, any>>(
|
export function mountComponentSync<T extends object = Record<string, any>>(
|
||||||
config: Config<T>
|
config: Config<T>
|
||||||
): ReactWrapper {
|
): ReactWrapper {
|
||||||
|
@ -55,6 +58,9 @@ export function mountComponentSync<T extends object = Record<string, any>>(
|
||||||
return mountWithIntl(<Comp {...config.props} />);
|
return mountWithIntl(<Comp {...config.props} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated - use @testing-library/react instead
|
||||||
|
*/
|
||||||
export async function mountComponentAsync<T extends object = Record<string, any>>(
|
export async function mountComponentAsync<T extends object = Record<string, any>>(
|
||||||
config: Config<T>
|
config: Config<T>
|
||||||
): Promise<ReactWrapper> {
|
): Promise<ReactWrapper> {
|
||||||
|
|
|
@ -35,6 +35,8 @@ const defaultConfig: TestBedConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated - use @testing-library/react instead
|
||||||
|
*
|
||||||
* Register a new Testbed to test a React Component.
|
* Register a new Testbed to test a React Component.
|
||||||
*
|
*
|
||||||
* @param Component The component under test
|
* @param Component The component under test
|
||||||
|
|
|
@ -7,19 +7,29 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Components using the @kbn/i18n module require access to the intl context.
|
|
||||||
* This is not available when mounting single components in Enzyme.
|
|
||||||
* These helper functions aim to address that and wrap a valid,
|
|
||||||
* intl context around them.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { I18nProvider } from '@kbn/i18n-react';
|
import { I18nProvider } from '@kbn/i18n-react';
|
||||||
|
import { EuiThemeProvider } from '@elastic/eui';
|
||||||
|
|
||||||
|
export const renderWithKibanaRenderContext = (...args: Parameters<typeof render>) => {
|
||||||
|
const [ui, ...remainingRenderArgs] = args;
|
||||||
|
return render(
|
||||||
|
<EuiThemeProvider>
|
||||||
|
<I18nProvider>{ui}</I18nProvider>
|
||||||
|
</EuiThemeProvider>,
|
||||||
|
...remainingRenderArgs
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const renderWithI18n = (...args: Parameters<typeof render>) => {
|
export const renderWithI18n = (...args: Parameters<typeof render>) => {
|
||||||
const [ui, ...remainingRenderArgs] = args;
|
const [ui, ...remainingRenderArgs] = args;
|
||||||
// Avoid using { wrapper: I18nProvider } in case the caller adds a custom wrapper.
|
// Avoid using { wrapper: I18nProvider } in case the caller adds a custom wrapper.
|
||||||
return render(<I18nProvider>{ui}</I18nProvider>, ...remainingRenderArgs);
|
return render(<I18nProvider>{ui}</I18nProvider>, ...remainingRenderArgs);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const renderWithEuiTheme = (...args: Parameters<typeof render>) => {
|
||||||
|
const [ui, ...remainingRenderArgs] = args;
|
||||||
|
// Avoid using { wrapper: EuiThemeProvider } in case the caller adds a custom wrapper.
|
||||||
|
return render(<EuiThemeProvider>{ui}</EuiThemeProvider>, ...remainingRenderArgs);
|
||||||
|
};
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getAutoFollowPatternMock } from './fixtures/auto_follow_pattern';
|
|
||||||
import './mocks';
|
import './mocks';
|
||||||
|
import { getAutoFollowPatternMock } from './fixtures/auto_follow_pattern';
|
||||||
import { setupEnvironment, pageHelpers, nextTick, delay, getRandomString } from './helpers';
|
import { setupEnvironment, pageHelpers, nextTick, delay, getRandomString } from './helpers';
|
||||||
|
|
||||||
const { setup } = pageHelpers.autoFollowPatternList;
|
const { setup } = pageHelpers.autoFollowPatternList;
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
import { getFollowerIndexMock } from './fixtures/follower_index';
|
|
||||||
import './mocks';
|
import './mocks';
|
||||||
|
import { getFollowerIndexMock } from './fixtures/follower_index';
|
||||||
import { setupEnvironment, pageHelpers, getRandomString } from './helpers';
|
import { setupEnvironment, pageHelpers, getRandomString } from './helpers';
|
||||||
|
|
||||||
const { setup } = pageHelpers.followerIndexList;
|
const { setup } = pageHelpers.followerIndexList;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue