---
description: 测试规范和代码质量保证
globs: ["**/*.test.ts", "**/*.spec.ts", "**/tests/**/*", "**/e2e/**/*"]
alwaysApply: false
---

# 测试规范

## 🧪 单元测试规范

### Jest/Vitest 配置
```typescript
// ✅ 使用 Jest 或 Vitest 进行单元测试，测试覆盖率必须达到 80% 以上
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import UserCard from './UserCard.vue';
import type { IUser } from './types';

describe('UserCard', () => {
  let wrapper: any;
  const mockUser: IUser = {
    id: '1',
    name: 'John Doe',
    email: 'john@example.com',
    role: 'user',
    createdAt: new Date(),
  };

  beforeEach(() => {
    wrapper = mount(UserCard, {
      props: {
        user: mockUser,
        canEdit: true,
      },
    });
  });

  it('应该正确渲染用户名称', () => {
    expect(wrapper.text()).toContain('John Doe');
  });

  it('应该正确渲染用户邮箱', () => {
    expect(wrapper.text()).toContain('john@example.com');
  });

  it('应该在可编辑时显示编辑按钮', () => {
    const editButton = wrapper.find('[data-testid="edit-button"]');
    expect(editButton.exists()).toBe(true);
  });

  it('应该触发 update 事件当用户信息更新时', async () => {
    const updatedUser = { ...mockUser, name: 'Jane Doe' };
    
    await wrapper.vm.handleUpdate(updatedUser);
    
    expect(wrapper.emitted('update')).toBeTruthy();
    expect(wrapper.emitted('update')[0]).toEqual([updatedUser]);
  });
});
```

### 服务层测试
```typescript
// ✅ 服务层单元测试
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { IUserRepository } from './interfaces/user-repository.interface';

describe('UserService', () => {
  let service: UserService;
  let userRepository: jest.Mocked<IUserRepository>;

  beforeEach(async () => {
    const mockUserRepository = {
      findById: jest.fn(),
      findByEmail: jest.fn(),
      findAll: jest.fn(),
      save: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: 'IUserRepository',
          useValue: mockUserRepository,
        },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
    userRepository = module.get('IUserRepository');
  });

  describe('createUser', () => {
    it('应该成功创建用户', async () => {
      const createUserDto = {
        email: 'test@example.com',
        password: 'password123',
        firstName: 'John',
        lastName: 'Doe',
      };

      const expectedUser = {
        id: '1',
        ...createUserDto,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      userRepository.save.mockResolvedValue(expectedUser);

      const result = await service.createUser(createUserDto);

      expect(result).toEqual(expectedUser);
      expect(userRepository.save).toHaveBeenCalledWith(createUserDto);
    });

    it('应该在邮箱已存在时抛出错误', async () => {
      const createUserDto = {
        email: 'existing@example.com',
        password: 'password123',
        firstName: 'John',
        lastName: 'Doe',
      };

      userRepository.findByEmail.mockResolvedValue({
        id: '1',
        email: 'existing@example.com',
        // ... 其他属性
      });

      await expect(service.createUser(createUserDto)).rejects.toThrow('邮箱已存在');
    });
  });
});
```

## 🎭 端到端测试

### Playwright 配置
```typescript
// ✅ 使用 Playwright 进行端到端测试
import { test, expect } from '@playwright/test';

test.describe('用户管理功能', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/users');
  });

  test('用户应该能够成功登录', async ({ page }) => {
    await page.goto('/login');
    
    await page.fill('[data-testid="email-input"]', 'user@example.com');
    await page.fill('[data-testid="password-input"]', 'password123');
    await page.click('[data-testid="login-button"]');
    
    await expect(page).toHaveURL('/dashboard');
    await expect(page.locator('[data-testid="welcome-message"]')).toBeVisible();
  });

  test('用户应该能够创建新用户', async ({ page }) => {
    await page.click('[data-testid="create-user-button"]');
    
    await page.fill('[data-testid="user-name-input"]', 'John Doe');
    await page.fill('[data-testid="user-email-input"]', 'john@example.com');
    await page.selectOption('[data-testid="user-role-select"]', 'user');
    
    await page.click('[data-testid="save-user-button"]');
    
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
    await expect(page.locator('[data-testid="user-list"]')).toContainText('John Doe');
  });

  test('用户应该能够编辑现有用户', async ({ page }) => {
    const userRow = page.locator('[data-testid="user-row"]').first();
    await userRow.locator('[data-testid="edit-button"]').click();
    
    await page.fill('[data-testid="user-name-input"]', 'Jane Doe');
    await page.click('[data-testid="save-user-button"]');
    
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
    await expect(userRow).toContainText('Jane Doe');
  });

  test('用户应该能够删除用户', async ({ page }) => {
    const userRow = page.locator('[data-testid="user-row"]').first();
    const userName = await userRow.locator('[data-testid="user-name"]').textContent();
    
    await userRow.locator('[data-testid="delete-button"]').click();
    
    // 确认删除对话框
    await page.click('[data-testid="confirm-delete-button"]');
    
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
    await expect(page.locator('[data-testid="user-list"]')).not.toContainText(userName);
  });
});
```

## 🔧 测试自动化

### Husky 和 Commitlint
```json
// ✅ package.json 配置
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix",
      "prettier --write",
      "git add"
    ],
    "*.{ts,tsx}": [
      "jest --findRelatedTests --passWithNoTests"
    ]
  }
}
```

### 测试覆盖率要求
```typescript
// ✅ vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      thresholds: {
        global: {
          branches: 80,
          functions: 80,
          lines: 80,
          statements: 80,
        },
      },
    },
  },
});
```

## 📊 测试策略

### 测试金字塔
```typescript
// ✅ 测试金字塔结构
/*
     /\
    /  \     E2E Tests (10%)
   /____\    - 关键用户流程
  /      \   - 跨浏览器测试
 /        \  - 集成测试
/__________\ Unit Tests (70%)
            - 组件测试
            - 服务测试
            - 工具函数测试
*/

// ✅ 单元测试示例
describe('用户验证工具', () => {
  it('应该验证有效的邮箱格式', () => {
    expect(validateEmail('user@example.com')).toBe(true);
    expect(validateEmail('test.email@domain.co.uk')).toBe(true);
  });

  it('应该拒绝无效的邮箱格式', () => {
    expect(validateEmail('invalid-email')).toBe(false);
    expect(validateEmail('user@')).toBe(false);
    expect(validateEmail('@domain.com')).toBe(false);
  });
});

// ✅ 集成测试示例
describe('用户 API 集成', () => {
  it('应该能够创建和获取用户', async () => {
    const userData = {
      name: 'John Doe',
      email: 'john@example.com',
    };

    const createdUser = await userService.createUser(userData);
    expect(createdUser.id).toBeDefined();

    const retrievedUser = await userService.getUserById(createdUser.id);
    expect(retrievedUser).toEqual(createdUser);
  });
});
```

### 测试数据管理
```typescript
// ✅ 测试数据工厂
export class UserFactory {
  static create(overrides: Partial<IUser> = {}): IUser {
    return {
      id: '1',
      name: 'John Doe',
      email: 'john@example.com',
      role: 'user',
      createdAt: new Date(),
      updatedAt: new Date(),
      ...overrides,
    };
  }

  static createAdmin(overrides: Partial<IUser> = {}): IUser {
    return this.create({
      role: 'admin',
      ...overrides,
    });
  }

  static createMany(count: number): IUser[] {
    return Array.from({ length: count }, (_, index) =>
      this.create({
        id: `${index + 1}`,
        name: `User ${index + 1}`,
        email: `user${index + 1}@example.com`,
      })
    );
  }
}

// ✅ 使用测试数据工厂
describe('用户服务', () => {
  it('应该能够获取所有用户', async () => {
    const users = UserFactory.createMany(3);
    userRepository.findAll.mockResolvedValue(users);

    const result = await userService.getAllUsers();

    expect(result).toHaveLength(3);
    expect(result[0]).toEqual(users[0]);
  });
});
```

## 🎯 测试最佳实践

### Arrange-Act-Assert 结构
```typescript
// ✅ 遵循 Arrange-Act-Assert 结构
describe('用户服务 - 创建用户', () => {
  it('应该成功创建用户', async () => {
    // Arrange - 准备测试数据
    const userData = {
      name: 'John Doe',
      email: 'john@example.com',
      password: 'password123',
    };
    const expectedUser = {
      id: '1',
      ...userData,
      createdAt: new Date(),
    };
    userRepository.save.mockResolvedValue(expectedUser);

    // Act - 执行被测试的方法
    const result = await userService.createUser(userData);

    // Assert - 验证结果
    expect(result).toEqual(expectedUser);
    expect(userRepository.save).toHaveBeenCalledWith(userData);
  });
});
```

### Given-When-Then 结构
```typescript
// ✅ 使用 Given-When-Then 结构
describe('用户登录功能', () => {
  it('应该允许有效用户登录', async () => {
    // Given - 给定条件
    const validCredentials = {
      email: 'user@example.com',
      password: 'password123',
    };
    const mockUser = UserFactory.create(validCredentials);
    userRepository.findByEmail.mockResolvedValue(mockUser);

    // When - 当执行操作时
    const result = await authService.login(validCredentials);

    // Then - 那么应该得到预期结果
    expect(result.success).toBe(true);
    expect(result.user).toEqual(mockUser);
    expect(result.token).toBeDefined();
  });
});
```

## 🔍 代码审核与测试

**[必需]** 代码生成完成后必须进行全面的审核，确保代码质量。

### 审核检查清单
- [ ] 规范符合性：是否符合编码规范和团队标准
- [ ] 代码质量：是否有冗余变量、函数和重复逻辑
- [ ] 面向对象思想：是否符合 OOP 设计原则
- [ ] 设计模式：是否合理应用设计模式
- [ ] 性能优化：是否存在性能问题
- [ ] 安全性：是否存在安全漏洞
- [ ] 可读性：代码是否易于理解和维护
- [ ] 可扩展性：是否便于后续功能扩展
- [ ] 可测试性：是否便于编写单元测试

### 审核工具
- **自动化检查**：ESLint、TypeScript、Prettier
- **静态分析**：SonarQube、CodeClimate
- **测试验证**：Jest/Vitest、Cypress/Playwright
- **性能监控**：Web Vitals、Bundle Analyzer

### 自动审核触发机制
- **触发条件**：仅对代码文件（.ts, .vue, .js, .tsx, .jsx）修改时触发
- **排除条件**：规则文件（.mdc, .md, .txt, .json）修改时不触发
- **防死循环**：审核过程本身不触发新的审核
- **审核时机**：代码生成或修改完成后立即执行

## 🎯 测试最佳实践

### 必需配置项
- ✅ 测试覆盖率必须达到 80% 以上
- ✅ 使用 Jest/Vitest 进行单元测试
- ✅ 使用 Cypress/Playwright 进行端到端测试
- ✅ 遵循 Arrange-Act-Assert 结构
- ✅ 使用测试数据工厂创建测试数据

### 参考文件
- `jest.config.js` - Jest 配置
- `vitest.config.ts` - Vitest 配置
- `cypress.config.ts` - Cypress 配置
- `playwright.config.ts` - Playwright 配置