ant-design/components/_util/__tests__/useZIndex.test.tsx

414 lines
12 KiB
TypeScript
Raw Normal View History

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,
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<{ containerType: ZIndexContainer }>> = ({
children,
containerType,
}) => {
const [, contextZIndex] = useZIndex(containerType);
return <zIndexContext.Provider value={contextZIndex}>{children}</zIndexContext.Provider>;
};
const containerComponent: Record<
ZIndexContainer,
React.FC<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: Record<ZIndexConsumer, React.FC<{ 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>
</>
),
};
function 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();
});
const containers = Object.keys(containerComponent);
const consumers = Object.keys(consumerComponent);
containers.forEach((containerKey) => {
consumers.forEach((key) => {
describe(`Test ${key} zIndex in ${containerKey}`, () => {
it('Test hooks', () => {
const fn = jest.fn();
const Child = () => {
const [zIndex] = useZIndex(key as ZIndexConsumer);
useEffect(() => {
fn(zIndex);
}, [zIndex]);
return <div>Child</div>;
};
const App = () => (
<WrapWithProvider containerType={containerKey as ZIndexContainer}>
<WrapWithProvider containerType={containerKey as ZIndexContainer}>
<WrapWithProvider containerType={containerKey as ZIndexContainer}>
<Child />
</WrapWithProvider>
</WrapWithProvider>
</WrapWithProvider>
);
render(<App />);
expect(fn).toHaveBeenLastCalledWith(
1000 +
containerBaseZIndexOffset[containerKey as ZIndexContainer] * 3 +
consumerBaseZIndexOffset[key as ZIndexConsumer],
);
});
it('Test Component', async () => {
const Container = containerComponent[containerKey as ZIndexContainer];
const Consumer = consumerComponent[key as ZIndexConsumer];
const App = () => (
<>
<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(selector1);
comps.forEach((comp) => {
expect((comp as HTMLDivElement).style.zIndex).toBeFalsy();
});
comps = document.querySelectorAll(selector2);
comps.forEach((comp) => {
const isColorPicker = (comp as HTMLDivElement).className.includes('comp-ColorPicker');
const consumerOffset = isColorPicker
? containerBaseZIndexOffset.Popover
: consumerBaseZIndexOffset[key as ZIndexConsumer];
const operOffset = comp.classList.contains('ant-image-preview-operations-wrapper')
? 1
: 0;
expect((comp as HTMLDivElement).style.zIndex).toBe(
String(
1000 +
containerBaseZIndexOffset[containerKey as ZIndexContainer] +
consumerOffset +
operOffset,
),
);
});
comps = document.querySelectorAll(selector3);
comps.forEach((comp) => {
const isColorPicker = (comp as HTMLDivElement).className.includes('comp-ColorPicker');
const consumerOffset = isColorPicker
? containerBaseZIndexOffset.Popover
: consumerBaseZIndexOffset[key as ZIndexConsumer];
const operOffset = comp.classList.contains('ant-image-preview-operations-wrapper')
? 1
: 0;
expect((comp as HTMLDivElement).style.zIndex).toBe(
String(
1000 +
containerBaseZIndexOffset[containerKey as ZIndexContainer] * 2 +
consumerOffset +
operOffset,
),
);
});
} else {
if (key === 'Tour') {
expect((document.querySelector(selector1) as HTMLDivElement).style.zIndex).toBe(
'1001',
);
} else {
expect(
(document.querySelector(selector1) as HTMLDivElement).style.zIndex,
).toBeFalsy();
}
expect((document.querySelector(selector2) as HTMLDivElement).style.zIndex).toBe(
String(
1000 +
containerBaseZIndexOffset[containerKey as ZIndexContainer] +
consumerBaseZIndexOffset[key as ZIndexConsumer],
),
);
expect((document.querySelector(selector3) as HTMLDivElement).style.zIndex).toBe(
String(
1000 +
containerBaseZIndexOffset[containerKey as ZIndexContainer] * 2 +
consumerBaseZIndexOffset[key as ZIndexConsumer],
),
);
}
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.',
);
});
});