feat(M02): 저장 안 한 고침 물음 단추 셋 — 취소 · 버리고 넘어감 · 저장 후 넘어감, settle 카드 저장 실패 흠(86-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -66,11 +66,14 @@ def test_알약은_전체_편집기와_같은_부품():
|
||||
def test_저장_안_한_카드_고침_물음():
|
||||
struct = _text(M02 / "M02_MasterTemplete_Structure.ts")
|
||||
show = struct.split("const show = ")[1].split("const pickPrice")[0]
|
||||
assert "cards.dirty()" in show and "showConfirmDialog(TEXT.discardCards)" in show
|
||||
assert "cards.dirty()" in show and "leaveOrStay(TEXT.discardCards," in show
|
||||
assert "opts.keepColumn?.(from)" in show
|
||||
main = _text(M02 / "M02_MasterTemplete_UI_Main.ts")
|
||||
assert "const busy = (): boolean => dirty || !!panel?.dirty() || toDrawing.waiting();" in main
|
||||
assert main.count("busy()") >= 3 # 떠나기 · 새로고침 막기 · 나무 고침(settle)
|
||||
assert main.count("busy()") >= 2 # 떠나기 · 새로고침 막기
|
||||
assert (
|
||||
'askUnsaved(L("M02_TreeUnsaved"))' in main
|
||||
) # 나무 고침(settle)도 같은 물음 · 카드 저장 실패까지 봄
|
||||
assert main.index("await panel.saveCards();") < main.index("await panel.reload();")
|
||||
assert "keepColumn: (colId) => sheet?.selectColumn(colId)" in main
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ def test_메뉴는_숨기고_저장은_도면_칸만():
|
||||
def test_저장_안_한_카드_고침은_물음():
|
||||
cards = _text("M02_MasterTemplete_ItemCards.ts")
|
||||
# 물음 한 번(31-7) — 어느 카드든(도면 · 엑셀) 고침이 있으면 · 틀의 저장 표시(saver)로 봄
|
||||
assert "if (!again && anyDirty() && col?.id !== column && !(await showConfirmDialog(" in cards
|
||||
assert "(await leaveOrStay(TEXT.discard, { save: saveAll, stillDirty: anyDirty })) === \"stay\"" in cards
|
||||
assert "const anyDirty = (): boolean => cards.some((c) => c.saver.dirty());" in cards
|
||||
assert "dirty: anyDirty," in cards
|
||||
assert "dirty: () => cards.dirty()," in _text("M02_MasterTemplete_Structure.ts")
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
"""저장 안 한 고침 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
|
||||
Reference in New Issue
Block a user