mirror of
https://github.com/ant-design/ant-design.git
synced 2024-12-23 23:18:36 +08:00
4c2a2b0c0d
* feat: FloatBtn support dynamic z-index * feat: FloatBtn support dynamic z-index * test: fix test case * fix: fix * test: add test case * test: fix test case * chore: clear
399 lines
12 KiB
TypeScript
399 lines
12 KiB
TypeScript
import type { PropsWithChildren } from 'react';
|
|
import React, { useEffect } from 'react';
|
|
import { render } from '@testing-library/react';
|
|
import type { ImageProps, MenuProps } from 'antd';
|
|
import {
|
|
AutoComplete,
|
|
Cascader,
|
|
ColorPicker,
|
|
DatePicker,
|
|
Drawer,
|
|
Dropdown,
|
|
FloatButton,
|
|
Image,
|
|
Menu,
|
|
Modal,
|
|
Popconfirm,
|
|
Popover,
|
|
Select,
|
|
Tooltip,
|
|
Tour,
|
|
TreeSelect,
|
|
} from 'antd';
|
|
|
|
import { waitFakeTimer } from '../../../tests/utils';
|
|
import type { ZIndexConsumer, ZIndexContainer } from '../hooks/useZIndex';
|
|
import { consumerBaseZIndexOffset, containerBaseZIndexOffset, useZIndex } from '../hooks/useZIndex';
|
|
import zIndexContext from '../zindexContext';
|
|
|
|
const WrapWithProvider: React.FC<PropsWithChildren<{ container: ZIndexContainer }>> = ({
|
|
children,
|
|
container,
|
|
}) => {
|
|
const [, contextZIndex] = useZIndex(container);
|
|
return <zIndexContext.Provider value={contextZIndex}>{children}</zIndexContext.Provider>;
|
|
};
|
|
|
|
const containerComponent: Partial<
|
|
Record<ZIndexContainer, React.FC<Readonly<PropsWithChildren<{ rootClassName?: string }>>>>
|
|
> = {
|
|
Modal: ({ children, ...restProps }) => (
|
|
<Modal {...restProps} open>
|
|
{children}
|
|
</Modal>
|
|
),
|
|
Drawer: ({ children, ...restProps }) => (
|
|
<Drawer {...restProps} open>
|
|
{children}
|
|
</Drawer>
|
|
),
|
|
Popover: ({ children, ...restProps }) => (
|
|
<Popover {...restProps} open content="test">
|
|
{children}
|
|
</Popover>
|
|
),
|
|
Popconfirm: ({ children, ...restProps }) => (
|
|
<Popconfirm {...restProps} open title="test">
|
|
{children}
|
|
</Popconfirm>
|
|
),
|
|
Tooltip: ({ children, ...restProps }) => (
|
|
<Tooltip {...restProps} open title="test">
|
|
{children}
|
|
</Tooltip>
|
|
),
|
|
Tour: ({ children, ...restProps }) => (
|
|
<Tour {...restProps} open steps={[{ title: 'cover title', description: children }]} />
|
|
),
|
|
};
|
|
|
|
const options = [
|
|
{
|
|
label: 'Option 1',
|
|
value: '1',
|
|
},
|
|
{
|
|
label: 'Option 2',
|
|
value: '2',
|
|
},
|
|
];
|
|
|
|
const items: MenuProps['items'] = [
|
|
{
|
|
label: 'Test',
|
|
key: 'SubMenu',
|
|
children: [
|
|
{
|
|
type: 'group',
|
|
label: 'Item 1',
|
|
children: [
|
|
{
|
|
label: 'Option 1',
|
|
key: 'setting:1',
|
|
},
|
|
{
|
|
label: 'Option 2',
|
|
key: 'setting:2',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'group',
|
|
label: 'Item 2',
|
|
children: [
|
|
{
|
|
label: 'Option 3',
|
|
key: 'setting:3',
|
|
},
|
|
{
|
|
label: 'Option 4',
|
|
key: 'setting:4',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const consumerComponent: Partial<
|
|
Record<ZIndexConsumer, React.FC<Readonly<{ rootClassName: string }>>>
|
|
> = {
|
|
SelectLike: ({ rootClassName, ...props }) => (
|
|
<>
|
|
<Select
|
|
{...props}
|
|
rootClassName={`${rootClassName} comp-item comp-Select`}
|
|
options={options}
|
|
open
|
|
/>
|
|
<Cascader
|
|
{...props}
|
|
rootClassName={`${rootClassName} comp-item comp-Cascader`}
|
|
options={options}
|
|
open
|
|
/>
|
|
<TreeSelect
|
|
{...props}
|
|
rootClassName={`${rootClassName} comp-item comp-TreeSelect`}
|
|
treeData={options}
|
|
open
|
|
/>
|
|
<AutoComplete
|
|
{...props}
|
|
rootClassName={`${rootClassName} comp-item comp-AutoComplete`}
|
|
options={options}
|
|
open
|
|
/>
|
|
<ColorPicker {...props} open rootClassName={`${rootClassName} comp-item comp-ColorPicker`} />
|
|
</>
|
|
),
|
|
Dropdown: (props) => (
|
|
<Dropdown
|
|
{...props}
|
|
menu={{
|
|
items: options.map((item) => ({
|
|
key: item.value,
|
|
label: item.label,
|
|
})),
|
|
}}
|
|
open
|
|
>
|
|
<button type="button">test</button>
|
|
</Dropdown>
|
|
),
|
|
DatePicker: ({ rootClassName, ...props }) => (
|
|
<>
|
|
<DatePicker {...props} rootClassName={`${rootClassName} comp-item comp-DatePicker`} open />
|
|
<DatePicker.TimePicker
|
|
{...props}
|
|
rootClassName={`${rootClassName} comp-item comp-TimePicker`}
|
|
open
|
|
/>
|
|
</>
|
|
),
|
|
Menu: (props) => <Menu {...props} items={items} defaultOpenKeys={['SubMenu']} />,
|
|
ImagePreview: ({ rootClassName }: ImageProps) => (
|
|
<>
|
|
<Image
|
|
src="xxx"
|
|
preview={{
|
|
visible: true,
|
|
rootClassName: `${rootClassName} comp-item comp-ImagePreview`,
|
|
}}
|
|
/>
|
|
<Image.PreviewGroup
|
|
preview={{
|
|
visible: true,
|
|
rootClassName: `${rootClassName} comp-item comp-ImagePreviewGroup`,
|
|
}}
|
|
>
|
|
<Image src="xxx" />
|
|
</Image.PreviewGroup>
|
|
</>
|
|
),
|
|
};
|
|
|
|
const getConsumerSelector = (baseSelector: string, consumer: ZIndexConsumer): string => {
|
|
let selector = baseSelector;
|
|
if (consumer === 'SelectLike') {
|
|
selector = ['Select', 'Cascader', 'TreeSelect', 'AutoComplete', 'ColorPicker']
|
|
.map((item) =>
|
|
item === 'ColorPicker'
|
|
? `${baseSelector}.ant-popover-placement-bottomLeft`
|
|
: `${baseSelector}.comp-${item}.ant-slide-up`,
|
|
)
|
|
.join(',');
|
|
} else if (consumer === 'DatePicker') {
|
|
selector = ['DatePicker', 'TimePicker']
|
|
.map((item) => `${baseSelector}.comp-${item}.ant-picker-dropdown`)
|
|
.join(',');
|
|
} else if (['Menu'].includes(consumer)) {
|
|
selector = `${baseSelector}.ant-menu-submenu-placement-rightTop`;
|
|
} else if (consumer === 'ImagePreview') {
|
|
selector = ['ImagePreview', 'ImagePreviewGroup']
|
|
.map(
|
|
(item) =>
|
|
`${baseSelector}.comp-${item} .ant-image-preview-wrap, ${baseSelector}.comp-${item}.ant-image-preview-operations-wrapper`,
|
|
)
|
|
.join(',');
|
|
}
|
|
return selector;
|
|
};
|
|
|
|
describe('Test useZIndex hooks', () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
Object.keys(containerComponent).forEach((containerKey) => {
|
|
Object.keys(consumerComponent).forEach((key) => {
|
|
const containerZIndexValue = containerBaseZIndexOffset[containerKey as ZIndexContainer];
|
|
const consumerZIndexValue = consumerBaseZIndexOffset[key as ZIndexConsumer];
|
|
describe(`Test ${key} zIndex in ${containerKey}`, () => {
|
|
it('Test hooks', () => {
|
|
const fn = jest.fn();
|
|
const Child: React.FC = () => {
|
|
const [zIndex] = useZIndex(key as ZIndexConsumer);
|
|
useEffect(() => {
|
|
fn(zIndex);
|
|
}, [zIndex]);
|
|
return <div>Child</div>;
|
|
};
|
|
|
|
const App: React.FC = () => (
|
|
<WrapWithProvider container={containerKey as ZIndexContainer}>
|
|
<WrapWithProvider container={containerKey as ZIndexContainer}>
|
|
<WrapWithProvider container={containerKey as ZIndexContainer}>
|
|
<Child />
|
|
</WrapWithProvider>
|
|
</WrapWithProvider>
|
|
</WrapWithProvider>
|
|
);
|
|
render(<App />);
|
|
|
|
expect(fn).toHaveBeenLastCalledWith(
|
|
1000 + containerZIndexValue * 3 + consumerZIndexValue,
|
|
);
|
|
});
|
|
|
|
it('Test Component', async () => {
|
|
const Container = containerComponent[containerKey as ZIndexContainer]!;
|
|
const Consumer = consumerComponent[key as ZIndexConsumer]!;
|
|
|
|
const App: React.FC = () => (
|
|
<>
|
|
<Consumer rootClassName="consumer1" />
|
|
<Container rootClassName="container1">
|
|
<Consumer rootClassName="consumer2" />
|
|
<Container rootClassName="container2">
|
|
<Consumer rootClassName="consumer3" />
|
|
</Container>
|
|
</Container>
|
|
</>
|
|
);
|
|
|
|
const { unmount } = render(<App />);
|
|
|
|
await waitFakeTimer(1000);
|
|
|
|
const selector1 = getConsumerSelector('.consumer1', key as ZIndexConsumer);
|
|
const selector2 = getConsumerSelector('.consumer2', key as ZIndexConsumer);
|
|
const selector3 = getConsumerSelector('.consumer3', key as ZIndexConsumer);
|
|
|
|
if (['SelectLike', 'DatePicker', 'ImagePreview'].includes(key)) {
|
|
let comps = document.querySelectorAll<HTMLElement>(selector1);
|
|
comps.forEach((comp) => {
|
|
expect(comp?.style.zIndex).toBeFalsy();
|
|
});
|
|
comps = document.querySelectorAll<HTMLElement>(selector2);
|
|
comps.forEach((comp) => {
|
|
const isColorPicker = comp?.className.includes('comp-ColorPicker');
|
|
const consumerOffset = isColorPicker
|
|
? containerBaseZIndexOffset.Popover
|
|
: consumerZIndexValue;
|
|
const operOffset = comp.classList.contains('ant-image-preview-operations-wrapper')
|
|
? 1
|
|
: 0;
|
|
expect(comp?.style.zIndex).toBe(
|
|
String(1000 + containerZIndexValue + consumerOffset + operOffset),
|
|
);
|
|
});
|
|
|
|
comps = document.querySelectorAll<HTMLElement>(selector3);
|
|
comps.forEach((comp) => {
|
|
const isColorPicker = comp?.className.includes('comp-ColorPicker');
|
|
const consumerOffset = isColorPicker
|
|
? containerBaseZIndexOffset.Popover
|
|
: consumerZIndexValue;
|
|
const operOffset = comp.classList.contains('ant-image-preview-operations-wrapper')
|
|
? 1
|
|
: 0;
|
|
expect(comp?.style.zIndex).toBe(
|
|
String(1000 + containerZIndexValue * 2 + consumerOffset + operOffset),
|
|
);
|
|
});
|
|
} else {
|
|
const element1 = document.querySelector<HTMLElement>(selector1);
|
|
const element2 = document.querySelector<HTMLElement>(selector2);
|
|
const element3 = document.querySelector<HTMLElement>(selector3);
|
|
expect(element1?.style.zIndex).toBe(key === 'Tour' ? '1001' : '');
|
|
expect(element2?.style.zIndex).toBe(
|
|
String(1000 + containerZIndexValue + consumerZIndexValue),
|
|
);
|
|
expect(element3?.style.zIndex).toBe(
|
|
String(1000 + containerZIndexValue * 2 + consumerZIndexValue),
|
|
);
|
|
}
|
|
unmount();
|
|
}, 20000);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Modal static func should always use max zIndex', async () => {
|
|
jest.useFakeTimers();
|
|
|
|
const instance = Modal.confirm({
|
|
title: 'bamboo',
|
|
content: <Select open />,
|
|
});
|
|
|
|
await waitFakeTimer();
|
|
|
|
expect(document.querySelector('.ant-modal-wrap')).toHaveStyle({
|
|
zIndex: '2000',
|
|
});
|
|
|
|
expect(document.querySelector('.ant-select-dropdown')).toHaveStyle({
|
|
zIndex: '2050',
|
|
});
|
|
|
|
instance.destroy();
|
|
|
|
await waitFakeTimer();
|
|
|
|
// Clean up for static method
|
|
document.body.innerHTML = '';
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('warning for too large zIndex auto offset', () => {
|
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
render(
|
|
<Drawer open zIndex={999999999}>
|
|
<Tooltip open title="test">
|
|
<div>test</div>
|
|
</Tooltip>
|
|
</Drawer>,
|
|
);
|
|
|
|
expect(errorSpy).toHaveBeenCalledWith(
|
|
'Warning: [antd: Tooltip] `zIndex` is over design token `zIndexPopupBase` too much. It may cause unexpected override.',
|
|
);
|
|
|
|
errorSpy.mockRestore();
|
|
});
|
|
|
|
it('FloatButton support zIndex', () => {
|
|
const { container, rerender } = render(
|
|
<WrapWithProvider container="FloatButton">
|
|
<FloatButton />
|
|
</WrapWithProvider>,
|
|
);
|
|
expect(container.querySelector<HTMLElement>('.ant-float-btn')?.style.zIndex).toBe(
|
|
// parentZIndex + containerBaseZIndexOffset["FloatButton"]
|
|
String(1100 + containerBaseZIndexOffset.FloatButton),
|
|
);
|
|
rerender(
|
|
<WrapWithProvider container="FloatButton">
|
|
<FloatButton style={{ zIndex: 666 }} />
|
|
</WrapWithProvider>,
|
|
);
|
|
expect(container.querySelector<HTMLElement>('.ant-float-btn')?.style.zIndex).toBe(String(666));
|
|
});
|
|
});
|