cangku/frontend/test/tableExport.test.mjs

75 lines
3.1 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildCsvContent,
buildWorksheetXml,
escapeXmlText,
resolveExportRows,
spreadsheetColumnName,
} from '../src/utils/tableExport.js';
test('spreadsheetColumnName maps indexes to excel column letters', () => {
assert.equal(spreadsheetColumnName(0), 'A');
assert.equal(spreadsheetColumnName(25), 'Z');
assert.equal(spreadsheetColumnName(26), 'AA');
assert.equal(spreadsheetColumnName(51), 'AZ');
assert.equal(spreadsheetColumnName(52), 'BA');
assert.equal(spreadsheetColumnName(701), 'ZZ');
});
test('escapeXmlText escapes xml special characters', () => {
assert.equal(escapeXmlText('a<b>&"c"\''), 'a&lt;b&gt;&amp;&quot;c&quot;&apos;');
});
test('resolveExportRows applies exportValue and falls back to dataIndex', () => {
const columns = [
{ title: '名称', dataIndex: 'name' },
{ title: '达标', dataIndex: 'qualified', exportValue: (value) => (value ? '达标' : '未达标') },
];
const rows = [{ name: '商品<1>', qualified: true }, { name: '商品,2', qualified: false }];
assert.deepEqual(resolveExportRows(columns, rows), [
['商品<1>', '达标'],
['商品,2', '未达标'],
]);
});
test('exportValue receives the cell value like antd render, not the row object', () => {
// 回归:导出 Invalid Date / NaN% 的根因是 exportValue 曾被传入整行对象
const formatDate = (value) => (value ? `D:${value.slice(0, 10)}` : '--');
const percent = (value) => (value === null || value === undefined ? '--' : `${(Number(value) * 100).toFixed(2)}%`);
const columns = [
{ title: '上架时间', dataIndex: 'firstCreatedAt', exportValue: formatDate },
{ title: '动销率', dataIndex: 'rate', exportValue: percent },
{ title: '是否达标', dataIndex: 'qualified', exportValue: (value) => (value ? '达标' : '未达标') },
];
const rows = [
{ firstCreatedAt: '2026-07-28T00:00:00.000Z', rate: 0.9667, qualified: true },
{ firstCreatedAt: null, rate: null, qualified: false },
];
assert.deepEqual(resolveExportRows(columns, rows), [
['D:2026-07-28', '96.67%', '达标'],
['--', '--', '未达标'],
]);
});
test('buildCsvContent quotes cells containing delimiters and keeps CRLF rows', () => {
const columns = [{ title: '商品,名称', dataIndex: 'name' }, { title: '数量', dataIndex: 'count' }];
const rows = [{ name: '含"引号"和\n换行', count: 3 }];
const content = buildCsvContent(columns, rows);
const lines = content.split('\r\n');
assert.equal(lines[0], '"商品,名称",数量');
assert.equal(lines[1], '"含""引号""和\n换行",3');
});
test('buildWorksheetXml emits header row, inline strings and numeric cells', () => {
const columns = [{ title: '名称<测试>', dataIndex: 'name' }, { title: '奖金', dataIndex: 'bonus' }];
const rows = [{ name: '商品&1', bonus: 400 }];
const xml = buildWorksheetXml(columns, rows);
assert.match(xml, /<sheetData>/);
assert.match(xml, /t="inlineStr"><is><t xml:space="preserve">名称&lt;测试&gt;<\/t><\/is><\/c>/);
assert.match(xml, /商品&amp;1/);
assert.match(xml, /<c r="B2"><v>400<\/v><\/c>/);
assert.match(xml, /<c r="A1"/);
});