- 공용 표 cellClass 옵션 하나(칸 class · 안 주면 같은 글) - B09: TabTable cellClass · choices → Shell 이 넘김 · 누른 칸 아래 B09 메뉴 한 벌(openMenuAt · 우클릭 메뉴와 공유) - 대비표: 적용 단가 칸 누름 = 값 있는 출처 · 견적 · 최저(자동) → pick(옛 수기 applied 지움) · 수기 적용 칸 · 출처 칸 돌림 없앰 · 적용 출처 단가 칸 is-picked 강조(테마 토큰) - 셈 · 저장 그대로(pick › applied › 최저 짝) - 시험: test_183_3_b09_applied_pick Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
114 lines
4.7 KiB
Python
114 lines
4.7 KiB
Python
"""183-3 자재단가대비표 적용 단가 — 칸 누름 고르기 메뉴 · 고른 출처 칸 강조 · 기본 최저.
|
||
|
||
- 공용 표 `cellClass` 옵션(칸 class · 안 주면 같은 글) — Node 로 `renderDataTableHtml`
|
||
- 고르기 항목 `appliedChoices`(값 있는 출처 · 견적 · 최저(자동) · ✓ 표시) — Node 로 함수만 떼어 돌림
|
||
- 배선: 수기 적용 칸 · 출처 칸 돌림 없앰 · Shell 이 cellClass · choices 를 넘김 · B09 메뉴 한 벌
|
||
셈 · 저장(pick › applied › 최저)은 그대로(test_144 · test_166_4).
|
||
"""
|
||
|
||
import json
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
TS = ROOT / "config" / "node_modules" / "typescript"
|
||
|
||
|
||
def _src(rel: str) -> str:
|
||
return (ROOT / rel).read_text(encoding="utf-8")
|
||
|
||
|
||
def _node(script: str):
|
||
out = subprocess.run(
|
||
["node", "-e", script], capture_output=True, text=True, encoding="utf-8", timeout=60
|
||
)
|
||
assert out.returncode == 0, out.stderr
|
||
return json.loads(out.stdout)
|
||
|
||
|
||
PRELUDE = f"""
|
||
const fs = require("fs");
|
||
const ts = require({json.dumps(str(TS))});
|
||
const js = (code) => ts.transpileModule(code, {{
|
||
compilerOptions: {{ module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }},
|
||
}}).outputText;
|
||
"""
|
||
|
||
|
||
def test_공용_표_cellClass_칸_class_안_주면_같은_글():
|
||
table = json.dumps(str(ROOT / "ui_template" / "ui_template_data_table.ts"))
|
||
got = _node(
|
||
PRELUDE
|
||
+ f"""
|
||
const box = {{ exports: {{}} }};
|
||
new Function("exports", "module", "require", js(fs.readFileSync({table}, "utf8")))(
|
||
box.exports, box, (p) => (p.endsWith(".css") ? {{}} : require(p)));
|
||
const m = box.exports;
|
||
const o = {{ columns: [{{ key: "a", label: "A" }}, {{ key: "b", label: "B" }}],
|
||
rows: [{{ a: 1, b: 2 }}, {{ a: 3, b: 4 }}] }};
|
||
const marked = m.renderDataTableHtml({{ ...o,
|
||
cellClass: (i, k) => (i === 0 && k === "b" ? "is-picked" : null) }});
|
||
console.log(JSON.stringify({{
|
||
plain: m.renderDataTableHtml(o),
|
||
none: m.renderDataTableHtml({{ ...o, cellClass: () => null }}),
|
||
count: marked.split("is-picked").length - 1,
|
||
row0: marked.split("<tbody>")[1].split("</tr>")[0],
|
||
}}));
|
||
"""
|
||
)
|
||
assert got["plain"] == got["none"]
|
||
assert got["count"] == 1 and '<td class="is-picked">2</td>' in got["row0"]
|
||
|
||
|
||
def test_고르기_항목_값_있는_출처_견적_최저_자동():
|
||
base = _src("B09_Estimation/B09_Estimation_UI_Tabs_Base.ts")
|
||
start = base.index("function appliedChoices")
|
||
body = base[start : base.index("\n}\n", start) + 3]
|
||
sources = _src("B09_Estimation/B09_Estimation_Calc_Base.ts")
|
||
at = sources.index("export const SOURCES")
|
||
line = sources[at : sources.index(";", at) + 1] + "\n"
|
||
got = _node(
|
||
PRELUDE
|
||
+ f"""
|
||
const appliedChoices = new Function(js({json.dumps(line.replace("export ", "") + body)}) + "; return appliedChoices;")();
|
||
const r = {{ prices: {{ 관급: null, 물가정보: 1200, 물가자료: 0, 유통물가: 1000, 견적: 1500 }},
|
||
lowest_from: "유통물가", applied_from: "유통물가", pick: null }};
|
||
const seen = [];
|
||
const items = appliedChoices(r, (s) => seen.push(s));
|
||
items.forEach((x) => Array.isArray(x) && x[1]());
|
||
const pick = {{ ...r, applied_from: "견적", pick: "견적" }};
|
||
console.log(JSON.stringify({{
|
||
labels: items.map((x) => (Array.isArray(x) ? x[0] : x)),
|
||
seen,
|
||
picked: appliedChoices(pick, () => 0).map((x) => (Array.isArray(x) ? x[0] : x)),
|
||
}}));
|
||
"""
|
||
)
|
||
assert got["labels"] == [
|
||
"물가정보 1,200",
|
||
"✓ 유통물가 1,000 · 최저",
|
||
"견적 1,500",
|
||
"-",
|
||
"✓ 최저(자동)",
|
||
]
|
||
assert got["seen"] == ["물가정보", "유통물가", "견적", None]
|
||
assert got["picked"][2] == "✓ 견적 1,500" and got["picked"][-1] == "최저(자동)"
|
||
|
||
|
||
def test_배선_수기_적용_칸_출처_돌림_없앰():
|
||
base = _src("B09_Estimation/B09_Estimation_UI_Tabs_Base.ts")
|
||
compare = base[base.index("export const buildCompare") : base.index("function usesOf")]
|
||
assert 'editable: (_i, key) => key === "p:견적",' in compare
|
||
assert "nextPick" not in base and 'key === "applied_from"' not in compare
|
||
assert 'key === `p:${rows[i]?.applied_from}` ? "is-picked"' in compare
|
||
assert 'if (key !== "applied" || !r) return null;' in compare
|
||
assert "delete cell.applied" in compare
|
||
shell = _src("B09_Estimation/B09_Estimation_UI_Shell.ts")
|
||
assert (
|
||
"cellClass: spec.cellClass," in shell and "openMenuAt(box.left, box.bottom, items)" in shell
|
||
)
|
||
edit = _src("B09_Estimation/B09_Estimation_UI_Tabs_Bill_Edit.ts")
|
||
assert "export function openMenuAt(" in edit and "const ctxMenu = sharedMenu();" in edit
|
||
css = _src("B09_Estimation/B09_Estimation_UI_Page.css")
|
||
assert "td.is-picked" in css and "var(--color-accent)" in css
|