102 行
3.5 KiB
TypeScript
102 行
3.5 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|