Skip to content

Commit

Permalink
test: add modal 组件测试
Browse files Browse the repository at this point in the history
  • Loading branch information
huabingtao committed Sep 18, 2024
1 parent 877dd4e commit a4f5901
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 111 deletions.
8 changes: 6 additions & 2 deletions src/components/mask/demo/basic.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Mask, Button, Image } from 'react-single-ui';
import { Button, Image, Mask } from 'react-single-ui';
import presentImage from './present.png';
export default () => {
const [visible, setVisible] = useState(false);
Expand Down Expand Up @@ -28,7 +28,11 @@ export default () => {
打开自定义内容的蒙层
</Button>

<Mask visible={visible} onMaskClick={() => setVisible(false)}></Mask>
<Mask
visible={visible}
className="custom-01"
onMaskClick={() => setVisible(false)}
></Mask>
<Mask
visible={visibleLight}
backgroundColor="#00000033"
Expand Down
121 changes: 59 additions & 62 deletions src/components/mask/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import * as ReactDOM from 'react-dom';
import React, { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { prefixCls } from '../../utils';

const IS_REACT_16 = !!ReactDOM.createPortal;
export interface MaskProps {
/**
* @description 是否显示遮罩层
Expand All @@ -13,6 +12,11 @@ export interface MaskProps {
* @default 999
*/
zIndex?: number;
/**
* className 自定义类名
* @default ''
*/
className?: string;
/**
* @description 点击遮罩
*/
Expand All @@ -28,75 +32,68 @@ export interface MaskProps {
backgroundColor?: string;
}

const div: HTMLDivElement = document.createElement('div');
document.body.appendChild(div);
const Mask: React.FC<MaskProps> = ({
visible = false,
className,
backgroundColor = '#00000066',
zIndex = 999,
onMaskClick,
children,
}) => {
const [container, setContainer] = useState<HTMLDivElement | null>(null);

class Mask extends React.Component<MaskProps, HTMLDivElement> {
container: HTMLDivElement | null = null;
maskDom = () => {
const { backgroundColor = '#00000066', zIndex = 999 } = this.props;
const style = {
backgroundColor,
zIndex,
};
// 创建 container 并在组件卸载时移除
useEffect(() => {
// console.log('useEffect visible:', visible); // 将日志放在 useEffect 中
if (visible) {
const newContainer = document.createElement('div');
newContainer.style.height = '100%';
const containerId = `${prefixCls}-container-${new Date().getTime()}`;
newContainer.setAttribute('id', containerId);
document.body.appendChild(newContainer);
setContainer(newContainer);

return (
<div
className={`${prefixCls}-mask`}
onClick={this.handleClickMask}
style={style}
>
{this.props.children}
</div>
);
};
// 禁用滚动
const preventDefault = (e: Event) => e.preventDefault();
document.body.addEventListener('touchmove', preventDefault, {
passive: false,
});
document.body.addEventListener('scroll', preventDefault, {
passive: false,
});

handleClickMask = () => {
// 点击遮罩层
this.removeContainer();
if (this.props.onMaskClick) {
this.props.onMaskClick();
return () => {
document.body.removeChild(newContainer);
document.body.removeEventListener('touchmove', preventDefault);
document.body.removeEventListener('scroll', preventDefault);
};
}
};
}, [visible]);

getContainer = () => {
if (!this.container) {
const container = document.createElement('div');
container.style.height = '100%';
const containerId = `${prefixCls}-container-${new Date().getTime()}`;
container.setAttribute('id', containerId);
document.body.appendChild(container);
this.container = container;
// 处理遮罩点击
const handleClickMask = () => {
if (onMaskClick) {
onMaskClick();
}
return this.container;
};

removeContainer = () => {
if (this.container) {
document.body.removeChild(this.container);
this.container = null;
}
};
const classes = `${prefixCls}-mask ${className}`;

preventDefault = (e: Event) => {
e.preventDefault();
};
const maskDom = (
<div
className={classes}
onClick={handleClickMask}
style={{ backgroundColor, zIndex }}
>
{children}
</div>
);

render() {
const { visible } = this.props;
if (IS_REACT_16 && visible) {
document.body.addEventListener('touchmove', this.preventDefault, {
passive: false,
});
document.body.addEventListener('scroll', this.preventDefault, {
passive: false,
});
return ReactDOM.createPortal(this.maskDom(), this.getContainer());
}
document.body.removeEventListener('touchmove', this.preventDefault, false);
document.body.removeEventListener('scroll', this.preventDefault, false);
return null;
if (visible && container) {
return createPortal(maskDom, container);
}
}

return null;
};

export default Mask;
105 changes: 73 additions & 32 deletions src/components/mask/mask.test.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,82 @@
import { render, fireEvent, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom';
// mask.test.tsx
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import Mask from './index';
import { prefixCls } from '../../utils';
import Mask from './';

afterEach(() => {
cleanup(); // 清除所有渲染的组件
});
describe('Mask Component', () => {
it('should not render mask when visible is false', () => {
render(<Mask visible={false} data-testid="mask" />);

// 确保在 visible 为 false 时不渲染遮罩
expect(screen.queryByTestId('mask')).toBeNull();
});

describe('Mask component visibility', () => {
it('should be hidden initially and visible after updating props', async () => {
render(<Mask visible={false} />);
const element = document.body.querySelector(
`.${prefixCls}-mask`,
) as HTMLElement;
// 初始状态,Mask 不可见
expect(element).toBeNull();
render(<Mask visible={true} />);
expect(
document.body.querySelector(`.${prefixCls}-mask`),
).toBeInTheDocument();
fireEvent.click(
document.body.querySelector(`.${prefixCls}-mask`) as HTMLElement,
it('should render mask when visible is true', async () => {
render(<Mask visible={true} className="test-02" />);

// 等待组件渲染完成
await waitFor(() => {
expect(document.querySelector('.test-02')).toBeInTheDocument();
});
});

it('should call onMaskClick when mask is clicked', async () => {
const mockOnMaskClick = jest.fn();

render(
<Mask visible={true} onMaskClick={mockOnMaskClick} className="test-03" />,
);
expect(document.body.querySelector(`.${prefixCls}-mask`)).toBeNull();

// 等待组件渲染完成
await waitFor(() => {
expect(document.querySelector('.test-03')).toBeInTheDocument();
});

// 触发点击事件
fireEvent.click(document.querySelector('.test-03')!);

// 确保点击事件处理程序被调用
expect(mockOnMaskClick).toHaveBeenCalled();
});

it('should apply zIndex and background correctly', () => {
render(<Mask visible={true} zIndex={100} backgroundColor="#ff0000" />);
const element = document.body.querySelector(
`.${prefixCls}-mask`,
) as HTMLElement;
expect(element).toHaveStyle({
zIndex: 100,
backgroundColor: '#ff0000',
it('should apply correct styles based on props', async () => {
const backgroundColor = 'rgba(0, 0, 0, 0.5)';
const zIndex = 1000;

render(
<Mask
visible={true}
backgroundColor={backgroundColor}
zIndex={zIndex}
className="test-04"
/>,
);

// 等待组件渲染完成
await waitFor(() => {
const maskElement = document.querySelector('.test-04') as HTMLElement;
expect(maskElement).toBeInTheDocument();
expect(maskElement).toHaveStyle(`background-color: ${backgroundColor}`);
expect(maskElement).toHaveStyle(`z-index: ${zIndex}`);
});
fireEvent.click(element);
expect(document.body.querySelector(`.${prefixCls}-mask`)).toBeNull();
});

it('should not call onMaskClick when mask is not visible', async () => {
const mockOnMaskClick = jest.fn();

render(
<Mask
visible={false}
onMaskClick={mockOnMaskClick}
className="test-05"
/>,
);

// 确保在 visible 为 false 时不渲染遮罩
expect(document.querySelector('.test-05')).toBeNull();

// 触发点击事件,不应调用 onMaskClick
fireEvent.click(document.body);
expect(mockOnMaskClick).not.toHaveBeenCalled();
});
});
2 changes: 1 addition & 1 deletion src/components/modal/alert.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// alert.tsx
import _ from 'lodash';
import React from 'react';
import { createRoot } from 'react-dom/client';
import Modal, { Action, ModalProps } from './modal';
import _ from 'lodash';

export default function Alert(props: ModalProps<React.CSSProperties>) {
const { title, message, footer } = props;
Expand Down
11 changes: 8 additions & 3 deletions src/components/modal/demo/basic.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Modal, Button } from 'react-single-ui';
import { Button, Modal } from 'react-single-ui';

export default () => {
const [visible1, setVisible1] = useState(false);
Expand All @@ -11,12 +11,17 @@ export default () => {
setVisible2(true);
};
const show3 = () => {
console.log(Modal.alert);
Modal.alert({
const { close } = Modal.alert({
title: '标题',
message: '这是通过 Modal.alert 的方式调用',
footer: footer3,
});
setTimeout(() => {
console.log('111111');

close();
}, 1000);
console.log('xxxx:', close);
};

const footer1 = [
Expand Down
3 changes: 1 addition & 2 deletions src/components/modal/demo/basic.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Modal, Button } from 'react-single-ui';
import { Button, Modal } from 'react-single-ui';

export default () => {
const [visible1, setVisible1] = useState(false);
Expand All @@ -11,7 +11,6 @@ export default () => {
setVisible2(true);
};
const show3 = () => {
console.log(Modal.alert);
Modal.alert({
title: '标题',
message: '这是通过 Modal.alert 的方式调用',
Expand Down
Loading

0 comments on commit a4f5901

Please sign in to comment.