import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; 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('源数据非法时,告警包含单元格内容和位置', () => { 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('无可继承的上方值'); }); });