"""244-14 B08 거리이정표 [+] 모달 「항목」 = 손 입력 + 제안 목록(사용자 120-답 ③ 「항목: 수기 입력」). 거래처 제안 = 견본 다섯 · 기준위치 제안 = 기준점 · 목록 밖 글도 받음 · 분류 바꾸면 제안만 바뀜(쓴 글 그대로) — node 로 실제 rowField 를 돌림(가짜 DOM). """ import json import re import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] MODAL = ROOT / "B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts" IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE) STUBS = """ type N = any; const el = (tag: string, o: any = {}): N => { const n: N = { tag, children: [] as N[], attrs: { ...(o.attrs ?? {}) }, dataset: {}, style: {}, value: "", placeholder: "", get id() { return n.attrs.id; }, setAttribute(k: string, v: string) { n.attrs[k] = v; }, append(...c: N[]) { n.children.push(...c); }, replaceChildren(...c: N[]) { n.children = c; } }; return n; }; const createInputField = (o: any) => { const input = el("input"); return { root: el("div", { children: [] }), input, label: o.label, setError: () => undefined }; }; const createSelectField = (o: any) => { const select = el("select"); return { root: el("div"), select, setOptions: () => undefined }; }; """ SCRIPT = """ const sug = (f: any) => f.item.root.children.find((c: N) => c.tag === "datalist") .children.map((c: N) => c.attrs.value); const a = rowField({ item: "", dest: "start", note: "" }, "route"); const empty = { value: a.item.input.value, list: sug(a), ph: a.item.input.placeholder, linked: a.item.input.attrs.list === a.item.root.children[0].id }; a.item.input.value = " 레미콘(25-21-150) "; // 목록 밖 손 글 a.setKind("base"); const base = { value: a.item.input.value, list: sug(a), ph: a.item.input.placeholder }; a.setKind("route"); const old = rowField({ item: "옛 항목", dest: "mid", note: "" }, "route"); const b = rowField({ item: "", dest: "start", note: "" }, "route"); console.log(JSON.stringify({ empty, base, back: a.item.input.value, list: sug(a), old: [old.item.input.value, old.dest.select.value, sug(old)], ids: new Set([a, old, b].map((f) => f.item.input.attrs.list)).size, tag: a.item.input.dataset.b08MilestoneAdd })); """ def test_항목_손_입력_제안_분류_바꿔도_글_유지(): src = STUBS + IMPORTS.sub("", MODAL.read_text(encoding="utf-8")) result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript"], input=src + "\n" + SCRIPT, capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert result.returncode == 0, result.stderr got = json.loads(result.stdout) five = ["장비(20톤)", "장비(8톤)", "혼합골재", "전석운반", "철근"] # 빈 줄 = 빈 손 입력 칸 + 거래처 제안 다섯(datalist 이어짐) assert got["empty"] == {"value": "", "list": five, "ph": "예 장비(20톤)", "linked": True} # 분류 → 기준위치 = 제안만 기준점 · 쓴 글 그대로 assert got["base"] == { "value": " 레미콘(25-21-150) ", "list": ["기준점"], "ph": "예 기준점", } assert got["back"] == " 레미콘(25-21-150) " and got["list"] == five # 옛 항목 글 = 그대로 칸에 · 목적지 그대로 assert got["old"] == ["옛 항목", "mid", five] assert got["ids"] == 3 and got["tag"] == "item" # 줄마다 제안 목록 따로 def test_저장_값_손_글_그대로(): modal = MODAL.read_text(encoding="utf-8") assert "item: first.item.input.value.trim()," in modal # 목록 밖 글도 받음(앞뒤 빈칸만 뺌) assert 'createSelectField({ label: "항목"' not in modal assert len(modal.splitlines()) <= 700