import { CardAnalyticsExportService, escapeCsvCell, generateCsvContent, RadarExportRow, } from '../../../src/modules/crm/service/analytics-export'; describe('Point 50: 访客雷达多维数据报表 CSV 导出引擎 (RFC 4180)', () => { let service: CardAnalyticsExportService; beforeEach(() => { service = new CardAnalyticsExportService(); }); describe('1. escapeCsvCell (RFC 4180 单元格转义规范)', () => { it('包含逗号、换行或双引号的单元格必须用双引号包裹且内部双引号翻倍', () => { expect(escapeCsvCell('Hello, World')).toBe('"Hello, World"'); expect(escapeCsvCell('Line 1\nLine 2')).toBe('"Line 1\nLine 2"'); expect(escapeCsvCell('She said "Hi"')).toBe('"She said ""Hi"""'); }); it('普通纯文本单元格无需包裹双引号', () => { expect(escapeCsvCell('Arthur Pendelton')).toBe('Arthur Pendelton'); expect(escapeCsvCell('Dubai')).toBe('Dubai'); expect(escapeCsvCell(100)).toBe('100'); }); it('空值、null 或 undefined 应返回空字符串', () => { expect(escapeCsvCell('')).toBe(''); expect(escapeCsvCell(null)).toBe(''); expect(escapeCsvCell(undefined)).toBe(''); }); }); describe('2. generateCsvContent (UTF-8 BOM 与多语言表头自适应)', () => { const mockRows: RadarExportRow[] = [ { visitorId: 'vis-001', name: 'Sheikh Mansour', country: 'AE', city: 'Abu Dhabi', stayDurationSeconds: 154, radarScore: 92, trafficChannel: 'nfc_tap', visitedAt: '2026-09-20 14:30:00', }, { visitorId: 'vis-002', name: 'Elena, Rostova', country: 'SE', city: 'Stockholm', stayDurationSeconds: 65, radarScore: 68, trafficChannel: 'whatsapp_chat', visitedAt: '2026-09-20 15:10:00', }, ]; it('导出的 CSV 必须以 UTF-8 BOM (\\uFEFF) 开头以防止 Excel 乱码', () => { const csv = generateCsvContent(mockRows, 'en'); expect(csv.startsWith('\uFEFF')).toBe(true); }); it('英语表头应包含准确的 RFC 4180 列名', () => { const csv = generateCsvContent(mockRows, 'en'); expect(csv).toContain('Visitor ID,Name,Country,City,Stay (Seconds),Radar Score,Channel,Visited Time'); expect(csv).toContain('"Elena, Rostova"'); // 包含逗号被正确转义 }); it('阿拉伯语表头应输出规范阿语文案', () => { const csv = generateCsvContent(mockRows, 'ar'); expect(csv).toContain('معرف الزائر,الاسم,الدولة'); }); it('中文表头应输出清晰中文列名', () => { const csv = generateCsvContent(mockRows, 'zh'); expect(csv).toContain('访客标识,姓名,国家,城市,停留时长(秒),意向评分,访问渠道,访问时间'); }); }); describe('3. CardAnalyticsExportService 报表导出元数据', () => { it('应生成包含文件名与行数统计的导出包', async () => { const result = await service.exportRadarDataset([ { visitorId: 'vis-101', name: 'David', country: 'GB', city: 'London', stayDurationSeconds: 200, radarScore: 85, trafficChannel: 'linkedin_share', visitedAt: '2026-09-20 16:00:00', }, ], 'en'); expect(result.fileName).toMatch(/radar_export_.*\.csv/); expect(result.rowCount).toBe(1); expect(result.csvContent.length).toBeGreaterThan(0); }); }); });