Files
Aislo/resources/tester/helper_345_2_3d_modal.cjs
T
eomsangdonandClaude Opus 5.5 519b54eb73 feat(종단보기): 345-4 3D 모달 열 때 낡았으면 자동 다시 짓기 — 닫혀 있는 동안 안 지음 · 「3D 늦음」 = setCorridorPending 그대로 · 단추 · 열기 겹쳐도 한 번
- ProfileViewState.corridorPending(표시와 한 칸) · sections.setCorridorPending · 모달 onOpen(지형 선 뒤 · 그 사이 닫으면 안 부름)
- 시험: test_345_4_corridor_worker 4(워커 = 주 스레드 · transfer 9/9 · 저장본 먼저) · test_345_2_3d_modal 5(닫힌 동안 짓기 0) · 관련 310 통과 · typecheck 내 파일 0 오류

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
2026-10-16 11:47:31 +09:00

186 lines
5.7 KiB
JavaScript

/* 345-2 3D 큰 모달 헬퍼 — `design_view_profile_3d_modal.ts` 를 트랜스파일해 가짜 DOM · 가짜 뷰어로
* 닫힌 동안 그리기 0 · 열면 그림 · 닫으면 숨긴 자리로 되돌림 · 지형은 처음 열 때 한 번 · 버림은 떠날 때만을 본다.
* 결과는 JSON 한 벌(표준출력).
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
class FakeNode {
constructor(tag) {
this.tagName = tag;
this.children = [];
this.parent = null;
this.hidden = false;
this.title = "";
this.listeners = {};
const classes = new Set();
this.classList = {
add: (...n) => n.forEach((x) => classes.add(x)),
contains: (n) => classes.has(n),
};
this._classes = classes;
}
set className(v) {
this._classes.clear();
String(v)
.split(/\s+/)
.filter(Boolean)
.forEach((x) => this._classes.add(x));
}
append(...kids) {
kids.forEach((kid) => {
if (kid.parent) kid.parent.children = kid.parent.children.filter((c) => c !== kid);
kid.parent = this;
this.children.push(kid);
});
}
remove() {
if (this.parent) this.parent.children = this.parent.children.filter((c) => c !== this);
this.parent = null;
}
addEventListener(type, fn) {
(this.listeners[type] ??= []).push(fn);
}
click() {
(this.listeners.click ?? []).forEach((fn) => fn({}));
}
}
global.document = { createElement: (tag) => new FakeNode(tag) };
const modals = [];
const STUBS = {
"@ui/ui_template_elements": {
createButton: ({ label, onClick }) => {
const button = new FakeNode("button");
button.label = label;
if (onClick) button.addEventListener("click", onClick);
return button;
},
},
"@ui/ui_template_modal": {
openModal: (opts) => {
const body = new FakeNode("div");
const record = { opts, body, open: true };
opts.mount(body, () => (record.open = false), new FakeNode("div"));
record.close = () => (record.open = false);
modals.push(record);
return { close: record.close };
},
},
"@ui/ui_template_progress": {
createProgressCircle: () => {
const root = new FakeNode("div");
return { root, set: () => undefined, remove: () => root.remove() };
},
},
};
function loadTs(file) {
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
const localRequire = (request) => STUBS[request] ?? require(request);
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
return box.exports;
}
const { createViewer3dModal } = loadTs(
path.join(ROOT, "A00_Common", "design", "design_view_profile_3d_modal.ts"),
);
// 가짜 뷰어 — 만들면 그리기 루프가 돎(진짜와 같음) · setActive 가 루프를 켜고 끔.
const viewer = {
root: new FakeNode("div"),
running: true,
draws: 0,
disposed: false,
setActive(active) {
this.running = active;
},
dispose() {
this.disposed = true;
},
};
const frames = (n) => {
for (let i = 0; i < n; i += 1) if (viewer.running) viewer.draws += 1;
};
let terrainLoads = 0;
let resolveTerrain;
let opens = 0; // 345-4 — 열 때(지형 뒤) 낡은 3D 길 모형 다시 짓기 부름
const view3d = createViewer3dModal({
viewer,
loadTerrain: () => {
terrainLoads += 1;
return new Promise((resolve) => (resolveTerrain = resolve));
},
visible: () => true,
onOpen: () => (opens += 1),
});
(async () => {
const out = {};
frames(10);
out.closedDraws = viewer.draws;
out.inPark = viewer.root.parent === view3d.park && view3d.park.hidden;
out.terrainBeforeOpen = terrainLoads;
view3d.button.click();
const first = modals[0];
out.wide = first.opts.wide === true;
out.inModal = viewer.root.parent === first.body;
await new Promise((r) => setImmediate(r));
out.opensBeforeTerrain = opens;
out.spinnerWhileLoading = viewer.root.children.length === 1;
frames(3);
out.openDraws = viewer.draws;
resolveTerrain();
await new Promise((r) => setImmediate(r));
out.spinnerGone = viewer.root.children.length === 0;
out.opensAfterTerrain = opens;
// 닫기 단추 · Esc · 바깥 누름 = beforeClose 뒤 닫힘(공용 모달 규칙)
out.beforeCloseOk = (await first.opts.beforeClose()) === true;
first.close();
frames(10);
out.afterCloseDraws = viewer.draws;
out.backInPark = viewer.root.parent === view3d.park;
out.disposedOnClose = viewer.disposed;
await new Promise((r) => setImmediate(r));
out.opensWhileClosed = opens;
view3d.open();
view3d.open(); // 열린 동안 또 눌러도 하나
out.modalCount = modals.length;
out.terrainLoads = terrainLoads;
frames(2);
out.reopenDraws = viewer.draws;
await new Promise((r) => setImmediate(r));
out.opensAfterReopen = opens;
view3d.dispose(); // 페이지 떠남 — 열려 있으면 닫고 버림
out.closedOnDispose = modals[1].open === false;
out.disposed = viewer.disposed;
out.runningAfterDispose = viewer.running;
// 지형 받는 사이 닫으면 — 다시 짓기 안 부름(닫힌 동안 짓기 0)
let lateOpens = 0;
let lateResolve;
const late = createViewer3dModal({
viewer: { root: new FakeNode("div"), setActive() {}, dispose() {} },
loadTerrain: () => new Promise((resolve) => (lateResolve = resolve)),
visible: () => true,
onOpen: () => (lateOpens += 1),
});
late.open();
late.close();
lateResolve();
await new Promise((r) => setImmediate(r));
out.closedBeforeTerrainOpens = lateOpens;
console.log(JSON.stringify(out));
})();