chore: Static warning if exist theme in CP (#41157)

* chore: warning for dynamic theme

* chore: warning for static context

* chore: add ignore
This commit is contained in:
二货爱吃白萝卜 2023-03-10 14:12:49 +08:00 committed by GitHub
parent 1df1034f20
commit 36f44575c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 203 additions and 3 deletions

View File

@ -27,6 +27,24 @@ import type { SizeType } from './SizeContext';
import SizeContext, { SizeContextProvider } from './SizeContext'; import SizeContext, { SizeContextProvider } from './SizeContext';
import useStyle from './style'; import useStyle from './style';
/**
* Since too many feedback using static method like `Modal.confirm` not getting theme,
* we record the theme register info here to help developer get warning info.
*/
let existThemeConfig = false;
export const warnContext: (componentName: string) => void =
process.env.NODE_ENV !== 'production'
? (componentName: string) => {
warning(
!existThemeConfig,
componentName,
`Static function can not consume context like dynamic theme. Please use 'App' component instead.`,
);
}
: /* istanbul ignore next */
null!;
export { export {
type RenderEmptyHandler, type RenderEmptyHandler,
ConfigContext, ConfigContext,
@ -192,6 +210,10 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
const mergedTheme = useTheme(theme, parentContext.theme); const mergedTheme = useTheme(theme, parentContext.theme);
if (process.env.NODE_ENV !== 'production') {
existThemeConfig = existThemeConfig || !!mergedTheme;
}
const baseConfig = { const baseConfig = {
csp, csp,
autoInsertSpaceInButton, autoInsertSpaceInButton,

View File

@ -0,0 +1,57 @@
import React from 'react';
import message, { actWrapper } from '..';
import { act, render, waitFakeTimer } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import { awaitPromise, triggerMotionEnd } from './util';
describe('message static warning', () => {
beforeAll(() => {
actWrapper(act);
});
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(async () => {
// Clean up
message.destroy();
await triggerMotionEnd();
jest.useRealTimers();
await awaitPromise();
});
// Follow test need keep order
it('no warning', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
message.success({
content: <div className="bamboo" />,
duration: 0,
});
await waitFakeTimer();
expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled();
});
it('warning if use theme', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<ConfigProvider theme={{}} />);
message.success({
content: <div className="light" />,
duration: 0,
});
await waitFakeTimer();
expect(document.querySelector('.light')).toBeTruthy();
expect(errSpy).toHaveBeenCalledWith(
"Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
);
});
});

View File

@ -1,6 +1,6 @@
import { render } from 'rc-util/lib/React/render'; import { render } from 'rc-util/lib/React/render';
import * as React from 'react'; import * as React from 'react';
import ConfigProvider, { globalConfig } from '../config-provider'; import ConfigProvider, { globalConfig, warnContext } from '../config-provider';
import type { import type {
ArgsProps, ArgsProps,
ConfigOptions, ConfigOptions,
@ -262,6 +262,11 @@ function open(config: ArgsProps): MessageType {
} }
function typeOpen(type: NoticeType, args: Parameters<TypeOpen>): MessageType { function typeOpen(type: NoticeType, args: Parameters<TypeOpen>): MessageType {
// Warning if exist theme
if (process.env.NODE_ENV !== 'production') {
warnContext('message');
}
const result = wrapPromiseFn((resolve) => { const result = wrapPromiseFn((resolve) => {
let closeFn: VoidFunction; let closeFn: VoidFunction;

View File

@ -0,0 +1,49 @@
import * as React from 'react';
import Modal from '..';
import { render, waitFakeTimer } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import { resetWarned } from '../../_util/warning';
describe('Modal.confirm warning', () => {
beforeEach(() => {
jest.useFakeTimers();
resetWarned();
});
afterEach(async () => {
Modal.destroyAll();
await waitFakeTimer();
document.body.innerHTML = '';
jest.clearAllTimers();
});
// Follow test need keep order
it('no warning', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
Modal.confirm({
content: <div className="bamboo" />,
});
await waitFakeTimer();
expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled();
});
it('warning if use theme', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<ConfigProvider theme={{}} />);
Modal.confirm({
content: <div className="light" />,
});
await waitFakeTimer();
expect(document.querySelector('.light')).toBeTruthy();
expect(errSpy).toHaveBeenCalledWith(
"Warning: [antd: Modal] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
);
});
});

View File

@ -1,6 +1,6 @@
import { render as reactRender, unmount as reactUnmount } from 'rc-util/lib/React/render'; import { render as reactRender, unmount as reactUnmount } from 'rc-util/lib/React/render';
import * as React from 'react'; import * as React from 'react';
import { globalConfig } from '../config-provider'; import { globalConfig, warnContext } from '../config-provider';
import warning from '../_util/warning'; import warning from '../_util/warning';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import destroyFns from './destroyFns'; import destroyFns from './destroyFns';
@ -23,6 +23,11 @@ export type ModalFunc = (props: ModalFuncProps) => {
export type ModalStaticFunctions = Record<NonNullable<ModalFuncProps['type']>, ModalFunc>; export type ModalStaticFunctions = Record<NonNullable<ModalFuncProps['type']>, ModalFunc>;
export default function confirm(config: ModalFuncProps) { export default function confirm(config: ModalFuncProps) {
// Warning if exist theme
if (process.env.NODE_ENV !== 'production') {
warnContext('Modal');
}
const container = document.createDocumentFragment(); const container = document.createDocumentFragment();
// eslint-disable-next-line @typescript-eslint/no-use-before-define // eslint-disable-next-line @typescript-eslint/no-use-before-define
let currentConfig = { ...config, close, open: true } as any; let currentConfig = { ...config, close, open: true } as any;

View File

@ -0,0 +1,57 @@
import React from 'react';
import notification, { actWrapper } from '..';
import { act, render, waitFakeTimer } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import { awaitPromise, triggerMotionEnd } from './util';
describe('notification static warning', () => {
beforeAll(() => {
actWrapper(act);
});
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(async () => {
// Clean up
notification.destroy();
await triggerMotionEnd();
jest.useRealTimers();
await awaitPromise();
});
// Follow test need keep order
it('no warning', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
notification.open({
message: <div className="bamboo" />,
duration: 0,
});
await waitFakeTimer();
expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled();
});
it('warning if use theme', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<ConfigProvider theme={{}} />);
notification.open({
message: <div className="light" />,
duration: 0,
});
await waitFakeTimer();
expect(document.querySelector('.light')).toBeTruthy();
expect(errSpy).toHaveBeenCalledWith(
"Warning: [antd: notification] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
);
});
});

View File

@ -1,6 +1,6 @@
import { render } from 'rc-util/lib/React/render'; import { render } from 'rc-util/lib/React/render';
import * as React from 'react'; import * as React from 'react';
import ConfigProvider, { globalConfig } from '../config-provider'; import ConfigProvider, { globalConfig, warnContext } from '../config-provider';
import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface'; import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface';
import PurePanel from './PurePanel'; import PurePanel from './PurePanel';
import useNotification, { useInternalNotification } from './useNotification'; import useNotification, { useInternalNotification } from './useNotification';
@ -201,6 +201,11 @@ function setNotificationGlobalConfig(config: GlobalConfigProps) {
} }
function open(config: ArgsProps) { function open(config: ArgsProps) {
// Warning if exist theme
if (process.env.NODE_ENV !== 'production') {
warnContext('notification');
}
taskQueue.push({ taskQueue.push({
type: 'open', type: 'open',
config, config,