diff --git a/src/components/loading/loading_elastic.test.tsx b/src/components/loading/loading_elastic.test.tsx
index 7b57964d4b..1038252515 100644
--- a/src/components/loading/loading_elastic.test.tsx
+++ b/src/components/loading/loading_elastic.test.tsx
@@ -6,8 +6,7 @@
import React from 'react';
import { render, mount } from 'enzyme';
import { requiredProps } from '../../test';
-import { getDeprecatedMessage } from '../../utils';
-import { OuiLoadingElastic, SIZES, WARNING } from './loading_elastic';
+import { OuiLoadingElastic, SIZES } from './loading_elastic';
describe('OuiLoadingElastic', () => {
test('is rendered', () => {
@@ -28,10 +27,14 @@ describe('OuiLoadingElastic', () => {
});
});
- it('should console warning about a deprecated component', () => {
+ it('should console deprecation warning', () => {
console.warn = jest.fn();
+
mount();
- expect(console.warn).toHaveBeenCalledWith(getDeprecatedMessage(WARNING));
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] OuiLoadingElastic is deprecated in favor of OuiLoadingDashboards and will be removed in v2.0.0.'
+ );
});
});
diff --git a/src/components/loading/loading_elastic.tsx b/src/components/loading/loading_elastic.tsx
index 19756c10c2..e15b9a18bb 100644
--- a/src/components/loading/loading_elastic.tsx
+++ b/src/components/loading/loading_elastic.tsx
@@ -32,7 +32,7 @@ import React, { HTMLAttributes, FunctionComponent } from 'react';
import classNames from 'classnames';
import { CommonProps, keysOf } from '../common';
import { OuiIcon } from '../icon';
-import { deprecated } from '../../utils';
+import { deprecatedComponentWarning } from '../../utils';
const sizeToClassNameMap = {
m: 'ouiLoadingElastic--medium',
@@ -43,9 +43,6 @@ const sizeToClassNameMap = {
export const SIZES = keysOf(sizeToClassNameMap);
-export const WARNING =
- 'OuiLoadingElastic is deprecated in favor of OuiLoadingDashboards and will be removed in v2.0.0.';
-
export interface OuiLoadingElasticProps {
size?: keyof typeof sizeToClassNameMap;
}
@@ -66,9 +63,12 @@ const OuiLoadingElasticComponent: FunctionComponent<
);
};
+OuiLoadingElasticComponent.displayName = 'OuiLoadingElastic';
+
/**
* @deprecated OuiLoadingElastic is deprecated in favor of OuiLoadingDashboards and will be removed in v2.0.0.
*/
-export const OuiLoadingElastic = deprecated(WARNING)(
- OuiLoadingElasticComponent
-);
+export const OuiLoadingElastic = deprecatedComponentWarning({
+ newComponentName: 'OuiLoadingDashboards',
+ version: '2.0.0',
+})(OuiLoadingElasticComponent);
diff --git a/src/components/loading/loading_kibana.test.tsx b/src/components/loading/loading_kibana.test.tsx
index 8dfb53bc05..a0a417eef1 100644
--- a/src/components/loading/loading_kibana.test.tsx
+++ b/src/components/loading/loading_kibana.test.tsx
@@ -31,8 +31,7 @@
import React from 'react';
import { render, mount } from 'enzyme';
import { requiredProps } from '../../test';
-import { getDeprecatedMessage } from '../../utils';
-import { OuiLoadingKibana, SIZES, WARNING } from './loading_kibana';
+import { OuiLoadingKibana, SIZES } from './loading_kibana';
describe('OuiLoadingKibana', () => {
test('is rendered', () => {
@@ -53,10 +52,14 @@ describe('OuiLoadingKibana', () => {
});
});
- it('should console warning about a deprecated component', () => {
+ it('should console deprecation warning', () => {
console.warn = jest.fn();
+
mount();
- expect(console.warn).toHaveBeenCalledWith(getDeprecatedMessage(WARNING));
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] OuiLoadingKibana is deprecated in favor of OuiLoadingLogo and will be removed in v2.0.0.'
+ );
});
});
diff --git a/src/components/loading/loading_kibana.tsx b/src/components/loading/loading_kibana.tsx
index f315595f7d..c54eee9a10 100644
--- a/src/components/loading/loading_kibana.tsx
+++ b/src/components/loading/loading_kibana.tsx
@@ -32,7 +32,7 @@ import React, { HTMLAttributes, FunctionComponent } from 'react';
import classNames from 'classnames';
import { CommonProps, keysOf } from '../common';
import { OuiIcon } from '../icon';
-import { deprecated } from '../../utils';
+import { deprecatedComponentWarning } from '../../utils';
const sizeToClassNameMap = {
m: 'ouiLoadingKibana--medium',
@@ -42,9 +42,6 @@ const sizeToClassNameMap = {
export const SIZES = keysOf(sizeToClassNameMap);
-export const WARNING =
- 'OuiLoadingKibana is deprecated in favor of OuiLoadingLogo and will be removed in v2.0.0.';
-
export type OuiLoadingKibanaProps = CommonProps &
HTMLAttributes & {
size?: keyof typeof sizeToClassNameMap;
@@ -70,7 +67,12 @@ const OuiLoadingKibanaComponent: FunctionComponent = ({
);
};
+OuiLoadingKibanaComponent.displayName = 'OuiLoadingKibana';
+
/**
* @deprecated OuiLoadingKibana is deprecated in favor of OuiLoadingLogo and will be removed in v2.0.0.
*/
-export const OuiLoadingKibana = deprecated(WARNING)(OuiLoadingKibanaComponent);
+export const OuiLoadingKibana = deprecatedComponentWarning({
+ newComponentName: 'OuiLoadingLogo',
+ version: '2.0.0',
+})(OuiLoadingKibanaComponent);
diff --git a/src/components/page/page_header/page_header.test.tsx b/src/components/page/page_header/page_header.test.tsx
index 10414b41da..afb02c7e84 100644
--- a/src/components/page/page_header/page_header.test.tsx
+++ b/src/components/page/page_header/page_header.test.tsx
@@ -29,8 +29,8 @@
*/
import React from 'react';
-import { render } from 'enzyme';
-import { requiredProps } from '../../../test/required_props';
+import { mount, render } from 'enzyme';
+import { requiredProps } from '../../../test';
import { OuiPageHeader, OuiPageHeaderProps } from './page_header';
import { ALIGN_ITEMS } from './page_header_content';
@@ -124,4 +124,48 @@ describe('OuiPageHeader', () => {
});
});
});
+
+ describe('deprecation', () => {
+ it('should console 1 deprecation warning without repetition', () => {
+ console.warn = jest.fn();
+
+ const component = mount();
+ component.setProps({ iconType: 'database' });
+
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] The `iconType` prop is deprecated and will be removed in v2.0.0.'
+ );
+ });
+
+ it('should console 2 deprecation warning without repetition', () => {
+ console.warn = jest.fn();
+
+ const component = mount(
+
+ );
+ component.setProps({
+ iconType: 'database',
+ iconProps: { color: 'blue' },
+ });
+
+ const results = [
+ '[DEPRECATED] The `iconType` prop is deprecated and will be removed in v2.0.0.',
+ '[DEPRECATED] The `iconProps` prop is deprecated and will be removed in v2.0.0.',
+ ];
+
+ expect(console.warn).toHaveBeenCalledTimes(2);
+ results.forEach((item) =>
+ expect(console.warn).toHaveBeenCalledWith(item)
+ );
+ });
+
+ it('should not console deprecation warning', () => {
+ console.warn = jest.fn();
+
+ mount();
+
+ expect(console.warn).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/src/components/page/page_header/page_header.tsx b/src/components/page/page_header/page_header.tsx
index 50cf06ac87..97e2359c3e 100644
--- a/src/components/page/page_header/page_header.tsx
+++ b/src/components/page/page_header/page_header.tsx
@@ -28,7 +28,7 @@
* under the License.
*/
-import React, { FunctionComponent, HTMLAttributes, useEffect } from 'react';
+import React, { FunctionComponent, HTMLAttributes } from 'react';
import classNames from 'classnames';
import { CommonProps, keysOf } from '../../common';
import {
@@ -39,6 +39,7 @@ import {
_OuiPageRestrictWidth,
setPropsForRestrictedPageWidth,
} from '../_restrict_width';
+import { useDeprecatedPropWarning } from '../../../utils';
const paddingSizeToClassNameMap = {
none: null,
@@ -92,13 +93,10 @@ export const OuiPageHeader: FunctionComponent = ({
style
);
- useEffect(() => {
- if (iconType || iconProps) {
- console.warn(
- 'WARNING: The `iconType` and `iconProps` properties in `OuiPageHeader` are deprecated and will be removed in the future. Please update your code accordingly.'
- );
- }
- }, [iconType, iconProps]);
+ useDeprecatedPropWarning({
+ props: { iconType, iconProps },
+ version: '2.0.0',
+ });
const classes = classNames(
'ouiPageHeader',
diff --git a/src/utils/deprecated/__snapshots__/deprecated.test.tsx.snap b/src/utils/deprecated/__snapshots__/deprecated.test.tsx.snap
index f31fac9b05..16811bd308 100644
--- a/src/utils/deprecated/__snapshots__/deprecated.test.tsx.snap
+++ b/src/utils/deprecated/__snapshots__/deprecated.test.tsx.snap
@@ -1,3 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`deprecated should render component 1`] = ``;
+exports[`deprecatedComponentWarning should render wrapped component 1`] = `
+
+`;
diff --git a/src/utils/deprecated/deprecated.test.tsx b/src/utils/deprecated/deprecated.test.tsx
index 331428e39c..69daed1c1b 100644
--- a/src/utils/deprecated/deprecated.test.tsx
+++ b/src/utils/deprecated/deprecated.test.tsx
@@ -3,37 +3,168 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React from 'react';
+import React, { FC } from 'react';
import { mount, render } from 'enzyme';
-import { deprecated, getDeprecatedMessage } from './deprecated';
-
-describe('deprecated', () => {
- const warning = 'This component is deprecated in favor of another.';
+import {
+ deprecatedComponentWarning,
+ useDeprecatedPropWarning,
+} from './deprecated';
+describe('deprecatedComponentWarning', () => {
it('should console warning', () => {
console.warn = jest.fn();
- const Component = () => ;
- const DeprecatedComponent = deprecated(warning)(Component);
- mount();
+ const ExampleComponent = () => ;
+ ExampleComponent.displayName = 'Example';
+
+ const Example = deprecatedComponentWarning({
+ newComponentName: 'NewComponent',
+ version: '2.0.0',
+ })(ExampleComponent);
+
+ const component = mount();
+ component.setProps({ name: 'new' });
- expect(console.warn).toHaveBeenCalledWith(getDeprecatedMessage(warning));
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] Example is deprecated in favor of NewComponent and will be removed in v2.0.0.'
+ );
});
- it('should render component', () => {
+ it('should console custom warning', () => {
console.warn = jest.fn();
- const Component = () => ;
- const DeprecatedComponent = deprecated(warning)(Component);
+ const ExampleComponent = () => ;
+ ExampleComponent.displayName = 'Example';
+
+ const Example = deprecatedComponentWarning({
+ getMessage: (componentName) => `Custom message for \`${componentName}\`.`,
+ })(ExampleComponent);
+
+ const component = mount();
+ component.setProps({ name: 'new' });
+
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] Custom message for `Example`.'
+ );
+ });
+
+ it('should render wrapped component', () => {
+ console.warn = jest.fn();
+
+ const ExampleComponent = () => ;
+ ExampleComponent.displayName = 'Example';
+
+ const Example = deprecatedComponentWarning({
+ newComponentName: 'New Component',
+ version: '2.0.0',
+ })(ExampleComponent);
- const component = render();
+ const component = render();
expect(component).toMatchSnapshot();
});
it('should properly name DeprecatedWrapper function', () => {
- const Component = () => ;
- const DeprecatedComponent = deprecated(warning)(Component);
+ const ExampleComponent = () => ;
+ ExampleComponent.displayName = 'Example';
+
+ const Example = deprecatedComponentWarning({
+ newComponentName: 'New Component',
+ version: '2.0.0',
+ })(ExampleComponent);
+
+ expect(Example.name).toEqual('Example');
+ });
+});
+
+describe('useDeprecatedPropWarning', () => {
+ interface IExampleComponent {
+ name?: string;
+ age?: number;
+ version?: string;
+ getMessage?: (propName: string) => string;
+ }
+
+ const ExampleDefaultMessageComponent: FC = ({
+ name,
+ age,
+ version,
+ }) => {
+ useDeprecatedPropWarning({ props: { name, age }, version });
+
+ return ;
+ };
+
+ const ExampleCustomMessageComponent: FC = ({
+ name,
+ age,
+ getMessage,
+ }) => {
+ useDeprecatedPropWarning({ props: { name, age }, getMessage });
+
+ return ;
+ };
+
+ it('should console 1 warning without repetition', () => {
+ console.warn = jest.fn();
+
+ const component = mount();
+ component.setProps({ name: 'new name' });
+
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] The `name` prop is deprecated and will be removed.'
+ );
+ });
+
+ it('should console 2 warning without repetition', () => {
+ console.warn = jest.fn();
+
+ const component = mount(
+
+ );
+ component.setProps({ name: 'new name', age: 22 });
+
+ const results = [
+ '[DEPRECATED] The `name` prop is deprecated and will be removed.',
+ '[DEPRECATED] The `age` prop is deprecated and will be removed.',
+ ];
+
+ expect(console.warn).toHaveBeenCalledTimes(2);
+ results.forEach((item) => expect(console.warn).toHaveBeenCalledWith(item));
+ });
+
+ it('should console warning with version', () => {
+ console.warn = jest.fn();
+
+ mount();
+
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] The `name` prop is deprecated and will be removed in v2.0.0.'
+ );
+ });
+
+ it('should console warning with custom message', () => {
+ console.warn = jest.fn();
+
+ mount(
+ `Custom message: \`${propName}\`.`}
+ />
+ );
+
+ expect(console.warn).toHaveBeenCalledWith(
+ '[DEPRECATED] Custom message: `name`.'
+ );
+ });
+
+ it('should not console warning', () => {
+ console.warn = jest.fn();
+
+ mount();
- expect(DeprecatedComponent.name).toEqual('Component');
+ expect(console.warn).not.toHaveBeenCalled();
});
});
diff --git a/src/utils/deprecated/deprecated.tsx b/src/utils/deprecated/deprecated.tsx
index 6d4c80b40f..926a2aba49 100644
--- a/src/utils/deprecated/deprecated.tsx
+++ b/src/utils/deprecated/deprecated.tsx
@@ -3,25 +3,75 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useEffect } from 'react';
+import React, { useEffect, useRef } from 'react';
+import { ExclusiveUnion } from '../../components/common';
export const getDeprecatedMessage = (message: string): string =>
`[DEPRECATED] ${message}`;
-export const deprecated = (message: string) => {
+type DeprecatedComponentWarning = ExclusiveUnion<
+ { newComponentName: string; version?: string },
+ { getMessage?: (deprecatedComponentName: string) => string }
+>;
+
+export const deprecatedComponentWarning = ({
+ newComponentName,
+ version,
+ getMessage,
+}: DeprecatedComponentWarning) => {
return >(Component: T): T => {
+ const deprecatedComponentName = Component.displayName || Component.name;
+
const DeprecatedWrapper = (props: React.ComponentProps) => {
useEffect(() => {
- console.warn(getDeprecatedMessage(message));
+ const defaultMessage = version
+ ? `${deprecatedComponentName} is deprecated in favor of ${newComponentName} and will be removed in v${version}.`
+ : `${deprecatedComponentName} is deprecated in favor of ${newComponentName} and will be removed.`;
+ const message = getMessage?.(deprecatedComponentName) || defaultMessage;
+ const deprecatedMessage = getDeprecatedMessage(message);
+
+ console.warn(deprecatedMessage);
}, []);
return ;
};
Object.defineProperty(DeprecatedWrapper, 'name', {
- value: Component.displayName || Component.name,
+ value: deprecatedComponentName,
});
return DeprecatedWrapper as T;
};
};
+
+type _DeprecatedPropWarningExclusiveProps = ExclusiveUnion<
+ { version?: string },
+ { getMessage?: (deprecatedComponentName: string) => string }
+>;
+
+type DeprecatedPropWarning = {
+ props: Record;
+} & _DeprecatedPropWarningExclusiveProps;
+
+export const useDeprecatedPropWarning = ({
+ props,
+ version,
+ getMessage,
+}: DeprecatedPropWarning): void => {
+ const warnedProps = useRef(new Set()).current;
+
+ useEffect(() => {
+ Object.entries(props).forEach(([name, value]) => {
+ if (value !== undefined && !warnedProps.has(name)) {
+ const defaultMessage = version
+ ? `The \`${name}\` prop is deprecated and will be removed in v${version}.`
+ : `The \`${name}\` prop is deprecated and will be removed.`;
+ const message = getMessage?.(name) || defaultMessage;
+ const deprecatedMessage = getDeprecatedMessage(message);
+
+ warnedProps.add(name);
+ console.warn(deprecatedMessage);
+ }
+ });
+ }, [warnedProps, props, version, getMessage]);
+};