Files
Aislo/resources/tester/helper_472_open_no_draft.mjs
T
eomsangdonandClaude Opus 5.5 913122ac4e refactor(B06): 카드 설계 줄 · 아래 띠 단추를 부품으로 — 분할 고르기 · 켜기 칩 · 단추 · 아이콘 단추 (PLAN 553-2)
- 측구 · 측구형식 · 경사 방향 묶음 = 부품 분할 고르기 20 · 토사 · 측구 · 단경사 켜고 끄기 = 켜기 칩 20 · [상세] = 단추(고스트) 20
- ⋯ 넘침 펼침 = 아이콘 단추(선) 22 꼴(펼침 = aria-pressed) · 암 경계 ▲▼↺ · 암 절토 ↺ = 아이콘 단추 22
- 지운 자체 CSS — b06-design__btn(--active) · 펼침 보라 채움 · rockb 단추 꼴 · 사이 금 · 글 0.72rem → --text-caption
- 부품 아이콘 단추의 display 가 hidden 을 덮어 선 칸 숨김 단추는 페이지에 숨김 한 줄
- 카드 높이 7 px 줄음(설계 줄 단추 24 → 20 · 지반 칩 24 → 20) · 그림 색 전후 같음(34 장면 × 102~120 선)
- 시험 헬퍼 둘 — helper_318 = 단추 줄 시험이 진짜 부품 파일을 흉내 노드로 실음 · helper_472 = 켜기 칩 글을 안쪽 span 에서 읽음
- Line_Cell · CutSlope 는 흉내 DOM 시험(370-3 등)이 @ui 를 안 풀어 부품 클래스 · data-size 계약으로 붙임

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:36:33 +09:00

199 lines
7.3 KiB
JavaScript

/* 472 덧(요구 272 C · D) 헬퍼 — 진짜 모듈(helper_472_open_no_draft_entry.ts)로
* ① 표준 횡단 패널 · 설정값 받기 = 세션 초안 칸 안 씀(같은 값이면 칸 없음) · 계산은 저장분
* ② 카드 단추 = 바꾼 칸만 보냄 · 세션 고름은 앞서 바꾼 칸에 얹음
* node helper_472_open_no_draft.mjs <묶음.mjs> <표준 횡단 고정 자료.json>
* 짝 = `test_472_open_no_draft.py` · 표준출력 JSON 한 벌.
*/
import { readFileSync } from "node:fs";
import { pathToFileURL } from "node:url";
const store = () => {
const m = new Map();
return {
getItem: (k) => (m.has(k) ? m.get(k) : null),
setItem: (k, v) => m.set(k, String(v)),
removeItem: (k) => m.delete(k),
key: (i) => [...m.keys()][i] ?? null,
get length() {
return m.size;
},
clear: () => m.clear(),
};
};
/** 작은 가짜 요소 — 자식 · 듣는 함수 · 글만 들고 나머지는 빈 함수. */
function element(tag) {
const base = {
tagName: String(tag).toUpperCase(),
children: [],
listeners: {},
attrs: {},
style: {},
dataset: {},
textContent: "",
classList: { add() {}, remove() {}, toggle() {}, contains: () => false },
append(...kids) {
base.children.push(...kids);
},
prepend(...kids) {
base.children.unshift(...kids);
},
insertBefore(kid) {
base.children.push(kid);
},
replaceChildren(...kids) {
base.children = kids;
},
addEventListener(type, fn) {
(base.listeners[type] ??= []).push(fn);
},
setAttribute(k, v) {
base.attrs[k] = String(v);
},
getAttribute: (k) => base.attrs[k] ?? null,
querySelector: () => null,
querySelectorAll: () => [],
closest: () => null,
clientWidth: 0,
offsetWidth: 0,
scrollWidth: 0,
};
return new Proxy(base, {
get: (t, k) => (k in t ? t[k] : typeof k === "string" ? () => undefined : undefined),
});
}
const noop = new Proxy(function () {}, {
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : noop),
apply: () => noop,
construct: () => noop,
});
globalThis.window = globalThis;
globalThis.sessionStorage = store();
globalThis.localStorage = store();
globalThis.document = new Proxy(
{ createElement: element, createElementNS: (_ns, tag) => element(tag) },
{ get: (t, k) => (k in t ? t[k] : noop) },
);
globalThis.ResizeObserver = class {
observe() {}
disconnect() {}
};
globalThis.getComputedStyle = () => ({ gap: "0" });
globalThis.requestAnimationFrame = (run) => setTimeout(run, 0);
globalThis.addEventListener = () => undefined;
globalThis.removeEventListener = () => undefined;
globalThis.fetch = async () => new Response("{}", { status: 404 });
const m = await import(pathToFileURL(process.argv[2]).href);
const draftKeys = () =>
Object.keys(
Object.fromEntries([...Array(sessionStorage.length)].map((_, i) => [sessionStorage.key(i), 1])),
).filter((k) => k.startsWith("aislo:draft:"));
const out = {};
// ① 표준 횡단 — config 와 저장분이 다른 프로젝트
const config = JSON.parse(readFileSync(process.argv[3], "utf8")).standard_cross_section;
const stored = JSON.parse(JSON.stringify(config));
stored.rock.ditch.top_width_m = 0.9; // 저장분 = 사용자가 고쳐 저장한 값(config 와 다름)
const context = {
project_id: "pS",
standard_cross_section: config,
stored_standard_cross_section: stored,
};
m.writeState("section-context", context, "pS");
await m.fetchSectionContext("pS"); // 캐시 길 — 예전엔 여기서 저장분을 초안 칸에 베낌
out.ctxDrafts = draftKeys();
const panel = m.createStandardPanel("pS", config, () => {});
out.panelDrafts = draftKeys();
out.panelRockDitch = panel.getValues().rock.ditch.top_width_m;
out.effectiveRockDitch = m.effectiveStandardCross("pS").rock.ditch.top_width_m;
panel.applyStored(stored);
out.afterStored = draftKeys();
// 옛 판이 남긴 같은 값 칸 — 열면 걷음
m.writeState("std-cross", stored, "pS2");
m.writeState("section-context", { ...context, project_id: "pS2" }, "pS2");
m.createStandardPanel("pS2", config, () => {});
out.oldSameCell = m.readState("std-cross", "pS2");
// 저장분 없는 프로젝트 — config 그대로 = 칸 없음 · 계산은 config
m.writeState("section-context", { project_id: "pC", standard_cross_section: config }, "pC");
m.createStandardPanel("pC", config, () => {});
out.configDrafts = draftKeys();
out.configRockDitch = m.effectiveStandardCross("pC").rock.ditch.top_width_m;
// 사용자가 고친 값은 칸에 남음
const edited = JSON.parse(JSON.stringify(stored));
edited.soil.road_width_m = 3.5;
m.writeState("std-cross", edited, "pE");
m.writeState("section-context", { ...context, project_id: "pE" }, "pE");
m.createStandardPanel("pE", config, () => {});
out.editedRoadWidth = m.readState("std-cross", "pE")?.soil.road_width_m ?? null;
// ② 카드 단추 — 리핑암 · 좌절토 · 측구 자동 판정
sessionStorage.clear();
const section = {
station_id: "No.1",
chainage_m: 20,
uphill_side: "left",
design: {
ground_type: "ripping_rock",
section_mode: "left_cut",
ditch_side: null,
ditch_type: "standard",
paved: false,
two_stage_slope: false,
ditch_choice: null,
ditch_enabled: true,
},
samples: [],
};
const sent = [];
const { bar, groundSegment } = m.buildDesignControls(section, (_c, change) => sent.push(change));
const buttons = [];
const walk = (node) => {
if (!node || typeof node !== "object") return;
if (node.tagName === "BUTTON") buttons.push(node);
(node.children ?? []).forEach(walk);
};
walk(groundSegment);
walk(bar);
const press = (pick) => buttons.find(pick)?.listeners.click?.forEach((fn) => fn({}));
// 부품 켜기 칩(553-2) — 글은 안쪽 span 에 · 자식 글을 이어 읽음
const text = (node) =>
typeof node === "string"
? node
: (node?.textContent ?? "") + (node?.children ?? []).map(text).join("");
out.soilPressed = buttons[0]?.getAttribute("aria-pressed");
press((b) => /측구/.test(text(b)));
out.ditchChange = sent.at(-1) ?? null;
// 새 카드 · 토사 단추 — 지반만(측구형식은 이미 일반이라 안 실림)
buttons.length = 0;
const again = m.buildDesignControls(section, (_c, change) => sent.push(change));
walk(again.groundSegment);
press(() => true);
out.soilChange = sent.at(-1) ?? null;
// 세션 고름 — 앞서 바꾼 칸에 얹음(지반은 안 박음)
const detail = { cross_sections: [section] };
const sync = m.createDesignSync({
projectId: "pD",
routeId: () => 7,
detail: () => detail,
view: () => noop,
});
await sync.handleDesignChange(20, { ditch_choice: false }).catch(() => undefined);
await sync.handleDesignChange(20, { two_stage_slope: true }).catch(() => undefined);
out.choice = Object.fromEntries(m.crossDesignChoices("pD", 7));
await sync.handleDesignChange(20, {}).catch(() => undefined);
out.choiceAfterEmpty = Object.fromEntries(m.crossDesignChoices("pD", 7));
// 소단 사본(시설 줄이 열 때 구조물에서 씀) — 떠남 확인에 안 잡힘 · 진짜 고침은 잡힘
sessionStorage.clear();
m.writeState(
"berm",
[{ start_m: 0, end_m: 40, width_m: 1, interval_m: 5, slope_deg: 45, depth_m: 1 }],
"pB",
7,
);
out.dirtyBermOnly = m.designDirty("pB");
m.writeCrossDesignChoice("pB", 7, 20, { ditch_choice: false });
out.dirtyWithEdit = m.designDirty("pB");
process.stdout.write(JSON.stringify(out));
process.exit(0);