- 텍스트 수식 줄 hover · 누름 고정(행 단위 선택) 걷음 - 변수 이름 식 = 변수 이름마다 낮은 단추 · 값 든 식 = 수 하나하나 낮은 단추 → 같은 줄의 상세 모달(적용 · 삭제 · 닫기) - 변수가 시험 입력(옵션)이면 옵션 고르기 칩으로 - 공용 상세 `lineOpener` 새로 — M02 SK 상세 · M01 단가산출 기초 같은 길(M01 = 시험 고침 고리) - 상세 보기 줄마다 붙던 옵션 값 칩(`쓰는입력`) 걷음 — 위 옵션 칸이 대신(748-3) - 새 파일 `LogicLab_FormulaTokens`(조각 나누기 순수 셈) · 시험 test_748_2 · 낡은 시험 고침 test_722(inputChip 줄 걷음) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
114 lines
5.1 KiB
Python
114 lines
5.1 KiB
Python
"""722 M01 단가산출 상세 개편 지킴 시험 — 글 · 순수 함수(화면 · 서버 없음).
|
|
|
|
- 722-1 공용 단가산출 상세(`M01_MasterData_UI_LogicLab_Ho.ts`) — 하위 박스(서버 묶음) · 장비 한 줄 + 펼침 · 계산식 열 · 박스 옵션
|
|
- 722-2 그 자리 고침(`M01_MasterData_UI_LogicLab_Cover.ts`) — M01 = 시험 값(줄 사본) · 정본 · 캐시 안 바뀜
|
|
"""
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
M01 = ROOT / "M01_MasterData"
|
|
HO = M01 / "M01_MasterData_UI_LogicLab_Ho.ts"
|
|
COVER = M01 / "M01_MasterData_UI_LogicLab_Cover.ts"
|
|
|
|
|
|
def _text(p: Path) -> str:
|
|
return p.read_text(encoding="utf-8")
|
|
|
|
|
|
def _pure(p: Path) -> str:
|
|
s = _text(p)
|
|
return s[s.index("/* ── 순수 셈 ── */") : s.index("/* ── 순수 셈 끝 ── */")]
|
|
|
|
|
|
def _node(code: str):
|
|
out = subprocess.run(
|
|
["node", "--experimental-strip-types", "--no-warnings",
|
|
"--input-type=module-typescript", "-e", code],
|
|
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
|
) # fmt: skip
|
|
assert out.returncode == 0, out.stderr
|
|
return json.loads(out.stdout)
|
|
|
|
|
|
def test_상세가_공용_부품을_씀():
|
|
detail = _text(M01 / "M01_MasterData_UI_LogicLab_Detail.ts")
|
|
assert "buildHo({ ...ctx, edit: trialEdit(ctx) })" in detail
|
|
ho = _text(HO)
|
|
# 부품 상자만 — 표 · 카드 · 계산 줄 · 변수 칸(calc_sheet) · 입력 칩(텍스트 수식)
|
|
for part in (
|
|
"tableShell(",
|
|
"tableRow(",
|
|
"createCard(",
|
|
"varRow(",
|
|
"sheetFormula(",
|
|
):
|
|
assert part in ho, part
|
|
assert "inputChip(" not in ho # 748-3 줄마다 붙던 옵션 값 칩 걷음(위 옵션 칸이 대신)
|
|
for s in (ho, _text(COVER)):
|
|
assert 'el("table"' not in s and 'el("button"' not in s and 'el("input"' not in s
|
|
assert "document.createElement" not in s and "sessionStorage" not in s
|
|
assert "export function varRow(" in _text(ROOT / "A00_Common" / "calc_sheet.ts")
|
|
for p in (HO, COVER, M01 / "M01_MasterData_UI_LogicLab.ts"):
|
|
assert len(_text(p).splitlines()) <= (
|
|
627 if p.name == "M01_MasterData_UI_LogicLab.ts" else 700
|
|
)
|
|
|
|
|
|
def test_시험_고침은_캐시_밖():
|
|
lab = _text(M01 / "M01_MasterData_UI_LogicLab.ts")
|
|
# 시험 계산에만 덮은 사본 · 고침이 있으면 줄을 보냄 · drafts(캐시)에는 원본 줄
|
|
assert ": trial.apply(row);" in lab
|
|
assert "dirty: () => current.id in drafts || trial.has(current.row)," in lab
|
|
assert "onTrial: drawCalc," in lab
|
|
cover = _text(COVER)
|
|
assert "drafts" not in cover and "touch" not in cover and "/save" not in cover
|
|
modal = _text(M01 / "M01_MasterData_UI_LogicLab_Modal.ts")
|
|
assert "...(target.edit ? [target.edit(close, head)] : [])" in modal
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
|
def test_박스_묶기():
|
|
body = """
|
|
const e = (idx, bundle, merge, amount) => ({ idx, bundle, merge, amount });
|
|
const got = boxesOf([
|
|
e(0, "인력", false, 10), e(1, "경비", true, 100), e(1, "경비", true, 20), e(1, "경비", true, 30),
|
|
e(2, "하위", true, 5), e(2, "하위", true, null), e(3, "재료", false, null),
|
|
], ["인력", "재료", "경비"]).map((b) => ({ 묶음: b.묶음, 소계: b.소계,
|
|
줄: b.줄.map((r) => "몫" in r ? [r.idx, r.금액, r.몫.length] : r.idx) }));
|
|
console.log(JSON.stringify(got));
|
|
"""
|
|
got = _node(_pure(HO) + "\n" + body)
|
|
assert [b["묶음"] for b in got] == ["인력", "재료", "경비", "하위"]
|
|
assert got[0] == {"묶음": "인력", "소계": 10, "줄": [0]}
|
|
assert got[1] == {"묶음": "재료", "소계": 0, "줄": [3]}
|
|
assert got[2] == {"묶음": "경비", "소계": 150, "줄": [[1, 150, 3]]} # 장비 한 줄 = 몫 셋 합
|
|
assert got[3]["줄"] == [[2, 5, 2]]
|
|
empty = _node(_pure(HO) + '\nconsole.log(JSON.stringify(boxesOf([], ["인력"])))')
|
|
assert empty == [{"묶음": "인력", "줄": [], "소계": 0}]
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
|
def test_고침_덮기():
|
|
body = """
|
|
const row = { 키: "GF1", 호표: [
|
|
{ 종류: "인력", 요소: "LB1", 수량: "찾기(Q).수량" },
|
|
{ 종류: "로직", 요소: "로직(GC1)", 수량: "1 / 작업량", 값묶음: { 경비: 3 } },
|
|
{ 종류: "재료", 요소: "MT1", 수량: "2" } ], 덧줄: [{ 이름: "공구", 식: "노무비 * 3 / 100" }] };
|
|
const out = applyCovers(row, { 0: { qty: 0.0000001, labor: 100000 }, 1: { labor: 50000 }, 9: { qty: 1 } });
|
|
console.log(JSON.stringify({ out, same: row.호표[0].수량, keep: out.호표[2] === row.호표[2] }));
|
|
"""
|
|
got = _node(_pure(COVER) + "\n" + body)
|
|
out = got["out"]
|
|
assert (
|
|
out["호표"][0]["수량"] == "0.0000001" and out["호표"][0]["단가"] == 100000
|
|
) # 지수 꼴 없음 · 손 단가
|
|
assert out["호표"][1]["값묶음"] == {"경비": 3, "노무비": 50000} and "단가" not in out["호표"][1]
|
|
assert got["keep"] and got["same"] == "찾기(Q).수량" # 원본 안 바뀜 · 안 고친 줄 그대로
|
|
assert out["덧줄"] == [{"이름": "공구", "식": "노무비 * 3 / 100"}]
|