"""B05 중간 노드에서 긋기(PLAN 771-2 · 요구 613) — TS `logic/D03_PlanAnalysis_Span.ts` `rejoinSpan`. 중간 노드에서 그은 점이 같은 선 다른 노드에 닿음 = 두 노드 사이 옛 구간을 새 점으로 갈음 · 한 줄 그대로 · 거꾸로(앞 노드에 닿음) = 점 차례 뒤집어 넣음 · 같은 노드 = 까닭 글(꼴 그대로) · 끝점에 닿음 = 끝까지 갈음 · 노드 기록 = 꼭짓점과 같은 길이. 774-2(요구 618) B05 = 늘 노선 하나 `logic/D03_PlanAnalysis_OneLine.ts` `joinTouching` = 맞닿은 토막 이음. 774-1(요구 616) 끝 판정 `rejoinMiss` = 다른 중간 노드만 끝 · 시작 노드 · 끝점 = 계속 긋기 까닭 글 · 시작 = 중간 노드만. ./venv/Scripts/python.exe -m pytest resources/tester/test_771_2_rejoin_span.py -q """ from __future__ import annotations import json import re import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] TS_FILE = ROOT / "D03_PlanAnalysis" / "logic" / "D03_PlanAnalysis_Span.ts" ONE_FILE = ROOT / "D03_PlanAnalysis" / "logic" / "D03_PlanAnalysis_OneLine.ts" TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" _RUNNER = """ import { writeFileSync } from "node:fs"; import * as plan from "./D03_PlanAnalysis/logic/D03_PlanAnalysis_Plan.js"; import * as span from "./D03_PlanAnalysis/logic/D03_PlanAnalysis_Span.js"; import * as one from "./D03_PlanAnalysis/logic/D03_PlanAnalysis_OneLine.js"; const two = (a, b) => { let m = plan.newLine(plan.emptyNetwork(), a[0]); for (const p of a.slice(1)) m = plan.appendVertex(m, 0, p); m = plan.newLine(m, b[0]); for (const p of b.slice(1)) m = plan.appendVertex(m, 1, p); return m; }; const flat = (m) => m.lines.map((l) => l.vertices); let n = plan.newLine(plan.emptyNetwork(), [0, 0]); for (const p of [[100, 0], [200, 0], [300, 0], [400, 0]]) n = plan.appendVertex(n, 0, p); const L = span.nodeRecord("length", 20, 9); const S = span.nodeRecord("slope", 20, 9); writeFileSync( process.argv[2], JSON.stringify({ forward: span.rejoinSpan(n, 0, 1, 3, [[150, 50], [250, 50]]), backward: span.rejoinSpan(n, 0, 3, 1, [[250, 50], [150, 50]]), same: span.rejoinSpan(n, 0, 2, 2, [[150, 50]]), head: span.rejoinSpan(n, 0, 2, 0, [[100, -50]]), tail: span.rejoinSpan(n, 0, 2, 4, [[300, 50]]), recorded: span.rejoinSpan(n, 0, 3, 1, [[250, 50], [150, 50]], [L, S]), same_text: span.REJOIN_SAME, miss: [[2, 1], [2, 3], [2, 2], [2, 0], [2, 4], [1, 3]].map(([s, v]) => span.rejoinMiss(5, s, v)), middle: [0, 1, 2, 3, 4].map((v) => span.isMiddleNode(5, v)), tip_text: span.REJOIN_TIP, hold_text: span.REJOIN_HOLD, join_tail_head: flat(one.joinTouching(two([[0, 0], [1, 0]], [[1, 0], [2, 0]]))), join_tail_tail: flat(one.joinTouching(two([[0, 0], [1, 0]], [[2, 0], [1, 0]]))), join_head_head: flat(one.joinTouching(two([[1, 0], [0, 0]], [[1, 0], [2, 0]]))), join_apart: flat(one.joinTouching(two([[0, 0], [1, 0]], [[5, 0], [6, 0]]))), join_branch: flat(one.joinTouching(plan.appendVertex(plan.addBranch(n, 0, 2), 1, [200, 50]))), }), ); """ def _run_ts(tmp_path: Path) -> dict: out = tmp_path / "js" subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(TSC), str(TS_FILE), str(ONE_FILE), "--outDir", str(out), "--rootDir", str(ROOT), "--module", "esnext", "--target", "es2022", "--moduleResolution", "bundler", "--ignoreConfig", ], cwd=str(ROOT), check=True, capture_output=True, ) for emitted in out.rglob("*.js"): emitted.write_text( re.sub(r'(from\s+"\.\.?/[^"]+)"', r'\1.js"', emitted.read_text(encoding="utf-8")), encoding="utf-8", ) (out / "runner.mjs").write_text(_RUNNER, encoding="utf-8") result = tmp_path / "out.json" subprocess.run( # noqa: S603 ["node", str(out / "runner.mjs"), str(result)], cwd=str(ROOT), check=True, capture_output=True, ) return json.loads(result.read_text(encoding="utf-8")) def _vertices(network: dict) -> list: assert len(network["lines"]) == 1 # 한 줄 그대로 return network["lines"][0]["vertices"] def test_forward_and_backward(tmp_path: Path) -> None: r = _run_ts(tmp_path) want = [[0, 0], [100, 0], [150, 50], [250, 50], [300, 0], [400, 0]] assert _vertices(r["forward"]) == want assert _vertices(r["backward"]) == want # 거꾸로 그어도 선 차례로 def test_same_node_and_ends(tmp_path: Path) -> None: r = _run_ts(tmp_path) assert r["same"] == r["same_text"] assert "같은 노드" in r["same"] assert _vertices(r["head"]) == [[0, 0], [100, -50], [200, 0], [300, 0], [400, 0]] assert _vertices(r["tail"]) == [[0, 0], [100, 0], [200, 0], [300, 50], [400, 0]] def test_backward_records(tmp_path: Path) -> None: line = _run_ts(tmp_path)["recorded"]["lines"][0] nodes = line["nodes"] assert len(nodes) == len(line["vertices"]) == 6 # 서버 400 막음 # 선 차례 [150,50] 앞 토막 = 닿은 자리(자유 · null) · [250,50] 앞 토막 = 둘째 찍은 점 기록(S) · 끝 노드 = 자유 assert nodes[2] is None assert nodes[3]["kind"] == "slope" assert nodes[4]["kind"] == "free" def test_end_only_other_middle_node(tmp_path: Path) -> None: """774-1 — 다른 중간 노드 = 끝(null) · 시작 노드 · 머리 · 꼬리 끝점 = 계속 긋기 글 · 시작 = 중간 노드만.""" r = _run_ts(tmp_path) tip, hold = r["tip_text"], r["hold_text"] assert r["miss"] == [None, None, hold, tip, tip, None] assert "중간 노드" in tip and "중간 노드" in hold assert r["middle"] == [False, True, True, True, False] def test_one_line_join_touching(tmp_path: Path) -> None: """774-2 — 맞닿은 두 토막 = 한 선(어느 끝이든 · 차례 이어짐) · 떨어진 토막 · 갈래 = 그대로.""" r = _run_ts(tmp_path) assert r["join_tail_head"] == [[[0, 0], [1, 0], [2, 0]]] assert r["join_tail_tail"] == [[[0, 0], [1, 0], [2, 0]]] assert r["join_head_head"] in ([[[0, 0], [1, 0], [2, 0]]], [[[2, 0], [1, 0], [0, 0]]]) assert len(r["join_apart"]) == 2 assert len(r["join_branch"]) == 2 # 갈래(D03 꼴)는 안 이음