| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
- import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
- import SalaryManagement from '../../src/components/SalaryManagement';
- import { salaryClientManager } from '../../src/api/salaryClient';
- import { areaClientManager } from '@d8d/area-management-ui/api';
- import { AreaSelect } from '@d8d/area-management-ui/components';
- // 完整的mock响应对象
- const createMockResponse = (status: number, data?: any) => ({
- status,
- ok: status >= 200 && status < 300,
- body: null,
- bodyUsed: false,
- statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
- headers: new Headers(),
- url: '',
- redirected: false,
- type: 'basic' as ResponseType,
- json: async () => data || {},
- text: async () => '',
- blob: async () => new Blob(),
- arrayBuffer: async () => new ArrayBuffer(0),
- formData: async () => new FormData(),
- clone: function() { return this; }
- });
- // 首先取消AreaSelect组件的mock,以便使用真实的组件
- vi.doUnmock('@d8d/area-management-ui/components');
- // // Mock AreaSelect组件的API
- // vi.mock('@d8d/area-management-ui/api', () => ({
- // areaClientManager: {
- // get: vi.fn(() => ({
- // index: {
- // $get: vi.fn()
- // }
- // }))
- // }
- // }));
- // Mock shared-ui-components的hc工具,避免axios调用
- vi.mock('@d8d/shared-ui-components/utils/hc', () => ({
- axiosFetch: vi.fn(() => Promise.resolve({
- status: 200,
- json: () => Promise.resolve({ data: [] })
- })),
- rpcClient: vi.fn(() => ({
- $url: vi.fn(),
- index: {
- $get: vi.fn()
- }
- }))
- }));
- // Mock API client
- vi.mock('../../src/api/salaryClient', () => {
- const mockSalaryClient = {
- list: {
- $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
- data: [
- {
- id: 1,
- provinceId: 110000,
- cityId: 110100,
- districtId: null,
- basicSalary: 5000.00,
- allowance: 1000.00,
- insurance: 500.00,
- housingFund: 800.00,
- totalSalary: 4700.00,
- updateTime: '2024-01-01T00:00:00Z',
- province: { id: 110000, name: '北京市' },
- city: { id: 110100, name: '北京市辖区' },
- district: null
- }
- ],
- total: 1
- })))
- },
- create: {
- $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
- id: 3,
- provinceId: 440000,
- cityId: 440100,
- districtId: null,
- basicSalary: 5500.00,
- allowance: 1100.00,
- insurance: 550.00,
- housingFund: 850.00,
- totalSalary: 5200.00,
- updateTime: '2024-01-03T00:00:00Z'
- }))),
- },
- update: {
- ':id': {
- $put: vi.fn(() => Promise.resolve(createMockResponse(200, {
- id: 1,
- provinceId: 110000,
- cityId: 110100,
- districtId: null,
- basicSalary: 5500.00,
- allowance: 1000.00,
- insurance: 500.00,
- housingFund: 800.00,
- totalSalary: 5200.00,
- updateTime: '2024-01-03T00:00:00Z'
- }))),
- }
- },
- delete: {
- ':id': {
- $delete: vi.fn(() => Promise.resolve(createMockResponse(200, { success: true }))),
- }
- },
- detail: {
- ':id': {
- $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
- id: 1,
- provinceId: 110000,
- cityId: 110100,
- districtId: null,
- basicSalary: 5000.00,
- allowance: 1000.00,
- insurance: 500.00,
- housingFund: 800.00,
- totalSalary: 4700.00,
- updateTime: '2024-01-01T00:00:00Z'
- }))),
- }
- },
- byProvinceCity: {
- $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
- id: 1,
- provinceId: 110000,
- cityId: 110100,
- districtId: null,
- basicSalary: 5000.00,
- allowance: 1000.00,
- insurance: 500.00,
- housingFund: 800.00,
- totalSalary: 4700.00,
- updateTime: '2024-01-01T00:00:00Z'
- }))),
- }
- };
- const mockClientManager = {
- get: vi.fn(() => mockSalaryClient),
- reset: vi.fn()
- };
- return {
- salaryClientManager: mockClientManager,
- salaryClient: mockSalaryClient
- };
- });
- describe('薪资管理表单中AreaSelect实际行为测试', () => {
- let queryClient: QueryClient;
- beforeEach(() => {
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
- });
- vi.clearAllMocks();
- // 检查AreaSelect是否被mock
- console.debug('AreaSelect is mocked?', vi.isMockFunction(AreaSelect));
- console.debug('AreaSelect type:', typeof AreaSelect);
- // 设置AreaSelect API的mock响应
- const mockAreaClient = areaClientManager.get();
- // 使用mockImplementation而不是mockImplementationOnce,以便根据查询参数返回不同的数据
- (mockAreaClient.index.$get as Mock).mockImplementation(({ query }: any) => {
- const filters = query?.filters ? JSON.parse(query.filters) : {};
- console.debug('API调用: 查询地区列表, query:', query, 'filters:', filters);
- if (filters.level === 1) {
- // 省份查询
- return Promise.resolve(createMockResponse(200, {
- data: [
- { id: 110000, name: '北京市', level: 1 },
- { id: 310000, name: '上海市', level: 1 },
- { id: 440000, name: '广东省', level: 1 }
- ]
- }));
- } else if (filters.level === 2 && filters.parentId === 110000) {
- // 北京市的城市查询
- return Promise.resolve(createMockResponse(200, {
- data: [
- { id: 110100, name: '北京市辖区', level: 2 },
- { id: 110200, name: '北京市其他', level: 2 }
- ]
- }));
- } else if (filters.level === 3 && filters.parentId === 110100) {
- // 北京市辖区的区县查询
- return Promise.resolve(createMockResponse(200, {
- data: [
- { id: 110101, name: '东城区', level: 3 },
- { id: 110102, name: '西城区', level: 3 }
- ]
- }));
- } else {
- // 默认返回空数组
- return Promise.resolve(createMockResponse(200, {
- data: []
- }));
- }
- });
- });
- const renderComponent = () => {
- return render(
- <QueryClientProvider client={queryClient}>
- <SalaryManagement />
- </QueryClientProvider>
- );
- };
- it('应该验证添加表单中AreaSelect组件的实际行为 - 区县字段不为必填', async () => {
- renderComponent();
- // 打开添加模态框
- const addButton = screen.getByTestId('add-salary-button');
- fireEvent.click(addButton);
- await waitFor(() => {
- const addSalaryTexts = screen.getAllByText('添加薪资');
- expect(addSalaryTexts.length).toBeGreaterThanOrEqual(2);
- });
- // 辅助函数:获取添加表单中的AreaSelect组件
- const getAddFormAreaSelect = () => screen.getByTestId('add-form-area-select');
- // 等待AreaSelect组件加载
- await waitFor(() => {
- const areaSelect = getAddFormAreaSelect();
- expect(areaSelect).toBeInTheDocument();
- });
- // 在添加表单的AreaSelect组件中查找表单标签
- const areaSelect1 = getAddFormAreaSelect();
- // 直接使用querySelector在AreaSelect内部查找FormLabel元素
- const formLabelsInAreaSelect = Array.from(
- areaSelect1.querySelectorAll('label[data-slot="form-label"]')
- ).filter(label =>
- ['省份', '城市', '区县'].some(text => label.textContent?.includes(text))
- );
- expect(formLabelsInAreaSelect.length).toBe(3); // 省份、城市、区县
- // 验证区县标签没有星号(必填标记)
- const districtLabel = formLabelsInAreaSelect.find(label =>
- label.textContent?.includes('区县')
- ) as HTMLElement;
- expect(districtLabel).toBeDefined();
- // 检查区县标签是否包含星号元素
- const districtStarElement = districtLabel.querySelector('span.text-destructive');
- expect(districtStarElement).toBeNull(); // 区县标签应该没有星号
- // 验证省份标签有星号(因为required=true)
- const provinceLabel = formLabelsInAreaSelect.find(label =>
- label.textContent?.includes('省份')
- ) as HTMLElement;
- expect(provinceLabel).toBeDefined();
- // 在添加表单中,省份标签应该包含星号
- const provinceStarElement = provinceLabel.querySelector('span.text-destructive');
- expect(provinceStarElement).not.toBeNull(); // 省份标签应该有星号
- // 验证城市标签没有星号(因为还没有选择省份)
- const cityLabel = formLabelsInAreaSelect.find(label =>
- label.textContent?.includes('城市')
- ) as HTMLElement;
- expect(cityLabel).toBeDefined();
- const cityStarElement = cityLabel.querySelector('span.text-destructive');
- expect(cityStarElement).toBeNull(); // 城市标签应该没有星号(未选择省份时)
- // 注意:我们不再测试复杂的交互,因为AreaSelect组件的单元测试已经验证了核心逻辑
- // 这里我们主要验证在薪资管理上下文中AreaSelect组件被正确使用
- });
- it('应该验证搜索区域中AreaSelect组件的实际行为 - required=false', async () => {
- renderComponent();
- // 等待组件渲染
- await waitFor(() => {
- expect(screen.getByText('薪资水平管理')).toBeInTheDocument();
- });
- // 查找搜索区域中的AreaSelect组件标签
- // 搜索区域的AreaSelect应该使用required=false
- const formLabels = screen.getAllByText(/省份|城市|区县/);
- // 搜索区域的标签应该没有星号
- const searchProvinceLabels = screen.getAllByText('省份');
- const searchProvinceLabel = searchProvinceLabels.find(label => {
- const element = label as HTMLElement;
- const starElement = element.querySelector('span.text-destructive');
- return starElement === null;
- });
- expect(searchProvinceLabel).toBeDefined();
- const searchCityLabels = screen.getAllByText('城市');
- const searchCityLabel = searchCityLabels.find(label => {
- const element = label as HTMLElement;
- const starElement = element.querySelector('span.text-destructive');
- return starElement === null;
- });
- expect(searchCityLabel).toBeDefined();
- const searchDistrictLabels = screen.getAllByText('区县');
- const searchDistrictLabel = searchDistrictLabels.find(label => {
- const element = label as HTMLElement;
- const starElement = element.querySelector('span.text-destructive');
- return starElement === null;
- });
- expect(searchDistrictLabel).toBeDefined();
- });
- });
|