Your Name e4959d584f feat: 完善代理商端业务逻辑与前后端框架
主要更新:
- 更新代理商端文档,明确项目由品牌方分配流程
- 新增Brief配置详情页(已配置)设计稿
- 完善工作台紧急待办中品牌新任务功能
- 整理Pencil设计文件中代理商端页面顺序
- 新增后端FastAPI框架及核心API
- 新增前端Next.js页面和组件库
- 添加.gitignore排除构建和缓存文件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:27:31 +08:00

417 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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