test(M02): 48-2 정의 화면 시나리오 · 종류 줄 · 배치 줄 안 접힘 · 48-1 시나리오 새 화면 고름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
2026-10-05 16:22:25 +09:00
co-authored by Claude Opus 5.5
parent 4b79e7f39c
commit f0745f714d
3 changed files with 174 additions and 6 deletions
@@ -156,8 +156,19 @@
font-weight: 600;
}
.m02-sod__kindrow {
flex-wrap: nowrap;
}
.m02-sod__kindrow > .m02-opts__kind {
flex: 1 1 auto;
min-width: 0;
}
.m02-sod__kindrow > button:not(.m02-opts__kind),
.m02-sod__family > button {
flex: none;
white-space: nowrap;
}
.m02-sod__block {
@@ -219,9 +230,21 @@
align-items: start;
}
.m02-sod__place {
flex-wrap: nowrap;
}
.m02-sod__placename {
flex: 1 1 8rem;
flex: 1 1 6rem;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.m02-sod__place > .m02-sod__num {
flex: none;
width: 4rem;
}
.m02-sod__grid {
@@ -5,7 +5,7 @@
* - def = { 판 ≥ 1, types: 37, options: 235 }(GET /api/m02/structure-definition)
* - types = { types: 37, options: 235, visibleAll: true, pipeGroups: ["관", "관 보호공(유입)", "관 보호공(유출)"] }
* (GET /api/projects/structure-types — 옛 꼴 그대로)
* - 좌측 「구조물 옵션 보이기」 → 종류 단추 kinds = 37(옵션 없는 종류도 · 옛 화면 그대로)
* - 좌측 「구조물 옵션 정의」 → 종류 단추 kinds = 37(옵션 없는 종류도 · 48-2 정의 화면)
* · pipe 단추에 그룹 표지 「3」
*/
(async () => {
@@ -41,14 +41,14 @@
pipeGroups: pipe.groups.map((g) => g.name),
};
const row = await until(() => $("[data-options-row]"));
if (!row) return JSON.stringify({ ...out, error: "옵션 보이기 줄 없음" });
if (!row) return JSON.stringify({ ...out, error: "정의 줄 없음" });
row.click();
await until(() => $$("[data-options-kind]").length);
const kinds = $$("[data-options-kind]");
await until(() => $$("[data-sod-kind]").length);
const kinds = $$("[data-sod-kind]");
out.screen = {
kinds: kinds.length,
withOptions: def.types.filter((t) => t.options.length).length,
pipeTag: $('[data-options-kind="pipe"]')?.innerText.replace(/\s+/g, " ").trim() ?? null,
pipeTag: $('[data-sod-kind="pipe"]')?.innerText.replace(/\s+/g, " ").trim() ?? null,
};
out.ok =
out.def.types === 37 &&
@@ -0,0 +1,145 @@
/* 48-2 — M02 구조물 옵션 정의 편집 화면(군 · 종류 · 옵션 · 선택지 · 배치 격자 · 그룹 형상 · 규칙 · 지난 판)
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?root=master) · 시스템 관리자
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌 · 20초 안)
* 쓰는 것: 없음 — 화면 사본만 고치고 [저장] 안 누름(저장 길은 test_m02_structure_def_screen.py 가 사본으로) ·
* 끝에 판이 그대로인지 GET 으로 봄 · 화면을 다시 띄우면 고침은 사라짐
* 기대 수치:
* - families = 구조물군 수 · kinds = 37 · pipe 줄 options = pipe 옵션 수 · cells = 쓰는 옵션 수 · base = true
* - 이름 바꿈 → 왼쪽 단추 글 따라감 · [저장] 켜짐
* - 선택지 펼침 = 3(흄관 · VR관 · 파형강관) · 「흄관」 → 「흄관X」 = 기본값 고르기에도 「흄관X」
* - 너비 2 → 미리보기 span 2 · 겹치게 두면 issues 에 「배치 겹침」 · [저장] 꺼짐 · 되돌리면 사라짐
* - 기준 측점 끔 → 미리보기 기준 측점 칸 0
* - 그룹 형상 「길이」 켬 → 뜻 칸 열림
* - [규칙 더함] → 규칙 줄 1 · 빨강 아님 · 지우기 → 0
* - [지난 판] → 목록 칸(판 단추 또는 「지난 판 없음」)
* - 끝 판 = 처음 판(저장 안 함)
*/
(async () => {
const W = (ms) => new Promise((r) => setTimeout(r, ms));
const $ = (s, root = document) => root.querySelector(s);
const $$ = (s, root = document) => [...root.querySelectorAll(s)];
const until = async (fn, ms = 8000) => {
for (let t = 0; t < ms; t += 120) {
const v = fn();
if (v) return v;
await W(120);
}
return null;
};
/** 칸 값 바꿈 — 글자 넣음(input) + 떠남(change) */
const put = (box, value) => {
box.value = value;
box.dispatchEvent(new Event("input", { bubbles: true }));
box.dispatchEvent(new Event("change", { bubbles: true }));
};
const tick = (box, on) => {
if (box.checked !== on) box.click();
};
const def = await (
await fetch("/api/m02/structure-definition", { credentials: "include" })
).json();
if (!def.types) return JSON.stringify({ error: "정의 못 읽음" });
const pipe = def.types.find((t) => t.type_id === "pipe");
const out = { 판: def.판 };
const row = await until(() => $("[data-options-row]"));
if (!row) return JSON.stringify({ error: "정의 줄 없음" });
row.click();
if (!(await until(() => $$("[data-sod-kind]").length)))
return JSON.stringify({ error: "종류 목록 없음" });
out.families = [$$("[data-sod-family]").length, Object.keys(def.구조물군).length];
out.kinds = $$("[data-sod-kind]").length;
$('[data-sod-kind="pipe"]').click();
await until(() => $('[data-sod-option="pipe_kind"]'));
const liveOpts = pipe.options.filter((o) => o.enabled !== false).length;
out.pipe = {
options: [$$("[data-sod-option]").length, pipe.options.length],
cells: [$$("[data-sod-cell]").length, liveOpts],
base: !!$(".m02-sod__cell.is-base"),
};
// 이름 바꿈 → 왼쪽 단추 · [저장] 켜짐
put($("[data-sod-kind-name]"), "배수관(시험)");
out.rename = {
left: $('[data-sod-kind="pipe"]').innerText.includes("배수관(시험)"),
save: !$("[data-sod-save]").disabled,
};
// 선택지 펼침 · 이름 바꿈 → 기본값 고르기에도
$('[data-sod-choices="pipe_kind"]').click();
await until(() => $$("[data-sod-choice]").length);
out.choices = $$("[data-sod-choice]").map((r) => $("input", r).value);
put($('[data-sod-choice="흄관"] [data-sod-choice-name]'), "흄관X");
const defaultPick = $('[data-sod-option="pipe_kind"] [data-sod-default]');
out.choiceRename = [...defaultPick.options].some((o) => o.value === "흄관X");
// 배치 — 너비 2 · 겹침 · 되돌림
put($('[data-sod-place-row="pipe_kind"] [data-sod-width]'), "2");
const cell = $('[data-sod-cell="pipe_kind"]');
out.width2 = cell.style.gridColumn.replace(/\s+/g, " ");
const other = pipe.options.filter((o) => o.enabled !== false)[1].key;
put($(`[data-sod-place-row="${other}"] [data-sod-row]`), "1");
put($(`[data-sod-place-row="${other}"] [data-sod-col]`), "2");
out.overlap = {
issue: $("[data-sod-issues]").innerText.includes("배치 겹침"),
red: $$(".m02-sod__cell.is-bad").length,
saveOff: $("[data-sod-save]").disabled,
};
put($('[data-sod-place-row="pipe_kind"] [data-sod-width]'), "1");
out.overlapGone = !$("[data-sod-issues]").innerText.includes("배치 겹침");
// 기준 측점 끔
tick($("[data-sod-base]"), false);
out.baseOff = $$(".m02-sod__cell.is-base").length;
// 그룹 형상 「길이」 켬 → 뜻 칸 열림
const shape = $("[data-sog-shape]");
const meanBefore = $(".m02-sog__mean", shape).disabled;
tick($("[data-sog-shape-0]", shape), true);
out.shape = { before: meanBefore, after: $(".m02-sog__mean", $("[data-sog-shape]")).disabled };
// 규칙 더함 · 지우기
$("[data-sod-rule-add]").click();
const rule = await until(() => $("[data-sod-rule]"));
out.rule = {
rows: $$("[data-sod-rule]").length,
bad: rule.classList.contains("is-bad"),
top: $("[data-sod-rule-top]", rule).value,
target: $("[data-sod-rule-target]", rule).value,
};
[...$$("button", rule)].find((b) => b.innerText.trim() === "지우기").click();
out.ruleGone = $$("[data-sod-rule]").length;
// 지난 판 목록
$("[data-sod-history-toggle]").click();
await until(() => $("[data-sod-history]").children.length);
out.history = $("[data-sod-history]").innerText.replace(/\s+/g, " ").trim().slice(0, 80);
const after = await (
await fetch("/api/m02/structure-definition", { credentials: "include" })
).json();
out.판그대로 = after.판 === def.판;
out.ok =
out.families[0] === out.families[1] &&
out.kinds === 37 &&
out.pipe.options[0] === out.pipe.options[1] &&
out.pipe.cells[0] === out.pipe.cells[1] &&
out.pipe.base &&
out.rename.left &&
out.rename.save &&
out.choices.length === 3 &&
out.choiceRename &&
out.width2.includes("span 2") &&
out.overlap.issue &&
out.overlap.saveOff &&
out.overlapGone &&
out.baseOff === 0 &&
out.shape.before === true &&
out.shape.after === false &&
out.rule.rows === 1 &&
!out.rule.bad &&
out.ruleGone === 0 &&
out.history.length > 0 &&
out.판그대로;
return JSON.stringify(out);
})();