- copyArea · copyMaterial · fetchStructureNumbers → {도번, 면적번, 재료번} · Kind area · material
- 옛 이름 copyBasis · 표번 · Kind basis 는 sub2 가 옮길 때까지 별칭(@deprecated)
- 좌측 목록 셋 · 줄 글 = 번호 맨 앞 + 이름 + 변수값
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
128 lines
5.6 KiB
Python
128 lines
5.6 KiB
Python
"""M02 구조물 설계 컨테이너(`M02_MasterTemplete_Structure.ts`) 붙는 자리 시험.
|
|
|
|
화면 조작은 ORCA 로 봄 — 여기서는 창끼리 계약이 안 어긋나는지만 봄.
|
|
· 집계표 이름이 서버(`STRUCTURE_TABLE`)와 같은 글인가
|
|
· 컨테이너가 쓰는 이름(표 부품 · 도면 부품 · 서버 호출)이 정말 내보내진 것인가
|
|
· 열 머리 글 잇기(`columnHead`)를 Node 로 바로 돌려 봄
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
M02 = ROOT / "M02_MasterTemplete"
|
|
PANEL = M02 / "M02_MasterTemplete_Structure.ts"
|
|
MAIN = M02 / "M02_MasterTemplete_UI_Main.ts"
|
|
SIDE = M02 / "M02_MasterTemplete_UI_Side.ts"
|
|
|
|
|
|
def _text(path: Path) -> str:
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def test_집계표_이름이_서버와_같음():
|
|
found = re.search(r'STRUCTURE_TABLE = "([^"]+)"', _text(PANEL))
|
|
assert found and found.group(1) == store.STRUCTURE_TABLE
|
|
|
|
|
|
def test_소스가_700줄을_넘지_않음():
|
|
for path in (PANEL, MAIN, M02 / "M02_MasterTemplete_Structure.css"):
|
|
assert len(_text(path).splitlines()) <= 700, path.name
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("source", "names", "target"),
|
|
[
|
|
(
|
|
"M02_MasterTemplete_Api_Fetch.ts",
|
|
["createStructure", "fetchStructureNumbers", "readTemplate"],
|
|
PANEL,
|
|
),
|
|
# 산출근거 저장은 컨테이너가 안 함 — [산출근거 수정] 이 여는 메인 칸 전체 편집이 함
|
|
("M02_MasterTemplete_Api_Fetch.ts", ["saveTemplate"], MAIN),
|
|
# 좌측 구조물 도면 · 면적산출 · 재료산출 줄 번호는 한 번에 받음
|
|
# (`{열 id: {도번, 면적번, 재료번}}`)
|
|
("M02_MasterTemplete_Api_Fetch.ts", ["fetchStructureNumbers"], SIDE),
|
|
("M02_MasterTemplete_Structure.ts", ["structureLabel", "isStructureColumn"], SIDE),
|
|
("M02_MasterTemplete_Drawing.ts", ["mountDrawingTemplate"], PANEL),
|
|
("../A00_Common/spreadsheet/spreadsheet.ts", ["createSpreadsheet"], PANEL),
|
|
],
|
|
)
|
|
def test_컨테이너가_쓰는_이름이_내보내진_것(source: str, names: list[str], target: Path):
|
|
"""도면 부품 · 스프레드시트(D) 가 이름을 바꾸면 여기서 먼저 걸림."""
|
|
body = _text(M02 / source)
|
|
used = _text(target)
|
|
for name in names:
|
|
assert re.search(rf"export (const|function|interface) {name}\b", body), f"{source}:{name}"
|
|
assert name in used, name
|
|
|
|
|
|
def test_sub2_가_부르는_이름과_모양():
|
|
"""브레인 확정 이름 · 모양 — sub2 가 이대로 부름(바꾸면 브레인에게)."""
|
|
body = _text(M02 / "M02_MasterTemplete_Api_Fetch.ts")
|
|
for name in ("copyStructure", "copyArea", "copyMaterial"):
|
|
assert re.search(
|
|
rf"export const {name} = async \(column: string, from: string\): Promise<void>", body
|
|
), name
|
|
assert "fetchStructureNumbers = async (): Promise<Record<string, StructureNumbers>>" in body
|
|
assert re.search(
|
|
r"interface StructureNumbers \{\s*도번: string;\s*면적번: string;\s*재료번: string;", body
|
|
)
|
|
assert re.search(r'export type Kind = [^;]*"area" \| "material"', body)
|
|
|
|
|
|
def test_집계표만_컨테이너를_붙임():
|
|
"""다른 표 양식에는 안 붙음 — 시스템 층 · 이름이 집계표일 때만."""
|
|
body = _text(MAIN)
|
|
assert 'at.layer === "system" && at.name === STRUCTURE_TABLE ? buildPanel() : null' in body
|
|
# 구조물 도면 저장은 `도면` 칸만 갈아 끼움 — 도번 · 산출근거는 읽은 그대로
|
|
assert '{ ...(loaded as StructureDoc), 도면: edited as StructureDoc["도면"] }' in body
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
|
def test_열_머리_글_잇기():
|
|
"""`columnHead` — 빈 층은 건너뛰고 빈칸으로 이음 · 다 비면 열 id ·
|
|
`structureLabel` — 번호가 맨 앞(「SD010101 돌기슭막이 …」) · 번호 없으면 이름만."""
|
|
funcs = [
|
|
re.search(rf"^export const {name}[\s\S]*?;$", _text(PANEL), re.M)
|
|
for name in ("columnHead", "structureLabel")
|
|
]
|
|
assert all(funcs), "columnHead · structureLabel 을 못 찾음"
|
|
# 부품 파일은 화면 모듈을 부르므로 통째로 못 싣음 — 그 함수만 떼어 Node 가 타입을 벗겨 돌림
|
|
script = (
|
|
"interface StructureColumn { id: string; 머리: (string | null)[] }\n"
|
|
+ "\n".join(f.group(0).replace("export ", "") for f in funcs)
|
|
+ "\nconsole.log(JSON.stringify(process.argv.slice(1)"
|
|
+ ".map((a) => columnHead(JSON.parse(a)))"
|
|
+ ".concat([structureLabel('SD010101', {id: 'rv', 머리: ['돌기슭막이', null, 'H=1.0']}),"
|
|
+ " structureLabel('', {id: 'rv', 머리: ['돌기슭막이', null, null]})])));"
|
|
)
|
|
cases = [
|
|
{"id": "pv_l", "머리": ["콘크리트포장", "T=(두께별)", "L"]},
|
|
{"id": "no", "머리": ["NO", None, None]},
|
|
{"id": "etc", "머리": [None, " ", None]},
|
|
]
|
|
result = subprocess.run(
|
|
["node", "--experimental-strip-types", "--no-warnings",
|
|
"--input-type=module-typescript", "-e", script,
|
|
*[json.dumps(c, ensure_ascii=False) for c in cases]],
|
|
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
|
) # fmt: skip
|
|
assert result.returncode == 0, result.stderr
|
|
assert json.loads(result.stdout) == [
|
|
"콘크리트포장 T=(두께별) L",
|
|
"NO",
|
|
"etc",
|
|
"SD010101 돌기슭막이 H=1.0",
|
|
"돌기슭막이",
|
|
]
|