import {describe,it,expect} from 'vitest'; import {buildPier} from '../src/core/geometry'; import {solplateGeometry} from '../src/core/solplate'; import {loadP1} from '../tests/helpers'; import {calculate,withTemplateDefaults,DETAIL,type QuantityInputs} from './model'; import {exportEdits} from './export'; import {template,templateHash} from './template-data'; import {cellKey} from './formula'; import {exportRecords,finishQuantityExport} from './export-state'; import type {QuantitySnapshot} from './project-inputs'; const base=loadP1(); const input={...base,sole:{h3Mm:181,marginMm:100,tByRow:{전열:35,후열:35},slopePct:Object.fromEntries(base.bearings.map(b=>[String(b.no),0]))}}; const model=buildPier(input); const stored:QuantityInputs={values:{r20:0,r21:2.8,r22:0,r23:0},reviewed:true}; const raw=()=>calculate(input,model,stored,1.25); const snapshot=():QuantitySnapshot=>({project:{name:'프로젝트',bridge:'교량'},selected:input.name,piers:[{input:{...input,quantityTakeoff:stored},model,dirty:false,rebarTon:1.25}]}); describe('W64 모델 기준 수량·Excel',()=>{ it('800×740×35mm 10개는 1.62652t이며 Excel에도 같은 숫자를 전달한다',()=>{ const result=raw(),row=result.results.find(r=>r.cell==='W64')!; expect(model.blocks).toHaveLength(10); expect(row.value).toBeCloseTo(1.62652,10); expect(row.sourceFormula).toBe(template[DETAIL].W64.value); expect(row.formula).toContain('3D 모델'); const payload=exportEdits({project:{name:'',bridge:''},pier:input.name,overrides:result.overrides,specs:withTemplateDefaults(stored).values,reviewed:true,copingMode:result.copingMode,solplateMode:result.solplateMode,templateHash}); expect(payload.edits[DETAIL].W64.value).toBeCloseTo(row.value!,12); expect(payload.edits[DETAIL].K64.value).toContain('3D 모델'); expect(template[DETAIL].W64.value).toBe('=(BG28+0.1)*(BG29+0.1)*BG34*BG30*7.85'); }); it('구형 요청은 모델 값을 원식의 캐시로 섞지 않고 기존 계산을 유지한다',()=>{ const result=raw(); const payload=exportEdits({project:{name:'',bridge:''},pier:input.name,overrides:result.overrides,specs:withTemplateDefaults(stored).values,reviewed:true,copingMode:result.copingMode,templateHash}); expect(payload.edits[DETAIL].W64.cached).toBeCloseTo(1.23088,10); expect(payload.edits[DETAIL].W64.value).toBeUndefined(); }); it('받침별 크기·경사·두께가 달라도 개별 모델 체적으로 합산한다',()=>{ const next={...input,sole:{...input.sole,plates:{[String(base.bearings[0].no)]:{aMm:900,bMm:800}},slopePct:{[String(base.bearings[0].no)]:2.1}}}; const m=buildPier(next),r=calculate(next,m,{...stored,values:{...stored.values,r40:999,r41:999,r46:999}},1.25); const actual=m.blocks.reduce((sum,b)=>sum+solplateGeometry(b,b.plan).volume*7.85,0); expect(r.results.find(x=>x.cell==='W64')!.value).toBeCloseTo(actual,12); expect(r.results.find(x=>x.cell==='W64')!.dependencies).toEqual([cellKey(DETAIL,'W64')]); }); }); describe('Excel 비동기 생성 후 입력 보존·범위',()=>{ it('선택 출력은 해당 교각만, 전체 출력은 미생성 교각도 검사 대상으로 반환한다',()=>{ const s=snapshot();s.piers.push({...s.piers[0],input:{...input,name:'P2'},model:null}); expect(exportRecords(s,'selected').map(r=>r.pier.input.name)).toEqual([input.name]); expect(exportRecords(s,'all').map(r=>r.pier.input.name)).toEqual([input.name,'P2']); }); it('생성 중 수정한 입력·검토 체크를 이전 스냅샷으로 저장하지 않는다',()=>{ const before=snapshot(),live=snapshot(),saved:unknown[]=[]; live.piers[0].input={...live.piers[0].input,quantityTakeoff:{values:{...stored.values,r21:6},reviewed:false}}; expect(finishQuantityExport(before,'all',live,(...args)=>saved.push(args))).toEqual([input.name]); expect(saved).toEqual([]); expect(live.piers[0].input.quantityTakeoff).toMatchObject({values:{r21:6},reviewed:false}); }); it('변경이 없으면 현재 입력에 계산 서명만 기록한다',()=>{ const s=snapshot(),saved:{name:string;data:QuantityInputs}[]=[]; expect(finishQuantityExport(s,'selected',s,(name,data)=>saved.push({name,data}))).toEqual([]); expect(saved[0].data).toMatchObject({values:stored.values,reviewed:true,lastSignature:raw().signature}); }); it('다른 프로젝트로 바뀌거나 모델이 재생성되면 이전 출력의 서명을 저장하지 않는다',()=>{ const before=snapshot(),live=snapshot();live.project={...live.project,name:'다른 프로젝트'}; expect(finishQuantityExport(before,'all',live,()=>{throw new Error('과거 입력 저장');})).toEqual([input.name]); live.project=before.project;live.piers[0].model=buildPier(input); expect(finishQuantityExport(before,'all',live,()=>{throw new Error('과거 입력 저장');})).toEqual([input.name]); }); });