mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 20:19:44 +08:00
f0189fd36f
* chore(App): add edge usage warning * test: add test * docs(App): add FAQ
258 lines
7.4 KiB
TypeScript
258 lines
7.4 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { SmileOutlined } from '@ant-design/icons';
|
|
import type { NotificationConfig } from 'antd/es/notification/interface';
|
|
import ConfigProvider from 'antd/es/config-provider';
|
|
|
|
import App from '..';
|
|
import mountTest from '../../../tests/shared/mountTest';
|
|
import rtlTest from '../../../tests/shared/rtlTest';
|
|
import { render, waitFakeTimer } from '../../../tests/utils';
|
|
import type { AppConfig } from '../context';
|
|
import { AppConfigContext } from '../context';
|
|
|
|
describe('App', () => {
|
|
mountTest(App);
|
|
rtlTest(App);
|
|
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllTimers();
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('single', () => {
|
|
// Sub page
|
|
const MyPage: React.FC = () => {
|
|
const { message } = App.useApp();
|
|
React.useEffect(() => {
|
|
message.success('Good!');
|
|
}, [message]);
|
|
|
|
return <div>Hello World</div>;
|
|
};
|
|
|
|
// Entry component
|
|
const MyApp: React.FC = () => (
|
|
<App>
|
|
<MyPage />
|
|
</App>
|
|
);
|
|
|
|
const { getByText, container } = render(<MyApp />);
|
|
expect(getByText('Hello World')).toBeTruthy();
|
|
expect(container.firstChild).toMatchSnapshot();
|
|
});
|
|
|
|
it('should work as message and notification config configured in app', async () => {
|
|
let consumedConfig: AppConfig | undefined;
|
|
const Consumer = () => {
|
|
const { message, notification } = App.useApp();
|
|
consumedConfig = React.useContext(AppConfigContext);
|
|
|
|
useEffect(() => {
|
|
message.success('Message 1');
|
|
message.success('Message 2');
|
|
notification.success({ message: 'Notification 1' });
|
|
notification.success({ message: 'Notification 2' });
|
|
notification.success({ message: 'Notification 3' });
|
|
}, [message, notification]);
|
|
|
|
return <div />;
|
|
};
|
|
const Wrapper = () => (
|
|
<App message={{ maxCount: 1 }} notification={{ maxCount: 2 }}>
|
|
<Consumer />
|
|
</App>
|
|
);
|
|
|
|
render(<Wrapper />);
|
|
|
|
await waitFakeTimer();
|
|
|
|
expect(consumedConfig?.message).toStrictEqual({ maxCount: 1 });
|
|
expect(consumedConfig?.notification).toStrictEqual({ maxCount: 2 });
|
|
|
|
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
|
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(2);
|
|
});
|
|
|
|
it('should be a merged config configured in nested app', async () => {
|
|
let offsetConsumedConfig: AppConfig | undefined;
|
|
let maxCountConsumedConfig: AppConfig | undefined;
|
|
const OffsetConsumer = () => {
|
|
offsetConsumedConfig = React.useContext(AppConfigContext);
|
|
return <div />;
|
|
};
|
|
const MaxCountConsumer = () => {
|
|
maxCountConsumedConfig = React.useContext(AppConfigContext);
|
|
return <div />;
|
|
};
|
|
const Wrapper = () => (
|
|
<App message={{ maxCount: 1 }} notification={{ maxCount: 2 }}>
|
|
<App message={{ top: 32 }} notification={{ top: 96 }}>
|
|
<OffsetConsumer />
|
|
</App>
|
|
<MaxCountConsumer />
|
|
</App>
|
|
);
|
|
|
|
render(<Wrapper />);
|
|
|
|
expect(offsetConsumedConfig?.message).toStrictEqual({ maxCount: 1, top: 32 });
|
|
expect(offsetConsumedConfig?.notification).toStrictEqual({ maxCount: 2, top: 96 });
|
|
expect(maxCountConsumedConfig?.message).toStrictEqual({ maxCount: 1 });
|
|
expect(maxCountConsumedConfig?.notification).toStrictEqual({ maxCount: 2 });
|
|
});
|
|
|
|
it('should respect config from props in priority', async () => {
|
|
let config: AppConfig | undefined;
|
|
const Consumer = () => {
|
|
config = React.useContext(AppConfigContext);
|
|
return <div />;
|
|
};
|
|
const Wrapper = () => (
|
|
<App message={{ maxCount: 10, top: 20 }} notification={{ maxCount: 30, bottom: 40 }}>
|
|
<App message={{ maxCount: 11 }} notification={{ bottom: 41 }}>
|
|
<Consumer />
|
|
</App>
|
|
</App>
|
|
);
|
|
|
|
render(<Wrapper />);
|
|
|
|
expect(config?.message).toStrictEqual({ maxCount: 11, top: 20 });
|
|
expect(config?.notification).toStrictEqual({ maxCount: 30, bottom: 41 });
|
|
});
|
|
|
|
it('should respect notification placement config from props in priority', async () => {
|
|
let consumedConfig: AppConfig | undefined;
|
|
|
|
const Consumer = () => {
|
|
const { notification } = App.useApp();
|
|
consumedConfig = React.useContext(AppConfigContext);
|
|
|
|
useEffect(() => {
|
|
notification.success({ message: 'Notification 1' });
|
|
notification.success({ message: 'Notification 2' });
|
|
notification.success({ message: 'Notification 3' });
|
|
}, [notification]);
|
|
|
|
return <div />;
|
|
};
|
|
|
|
const config: NotificationConfig = {
|
|
placement: 'bottomLeft',
|
|
top: 100,
|
|
bottom: 50,
|
|
};
|
|
|
|
const Wrapper = () => (
|
|
<App notification={config}>
|
|
<Consumer />
|
|
</App>
|
|
);
|
|
|
|
render(<Wrapper />);
|
|
await waitFakeTimer();
|
|
|
|
expect(consumedConfig?.notification).toStrictEqual(config);
|
|
expect(document.querySelector('.ant-notification-topRight')).not.toBeInTheDocument();
|
|
expect(document.querySelector('.ant-notification-bottomLeft')).toHaveStyle({
|
|
top: '',
|
|
left: '0px',
|
|
bottom: '50px',
|
|
});
|
|
});
|
|
|
|
it('support className', () => {
|
|
const { container } = render(
|
|
<App className="test-class">
|
|
<div>test</div>
|
|
</App>,
|
|
);
|
|
expect(container.querySelector<HTMLDivElement>('.ant-app')).toHaveClass('test-class');
|
|
});
|
|
|
|
it('support style', () => {
|
|
const { container } = render(
|
|
<App style={{ color: 'blue' }}>
|
|
<div>test</div>
|
|
</App>,
|
|
);
|
|
expect(container.querySelector<HTMLDivElement>('.ant-app')).toHaveStyle('color: blue;');
|
|
});
|
|
|
|
// https://github.com/ant-design/ant-design/issues/41197#issuecomment-1465803061
|
|
describe('restIcon style', () => {
|
|
beforeEach(() => {
|
|
Array.from(document.querySelectorAll('style')).forEach((style) => {
|
|
style.parentNode?.removeChild(style);
|
|
});
|
|
});
|
|
|
|
it('should work by default', () => {
|
|
const { container } = render(
|
|
<App>
|
|
<SmileOutlined />
|
|
</App>,
|
|
);
|
|
|
|
expect(container.querySelector('.anticon')).toBeTruthy();
|
|
const dynamicStyles = Array.from(document.querySelectorAll('style[data-css-hash]'));
|
|
expect(
|
|
dynamicStyles.some((style) => {
|
|
const { innerHTML } = style;
|
|
return innerHTML.startsWith('.anticon');
|
|
}),
|
|
).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('component', () => {
|
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
afterEach(() => {
|
|
errorSpy.mockReset();
|
|
});
|
|
|
|
afterAll(() => {
|
|
errorSpy.mockRestore();
|
|
});
|
|
|
|
it('replace', () => {
|
|
const { container } = render(
|
|
<App component="section">
|
|
<p />
|
|
</App>,
|
|
);
|
|
|
|
expect(container.querySelector('section.ant-app')).toBeTruthy();
|
|
});
|
|
|
|
it('to false', () => {
|
|
const { container } = render(
|
|
<App component={false}>
|
|
<p />
|
|
</App>,
|
|
);
|
|
expect(errorSpy).not.toHaveBeenCalled();
|
|
expect(container.querySelector('.ant-app')).toBeFalsy();
|
|
});
|
|
|
|
it('should warn if component is false and cssVarCls is not empty', () => {
|
|
render(
|
|
<ConfigProvider theme={{ cssVar: true }}>
|
|
<App component={false} />
|
|
</ConfigProvider>,
|
|
);
|
|
|
|
expect(errorSpy).toHaveBeenCalledWith(
|
|
'Warning: [antd: App] When using cssVar, ensure `component` is assigned a valid React component string.',
|
|
);
|
|
});
|
|
});
|
|
});
|