Files
Aislo/resources/tester/helper_150_detail_pick.cjs
T
eomsangdonandClaude Sonnet 5.5 db83a06a6b test(508-1b 옛 헬퍼 공용 불러오기 1/7): 헬퍼 26 곳 loadTs 베낌 걷고 makeLoader 로 — 공용 불러오기에 옵션 넷(requireFor · interop · cache · fileStub) 늘림
- helper_ts_stub_loader: 옛 헬퍼가 저마다 다르게 쓰던 것(풀이 함수 직접 · esModuleInterop · 캐시 끔 · 파일 통째 대역)을 옵션으로 · 기본 풀이는 그대로
- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침
- 확인: 이 묶음 헬퍼를 쓰는 시험 241 개 전 241 통과 → 후 241 통과 (비교 = 시험별 junit)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 17:06:06 +09:00

145 lines
5.1 KiB
JavaScript
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.
/* PLAN 150-4 단위검증 헬퍼 — [상세] 수량표 칸 → 그림 복제 강조(`B06_Section_UI_Cross_Detail_Pick.ts`).
* 화면 TS 를 그 자리에서 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌. 짝 = `test_150_detail_pick.py`.
*/
const fs = require("fs");
const path = require("path");
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
const ROOT = path.join(__dirname, "..", "..");
// 조정창 공용 부품(Design_Pick → Cross_Panel_Base · 151-3)이 불러올 때 문서에 귀를 닮
globalThis.document = { addEventListener: () => {}, removeEventListener: () => {} };
const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base);
const { loadTs } = makeLoader({
root: ROOT,
requireFor: (full) => (request) => {
if (request.endsWith(".css")) return {};
if (request.startsWith("@util/"))
return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6))));
return loadTs(resolve(path.join(path.dirname(full), request)));
},
});
const pick = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Detail_Pick.ts"));
// ── 그림 복제 흉내 — 평평한 요소 목록 · 「tag.class」 · 「.class」 · 「g.class line」 · 「:not(.class)」 만 ──
function node(tag, classes, attrs = {}, parent = null) {
const set = new Set(classes);
return {
tag,
parent,
set,
getAttribute: (name) => attrs[name] ?? null,
classList: {
add: (c) => set.add(c),
remove: (c) => set.delete(c),
contains: (c) => set.has(c),
},
};
}
const one = (el, part) => {
const nots = [...part.matchAll(/:not\(\.([\w-]+)\)/g)].map((m) => m[1]); // 370-3 암 판정 선 뺌
const [tag, ...cls] = part.replace(/:not\([^)]*\)/g, "").split(".");
return (
(!tag || el.tag === tag) && cls.every((c) => el.set.has(c)) && !nots.some((c) => el.set.has(c))
);
};
const matches = (el, selector) => {
const parts = selector.split(" ");
if (!one(el, parts.at(-1))) return false;
return parts.length === 1 || (el.parent !== null && one(el.parent, parts[0]));
};
function fakeSvg(ppm, x0) {
const center = node("g", ["b06-chart__center"]);
const ground = node("g", ["b06-chart__ground-center"]); // 원지반 십자(413)
const els = [
ground,
center,
node("line", [], { x1: String(x0) }, center),
node("g", ["b06-chart__area", "b06-chart__area--cut_soil"]),
node("g", ["b06-chart__area", "b06-chart__area--cut_rock", "b06-chart__area--cut_rr"]),
node("g", ["b06-chart__area", "b06-chart__area--fill", "is-active"]), // 카드에서 묻어 온 강조
node("polyline", ["b06-chart__spoil-fill"]),
];
// 판정 선: offset 구간(m) → 화면 x = x0 − offset × ppm
const hit = (a, b) =>
node("polyline", ["b06-chart__design-hit"], {
points: `${x0 - a * ppm},10 ${x0 - b * ppm},20`,
});
const hits = {
road: hit(-2, 2),
ditch: hit(2.5, 3.0),
cutSlope: hit(3.0, 6.0),
fillSlope: hit(-2.5, -6.0),
};
els.push(...Object.values(hits));
return {
els,
hits,
getAttribute: (name) => (name === "data-px-per-m" ? String(ppm) : null),
querySelectorAll: (sel) =>
els.filter((el) => sel.split(",").some((s) => matches(el, s.trim()))),
querySelector: (sel) => els.find((el) => matches(el, sel)) ?? null,
};
}
const design = {
road_edges: {
left: { offset_m: 2.5, elevation_m: 99.95 },
right: { offset_m: -2.5, elevation_m: 99.95 },
},
ditch: { type: "standard", top_width_m: 0.5, bottom_width_m: 0.2, depth_m: 0.3 },
ditch_side: "left",
ditch_enabled: true,
section_mode: "left_cut",
};
const activeOf = (svg) => {
const names = [];
for (const [name, el] of Object.entries(svg.hits)) if (el.set.has("is-active")) names.push(name);
for (const el of svg.els)
if (el.set.has("is-active") && !el.set.has("b06-chart__design-hit"))
names.push(
[...el.set].find(
(c) =>
c.includes("--") ||
c === "b06-chart__center" ||
c === "b06-chart__ground-center" ||
c === "b06-chart__spoil-fill",
),
);
return names;
};
const out = { targets: Object.keys(pick.DETAIL_TARGETS), zone: {}, active: {} };
out.zone = {
road: pick.lineZone(design, 0),
ditch: pick.lineZone(design, 2.8),
cutSlope: pick.lineZone(design, 4),
fillSlope: pick.lineZone(design, -4),
};
for (const key of [
null,
"cut_soil",
"cut_rock",
"embankment",
"spoil_fill",
"ground",
"planned",
"cut",
"ditch_soil",
"grading_cut",
"grading_fill",
"road_compaction",
"fence",
]) {
const svg = fakeSvg(20, 300);
pick.highlightDetail(svg, design, key);
out.active[String(key)] = activeOf(svg);
}
// 164-2 — 그 카드에 대상이 있는 키(암석 띠 · 사토장 없는 카드 = 그 칸 빠짐)
out.present = [...pick.presentKeys(fakeSvg(20, 300), design)].sort();
const bare = fakeSvg(20, 300);
const kept = bare.els.filter(
(el) => !el.set.has("b06-chart__area--cut_rock") && !el.set.has("b06-chart__spoil-fill"),
);
bare.els.splice(0, bare.els.length, ...kept);
out.presentBare = [...pick.presentKeys(bare, design)].sort();
process.stdout.write(JSON.stringify(out));