import * as XLSX from 'xlsx'; import { describe, expect, it } from 'vitest'; import { parseWorkbook } from '../src/core/parseWorkbook'; import { loadInputWorkbook, loadP1, loadWorkbook } from './helpers'; describe('parseWorkbook (P1 sheet intent.PR)', () => { it('finds exactly one pier P1 with the input values of the sheet', () => { const { piers, issues } = parseWorkbook(loadWorkbook()); expect(issues).toEqual([]); expect(piers.map((p) => p.name)).toEqual(['P1']); const p1 = loadP1(); expect(p1.type).toBe('T형'); expect(p1.foundationType).toBe('말뚝기초'); expect(p1.copingTopEl).toBeCloseTo(43.411, 6); expect(p1.footingBottomEl).toBeCloseTo(26.208, 6); expect(p1.groundEl).toBeCloseTo(28.931, 6); expect(p1.pileLengthMm).toBe(24000); expect(p1.copingWidthMm).toBe(3100); expect(p1.copingLengthMm).toBe(12000); expect(p1.columnCount).toBe(1); }); it('reads 4 IP points and 10 bearings (5 front, 5 back)', () => { const p1 = loadP1(); expect(p1.ips.map((p) => p.name)).toEqual(['IP1', 'IP2', 'IP3', 'IP4']); expect(p1.bearings).toHaveLength(10); expect(p1.bearings.filter((b) => b.row === '전열')).toHaveLength(5); expect(p1.bearings.filter((b) => b.row === '후열')).toHaveLength(5); }); it('converts the bearing azimuth strings to decimal degrees', () => { const p1 = loadP1(); expect(p1.bearings[0].azimuthDeg).toBeCloseTo(14.0511, 4); expect(p1.bearingAzimuthFrame).toBe('model-xy'); }); }); describe('sheet report (spec §2.3)', () => { it('marks the P1 sheet as selectable and counts its tables', () => { const { sheets } = parseWorkbook(loadWorkbook()); expect(sheets).toHaveLength(1); expect(sheets[0]).toMatchObject({ name: 'intent.PR', ok: true, piers: 1, tables: { pier: 1, ip: 1, bearing: 1 } }); }); it('marks a sheet with a bad azimuth (E03) as not selectable and drops its pier', () => { const wb = loadWorkbook(); wb.Sheets['intent.PR']['F32'].v = '75.9'; const { piers, sheets, issues } = parseWorkbook(wb); expect(issues.map((i) => i.code)).toContain('E03'); expect(sheets[0].ok).toBe(false); expect(piers).toHaveLength(0); }); }); describe('tables split across sheets (spec §2.2: 시트 1장 = 교각 1기)', () => { it('reports E05 and drops the pier when the IP table is not on the same sheet', () => { const wb = loadWorkbook(); wb.Sheets['intent.PR']['B25'].v = '기타'; const { piers, sheets, issues } = parseWorkbook(wb); expect(issues.map((i) => i.code)).toContain('E05'); expect(piers).toHaveLength(0); expect(sheets[0].ok).toBe(false); }); }); describe('three-sheet input (교각 · IP · 받침 on separate sheets, joined by 교각명)', () => { /** Splits the P1 sheet into the three sheets the user prepares: rows 22–23, 25–29, 31–41 (1-based). */ function threeSheets(): XLSX.WorkBook { const src = loadWorkbook(); const grid = XLSX.utils.sheet_to_json<(string | number | null)[]>(src.Sheets['intent.PR'], { header: 1, raw: true, defval: null, }); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(grid.slice(21, 23)), '교각'); XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(grid.slice(24, 29)), 'IP'); XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(grid.slice(30, 41)), '받침'); return wb; } it('joins the three sheets into one pier with 4 IP points and 10 bearings', () => { const { piers, issues, sheets } = parseWorkbook(threeSheets()); expect(issues).toEqual([]); expect(piers).toHaveLength(1); expect(piers[0]).toMatchObject({ name: 'P1', sheet: '교각' }); expect(piers[0].ips).toHaveLength(4); expect(piers[0].bearings).toHaveLength(10); expect(sheets.map((s) => [s.name, s.ok, s.piers])).toEqual([ ['교각', true, 1], ['IP', true, 0], ['받침', true, 0], ]); }); }); describe('user input file (RAG/운곡교P1입력.xlsx)', () => { it('accepts π형 as 2주형 without changing the source workbook value', () => { const wb = loadInputWorkbook(); const grid = XLSX.utils.sheet_to_json<(string | number | null)[]>(wb.Sheets['교각'], { header: 1, defval: null }); const header = grid.findIndex((row) => row[0] === '교각명' && row[1] === '교각형식'); expect(header).toBeGreaterThanOrEqual(0); grid[header + 1][1] = ' π형 '; grid[header + 1][9] = 2; wb.Sheets['교각'] = XLSX.utils.aoa_to_sheet(grid); const { piers, issues } = parseWorkbook(wb); expect(issues).toEqual([]); expect(piers).toHaveLength(1); expect(piers[0]).toMatchObject({ type: '2주형', columnCount: 2 }); expect(XLSX.utils.sheet_to_json<(string | number | null)[]>(wb.Sheets['교각'], { header: 1 })[header + 1][1]).toBe(' π형 '); }); it('has the three sheets 교각 · IP · 받침 and parses to one pier, P1, with no issues', () => { const wb = loadInputWorkbook(); expect(wb.SheetNames).toEqual(['교각', 'IP', '받침']); const { piers, issues } = parseWorkbook(wb); expect(issues).toEqual([]); expect(piers.map((p) => [p.name, p.sheet])).toEqual([['P1', '교각']]); }); });