- 등록 모달 [+ 줄] · 같은 이름 합침 없앰 · 기준위치 = 항목 · 목적지 없음 · 「고침」 → 「수정」 - 다이어그램 · 표 폭 전체 · 틀 높이 고정 · 틀 비율 그림 범위 · 처음 배율 = 상자 범위 맞춤 - 카카오 지도 높이 어디서나 480px - 도로 구분 = 선 자리(거래처 → 기준점 2차포장 · 기준점 → 시점 1차포장 · 그 밖 포장 · 비포장만 고름) · 파이썬 · TS 짝 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
84 lines
3.7 KiB
Python
84 lines
3.7 KiB
Python
"""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
|