"""EST 테스트 앱 수량산출서 복사·순서 이동과 ESTX 저장 거부.""" import json import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] MAIN = Path("C:/Dasansoft/Est Plus 2/Data/ProjectData/main.estx") LOAD = """ const fs = require('fs'), vm = require('vm'); const ctx = vm.createContext({ structuredClone, console }); vm.runInContext(fs.readFileSync('tools/est-test/native-quantity-sheets.js', 'utf8'), ctx); const run = (code) => vm.runInContext(code, ctx); """ def node(script: str, *args: str): out = subprocess.check_output(["node", "-e", LOAD + script, *args], cwd=ROOT) return json.loads(out.decode("utf-8")) def test_copy_inserts_below_with_new_code_and_cleared_links(): result = node(""" ctx.project = { quantitySheets: [ { Code: 'A', Name: '토공', items: [{ Code: 'r000000', TargetCode: 'D1', TargetKey: 'id:3', LinkCode: 'D1' }], rows: [{ LineData: 'x', markers: ['r000000'] }] }, { Code: 'B', Name: '포장', items: [], rows: [] }, ] }; const first = run("EstQuantitySheets.copy(project, 'A', 'u1')"); const second = run("EstQuantitySheets.copy(project, 'A', 'u1')"); const p = ctx.project; process.stdout.write(JSON.stringify({ first, second, order: p.quantitySheets.map((s) => s.Code), name: p.quantitySheets[1].Name, link: p.quantitySheets[1].items[0], original: p.quantitySheets[0].items[0].TargetCode, rows: p.quantitySheets[1].rows[0].markers, })); """) assert result == { "first": "Qu1", "second": "Qu1_2", "order": ["A", "Qu1_2", "Qu1", "B"], "name": "토공 복사본", "link": {"Code": "r000000", "TargetCode": "", "TargetKey": ""}, "original": "D1", "rows": ["r000000"], } def test_move_swaps_and_stops_at_edges(): result = node(""" ctx.project = { quantitySheets: [{ Code: 'A' }, { Code: 'B' }, { Code: 'C' }] }; const out = [run("EstQuantitySheets.move(project, 'B', -1)"), run("EstQuantitySheets.move(project, 'B', -1)"), run("EstQuantitySheets.move(project, 'C', 1)")]; process.stdout.write(JSON.stringify({ out, order: ctx.project.quantitySheets.map((s) => s.Code) })); """) assert result == {"out": [True, False, False], "order": ["B", "A", "C"]} def test_estx_export_refuses_reordered_sheets(): if not MAIN.exists(): pytest.skip("installed EST main sample unavailable") result = node( """ const imp = require('./tools/est-test/import-estx.cjs'), ex = require('./tools/est-test/export-estx.cjs'); ctx.project = imp.importEstx(fs.readFileSync(process.argv[1])); run("EstQuantitySheets.move(project, project.quantitySheets[0].Code, 1)"); let message = ''; try { ex.exportEstx(ctx.project); } catch (e) { message = e.message; } process.stdout.write(JSON.stringify(message)); """, str(MAIN), ) assert result.startswith("수량산출서 순서 변경은 ESTX") MARK = r"""(()=>{ const sheet={Code:'N1',options:{digits:2,roundMode:2},items:[{Code:'r000000',Name:'가'}], rows:[{LineData:' \x0e잔토\x0e \x130.15\x13 2×3 = 6 \x15㎥\x15 그리고 \x125\x12',markers:['r000000']}]}; const shown=EstText.display(sheet.rows[0].LineData); const at=shown.indexOf('6 '); const out=[]; const t=(fn)=>{try{fn();out.push('ok');}catch(e){out.push(e.message.slice(0,6));}}; t(()=>EstQuantitySheets.mark(sheet,0,at,at+1,'r000001')); const item=sheet.items.find(i=>i.Code==='r000001'); const markers=[...sheet.rows[0].markers]; const calc=EstQuantity.calculate(sheet).items.map(i=>[i.Code,i.GN]); t(()=>EstQuantitySheets.mark(sheet,0,at+4,at+5,'r000002')); t(()=>EstQuantitySheets.mark(sheet,0,0,3,'r000002')); const inside=EstText.display(sheet.rows[0].LineData).indexOf('⟦결과⟧6')+5; t(()=>EstQuantitySheets.free(sheet,0,inside)); const after=EstText.display(sheet.rows[0].LineData); sheet.items[0].TargetCode='D1'; const last=after.lastIndexOf('⟦결과⟧')-1; t(()=>EstQuantitySheets.free(sheet,0,last)); return {out,markers,item:[item.Name,item.Size,item.Unite],calc, back:after===shown,items:sheet.items.map(i=>i.Code)}; })()""" def test_mark_and_free_quantity_in_existing_line(): files = ["native-math.js", "native-formula.js", "native-text.js", "native-quantity.cjs"] result = node( f""" for (const f of {json.dumps(files)}) vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, {{ filename: f }}); process.stdout.write(JSON.stringify(run({json.dumps(MARK)}))); """ ) assert result["markers"] == ["r000001", "r000000"] assert result["item"] == ["잔토", "0.15", "㎥"] assert ["r000001", 6] in result["calc"] assert result["back"] is True assert result["items"] == ["r000000"] assert [m[:2] for m in result["out"]] == ["ok", "이미", "산출", "ok", "내역"] def test_estx_export_refuses_marker_edit(): if not MAIN.exists(): pytest.skip("installed EST main sample unavailable") files = ["native-math.js", "native-formula.js", "native-text.js", "native-quantity.cjs"] result = node( f""" for (const f of {json.dumps(files)}) vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, {{ filename: f }}); const imp = require('./tools/est-test/import-estx.cjs'), ex = require('./tools/est-test/export-estx.cjs'); ctx.project = imp.importEstx(fs.readFileSync(process.argv[1])); run(`(() => {{ for (const s of project.quantitySheets) for (let i = 0; i < s.rows.length; i++) {{ const code = s.rows[i].markers[0], item = s.items.find((x) => x.Code === code); if (!code || !item || item.TargetCode || item.TargetKey) continue; const t = EstText.display(s.rows[i].LineData); return EstQuantitySheets.free(s, i, t.indexOf('⟦결과⟧') + 4); }} }})()`); let message = ''; try {{ ex.exportEstx(ctx.project); }} catch (e) {{ message = e.message; }} process.stdout.write(JSON.stringify(message)); """, str(MAIN), ) assert result.startswith("산출 줄의 수량 지정·해제는 ESTX") def test_sheet_search_ignores_spaces_bars_and_brackets(): result = node(""" const sheet = { Code: 'N1', Name: '토 공(터파기)', items: [{ Name: '잔토|처리', Size: '0.7㎥' }] }; process.stdout.write(JSON.stringify([ run("EstQuantitySheets.matches(" + JSON.stringify(sheet) + ", '토공터파기')"), run("EstQuantitySheets.matches(" + JSON.stringify(sheet) + ", '잔토 처리')"), run("EstQuantitySheets.matches(" + JSON.stringify(sheet) + ", '포장')"), run("EstQuantitySheets.matches(" + JSON.stringify(sheet) + ", '')"), ])); """) assert result == [True, True, False, True]