import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ReportMaterialImportModal } from './ReportMaterialImportModal'; const { adminApi } = vi.hoisted(() => ({ adminApi: { analyzeReportMaterialImport: vi.fn(), commitReportMaterialImport: vi.fn(), getReportMaterialImportAnalysisStatus: vi.fn(), listDrainageFields: vi.fn(), listEnterpriseApplicationOptions: vi.fn(), listReportImportProfiles: vi.fn(), listTenantOptions: vi.fn(), }, })); vi.mock('@/api/adminApi', () => ({ adminApi })); describe('ReportMaterialImportModal mapping profile action', () => { beforeEach(() => { Object.values(adminApi).forEach((method) => method.mockReset()); adminApi.listTenantOptions.mockResolvedValue([ { id: 'tenant-1', name: '测试企业', code: 'T001', status: 'active' }, ]); adminApi.listEnterpriseApplicationOptions.mockResolvedValue([ { id: 'app-1', tenantId: 'tenant-1', name: '测试应用', status: 'active' }, ]); adminApi.listDrainageFields.mockResolvedValue([]); adminApi.listReportImportProfiles.mockResolvedValue([]); adminApi.analyzeReportMaterialImport.mockResolvedValue({ id: 'analysis-1', status: 'queued', progress: 10, progressStage: '文件已上传,等待解析', }); adminApi.getReportMaterialImportAnalysisStatus.mockResolvedValue({ id: 'analysis-1', status: 'analyzed', progress: 100, progressStage: '解析完成', columns: [ { sourceColumnIndex: 0, columnLetter: 'A', sourceHeader: '签名', sourceHeaderPath: '签名', imageCount: 0 }, ], rows: [], suggestedMappings: [], }); adminApi.commitReportMaterialImport.mockResolvedValue({ id: 'analysis-1', reportType: 'signature', status: 'pending_review', successCount: 1, failedCount: 0, }); }); it('renders the reusable mapping choice as a clear pressed-state shared button', async () => { const user = userEvent.setup(); render(); await waitFor(() => expect(adminApi.listTenantOptions).toHaveBeenCalledTimes(1)); const tenantSelect = screen.getByText('所属企业').closest('label')?.querySelector('button'); expect(tenantSelect).not.toBeNull(); await user.click(tenantSelect!); await user.click(screen.getByRole('option', { name: /测试企业/ })); const applicationSelect = screen.getByText('企业应用(必选)').closest('label')?.querySelector('button'); await user.click(applicationSelect!); await user.click(screen.getByRole('option', { name: '测试应用' })); const fileInput = document.querySelector('input[type="file"]'); expect(fileInput).not.toBeNull(); fireEvent.change(fileInput!, { target: { files: [new File(['xlsx'], 'mapping.xlsx')] } }); await user.click(screen.getByRole('button', { name: '解析文件并配置映射' })); const toggle = await screen.findByRole('button', { name: '保存为可复用映射方案' }); expect(toggle).toHaveClass('ui-button', 'report-import-profile__toggle'); expect(toggle).toHaveAttribute('aria-pressed', 'false'); await user.click(toggle); await waitFor(() => expect(screen.getByRole('button', { name: '本次将保存/更新映射方案' })).toHaveAttribute('aria-pressed', 'true'), ); expect(screen.getByLabelText('映射方案名称')).toBeVisible(); }, 10_000); it('keeps signature category separate from the signature name and submits the review batch', async () => { adminApi.getReportMaterialImportAnalysisStatus.mockResolvedValue({ id: 'analysis-1', status: 'analyzed', progress: 100, progressStage: '解析完成', columns: [ { sourceColumnIndex: 1, columnLetter: 'A', sourceHeader: '签名*', sourceHeaderPath: '签名*', imageCount: 0 }, { sourceColumnIndex: 2, columnLetter: 'B', sourceHeader: '签名类别*', sourceHeaderPath: '签名类别*\n1-营业执照\n2-商标\n3-APP', imageCount: 0, }, ], rows: [{ rowNumber: 2, values: { 1: '【真实签名】', 2: '1' }, imageColumns: [] }], suggestedMappings: [ { sourceHeader: '签名*', sourceHeaderPath: '签名*', sourceColumnIndex: 1, targetFieldCode: 'signature_name', targetKind: 'signatureName', fieldType: 'string', required: true, }, { sourceHeader: '签名类别*', sourceHeaderPath: '签名类别*\n1-营业执照\n2-商标\n3-APP', sourceColumnIndex: 2, targetFieldCode: 'purpose', targetKind: 'purpose', fieldType: 'string', }, ], }); const user = userEvent.setup(); const onCompleted = vi.fn(); const refresh = vi.fn(); window.addEventListener('cmpp-audit-count-refresh', refresh); render(); await waitFor(() => expect(adminApi.listTenantOptions).toHaveBeenCalledTimes(1)); await user.click(screen.getByText('所属企业').closest('label')!.querySelector('button')!); await user.click(screen.getByRole('option', { name: /测试企业/ })); await user.click(screen.getByText('企业应用(必选)').closest('label')!.querySelector('button')!); await user.click(screen.getByRole('option', { name: '测试应用' })); fireEvent.change(document.querySelector('input[type="file"]')!, { target: { files: [new File(['xlsx'], 'mapping.xlsx')] }, }); await user.click(screen.getByRole('button', { name: '解析文件并配置映射' })); const preview = await screen.findByText('查看前 1 行解析预览'); await user.click(preview); expect(preview.closest('details')).toHaveAttribute('open'); await user.click(screen.getByRole('button', { name: '提交导入审核' })); await waitFor(() => expect(adminApi.commitReportMaterialImport).toHaveBeenCalledWith( 'analysis-1', expect.objectContaining({ mappings: expect.arrayContaining([ expect.objectContaining({ targetKind: 'signatureName', targetFieldCode: 'signature_name' }), expect.objectContaining({ targetKind: 'purpose', targetFieldCode: 'purpose' }), ]), }), ), ); expect(onCompleted).toHaveBeenCalledWith(expect.objectContaining({ id: 'analysis-1', status: 'pending_review' })); expect(refresh).toHaveBeenCalledTimes(1); window.removeEventListener('cmpp-audit-count-refresh', refresh); }, 10_000); });