from test_est_rea_parity import run def test_quantity_apply_requires_one_detail_row_per_code(): result = run( "math", """(()=>{ function apply(rows,items=[{Code:'r1',GN:7,TargetCode:'X'}]){ const handlers={}; globalThis.document={addEventListener:(name,fn)=>handlers[name]=fn}; const sheet={Code:'Q',rows:[],items,options:{}}; const state={quantitySheet:'Q',project:{quantitySheets:[sheet],tables:{EstMainDat:rows}}}; globalThis.EstApp={state,change:fn=>fn(),render:()=>{}}; EstQuantity.install();handlers.click({target:{closest:selector=>selector==='[data-quantity-apply]'?{}:null}}); return {rows,notice:state.notice||''}; } return {duplicate:apply([{Code:'X',Level:0,GN:1},{Code:'X',Level:0,GN:2}]), heading:apply([{Code:'X',Level:1,GN:1}]), valid:apply([{Code:'X',Level:1,GN:1},{Code:'X',Level:0,GN:2}]), partial:apply([{Code:'X',Level:0,GN:1}],[{Code:'r1',GN:7,TargetCode:'X'},{Code:'r2',GN:4,TargetCode:'missing'}]), disconnected:apply([{Code:'X',Level:0,GN:1}],[{Code:'r1',GN:7,TargetCode:'',LinkCode:'X'}])}; })()""", ) assert [r["GN"] for r in result["duplicate"]["rows"]] == [1, 2] assert result["duplicate"]["notice"] assert result["heading"]["rows"][0]["GN"] == 1 assert result["heading"]["notice"] assert [r["GN"] for r in result["valid"]["rows"]] == [1, 7] assert result["valid"]["notice"] == "" assert result["partial"]["rows"][0]["GN"] == 1 assert result["partial"]["notice"] assert result["disconnected"]["rows"][0]["GN"] == 1 assert result["disconnected"]["notice"] def test_create_edit_apply_and_delete_quantity_sheet(): result = run( "math", """(()=>{ const handlers={};let id=0; globalThis.document={addEventListener:(name,fn)=>handlers[name]=fn}; globalThis.crypto={randomUUID:()=>String(++id)};globalThis.confirm=()=>true; const state={quantitySheet:'manual',project:{quantitySheets:[],tables:{EstMainDat:[{Code:'X',Level:0,GN:1}]}}}; globalThis.EstApp={state,change:fn=>fn(),render:()=>{}}; EstQuantity.install(); const click=(selector,dataset={})=>handlers.click({target:{closest:s=>s===selector?{dataset}:null}}); const change=(dataset,value)=>handlers.change({target:{dataset,value}}); click('[data-quantity-new]');click('[data-quantity-add]'); change({quantityName:''},'Earthwork'); change({quantityItem:'Name',index:'0'},'excavation'); change({quantityTarget:'0'},'X'); change({quantityLine:'0'},'2*3 = ⟦결과⟧0⟦결과⟧'); const saved=JSON.parse(JSON.stringify(state.project)); click('[data-quantity-apply]'); const applied=state.project.tables.EstMainDat[0].GN; const sheet=state.project.quantitySheets[0]; const computed=EstQuantity.calculate(sheet); click('[data-quantity-delete-row]',{quantityDeleteRow:'0'}); const empty=[sheet.rows.length,sheet.items.length]; click('[data-quantity-delete-sheet]'); return {saved,applied,computed,empty,remaining:state.project.quantitySheets.length,selection:state.quantitySheet}; })()""", ) assert result["saved"]["quantitySheets"][0]["Name"] == "Earthwork" assert result["saved"]["quantitySheets"][0]["items"][0]["Name"] == "excavation" assert result["computed"]["errors"] == [] assert result["computed"]["items"][0]["GN"] == 6 assert result["applied"] == 6 assert result["empty"] == [0, 0] assert result["remaining"] == 0 assert result["selection"] == "manual" def test_insert_and_move_quantity_rows_keeps_item_results_linked(): result = run( "math", """(()=>{ const handlers={};let id=0; globalThis.document={addEventListener:(name,fn)=>handlers[name]=fn}; globalThis.crypto={randomUUID:()=>String(++id)}; const state={quantitySheet:'manual',project:{quantitySheets:[],tables:{EstMainDat:[]}}}; globalThis.EstApp={state,change:fn=>fn(),render:()=>{}};EstQuantity.install(); const click=(selector,dataset={})=>handlers.click({target:{closest:s=>s===selector?{dataset}:null}}); click('[data-quantity-new]');click('[data-quantity-add]');click('[data-quantity-add]'); const sheet=state.project.quantitySheets[0]; sheet.rows[0].LineData='1+2 = \\x120\\x12';sheet.rows[1].LineData='4+5 = \\x120\\x12'; click('[data-quantity-add]',{index:'0'}); sheet.rows[1].LineData='6+7 = \\x120\\x12'; const before=EstQuantity.calculate(sheet).items.map(x=>x.GN); click('[data-quantity-move]',{index:'2',quantityMove:'-1'}); const after=EstQuantity.calculate(sheet).items.map(x=>x.GN); const order=sheet.rows.map(x=>x.LineData); sheet.rows[0].LineData='A=2 = \\x120\\x12';sheet.rows[1].LineData='A*3 = \\x120\\x12'; const linked=EstQuantity.calculate(sheet); click('[data-quantity-move]',{index:'1',quantityMove:'-1'}); const forward=EstQuantity.calculate(sheet); return {before,after,order,linked,forward}; })()""", ) assert result["before"] == result["after"] == [3, 9, 13] assert result["order"][1].startswith("4+5") assert result["linked"]["errors"] == [] assert result["linked"]["items"][1]["GN"] == 6 assert result["forward"]["errors"] def test_manual_and_selected_sheet_totals_validate_before_application(): result = run( "math", """(()=>{ const p=EstNative.empty(); p.tables.EstMainDat=[{Code:'X',Level:0,GN:1,MulCode:{binary:'YWJj'}}]; p.quantities=[{Code:'A',formula:'2',TargetCode:'X'}, {Code:'B',formula:'[A]*3',TargetCode:'X'}]; const manual=EstQuantity.totals(p,'manual');EstQuantity.applyTotals(p,manual); const a=structuredClone(p.tables.EstMainDat[0]); p.quantitySheets=[{Code:'Q',options:{},rows:[{LineData:'4+5 = \\x120\\x12',markers:['r1']}], items:[{Code:'r1',GN:0,TargetCode:'X'}]}]; const sheet=EstQuantity.totals(p,'Q');EstQuantity.applyTotals(p,sheet); let invalid='';p.quantities.push({Code:'C',formula:'1',TargetCode:'missing'}); try{EstQuantity.totals(p,'manual')}catch(e){invalid=e.message} return {a,b:p.tables.EstMainDat[0],invalid}; })()""", ) assert result["a"]["GN"] == 8 assert result["a"]["MulCode"] == {"binary": "YWJj"} assert result["b"]["GN"] == 9 assert result["b"]["MulCode"] == {"binary": "YWJj"} assert result["invalid"]