/** * Modal 组件测试 * 测试覆盖: Modal, ConfirmModal, 副作用(ESC、overflow) */ import { render, screen, fireEvent, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { Modal, ConfirmModal } from './Modal'; describe('Modal', () => { const mockOnClose = vi.fn(); beforeEach(() => { mockOnClose.mockClear(); document.body.style.overflow = ''; }); afterEach(() => { // 确保副作用被清理 document.body.style.overflow = ''; }); // ==================== 基础渲染测试 ==================== describe('基础渲染', () => { it('isOpen=true 时渲染内容', () => { render(

模态框内容

); expect(screen.getByText('模态框内容')).toBeInTheDocument(); }); it('isOpen=false 时不渲染', () => { render(

模态框内容

); expect(screen.queryByText('模态框内容')).not.toBeInTheDocument(); }); it('渲染标题', () => { render( 内容 ); expect(screen.getByText('弹窗标题')).toBeInTheDocument(); }); it('渲染页脚', () => { render( 确定}> 内容 ); expect(screen.getByText('确定')).toBeInTheDocument(); }); }); // ==================== 关闭按钮测试 ==================== describe('关闭按钮', () => { it('默认显示关闭按钮', () => { render( 内容 ); // 使用 aria-label 精确选择关闭按钮 expect(screen.getByLabelText('关闭')).toBeInTheDocument(); }); it('点击关闭按钮触发 onClose', () => { render( 内容 ); // 使用 aria-label 精确选择关闭按钮 const closeButton = screen.getByLabelText('关闭'); fireEvent.click(closeButton); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it('showCloseButton=false 隐藏关闭按钮', () => { render( 内容 ); // 关闭按钮不存在 expect(screen.queryByLabelText('关闭')).not.toBeInTheDocument(); }); }); // ==================== 遮罩点击测试 ==================== describe('遮罩点击', () => { it('点击遮罩默认关闭', () => { render( 内容 ); const overlay = document.querySelector('.bg-black\\/60'); fireEvent.click(overlay!); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it('closeOnOverlay=false 禁用遮罩点击关闭', () => { render( 内容 ); const overlay = document.querySelector('.bg-black\\/60'); fireEvent.click(overlay!); expect(mockOnClose).not.toHaveBeenCalled(); }); }); // ==================== ESC 键测试 ==================== describe('ESC 键关闭', () => { it('按 ESC 键默认关闭', async () => { render( 内容 ); await act(async () => { fireEvent.keyDown(document, { key: 'Escape' }); }); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it('closeOnEsc=false 禁用 ESC 关闭', async () => { render( 内容 ); await act(async () => { fireEvent.keyDown(document, { key: 'Escape' }); }); expect(mockOnClose).not.toHaveBeenCalled(); }); it('其他按键不触发关闭', async () => { render( 内容 ); await act(async () => { fireEvent.keyDown(document, { key: 'Enter' }); }); expect(mockOnClose).not.toHaveBeenCalled(); }); }); // ==================== Body Overflow 副作用测试 ==================== describe('Body overflow 副作用', () => { it('打开时锁定 body 滚动', () => { render( 内容 ); expect(document.body.style.overflow).toBe('hidden'); }); it('关闭时解锁 body 滚动', () => { const { rerender } = render( 内容 ); expect(document.body.style.overflow).toBe('hidden'); rerender( 内容 ); expect(document.body.style.overflow).toBe(''); }); it('卸载时清理 overflow', () => { const { unmount } = render( 内容 ); expect(document.body.style.overflow).toBe('hidden'); unmount(); expect(document.body.style.overflow).toBe(''); }); }); // ==================== Size 测试 ==================== describe('Size 样式', () => { it('默认 md size', () => { const { container } = render( 内容 ); expect(container.querySelector('.max-w-md')).toBeInTheDocument(); }); it('sm size', () => { const { container } = render( 内容 ); expect(container.querySelector('.max-w-sm')).toBeInTheDocument(); }); it('lg size', () => { const { container } = render( 内容 ); expect(container.querySelector('.max-w-lg')).toBeInTheDocument(); }); it('xl size', () => { const { container } = render( 内容 ); expect(container.querySelector('.max-w-xl')).toBeInTheDocument(); }); }); // ==================== ClassName 测试 ==================== describe('ClassName', () => { it('支持自定义 className', () => { const { container } = render( 内容 ); expect(container.querySelector('.custom-modal')).toBeInTheDocument(); }); }); }); describe('ConfirmModal', () => { const mockOnClose = vi.fn(); const mockOnConfirm = vi.fn(); beforeEach(() => { mockOnClose.mockClear(); mockOnConfirm.mockClear(); }); // ==================== 基础渲染测试 ==================== describe('基础渲染', () => { it('渲染标题和消息', () => { render( ); expect(screen.getByText('确认删除')).toBeInTheDocument(); expect(screen.getByText('确定要删除吗?')).toBeInTheDocument(); }); it('渲染确认和取消按钮', () => { render( ); // Modal 有关闭按钮(X),ConfirmModal 有确认和取消按钮,共 3 个 const buttons = screen.getAllByRole('button'); expect(buttons.length).toBeGreaterThanOrEqual(2); expect(screen.getByText('确定')).toBeInTheDocument(); expect(screen.getByText('取消')).toBeInTheDocument(); }); }); // ==================== 按钮文本自定义测试 ==================== describe('按钮文本自定义', () => { it('支持自定义确认按钮文本', () => { render( ); expect(screen.getByText('删除')).toBeInTheDocument(); }); it('支持自定义取消按钮文本', () => { render( ); expect(screen.getByText('返回')).toBeInTheDocument(); }); }); // ==================== 事件处理测试 ==================== describe('事件处理', () => { it('点击确认按钮触发 onConfirm', () => { render( ); fireEvent.click(screen.getByText('确定')); expect(mockOnConfirm).toHaveBeenCalledTimes(1); }); it('点击取消按钮触发 onClose', () => { render( ); fireEvent.click(screen.getByText('取消')); expect(mockOnClose).toHaveBeenCalledTimes(1); }); }); // ==================== Variant 测试 ==================== describe('Variant 样式', () => { it('danger variant 使用红色确认按钮', () => { render( ); const confirmButton = screen.getByText('确认').closest('button'); expect(confirmButton).toHaveClass('bg-accent-coral'); }); }); // ==================== Loading 测试 ==================== describe('Loading 状态', () => { it('loading 时确认按钮显示加载状态', () => { render( ); const confirmButton = screen.getByText('确定').closest('button'); expect(confirmButton).toBeDisabled(); }); it('loading 时取消按钮也被禁用', () => { render( ); const cancelButton = screen.getByText('取消').closest('button'); expect(cancelButton).toBeDisabled(); }); }); });