"""477-3 · 477-4 (요구 433) — ↶ 바로 켜짐 · 소단 사본 = 결과 통. 못박는 것 - 477-3 조작 놓은 직후(찍기 기다림 400 ms 안) ↶ 켜짐 · 단추 맞춤 알림 · 바뀐 것 없는 누름은 안 켜짐. - 기다림 중 ↶ = 먼저 찍고 되돌림(한 번에 조작 전) · ↷ = 다시 · 서버 다시 셈 뒤 쓰기 묶음은 그대로 한 걸음. - 477-4 결과 통 칸은 사진 밖 · 소단 사본 칸 `berm` 은 걷음(577-2 · 측점에 얹힌 제원) · 떠남 목록에 줄 없음. ./venv/Scripts/python.exe -m pytest resources/tester/test_477_undo_now_berm.py -q """ from __future__ import annotations import json import re import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] TS_FILE = ROOT / "B05_Profile" / "B05_Profile_UI_Profile_History.ts" LOADER = Path(__file__).resolve().parent / "helper_ts_cjs_load.cjs" DRIVER = r""" import { writeFileSync } from "node:fs"; import { createRequire } from "node:module"; const data = new Map(); globalThis.sessionStorage = { get length() { return data.size; }, key: (i) => [...data.keys()][i] ?? null, getItem: (k) => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)), removeItem: (k) => data.delete(k), }; globalThis.window = new EventTarget(); window.setTimeout = setTimeout; window.sessionStorage = sessionStorage; window.localStorage = { getItem: (k) => (k === "frd_current_project_id" ? "p1" : null) }; const { loadTs } = createRequire(import.meta.url)(process.argv[3]); const { createProfileHistory, onPageHistory } = loadTs(process.argv[4]); const wait = (ms) => new Promise((r) => setTimeout(r, ms)); const PIPES = "aislo:draft:pipes:p1"; const CROSS = "aislo:draft:crossdesign:p1:7"; const BERM = "aislo:result:design-stations:p1"; const out = {}; let changes = 0; onPageHistory({ change: () => (changes += 1) }); const press = () => window.dispatchEvent(new Event("pointerdown")); const lift = () => window.dispatchEvent(new Event("pointerup")); // 1. 관 지움 — 놓은 한 틱 뒤 ↶ 켜짐(기다림 400 ms 안) sessionStorage.setItem(PIPES, '[{"c":10},{"c":20}]'); sessionStorage.setItem(BERM, "[]"); const h = createProfileHistory(() => {}); out.openCanUndo = h.canUndo(); press(); sessionStorage.setItem(PIPES, '[{"c":10}]'); lift(); await wait(30); out.soonCanUndo = h.canUndo(); out.soonChanges = changes; // 바로 ↶ 단추(누름 · 놓음 · 누름 처리) — 기다리던 찍기 먼저 · 한 번에 조작 전 press(); lift(); out.undo = h.undo(); out.afterUndo = sessionStorage.getItem(PIPES); out.undoLeft = h.canUndo(); out.canRedo = h.canRedo(); await wait(450); out.redo = h.redo(); out.afterRedo = sessionStorage.getItem(PIPES); out.redoLeft = h.canRedo(); // 2. 서버 다시 셈 뒤 쓰기(200 ms) — 기다림이 끝나면 조작과 한 걸음 data.clear(); sessionStorage.setItem(PIPES, "a"); sessionStorage.setItem(CROSS, "x"); const h2 = createProfileHistory(() => {}); press(); sessionStorage.setItem(PIPES, "b"); lift(); await wait(200); sessionStorage.setItem(CROSS, "y"); sessionStorage.setItem(BERM, '[{"s":1}]'); // 결과 통 칸 — 사진 밖 await wait(300); h2.undo(); out.grouped = [sessionStorage.getItem(PIPES), sessionStorage.getItem(CROSS)]; out.groupedLeft = h2.canUndo(); out.bermKept = sessionStorage.getItem(BERM); // 3. 바뀐 것 없는 누름 — ↶ 안 켜짐(깜빡임 없음) data.clear(); sessionStorage.setItem(PIPES, "a"); const h3 = createProfileHistory(() => {}); press(); lift(); await wait(30); out.idleCanUndo = h3.canUndo(); writeFileSync(process.argv[2], JSON.stringify(out)); """ @pytest.fixture(scope="module") def result(tmp_path_factory: pytest.TempPathFactory) -> dict: out = tmp_path_factory.mktemp("ts_477") (out / "driver.mjs").write_text(DRIVER, encoding="utf-8") run = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(out / "driver.mjs"), str(out / "out.json"), str(LOADER), str(TS_FILE)], capture_output=True, text=True, cwd=str(out), ) # fmt: skip assert run.returncode == 0, run.stderr return json.loads((out / "out.json").read_text(encoding="utf-8")) def test_놓은_직후_되돌리기_켜짐(result) -> None: assert result["openCanUndo"] is False assert result["soonCanUndo"] is True assert result["soonChanges"] >= 1 # 단추 맞춤 알림이 기다림 전에 감 def test_기다림_중_되돌리기_먼저_찍고_한_걸음(result) -> None: assert result["undo"] is True assert result["afterUndo"] == '[{"c":10},{"c":20}]' assert result["undoLeft"] is False and result["canRedo"] is True assert result["redo"] is True and result["afterRedo"] == '[{"c":10}]' assert result["redoLeft"] is False def test_서버_다시_셈_묶음_그대로(result) -> None: assert result["grouped"] == ["a", "x"] and result["groupedLeft"] is False assert result["bermKept"] == '[{"s":1}]' # 결과 통 칸은 되돌리기가 안 건드림 def test_바뀐_것_없는_누름은_안_켜짐(result) -> None: assert result["idleCanUndo"] is False def test_소단_사본_칸_없음() -> None: state = (ROOT / "A00_Common" / "b_page_state.ts").read_text(encoding="utf-8") assert not re.search(r"^\s*berm: \{", state, re.M) # 577-2 — 측점에 얹힌 제원(사본 걷음) leave = (ROOT / "A00_Common" / "design" / "design_leave.ts").read_text(encoding="utf-8") assert '"berm"' not in leave