"""364-1 보기 손길 · 쉬는 동안 온 값은 되돌리기 걸음 아님 — 설계 값이 바뀐 때만 걸음. · 새 페이지 → 뒤에서 온 값(관 자리 불러옴 · 표준 횡단 · 따라간 구조물) 뒤 판 열기 · 회전 · 맞춤 · 미리보기 · 도움말 = canUndo false · 꺾임점 끌기 · 계획고 ▲ = 걸음 하나씩 · 되돌리기 한 걸음 = 그 편집만 · 막이 — 놓은 뒤 기다리는 사이 손길 · 글 치는 중 다른 곳 누름은 앞 편집을 덮지 않음 ./venv/Scripts/python.exe -m pytest resources/tester/test_364_1_view_no_step.py -q """ from __future__ import annotations import json 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" TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" DRIVER = r""" import { writeFileSync } from "node:fs"; 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; const { createProfileHistory, seedPageHistory } = await import("./B05_Profile_UI_Profile_History.js"); const wait = (ms) => new Promise((r) => setTimeout(r, ms)); const fire = (type) => window.dispatchEvent(new Event(type)); /** 누르고 놓음 — 보기 손길(세션 안 씀) 또는 편집(누른 동안 write). */ async function click(write) { fire("pointerdown"); write?.(); fire("pointerup"); await wait(450); } const K = { route: "aislo:draft:route-draft:p1", grade: "aislo:draft:alignment-draft:p1:7", std: "aislo:draft:std-cross:p1", structures: "aislo:draft:structures:p1", cross: "aislo:draft:crossdesign:p1:7", pipes: "b05:pipes", }; const out = {}; // 새 페이지 — 관 자리는 찍을 때만 옮겨 적음(Panel_Tools syncPipes 꼴) let pipes = []; sessionStorage.setItem(K.route, "r0"); const h = createProfileHistory(() => {}, 100, () => sessionStorage.setItem(K.pipes, JSON.stringify(pipes))); h.record(); // 열 때 찍기 // 손길 없이 뒤에서 옴 — 배수 판이 관 자리 불러옴 · 표준 횡단 · 따라간 구조물 pipes = [10, 20]; sessionStorage.setItem(K.std, "std"); sessionStorage.setItem(K.structures, "s0"); await click(); // [횡단] 판 열기 out.sideOpen = h.canUndo(); sessionStorage.setItem(K.structures, "s1"); // 또 뒤에서 옴(파생 값) await click(); // 회전 fire("keydown"); // 도움말 · 맞춤 단축키 await wait(450); await click(); // 측점 눈금 횡단 미리보기 await click(); // 판 닫기 out.views = h.canUndo(); // 꺾임점 끌기 — 누른 동안 여러 번 찍어도 놓을 때 한 걸음 fire("pointerdown"); for (const v of ["r1", "r2", "r3"]) { sessionStorage.setItem(K.route, v); h.record(); } fire("pointerup"); await wait(450); out.drag = h.canUndo(); sessionStorage.setItem(K.structures, "s2"); // 끈 노선 따라 구조물 다시 섬(뒤에서) await click(); // 맞춤 // 계획고 ▲ await click(() => sessionStorage.setItem(K.grade, "g1")); out.gradeUndo = h.undo(); out.afterGradeUndo = { route: sessionStorage.getItem(K.route), grade: sessionStorage.getItem(K.grade), structures: sessionStorage.getItem(K.structures) }; out.dragUndo = h.undo(); out.afterDragUndo = { route: sessionStorage.getItem(K.route), structures: sessionStorage.getItem(K.structures), pipes: sessionStorage.getItem(K.pipes), std: sessionStorage.getItem(K.std) }; out.noMore = !h.canUndo(); // 막이 1 — 놓은 뒤 기다리는 사이(400 ms) 다른 곳 누름은 앞 편집을 덮지 않음 data.clear(); const h2 = createProfileHistory(() => {}); fire("pointerdown"); h2.record(); // 누른 동안 찍기 → 놓을 때 찍힘(바뀐 것 없음) fire("pointerup"); await wait(0); sessionStorage.setItem(K.cross, "c1"); // 카드 단추 — 놓은 뒤 씀 · 찍는 줄 없음 await wait(50); await click(); // 곧바로 판 열기 out.settleKept = h2.canUndo(); // 막이 2 — 글 치는 중(키마다 씀) 다른 곳 누름 = 앞 편집 안 덮음 data.clear(); const h3 = createProfileHistory(() => {}); fire("keydown"); sessionStorage.setItem(K.grade, "typed"); await wait(450); await click(); // 칸 밖 누름 fire("change"); await wait(450); out.typedKept = h3.canUndo(); // 씨앗 — 계획고 고친 뒤 [횡단 보기] 첫 열기가 비어 있던 표준 횡단을 채움(누른 사이) = 걸음 아님 · 되돌려도 남음 data.clear(); const h4 = createProfileHistory(() => {}); await click(() => sessionStorage.setItem(K.grade, "g1")); await click(() => { sessionStorage.setItem(K.std, "seed"); seedPageHistory(K.std); }); out.seedSteps = [h4.undo(), h4.canUndo()]; out.seedKept = { std: sessionStorage.getItem(K.std), grade: sessionStorage.getItem(K.grade) }; 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_364_1") subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(TSC), str(TS_FILE), "--outDir", str(out), "--module", "esnext", "--target", "es2022", "--moduleResolution", "bundler", "--ignoreConfig", "--noCheck", "--noResolve"], cwd=str(ROOT), check=True, capture_output=True, ) # fmt: skip (out / "driver.mjs").write_text(DRIVER, encoding="utf-8") run = subprocess.run( # noqa: S603 ["node", str(out / "driver.mjs"), str(out / "out.json")], 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["sideOpen"] is False assert result["views"] is False def test_편집만_걸음_되돌리기는_그_편집만(result) -> None: assert result["drag"] is True assert result["gradeUndo"] is True # 계획고만 되돌아감 — 끌기 · 뒤에서 온 구조물은 그대로 assert result["afterGradeUndo"] == {"route": "r3", "grade": None, "structures": "s2"} assert result["dragUndo"] is True # 끌기 전 모습 = 열고 쉬는 동안 온 값까지(그건 걸음이 아님) assert result["afterDragUndo"] == { "route": "r0", "structures": "s1", "pipes": "[10,20]", "std": "std", } assert result["noMore"] is True def test_씨앗_값은_걸음_아님_되돌려도_남음(result) -> None: assert result["seedSteps"] == [True, False] # 계획고 한 걸음뿐 assert result["seedKept"] == {"std": "seed", "grade": None} def test_앞_편집은_덮지_않음(result) -> None: assert result["settleKept"] is True assert result["typedKept"] is True