- 433-16: 새로고침 · 새 탭 = 세션 상세로 먼저 섬 · 뒤에서 상세 GET(ETag 304) · 판이 다르면 서버 값 위에 이 탭에서 고친 사용자 칸만 · [저장] patch 바탕도 새 판 · 노선 초안 hold 는 버릴 때 새 판 - 440-3: 숨은 종단 보기도 관 세션 id 장부 돌림(평면 마커 누름 = 초안 자리) · 그리기는 보일 때 - 384_3: pvi 단언 = 저장 꼭짓점 + 심은 측점마다 세운 꼭짓점(427) - 384_7: 세션 쓰기 = 받을 때 · 판 확인 새 판 둘 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
"""387-7(384 단계 7) 결과 층 — 세션 상세 = 저장 정본만 · 열 때 세션 상세 JSON.parse 한 번.
|
|
|
|
· 상세 가게(새로고침)와 묶음 cross 입력이 어느 쪽이 먼저 읽어도 세션 글은 한 번만 풂(`_Section_Canon`)
|
|
· 정본 사본은 캐시 객체와 따로 — 다시 셈 · 초안 얹기가 캐시를 고쳐도 묶음 입력의 정본은 그대로
|
|
· 갈아끼움 · 되돌림은 세션 상세를 안 씀(`test_365_page_pipeline` 365-1 · 이 파일은 소스로)
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_384_7_section_canon.py -q
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
LOADER = Path(__file__).resolve().parent / "helper_ts_cjs_load.cjs"
|
|
|
|
DRIVER = r"""
|
|
const data = new Map();
|
|
const fake = {
|
|
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 = { sessionStorage: fake, localStorage: fake };
|
|
const { loadTs } = require(process.argv[1]);
|
|
const canon = loadTs(process.argv[2]);
|
|
const state = loadTs(process.argv[3]);
|
|
const detail = {
|
|
longitudinal: {},
|
|
cross_sections: [
|
|
{ chainage_m: 0, design: { ground_type: "soil", cut_area_m2: 1 } },
|
|
{ chainage_m: 20, design: { ground_type: "rock", cut_area_m2: 2 } },
|
|
],
|
|
};
|
|
let parses = 0;
|
|
const parse = JSON.parse;
|
|
JSON.parse = (text, ...rest) => {
|
|
if (typeof text === "string" && text.includes("cross_sections")) parses += 1;
|
|
return parse(text, ...rest);
|
|
};
|
|
const out = {};
|
|
// 묶음 입력이 먼저 → 상세 가게
|
|
state.writeState("section-detail", detail, "p", 1);
|
|
const rows = canon.savedDesigns("p", 1);
|
|
const shown = canon.sessionDetail("p", 1);
|
|
out.cross = { parses, rows: rows.length };
|
|
// 정본 사본 = 캐시 객체와 따로
|
|
shown.cross_sections[0].design.cut_area_m2 = 99;
|
|
out.canonKept = canon.savedDesigns("p", 1)[0].design.cut_area_m2;
|
|
// 상세 가게가 먼저 → 묶음 입력
|
|
parses = 0;
|
|
state.writeState("section-detail", detail, "p", 2);
|
|
canon.sessionDetail("p", 2);
|
|
canon.savedDesigns("p", 2);
|
|
out.storeFirst = parses;
|
|
// 버림 → 다시 받기 전엔 정본 없음
|
|
canon.forgetCanon("p");
|
|
state.writeState("section-detail", null, "p", 1);
|
|
out.forgot = canon.savedDesigns("p", 1);
|
|
process.stdout.write(JSON.stringify(out));
|
|
"""
|
|
|
|
|
|
def _run() -> dict:
|
|
done = subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
[
|
|
"node",
|
|
"-e",
|
|
DRIVER,
|
|
str(LOADER),
|
|
str(ROOT / "B06_Section" / "B06_Section_Section_Canon.ts"),
|
|
str(ROOT / "A00_Common" / "b_page_state.ts"),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
cwd=str(ROOT),
|
|
)
|
|
assert done.returncode == 0, done.stderr
|
|
return json.loads(done.stdout)
|
|
|
|
|
|
def test_열_때_세션_상세_한_번만_풂() -> None:
|
|
out = _run()
|
|
assert out["cross"] == {"parses": 1, "rows": 2}
|
|
assert out["storeFirst"] == 1
|
|
assert out["canonKept"] == 1 # 캐시를 고쳐도 정본 사본 그대로
|
|
assert out["forgot"] is None
|
|
|
|
|
|
def test_세션_상세_쓰는_곳_받을_때_하나() -> None:
|
|
store = (ROOT / "B06_Section" / "B06_Section_Section_Store.ts").read_text(encoding="utf-8")
|
|
code = re.sub(r"/\*.*?\*/|//[^\n]*", "", store, flags=re.S)
|
|
# 서버에서 받은 정본만 — 받을 때 · 세션 판 확인(451 · 433-16)이 받은 새 판
|
|
assert re.findall(r'writeState\("section-detail", (\w+),', code) == ["detail", "fresh"]
|
|
cross = (ROOT / "A00_Common" / "design" / "design_pipeline_cross.ts").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert "section-detail" not in re.sub(r"/\*.*?\*/|//[^\n]*", "", cross, flags=re.S)
|