"""EST 테스트 앱 엑셀형 수량산출서 계산·편집(원본 TFRMMULEXCEL, .xls BIFF 수식).""" 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") def node(script: str, *args: str): return json.loads( subprocess.check_output(["node", "-e", script, *args], cwd=ROOT).decode("utf-8") ) def test_evaluate_and_directional_sum(): out = node(""" const X = require(process.cwd() + '/tools/est-test/native-excel-sheet.cjs'); const cells = { '0,0': { value: '품명' }, '1,0': { value: 2 }, '2,0': { value: 3 }, '3,0': {}, '1,1': { value: 0, formula: '(A2+A3)/2*4' }, '1,2': { value: 0, formula: 'SUM(A2:B2)' }, '4,4': { value: 0, formula: 'ROUND(10/3,2)+ROUNDUP(1.21,1)+ROUNDDOWN(1.29,1)' }, '5,5': { value: 0, formula: 'F6+1' } }; const errors = X.recalc(cells); const down = X.sumFormula(cells, 3, 0, 'up'); let none = ''; try { X.sumFormula({ '0,0': { value: 'x' } }, 1, 0, 'up'); } catch (e) { none = e.message; } process.stdout.write(JSON.stringify({ b2: cells['1,1'].value, c2: cells['1,2'].value, e5: cells['4,4'].value, cycle: [cells['5,5'].value, errors.length], down, none })); """) assert out["b2"] == 10 and out["c2"] == 12 assert out["e5"] == 3.33 + 1.3 + 1.2 assert out["cycle"] == ["#오류", 1] assert out["down"] == "SUM(A2:A3)" assert out["none"] == "더할 셀이 없습니다." def test_real_sheets_recompute_and_link_items(): if not MAIN.exists(): pytest.skip("installed EST main sample unavailable") out = node( """ const fs = require('fs'), vm = require('vm'); const imp = require(process.cwd() + '/tools/est-test/import-estx.cjs'), ex = require(process.cwd() + '/tools/est-test/export-estx.cjs'); const p = imp.importEstx(fs.readFileSync(process.argv[1])); const s = p.quantitySheets.find((x) => x.Code === 'N0000000136'); const formulas = p.quantitySheets.filter((x) => x.excel).flatMap((x) => Object.values(x.excel[0].cells)).filter((c) => c.formula); const same = Buffer.compare(ex.exportEstx(p), fs.readFileSync(process.argv[1])) === 0; const ctx = vm.createContext({ structuredClone }); for (const f of ['native-math.js', 'native-formula.js', 'native-text.js', 'native-excel-sheet.cjs', 'native-quantity.cjs']) vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, { filename: f }); ctx.s = s; const before = vm.runInContext('EstQuantity.calculate(s).items.map((i) => i.GN)', ctx); vm.runInContext("s.excel[0].cells['6,2'].value = 40; EstExcelSheet.recalc(s.excel[0].cells)", ctx); const after = vm.runInContext('EstQuantity.calculate(s).items.map((i) => i.GN)', ctx); s.excelEdited = true; let message = ''; try { ex.exportEstx(p); } catch (e) { message = e.message; } process.stdout.write(JSON.stringify({ decoded: formulas.every((c) => typeof c.formula === 'string'), count: formulas.length, links: Object.fromEntries(Object.entries(s.excelLinks).map(([k, l]) => [k, [l.row, l.col, l.inferred]])), before, after, same, message })); """, str(MAIN), ) assert out["decoded"] is True and out["count"] == 221 assert out["links"] == { "V000001": [32, 4, True], "V000002": [32, 6, True], "V000003": [32, 8, True], } assert out["before"][:3] == [17, 12, 58] assert out["after"][:3] != out["before"][:3] assert out["same"] is True assert out["message"].startswith("엑셀형 수량산출서 편집은 ESTX") def test_edit_rows_columns_clipboard_merge_and_style(): out = node(r""" const S = require(process.cwd() + '/tools/est-test/native-excel-sheet.cjs'); const E = require(process.cwd() + '/tools/est-test/native-excel-edit.cjs'); const s = { cells: { '0,0': { value: 1 }, '1,0': { value: 2 }, '2,0': { value: 3 }, '3,0': { value: 0, formula: 'SUM(A1:A3)' }, '3,1': { value: 0, formula: 'A2*$A$1+LOG10(2)' } } }; S.recalc(s.cells); const links = { X: { n: 0, row: 3, col: 0 } }; const r = {}; E.shift(s, 'row', 1, 1, links); r.insert = [s.cells['4,0'].formula, s.cells['4,0'].value, s.cells['4,1'].formula, links.X.row]; E.shift(s, 'row', 2, -1, links); r.remove = [s.cells['3,0'].formula, s.cells['3,0'].value, s.cells['3,1'].formula, s.cells['3,1'].value]; E.shift(s, 'col', 0, 1, links); r.col = [s.cells['3,1'].formula, links.X.col]; const clip = E.copyRange(s, { r1: 3, c1: 1, r2: 3, c2: 1 }); E.pasteRange(s, clip, 5, 3); r.paste = [s.cells['5,3'].formula, E.offsetFormula('SUM(A1:B2)+$C$3+A$1', 1, 1)]; E.merge(s, { r1: 0, c1: 0, r2: 1, c2: 2 }); E.merge(s, { r1: 1, c1: 1, r2: 2, c2: 2 }); r.merge = s.merges; E.shift(s, 'row', 0, -1, {}); r.mergeShift = s.merges; E.split(s, { r1: 1, c1: 1, r2: 1, c2: 1 }); r.split = s.merges.length; E.style(s, { r1: 0, c1: 0, r2: 0, c2: 1 }, 'b'); E.style(s, { r1: 0, c1: 0, r2: 0, c2: 0 }, 'diag', 'left'); r.style = [s.cells['0,0'].style, s.cells['0,1'].style]; E.style(s, { r1: 0, c1: 0, r2: 0, c2: 1 }, 'b'); r.unstyle = [s.cells['0,0'].style, s.cells['0,1'].style ?? null]; E.clearRange(s, { r1: 0, c1: 0, r2: 0, c2: 0 }, true); r.clear = s.cells['0,0']; process.stdout.write(JSON.stringify(r)); """) assert out["insert"] == ["SUM(A1:A4)", 6, "A3*$A$1+LOG10(2)", 4] assert out["remove"] == ["SUM(A1:A3)", 4, "#REF!*$A$1+LOG10(2)", "#오류"] assert out["col"] == ["SUM(B1:B3)", 1] assert out["paste"] == ["SUM(D3:D5)", "SUM(B2:C3)+$C$3+B$1"] assert out["merge"] == [{"r1": 1, "r2": 2, "c1": 1, "c2": 2}] assert out["mergeShift"] == [{"r1": 0, "r2": 1, "c1": 1, "c2": 2}] assert out["split"] == 0 assert out["style"] == [{"b": True, "diag": "left"}, {"b": True}] assert out["unstyle"] == [{"diag": "left"}, None] assert out["clear"] == {"value": "", "style": {"diag": "left"}} def test_real_sheets_read_merged_cells(): if not MAIN.exists(): pytest.skip("installed EST main sample unavailable") out = node( """ const { importEstx } = require(process.cwd() + '/tools/est-test/import-estx.cjs'); const p = importEstx(require('fs').readFileSync(process.argv[1])); const s = p.quantitySheets.find((x) => x.Code === 'N0000000136'); process.stdout.write(JSON.stringify({ count: s.excel[0].merges.length, first: s.excel[0].merges[0] })); """, str(MAIN), ) assert out["count"] == 14 assert out["first"] == {"r1": 2, "r2": 4, "c1": 1, "c2": 1}