"""컨테이너 안 「불러오는 중」(PLAN 30-6 · `ui_template_loading.ts`) — 0.3초 · 실패 글 · M02 자리. ① `delayedShow` 를 Node 로 가짜 시계로 돌림 — 0.3초 전에 끝나면 안 보임(깜빡임 없음) · 넘으면 보이고 끝나면 걷힘 · 끝낸 뒤 시계가 와도 안 보임. ② 실패 자리 = 까닭 글(「불러오지 못함 — 까닭」) + [다시 불러오기] · 화면 전체 잠금 안 씀. ③ M02 나무 · 상세(표 · 도면) · 조합 목록 · 조합 상세 · 항목 카드가 이 부품을 씀. """ from __future__ import annotations import json import re import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] LOADING = ROOT / "ui_template" / "ui_template_loading.ts" M02 = ROOT / "M02_MasterTemplete" def _text(path: Path) -> str: return path.read_text(encoding="utf-8") @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def test_0점3초_넘을_때만_보임(): found = re.search(r"^export function delayedShow[\s\S]*?^}", _text(LOADING), re.M) assert found, "delayedShow 를 못 찾음" script = ( "const LOADING_DELAY_MS = 300;\n" + found.group(0).replace("export ", "") + """ let now = 0; const jobs = []; const timer = { set: (fn, ms) => { jobs.push({ at: now + ms, fn, live: true }); return jobs.length - 1; }, clear: (id) => { if (jobs[id]) jobs[id].live = false; }, }; const tick = (ms) => { now += ms; for (const j of jobs) if (j.live && j.at <= now) { j.live = false; j.fn(); } }; const log = []; const on = (n) => () => log.push(n + ":show"); const off = (n) => () => log.push(n + ":hide"); const fast = delayedShow(on("fast"), off("fast"), undefined, timer); tick(200); fast(); tick(500); const slow = delayedShow(on("slow"), off("slow"), undefined, timer); tick(299); log.push("299"); tick(1); slow(); tick(500); console.log(JSON.stringify(log)); """ ) result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-e", script], capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == ["299", "slow:show", "slow:hide"] def test_실패_글과_다시_불러오기_M02_자리(): src = _text(LOADING) assert 'reason ? `${t("Common_Load_Failed")} — ${reason}` : t("Common_Load_Failed")' in src assert 'createButton({ label: t("Common_Reload")' in src and 'role: "alert"' in src assert "showLoadingOverlay()" not in src # 칸 안만(화면 전체 잠금 안 부름) # 브라우저 setTimeout 을 객체 메서드로 부르면 Illegal invocation — 감싼 기본값(ORCA 흠) assert "set: (fn, ms) => setTimeout(fn, ms)," in src assert "clear: (id) => clearTimeout(id)," in src locale = _text(ROOT / "ui_template" / "ui_template_locale_common.ts") assert 'Common_Load_Failed: ["불러오지 못함"' in locale assert 'Common_Reload: ["다시 불러오기"' in locale tree = _text(M02 / "M02_MasterTemplete_TreePanel.ts") assert "const stop = showLoading(body);" in tree assert "loadingError(why(error), () => void refresh())" in tree main = _text(M02 / "M02_MasterTemplete_UI_Main.ts") assert "const stop = showLoading(host);" in main assert "loadingError(why, () => void open(next))" in main edit = _text(M02 / "M02_MasterTemplete_Labor_Edit.ts") assert "loadingError(reason(error), () => void open(key))" in edit assert "(current ? () => {} : showLoading(view))" in edit assert "const stop = editor.loading();" in _text(M02 / "M02_MasterTemplete_Labor.ts") assert "cards.map((c) => c.shell.loading())" in _text(M02 / "M02_MasterTemplete_ItemCards.ts") assert "loading: () => showLoading(host)," in _text(M02 / "M02_MasterTemplete_CardShell.ts")