Skip to content

Commit

Permalink
Use ExclusiveUnion in interfaces (#761)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrey Myssak <[email protected]>
Signed-off-by: Sergey Myssak <[email protected]>
  • Loading branch information
SergeyMyssak and andreymyssak committed May 25, 2023
1 parent b2a94a5 commit 6114846
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 36 deletions.
6 changes: 3 additions & 3 deletions src/components/page/page_header/page_header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ describe('OuiPageHeader', () => {

expect(console.warn).toHaveBeenCalledTimes(1);
expect(console.warn).toHaveBeenCalledWith(
'[DEPRECATED] The `iconType` prop is deprecated and will be removed in v2.0.0'
'[DEPRECATED] The `iconType` prop is deprecated and will be removed in v2.0.0.'
);
});

Expand All @@ -150,8 +150,8 @@ describe('OuiPageHeader', () => {
});

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',
'[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);
Expand Down
94 changes: 77 additions & 17 deletions src/utils/deprecated/deprecated.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import React, { FC } from 'react';
import { mount, render } from 'enzyme';
import {
deprecatedComponentWarning,
Expand Down Expand Up @@ -31,6 +31,25 @@ describe('deprecatedComponentWarning', () => {
);
});

it('should console custom warning', () => {
console.warn = jest.fn();

const ExampleComponent = () => <div id="example-component" />;
ExampleComponent.displayName = 'Example';

const Example = deprecatedComponentWarning({
getMessage: (componentName) => `Custom message for \`${componentName}\`.`,
})(ExampleComponent);

const component = mount(<Example />);
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();

Expand Down Expand Up @@ -60,50 +79,91 @@ describe('deprecatedComponentWarning', () => {
});

describe('useDeprecatedPropWarning', () => {
const ExampleComponent = ({ name, age }: { name?: string; age?: number }) => {
useDeprecatedPropWarning({
props: { name, age },
version: '2.0.0',
});
interface IExampleComponent {
name?: string;
age?: number;
version?: string;
getMessage?: (propName: string) => string;
}

const ExampleDefaultMessageComponent: FC<IExampleComponent> = ({
name,
age,
version,
}) => {
useDeprecatedPropWarning({ props: { name, age }, version });

return <div />;
};

return (
<div>
{name} {age}
</div>
);
const ExampleCustomMessageComponent: FC<IExampleComponent> = ({
name,
age,
getMessage,
}) => {
useDeprecatedPropWarning({ props: { name, age }, getMessage });

return <div />;
};

it('should console 1 warning without repetition', () => {
console.warn = jest.fn();

const component = mount(<ExampleComponent name="name" />);
const component = mount(<ExampleDefaultMessageComponent name="name" />);
component.setProps({ name: 'new name' });

expect(console.warn).toHaveBeenCalledTimes(1);
expect(console.warn).toHaveBeenCalledWith(
'[DEPRECATED] The `name` prop is deprecated and will be removed in v2.0.0'
'[DEPRECATED] The `name` prop is deprecated and will be removed.'
);
});

it('should console 2 warning without repetition', () => {
console.warn = jest.fn();

const component = mount(<ExampleComponent name="name" age={21} />);
const component = mount(
<ExampleDefaultMessageComponent name="name" age={21} />
);
component.setProps({ name: 'new name', age: 22 });

const results = [
'[DEPRECATED] The `name` prop is deprecated and will be removed in v2.0.0',
'[DEPRECATED] The `age` prop is deprecated and will be removed in v2.0.0',
'[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(<ExampleDefaultMessageComponent name="name" version="2.0.0" />);

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(
<ExampleCustomMessageComponent
name="name"
getMessage={(propName: string) => `Custom message: \`${propName}\`.`}
/>
);

expect(console.warn).toHaveBeenCalledWith(
'[DEPRECATED] Custom message: `name`.'
);
});

it('should not console warning', () => {
console.warn = jest.fn();

mount(<ExampleComponent />);
mount(<ExampleDefaultMessageComponent />);

expect(console.warn).not.toHaveBeenCalled();
});
Expand Down
42 changes: 26 additions & 16 deletions src/utils/deprecated/deprecated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,30 @@
*/

import React, { useEffect, useRef } from 'react';
import { ExclusiveUnion } from '../../components/common';

export const getDeprecatedMessage = (message: string): string =>
`[DEPRECATED] ${message}`;

interface IDeprecatedComponentWarning {
newComponentName: string;
version?: string;
getMessage?: (deprecatedComponentName: string) => string;
}
type DeprecatedComponentWarning = ExclusiveUnion<
{ newComponentName: string; version?: string },
{ getMessage?: (deprecatedComponentName: string) => string }
>;

export const deprecatedComponentWarning = ({
newComponentName,
version,
getMessage,
}: IDeprecatedComponentWarning) => {
}: DeprecatedComponentWarning) => {
return <T extends React.ComponentType<any>>(Component: T): T => {
const deprecatedComponentName = Component.displayName || Component.name;

const DeprecatedWrapper = (props: React.ComponentProps<T>) => {
useEffect(() => {
const message =
getMessage?.(deprecatedComponentName) ||
`${deprecatedComponentName} is deprecated in favor of ${newComponentName} and will be removed in v${version}.`;
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);
Expand All @@ -43,25 +44,34 @@ export const deprecatedComponentWarning = ({
};
};

interface IDeprecatedPropWarning {
props: Record<string, any>;
version: string;
}
type _DeprecatedPropWarningExclusiveProps = ExclusiveUnion<
{ version?: string },
{ getMessage?: (deprecatedComponentName: string) => string }
>;

type DeprecatedPropWarning = {
props: Record<string, unknown>;
} & _DeprecatedPropWarningExclusiveProps;

export const useDeprecatedPropWarning = ({
props,
version,
}: IDeprecatedPropWarning): void => {
getMessage,
}: DeprecatedPropWarning): void => {
const warnedProps = useRef(new Set()).current;

useEffect(() => {
Object.entries(props).forEach(([name, value]) => {
if (value !== undefined && !warnedProps.has(name)) {
const message = `The \`${name}\` prop is deprecated and will be removed in v${version}`;
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]);
}, [warnedProps, props, version, getMessage]);
};

0 comments on commit 6114846

Please sign in to comment.