Files
Aislo/resources/tester/test_m02_df_formula_view.py
T

181 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""단가산출 흐름도 「식 보기」(`M02_DF_FormulaView.ts`) · 엑셀 「조건 만들기」 글 쪽(`spreadsheet_condition_text.ts`).
화면 TS 를 그 자리에서 트랜스파일해 node 로 돌림 — 상대 경로 TS(엔진 포함)는 이어 트랜스파일 · 화면 부품(@ui 의 elements · locale · modal)은 빈 흉내 · 엔진이 쓰는 @ui/sheet 는 그대로.
견본 함수 상자는 이 시험 안에 둠.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
VIEW = ROOT / "M02_MasterTemplete" / "DataFlow" / "M02_DF_FormulaView.ts"
TEXT = ROOT / "A00_Common" / "spreadsheet" / "spreadsheet_condition_text.ts"
RUNNER = r"""
const fs = require("fs");
const path = require("path");
const [root, viewFile, textFile] = process.argv.slice(1);
const ts = require(path.join(root, "config", "node_modules", "typescript"));
const stub = new Proxy({}, { get: (_t, k) => (k === "currentLanguageIndex" ? 0 : () => undefined) });
const cache = {};
function load(f) {
if (cache[f]) return cache[f].exports;
const js = ts.transpileModule(fs.readFileSync(f, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = (cache[f] = { exports: {} });
const req = (id) => {
if (id.startsWith(".") || id.startsWith("@ui/sheet/")) {
const p = id.startsWith("@ui/sheet/")
? path.join(root, "ui_template", "sheet", id.slice("@ui/sheet/".length))
: path.resolve(path.dirname(f), id);
return fs.existsSync(p + ".ts") ? load(p + ".ts") : stub;
}
return stub;
};
new Function("exports", "module", "require", js)(box.exports, box, req);
return box.exports;
}
const v = load(viewFile);
const t = load(textFile);
const jobs = JSON.parse(fs.readFileSync(0, "utf8"));
const out = jobs.map((job) => {
if (job.op === "lines") {
const values = v.cellValuesOf(job.box, job.arrived);
return { values, lines: v.formulaLines(job.box, values) };
}
if (job.op === "toFormula") return t.draftToFormula(job.draft);
if (job.op === "toDraft") return t.formulaToDraft(job.formula);
return null;
});
process.stdout.write(JSON.stringify(out));
"""
def run(jobs: list[dict]) -> list:
if shutil.which("node") is None:
pytest.skip("node 가 없음")
done = subprocess.run(
["node", "-e", RUNNER, str(ROOT), str(VIEW), str(TEXT)],
input=json.dumps(jobs, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(done.stdout)
def func_box(cells: dict, inputs: list, variables: list, out_cell: str, out_name: str) -> dict:
return {
"id": "f1",
"kind": "function",
"name": "석공 품",
"color": "",
"unit": "인",
"x": 0,
"y": 0,
"func": {
"sheet": {"id": "s1", "이름": "석공 품", "칸": cells},
"inputs": inputs,
"variables": variables,
"output": {"cell": out_cell, "name": out_name, "unit": "인"},
},
}
def var(name: str, cell: str, example) -> dict:
return {"name": name, "type": "number", "unit": "-", "example": example, "cell": cell}
# 석공량(받는 알약 B1) · 높이(변수 B2) → B3 = 석공량 × (1 + 높이 ÷ 100)
PLAIN = func_box(
{
"A1": {"값": "석공량"},
"B3": {"식": "B1*(1+B2/100)"},
},
[{"port": "석공량", "cell": "B1"}],
[var("높이", "B2", 4)],
"B3",
"석공 인",
)
def test_이름_식과_값_식():
(got,) = run([{"op": "lines", "box": PLAIN, "arrived": {"석공량": 0.15}}])
assert got["values"]["B3"] == pytest.approx(0.156)
(line,) = got["lines"]
assert line["name"] == "석공 인"
assert line["named"] == "석공량 × (1 + 높이 ÷ 100)"
assert line["valued"] == "0.15 × (1 + 4 ÷ 100) = 0.156"
assert "branches" not in line
def test_중간_칸이_먼저_결과_칸이_마지막():
box = func_box(
{"B3": {"식": "B1*B2"}, "B4": {"식": "B3+1"}},
[{"port": "석공량", "cell": "B1"}],
[var("높이", "B2", 4)],
"B4",
"합",
)
(got,) = run([{"op": "lines", "box": box, "arrived": {"석공량": 2}}])
assert [x["name"] for x in got["lines"]] == ["B3", "합"] # 연결 없는 칸은 주소 그대로
assert got["lines"][1]["named"] == "B3 + 1"
assert got["lines"][1]["valued"] == "8 + 1 = 9"
@pytest.mark.parametrize(
("ground", "now"),
[("토사", [True, False]), ("암", [False, True])],
)
def test_IF_줄과_지금_갈래(ground, now):
box = func_box(
{"B3": {"식": 'IF(B4="토사",B1*2,B1*3)'}},
[{"port": "석공량", "cell": "B1"}],
[{"name": "지반", "type": "choice", "unit": "-", "example": ground, "cell": "B4"}],
"B3",
"결과",
)
(got,) = run([{"op": "lines", "box": box, "arrived": {"석공량": 10}}])
(line,) = got["lines"]
assert [b["now"] for b in line["branches"]] == now
assert line["branches"][0]["when"] == '지반 = "토사"'
assert line["branches"][0]["then"] == "석공량 × 2"
assert line["branches"][1] == {"when": "아니면", "then": "석공량 × 3", "now": now[1]}
assert line["valued"].endswith("= 20" if ground == "토사" else "= 30")
def test_조건_만들기_식과_다시_줄():
draft = {
"branches": [
{
"left": "B1",
"op": "=",
"right": {"kind": "value", "value": "토사"},
"then": {"kind": "cell", "cell": "C1"},
},
{
"left": "B1",
"op": "≥",
"right": {"kind": "value", "value": 3},
"then": {"kind": "value", "value": 0.5},
},
],
"otherwise": {"kind": "value", "value": 0},
}
(formula,) = run([{"op": "toFormula", "draft": draft}])
assert formula == 'IF(B1="토사",C1,IF(B1>=3,0.5,0))'
(back,) = run([{"op": "toDraft", "formula": formula}])
assert back == draft
@pytest.mark.parametrize("formula", ["SUM(B1:B2)", "IF(B1+1>2,1,2)", "IF(B1>2,B2*2,0)"])
def test_못_바꾸는_식은_null(formula):
(back,) = run([{"op": "toDraft", "formula": formula}])
assert back is None