feat(profile): 종단 알약 글 — 기타 = 손 이름 머리 + 번호 · 빈 이름 = 기타 N(54-9)
- 기타 알약 = 손 이름 앞 3글자(빈칸 뺌 · 다른 손 이름과 머리가 같으면 갈릴 때까지 늘림) + 목록과 같은 번호 - 빈 이름 = 「기타 N」 · 다른 종류 알약 · 툴팁 · 목록 이름 그대로 - structurePillTexts 한 곳이 B05 · B06 알약을 함께 그림 · 시험 test_54_9_etc_pill Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPcuokNm1ZR2kqgGGDfaJA
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/* 54-9 종단 알약 글 단위검증 헬퍼 — TS 를 그 자리에서 트랜스파일해 Node 로 돌린다.
|
||||
* 결과는 표준출력에 JSON 한 벌. 화면 그릇은 쓰지 않는다(세션 저장소만 흉내).
|
||||
*
|
||||
* ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_structures_form_summary.cjs` 머리와 같다.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
|
||||
const session = new Map();
|
||||
const fakeStorage = {
|
||||
getItem: (k) => session.get(k) ?? null,
|
||||
setItem: (k, v) => session.set(k, String(v)),
|
||||
removeItem: (k) => session.delete(k),
|
||||
};
|
||||
globalThis.window = { sessionStorage: fakeStorage, localStorage: fakeStorage };
|
||||
globalThis.sessionStorage = fakeStorage;
|
||||
globalThis.localStorage = fakeStorage;
|
||||
|
||||
const STUBS = {
|
||||
"@config/config_frontend": { API_BASE_URL: "/api", API_TIMEOUT_MS: 1000 },
|
||||
};
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
if (request.startsWith("@")) return new Proxy({}, { get: () => () => {} });
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
cache.set(full, box.exports);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const marks = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structures_Marks.ts"));
|
||||
|
||||
const typeMap = new Map([
|
||||
["etc", { type_id: "etc", name: "기타", style: { abbr: "기타" }, options: [] }],
|
||||
[
|
||||
"retaining_wall",
|
||||
{ type_id: "retaining_wall", name: "옹벽", style: { abbr: "옹" }, options: [] },
|
||||
],
|
||||
]);
|
||||
const etc = (id, chainage, name) => ({
|
||||
structure_id: id,
|
||||
type_id: "etc",
|
||||
placement: "point",
|
||||
chainage_m: chainage,
|
||||
options: name === undefined ? {} : { name },
|
||||
});
|
||||
const wall = (id, chainage) => ({
|
||||
structure_id: id,
|
||||
type_id: "retaining_wall",
|
||||
placement: "point",
|
||||
chainage_m: chainage,
|
||||
options: {},
|
||||
});
|
||||
const run = (structures) =>
|
||||
Object.fromEntries(
|
||||
[...marks.structurePillTexts(structures, typeMap)].map(([id, text]) => [id, text]),
|
||||
);
|
||||
|
||||
const out = {
|
||||
// 53-2 시나리오 — 같은 이름 둘 · 다른 이름 · 빈 이름 둘(빈칸 이름 = 빈 이름)
|
||||
scenario: run([
|
||||
etc("e1", 40, "배수로정비"),
|
||||
etc("e2", 60, "울타리"),
|
||||
etc("e3", 80, ""),
|
||||
etc("e4", 100, "배수로정비"),
|
||||
etc("e5", 120, " "),
|
||||
]),
|
||||
// 머리가 같은 다른 이름 — 갈릴 때까지 늘림 · 이름이 남의 머리면 그 이름 그대로
|
||||
prefix: run([
|
||||
etc("p1", 10, "콘크리트 블록"),
|
||||
etc("p2", 20, "콘크리트 옹벽"),
|
||||
etc("p3", 30, "배수로"),
|
||||
etc("p4", 40, "배수로공"),
|
||||
etc("p5", 50, "기타 시설"),
|
||||
etc("p6", 60),
|
||||
]),
|
||||
// 다른 종류 규칙은 그대로 — 약호 + 중복 번호 · 기타 하나 빈 이름 = 「기타」
|
||||
others: run([wall("w1", 10), wall("w2", 30), etc("x1", 20, "가로등"), etc("x2", 40)]),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""54-9 종단 알약 글 — 기타가 여럿이어도 알약끼리 구별됨.
|
||||
|
||||
• 손 이름 앞 3글자 · 같은 이름이면 목록과 같은 번호 · 빈 이름 = 「기타 N」
|
||||
• 머리가 같은 다른 이름은 갈릴 때까지 늘림
|
||||
• 다른 종류 알약(약호 + 중복 번호) · 툴팁 · 목록 이름(손 이름 그대로)은 그대로
|
||||
화면 그릇 없이 Node 헬퍼가 TS 를 트랜스파일해 돌린다(`helper_54_9_etc_pill.cjs`).
|
||||
B05 · B06 은 같은 알약 레인(`buildStructureLane`)을 씀 — 실제 화면은 ORCA 로 따로 본다.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out():
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_54_9_etc_pill.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_53_2_기타_다섯_알약이_서로_다르다(out):
|
||||
pills = {key: value["pill"] for key, value in out["scenario"].items()}
|
||||
assert pills == {
|
||||
"e1": "배수로 1",
|
||||
"e2": "울타리",
|
||||
"e3": "기타 1",
|
||||
"e4": "배수로 2",
|
||||
"e5": "기타 2",
|
||||
}
|
||||
assert len(set(pills.values())) == 5
|
||||
|
||||
|
||||
def test_툴팁_이름은_손_이름_그대로(out):
|
||||
names = [value["name"] for value in out["scenario"].values()]
|
||||
assert names == ["배수로정비 1", "울타리", "기타 1", "배수로정비 2", "기타 2"]
|
||||
|
||||
|
||||
def test_머리가_같은_다른_이름은_갈릴_때까지_늘린다(out):
|
||||
pills = {key: value["pill"] for key, value in out["prefix"].items()}
|
||||
assert pills == {
|
||||
"p1": "콘크리트블",
|
||||
"p2": "콘크리트옹",
|
||||
"p3": "배수로",
|
||||
"p4": "배수로공",
|
||||
"p5": "기타시",
|
||||
"p6": "기타",
|
||||
}
|
||||
|
||||
|
||||
def test_다른_종류_알약은_약호와_번호_그대로(out):
|
||||
pills = {key: value["pill"] for key, value in out["others"].items()}
|
||||
assert pills == {"w1": "옹 1", "w2": "옹 2", "x1": "가로등", "x2": "기타"}
|
||||
|
||||
|
||||
def test_알약_레인이_글을_한_곳에서_받는다():
|
||||
with open(
|
||||
os.path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structures_Marks.ts"), encoding="utf-8"
|
||||
) as handle:
|
||||
text = handle.read()
|
||||
assert "mark.textContent = pill;" in text
|
||||
assert text.count("structurePillTexts(") == 2 # 정의 하나 · 레인 하나
|
||||
Reference in New Issue
Block a user