142 lines
4.7 KiB
TypeScript
142 lines
4.7 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { describe, expect, it } from 'vitest';
|
|
import * as XLSX from 'xlsx';
|
|
import { buildSankeyData, parseCsvText, parseXlsxBuffer } from '../src/core';
|
|
|
|
describe('core parser & sankey', () => {
|
|
it('可以解析 CSV 并生成列名与数据行', () => {
|
|
const table = parseCsvText('A,B,C\n1,2,3\n4,5,6');
|
|
|
|
expect(table.headers).toEqual(['A', 'B', 'C']);
|
|
expect(table.rows).toEqual([
|
|
['1', '2', '3'],
|
|
['4', '5', '6']
|
|
]);
|
|
});
|
|
|
|
it('支持合并单元格语义向下补全并聚合', () => {
|
|
const table = {
|
|
headers: ['站点', '值', '目标', '模型'],
|
|
rows: [
|
|
['宁波北欧10', '2582', '嘉兴四级算力池', '小模型'],
|
|
['宁波北欧12', '2610', '', ''],
|
|
['宁波鄞中15', '507', '嘉兴四级算力池', '小模型']
|
|
]
|
|
};
|
|
|
|
const result = buildSankeyData(table, {
|
|
sourceDataColumn: 1,
|
|
sourceDescriptionColumns: [0, 1],
|
|
targetDescriptionColumns: [2, 3],
|
|
delimiter: '-'
|
|
});
|
|
|
|
expect(result.meta.droppedRows).toBe(0);
|
|
expect(result.links).toHaveLength(3);
|
|
expect(result.links[1].target).toBe('嘉兴四级算力池-小模型');
|
|
});
|
|
|
|
it('可以解析 data/example0.xlsx', () => {
|
|
const filePath = resolve(process.cwd(), 'data/example0.xlsx');
|
|
const fileBuffer = readFileSync(filePath);
|
|
const table = parseXlsxBuffer(fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength));
|
|
|
|
expect(table.headers.length).toBeGreaterThan(1);
|
|
expect(table.rows.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('xlsx !ref 范围虚高时,不应错误膨胀行列数量', () => {
|
|
const sheet = XLSX.utils.aoa_to_sheet([
|
|
['source', 'target', 'value'],
|
|
['A', 'B', 1]
|
|
]);
|
|
sheet['!ref'] = 'A1:H20';
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, sheet, 'S1');
|
|
const buffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
|
|
const table = parseXlsxBuffer(
|
|
buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
|
);
|
|
|
|
expect(table.headers).toEqual(['source', 'target', 'value']);
|
|
expect(table.rows).toEqual([['A', 'B', '1']]);
|
|
});
|
|
|
|
it('xlsx 中 0 值应被正确保留', () => {
|
|
const sheet = XLSX.utils.aoa_to_sheet([
|
|
['source', 'target', 'value'],
|
|
['A', 'B', 0]
|
|
]);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, sheet, 'S1');
|
|
const buffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
|
|
const table = parseXlsxBuffer(
|
|
buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
|
);
|
|
|
|
expect(table.rows).toEqual([['A', 'B', '0']]);
|
|
});
|
|
|
|
it('源数据非法时,告警包含单元格内容和位置', () => {
|
|
const table = {
|
|
headers: ['source', 'value', 'target'],
|
|
rows: [['A', 'abc', 'T1']]
|
|
};
|
|
|
|
const result = buildSankeyData(table, {
|
|
sourceDataColumn: 1,
|
|
sourceDescriptionColumns: [0],
|
|
targetDescriptionColumns: [2],
|
|
delimiter: '-'
|
|
});
|
|
|
|
expect(result.meta.droppedRows).toBe(1);
|
|
expect(result.meta.warnings[0]).toContain('第 2 行');
|
|
expect(result.meta.warnings[0]).toContain('第 2 列');
|
|
expect(result.meta.warnings[0]).toContain('value');
|
|
expect(result.meta.warnings[0]).toContain('abc');
|
|
});
|
|
|
|
it('源描述为空时,告警包含描述字段内容和位置', () => {
|
|
const table = {
|
|
headers: ['source', 'value', 'target'],
|
|
rows: [['', '12', 'T1']]
|
|
};
|
|
|
|
const result = buildSankeyData(table, {
|
|
sourceDataColumn: 1,
|
|
sourceDescriptionColumns: [0],
|
|
targetDescriptionColumns: [2],
|
|
delimiter: '-'
|
|
});
|
|
|
|
expect(result.meta.droppedRows).toBe(1);
|
|
expect(result.meta.warnings[0]).toContain('源描述为空');
|
|
expect(result.meta.warnings[0]).toContain('第 1 列');
|
|
expect(result.meta.warnings[0]).toContain('source');
|
|
expect(result.meta.warnings[0]).toContain('(空)');
|
|
});
|
|
|
|
it('目标描述为空时,告警包含描述字段内容和位置', () => {
|
|
const table = {
|
|
headers: ['source', 'value', 'target'],
|
|
rows: [['S1', '12', '']]
|
|
};
|
|
|
|
const result = buildSankeyData(table, {
|
|
sourceDataColumn: 1,
|
|
sourceDescriptionColumns: [0],
|
|
targetDescriptionColumns: [2],
|
|
delimiter: '-'
|
|
});
|
|
|
|
expect(result.meta.droppedRows).toBe(1);
|
|
expect(result.meta.warnings[0]).toContain('目标描述为空');
|
|
expect(result.meta.warnings[0]).toContain('第 3 列');
|
|
expect(result.meta.warnings[0]).toContain('target');
|
|
expect(result.meta.warnings[0]).toContain('(空)');
|
|
expect(result.meta.warnings[0]).toContain('无可继承的上方值');
|
|
});
|
|
});
|