Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
126 lines
4.3 KiB
Python
126 lines
4.3 KiB
Python
"""저장 안 한 고침 3단추 물음(PLAN 86-1 · `ui_template_leave_dialog.ts`) — Node 로 TS 를 돌려 판정.
|
|
|
|
못박는 것
|
|
- 단추 순서 [취소] [버리고 넘어감] [저장 후 넘어감] · 기본 초점 = 취소 · Esc = 취소.
|
|
- 저장 후 넘어감: 저장 성공 + 고침 없음 = saved / 저장 던짐 · 고침 남음 = stay(머묾).
|
|
- 버리고 넘어감: discard 갈고리 호출 · 취소: 아무것도 안 부름.
|
|
- M02 자리(UI_Main · Structure · ItemCards)가 이 물음을 씀.
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
M02 = ROOT / "M02_MasterTemplete"
|
|
SCRIPT = r"""
|
|
const fs = require("fs");
|
|
const ts = require(process.argv[1] + "/config/node_modules/typescript");
|
|
const src = fs.readFileSync(process.argv[1] + "/ui_template/ui_template_leave_dialog.ts", "utf8");
|
|
const js = ts.transpileModule(src, {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
let focused = null;
|
|
let overlay = null;
|
|
let buttons = [];
|
|
const node = (tag, o = {}) => ({
|
|
tag, ...o, kids: [], on: {},
|
|
append(...x) { this.kids.push(...x); },
|
|
addEventListener(k, f) { this.on[k] = f; },
|
|
remove() { overlay = null; },
|
|
});
|
|
const stubs = {
|
|
"./ui_template_elements": {
|
|
el: node,
|
|
createButton: (o) => {
|
|
const b = node("button", { label: o.label, variant: o.variant });
|
|
b.focus = () => { focused = b.label; };
|
|
b.click = o.onClick;
|
|
buttons.push(b);
|
|
return b;
|
|
},
|
|
},
|
|
"./ui_template_locale": { t: (k) => k },
|
|
};
|
|
globalThis.document = { activeElement: null, body: { append(x) { overlay = x; } } };
|
|
const box = { exports: {} };
|
|
new Function("exports", "module", "require", js)(box.exports, box, (n) => stubs[n] ?? require(n));
|
|
const { leaveOrStay } = box.exports;
|
|
|
|
const run = async (pick, hooks) => {
|
|
buttons = [];
|
|
focused = null;
|
|
const calls = [];
|
|
const wrap = {
|
|
save: async () => { calls.push("save"); if (hooks.throws) throw new Error("x"); },
|
|
stillDirty: () => !!hooks.left,
|
|
discard: () => calls.push("discard"),
|
|
};
|
|
const p = leaveOrStay("msg", wrap);
|
|
const labels = buttons.map((b) => b.label);
|
|
const first = focused;
|
|
if (pick === "esc") overlay.on.keydown({ key: "Escape", stopPropagation() {} });
|
|
else buttons[pick].click();
|
|
return { got: await p, calls, labels, first };
|
|
};
|
|
|
|
(async () => {
|
|
const out = {
|
|
cancel: await run(0, {}),
|
|
esc: await run("esc", {}),
|
|
discard: await run(1, {}),
|
|
save: await run(2, {}),
|
|
saveThrows: await run(2, { throws: true }),
|
|
saveLeft: await run(2, { left: true }),
|
|
};
|
|
process.stdout.write(JSON.stringify(out));
|
|
})();
|
|
"""
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def got() -> dict:
|
|
done = subprocess.run(
|
|
["node", "-e", SCRIPT, str(ROOT)],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=True,
|
|
)
|
|
return json.loads(done.stdout)
|
|
|
|
|
|
def test_단추_순서_기본_초점_취소(got):
|
|
row = got["cancel"]
|
|
assert row["labels"] == ["M02_TreeCancel", "M02_TreeDiscardGo", "M02_TreeSaveGo"]
|
|
assert row["first"] == "M02_TreeCancel"
|
|
|
|
|
|
def test_취소_Esc는_머묾(got):
|
|
assert got["cancel"]["got"] == "stay" and got["cancel"]["calls"] == []
|
|
assert got["esc"]["got"] == "stay" and got["esc"]["calls"] == []
|
|
|
|
|
|
def test_버리고_넘어감(got):
|
|
assert got["discard"]["got"] == "discarded"
|
|
assert got["discard"]["calls"] == ["discard"]
|
|
|
|
|
|
def test_저장_후_넘어감과_실패_머묾(got):
|
|
assert got["save"] == {**got["save"], "got": "saved", "calls": ["save"]}
|
|
assert got["saveThrows"]["got"] == "stay" # 저장 던짐
|
|
assert got["saveLeft"]["got"] == "stay" # 저장 뒤에도 고침이 남음(카드 저장 실패 등)
|
|
|
|
|
|
def test_M02_자리가_3단추_물음을_씀():
|
|
main = (M02 / "M02_MasterTemplete_UI_Main.ts").read_text(encoding="utf-8")
|
|
assert "leaveOrStay(message, { save: doSave, stillDirty: busy" in main
|
|
assert "askSettle" not in main
|
|
assert "!(await showConfirmDialog(L(" not in main
|
|
cards = (M02 / "M02_MasterTemplete_ItemCards.ts").read_text(encoding="utf-8")
|
|
assert "leaveOrStay(TEXT.discard" in cards
|
|
struct = (M02 / "M02_MasterTemplete_Structure.ts").read_text(encoding="utf-8")
|
|
assert struct.count("leaveOrStay(TEXT.discardCards") == 2
|