415 lines
20 KiB
Python
415 lines
20 KiB
Python
"""일위대가 실무자료 화면(PLAN 324-4 · 338-3) — 좌측 판 · 본 화면 · 확인 창 · 고르기 창 ·
|
||
별칭 장부 · [로직 추가] 「실무 호표」 줄 · 엑셀 보기 창.
|
||
|
||
- 확인 창 도우미(Node · fetch · sessionStorage 대역 · `helper_m02_hopyo_check.cjs`):
|
||
체크 → 몸(체크한 줄만 · 뺌 · 고른 대상 · 몫 · 배 · 호표) · 빠질 줄 까닭 · 캐시 키 ·
|
||
최신 응답만 · 품 비교 · 만들기 · 통째 · 만들 층 · 미리 셈 · 장부 길 · 장부 화면 합침 ·
|
||
하위 호표 먼저 · 확인된 것 = 바로 참조
|
||
- 고르기 창 덧줄(`helper_m02_hopyo_pick.cjs`): 이 층 · 위 층 호표 줄 · 종류 고르기 ·
|
||
프로젝트 층엔 없음 · 글 도우미 · 근거 꼬리표 · 엑셀 보기 창 행 → 호표
|
||
- 소스: 옛 즉시 확정 · 바로 만들기 없음(saveHopyoMemory · [확정] · [같음 모두 확정] ·
|
||
[근거 맞음 모두 확정] · [사전조합 만들기]) · `buildHopyo(` 는 확인 창 · 엑셀 보기 창 펼침에서만 ·
|
||
좌측 판 자리 · 700 줄 · 색 = 공용 토큰
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import re
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
M02 = Path(HERE).parents[1] / "M02_MasterTemplete"
|
||
NEW = (
|
||
"Hopyo_Api",
|
||
"Hopyo_Pick",
|
||
"Hopyo_Side",
|
||
"Hopyo_View",
|
||
"Hopyo_Sheet",
|
||
"Hopyo_Check",
|
||
"Hopyo_Choose",
|
||
"Hopyo_Ledger",
|
||
)
|
||
ROW = {
|
||
"층": "system",
|
||
"주인": None,
|
||
"키": "HP-000001",
|
||
"파일": "북부청.xls",
|
||
"번": 3,
|
||
"이름": "임도표지석",
|
||
"규격": "910×600×150",
|
||
"단위": "개소",
|
||
"맞춤": [2, 4],
|
||
}
|
||
SURE = {"로직": "GC000198", "이름": "장비사용타설", "입력값": {"소량감률": 50}, "배": 1, "몫": None}
|
||
QTY = {
|
||
"엑셀": {"인력": {"콘크리트공": 0.1091}, "장비": [{"갈래": "굴착기", "시간": 0.29}]},
|
||
"우리": {"인력": {"콘크리트공": 0.1091}, "장비": [{"갈래": "굴착기", "시간": 0.31}]},
|
||
"같음": {"콘크리트공": True, "굴착기": False},
|
||
}
|
||
|
||
QTY_MANY = { # 같은 장비 여러 줄(229-4 ②) — 합쳐 한 줄로 견줌
|
||
"엑셀": {"장비": [{"갈래": "굴착기", "시간": t} for t in (0.0334, 0.0371, 0.0889)]},
|
||
"우리": {"장비": [{"갈래": "굴착기", "시간": 0.0889}]},
|
||
"같음": {"굴착기": False},
|
||
}
|
||
|
||
|
||
def _text(name: str) -> str:
|
||
return (M02 / name).read_text(encoding="utf-8")
|
||
|
||
|
||
def _node(helper: str, payload: dict) -> dict:
|
||
proc = subprocess.run(
|
||
["node", os.path.join(HERE, helper)],
|
||
input=json.dumps(payload, ensure_ascii=False),
|
||
capture_output=True,
|
||
text=True,
|
||
encoding="utf-8",
|
||
check=False,
|
||
)
|
||
assert proc.returncode == 0, proc.stderr
|
||
return json.loads(proc.stdout)
|
||
|
||
|
||
def _line(no, state, check, parts, **extra):
|
||
match = {"열쇠": f"줄{no}||m3", "상태": state, "체크": check, "대상": parts, **extra}
|
||
return {"차례": no, "이름": f"줄{no}", "규격": "", "수량": 1, "단위": "㎥", "갈래": "단가산출",
|
||
"맞춤": match} # fmt: skip
|
||
|
||
|
||
def _check() -> dict:
|
||
lines = [
|
||
_line(0, "후보", False, [{"자재": "MT004258", "입력값": {}, "배": 1}]),
|
||
_line(1, "맞음", True, [SURE], 품=QTY),
|
||
_line(2, "별칭", True, [{"로직": "GF000101", "입력값": {}, "배": 1}]),
|
||
_line(3, "다른 점", False, [{"로직": "GF000103", "입력값": {}, "배": 1}], 품=QTY_MANY),
|
||
_line(4, "후보", False, []),
|
||
_line(5, "%", False, []),
|
||
_line(6, "호표", True, [{"호표": 46, "입력값": {}, "배": 2.3}]),
|
||
_line(7, "노임", False, [{"로직": "GF000300", "입력값": {}, "배": None}]),
|
||
]
|
||
ledger = [
|
||
{"열쇠": "가|b|m3", "원래": {"품명": "가", "규격": "b", "단위": "m3"}, "갈래": "짝",
|
||
"짝": [{"로직": "GC000198", "입력값": {}, "배": 1, "몫": None}], "층": "personal",
|
||
"주인": "7/42", "고침": True},
|
||
{"열쇠": "나||m3", "원래": {"품명": "나", "규격": "", "단위": "m3"}, "갈래": "짝",
|
||
"짝": [{"로직": "GF000101", "입력값": {}, "배": 1, "몫": None}], "층": "personal",
|
||
"주인": "7/42", "고침": True},
|
||
{"열쇠": "다||개", "원래": {"품명": "다", "규격": "", "단위": "개"}, "갈래": "짝",
|
||
"짝": [{"자재": "MT1", "입력값": {}, "배": 1, "몫": None}], "층": "system",
|
||
"주인": None, "고침": False},
|
||
] # fmt: skip
|
||
rows = [
|
||
{**ROW, "확인": {"층": "company", "주인": "7", "키": "SC-000009"}},
|
||
{**ROW, "번": 4, "이름": "배수관", "확인": None},
|
||
]
|
||
return _node("helper_m02_hopyo_check.cjs", {"lines": lines, "ledger": ledger, "rows": rows})
|
||
|
||
|
||
def test_확인_창_체크한_줄만_몸():
|
||
got = _check()["check"]
|
||
assert got["key"] == "m02-hopyo-check:personal::HP-000001:3"
|
||
assert got["suggested"] == [ # 서버 체크 제안 그대로 · 배 없는 줄 · % 줄 · 후보는 안 감
|
||
{"차례": 1, "대상": [{"로직": "GC000198", "입력값": {"소량감률": 50}, "배": 1}]},
|
||
{"차례": 2, "대상": [{"로직": "GF000101", "입력값": {}, "배": 1}]},
|
||
{"차례": 6, "대상": [{"호표": 46, "배": 2.3}]},
|
||
]
|
||
assert got["edited"] == [ # 캐시 — 맞음 줄 체크 끔 · 뺌 · 고른 대상(몫 · 배)
|
||
{"차례": 2, "대상": [{"로직": "GF000101", "입력값": {}, "배": 1}]},
|
||
{"차례": 3, "뺌": "우리에 없음"},
|
||
{
|
||
"차례": 4,
|
||
"대상": [{"로직": "GF000211", "입력값": {"유형": "보통"}, "배": 0.1, "몫": 0.1}],
|
||
},
|
||
{"차례": 6, "대상": [{"호표": 46, "배": 2.3}]},
|
||
]
|
||
assert got["leftOut"] == {"확인 안 함": 3, "뺌": 1, "% 줄": 1}
|
||
assert got["checkable"] == [True, True, True, True, False, False, True, False]
|
||
assert got["sure"] == [1, 2] # [맞음 · 별칭 모두 체크]
|
||
assert got["compare"] == "콘크리트공 0.1091 → 0.1091 ✓ · 굴착기 0.29 → 0.31 ≠"
|
||
assert got["compareMany"] == "굴착기 0.1594 → 0.0889 ≠" # 같은 장비 여러 줄 = 합쳐 한 줄
|
||
assert got["toTarget"] == [{"호표": 46, "배": 2.3}, {"자재": "MT1", "배": 1}]
|
||
assert got["newest"] == [False, True, True] # 같은 줄 늦은 응답 버림 · 다른 줄은 따로
|
||
assert got["dropped"] is False # 만들기 성공 = 캐시 지움
|
||
|
||
|
||
def test_고르기_창_도우미():
|
||
got = _check()["choose"]
|
||
assert got["hint"] == ["6-1-1", "고임돌"] # 인용 조항 번호 → 없으면 품명 첫 뜻 낱말
|
||
assert got["picked"] == [
|
||
{"로직": "GC000206", "입력값": {}, "배": 1, "몫": None},
|
||
{"로직": "GF000211", "입력값": {}, "배": 0.1, "몫": 0.1},
|
||
]
|
||
assert got["inputs"] == "소량감률=50(블록), 지역=서울 · ⚠ 못 가름: 측압"
|
||
assert got["word"] == ["만듦", "있던 것을 확인한 내용으로 덮음"]
|
||
assert got["qty"] == "콘크리트공 0.1091 · 굴착기 0.29hr"
|
||
assert got["target"] == "제46호표 [㎡]"
|
||
|
||
|
||
def test_별칭_장부_화면_합침():
|
||
got = _check()["ledger"]
|
||
assert got["key"] == "m02-hopyo-ledger:company:7"
|
||
assert got["merged"] == [
|
||
["나||m3", "personal", True, True, "안 씀"], # 고친 줄
|
||
["다||개", "personal", True, True, "짝"], # 위 층 → 내 층으로 고쳐 씀
|
||
["다||개", "system", False, False, "짝"], # 위 층 = 그대로 읽기만
|
||
] # 지운 줄(가) 없음
|
||
assert got["filter"] == ["다||개", "다||개"] and got["kind"] == ["나||m3"]
|
||
assert got["pair"] == "GC000198 × 1 · 몫 0.1 · 소량감률=50"
|
||
assert got["entry"]["짝"] == [] and "층" not in got["entry"]
|
||
|
||
|
||
def test_길_만들기_통째_만들_층_미리_셈_장부():
|
||
calls = _check()["calls"]
|
||
build = "/api/m02/layers/company/hopyo-build?owner=7"
|
||
head = {"층": "system", "주인": None, "키": "HP-000001", "번": 3}
|
||
assert calls[0]["url"] == build and calls[0]["body"]["줄"][0]["차례"] == 1
|
||
assert {k: calls[0]["body"][k] for k in head} == head and "새로" not in calls[0]["body"]
|
||
assert calls[1]["body"] == {**head, "통째": {"대상": [{"로직": "GF000225", "배": 1}]}}
|
||
assert calls[2] == {
|
||
"url": "/api/m02/layers/system/hopyo/HP-000001/3?to=company&to_owner=7",
|
||
"method": "GET",
|
||
"body": None,
|
||
}
|
||
assert calls[3]["url"] == "/api/m02/layers/personal/hopyo/HP-000001/3/guess"
|
||
assert calls[3]["body"]["대상"][0]["몫"] == 0.1
|
||
assert calls[4]["url"] == "/api/m02/layers/company/hopyo-ledger?owner=7"
|
||
assert calls[5]["method"] == "PUT" and calls[5]["body"] == {"줄": {"가|b|m3": None}}
|
||
|
||
|
||
def test_로직_추가_확인된_것은_바로_참조_아니면_확인_창():
|
||
got = _check()["pick"]
|
||
assert got["picked"] == [
|
||
{
|
||
"ref": {"층": "company", "주인": "7", "키": "SC-000009"},
|
||
"name": "임도표지석",
|
||
"alias": "임도표지석",
|
||
}
|
||
]
|
||
assert got["calls"] == [] # 확인 없이 만들지 않음
|
||
assert got["opened"] == ["확인 — 북부청.xls 제4호표 배수관"]
|
||
|
||
|
||
def test_엑셀_보기_창_하위_호표_먼저():
|
||
assert _check()["sheet"]["order"] == [46, 45, 3, 7, 8] # 순환은 남은 차례
|
||
|
||
|
||
def _pick() -> dict:
|
||
row = {**ROW, "확인": {"층": "company", "주인": "7", "키": "SC-000009"}}
|
||
return _node("helper_m02_hopyo_pick.cjs", {"rows": [row], "built": {}})
|
||
|
||
|
||
def test_고르기_창_실무_호표_줄():
|
||
got = _pick()
|
||
assert got["calls"][0] == {
|
||
"url": "/api/m02/layers/company/hopyo-picks?owner=7",
|
||
"method": "GET",
|
||
"body": None,
|
||
}
|
||
assert got["kinds"] == ["logic", "precombo", "hopyo"]
|
||
assert got["rows"] == [
|
||
{
|
||
"kind": "hopyo",
|
||
"key": "system||HP-000001|3",
|
||
"tag": "실무 호표",
|
||
"name": "임도표지석 910×600×150",
|
||
"sub": "북부청.xls · 제3호표 · 마스터",
|
||
"unit": "개소 · 확인 SC-000009",
|
||
},
|
||
{
|
||
"kind": "hopyo",
|
||
"key": "system||HP-000001|엑셀",
|
||
"tag": "실무 호표",
|
||
"name": "엑셀로 보기 · 여러 호표",
|
||
"sub": "북부청.xls · 마스터",
|
||
"unit": "호표 1",
|
||
},
|
||
]
|
||
assert got["picked"][0]["ref"]["키"] == "SC-000009"
|
||
assert got["project"] is True # 프로젝트 층 = 덧줄 없음
|
||
|
||
|
||
def test_글_도우미와_근거_꼬리표():
|
||
got = _pick()
|
||
view = got["view"]
|
||
assert view["target"] == ["구조물터파기 [㎥]", "레미콘 25-21-120 [㎥]", "제46호표 [㎡]"]
|
||
assert view["inputs"] == "횟수=4회사용시, 지역=서울 · ⚠ 못 가름: 보정작업"
|
||
basis = got["basis"]
|
||
assert basis["ok"] == [True, False, False, False, False] # 조항 같음 · 직종 · 단위 모두
|
||
assert basis["plain"] == [True, True] # 줄 직종이 보통인부뿐 · 없음 = 직종 안 봄
|
||
assert basis["tag"] == [
|
||
"조항 건설 공통 3-2-1 · 직종 특별인부 · 단위 같음",
|
||
"조항 건설 공통 3-2-1 · 단위 같음",
|
||
"",
|
||
]
|
||
assert basis["source"] == [
|
||
"출처 건설표준품셈 3-2-1 · 직종 특별인부 · 단가산출 93–101행",
|
||
"단가산출 7행",
|
||
"",
|
||
]
|
||
|
||
|
||
def test_엑셀_보기_창_행과_호표():
|
||
got = _pick()
|
||
assert got["calls"][1] == {
|
||
"url": "/api/m02/layers/company/hopyo-grid/HP-000001?owner=7",
|
||
"method": "GET",
|
||
"body": None,
|
||
}
|
||
sheet = got["sheet"]
|
||
assert sheet["at"] == [None, 3, 3, 3, 4, 4, None] # 22행 → 제3호표 · 범위 밖 = 없음
|
||
assert sheet["span"] == "B20:O24" # 제3호표 영역 = B열 ~ 끝 열(열 폭 정보 O)
|
||
assert sheet["last"] == ["AA", "A"]
|
||
assert sheet["rows"] == ["4345:4345", "93:101"] # 단가산출 행 전체
|
||
|
||
|
||
# ── 소스 ──────────────────────────────────────────────────────────────
|
||
def test_옛_즉시_확정과_바로_만들기_없음():
|
||
texts = {name: _text(f"M02_MasterTemplete_{name}.ts") for name in NEW}
|
||
every = "\n".join(texts.values())
|
||
for gone in ("saveHopyoMemory", "hopyo-memory", "HopyoMemory", "memoryOf", "basisPick"):
|
||
assert gone not in every, gone
|
||
for label in (
|
||
"같음 모두 확정",
|
||
"근거 맞음 모두 확정",
|
||
'"확정"',
|
||
'"사전조합 만들기"',
|
||
"불러오기",
|
||
):
|
||
assert label not in every, label
|
||
users = sorted(name for name, text in texts.items() if "buildHopyo(" in text)
|
||
assert users == ["Hopyo_Check", "Hopyo_Sheet"] # 확인 창 · 펼침 뒤 한 번
|
||
check = texts["Hopyo_Check"]
|
||
assert '"확인하고 사전조합 만들기"' in check and "맞음 · 별칭 모두 체크" in check
|
||
assert "showLoadingOverlay()" in check and "dropDraft(k)" in check
|
||
assert "m02-hopyo-check:" in check and "POLL_MS" in check
|
||
sheet = texts["Hopyo_Sheet"]
|
||
assert "확인하고 만들기" in sheet and "이대로 ${ready.length}개 만들기" in sheet
|
||
assert "확인 창 열기" in sheet and "buildOrder(" in sheet
|
||
pick = texts["Hopyo_Pick"]
|
||
assert "if (row.확인)" in pick and 'import("./M02_MasterTemplete_Hopyo_Check")' in pick
|
||
view = texts["Hopyo_View"]
|
||
assert "buildHopyoCheck(" in view and "choose(" not in view
|
||
ledger = texts["Hopyo_Ledger"]
|
||
assert "m02-hopyo-ledger:" in ledger and "saveLedger(" in ledger
|
||
for gone in ("발주처", "회사로 올리기", "쓰인 횟수"):
|
||
assert gone not in ledger.split("*/", 1)[1] # 머리 글 「안 둠」 밖
|
||
|
||
|
||
def test_좌측_판_자리와_권한():
|
||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||
assert 'import { buildHopyoSection } from "./M02_MasterTemplete_Hopyo_Side";' in side
|
||
host = side.split('className: "m02-side__lists ui-sidebar-panel"')[1]
|
||
assert host.index("precomboSec,") < host.index("section(hopyo.title, hopyo.body),")
|
||
assert host.index("section(hopyo.title, hopyo.body),") < host.index("extras ?")
|
||
assert "canWrite: canEdit(layer, opt.isAdmin)," in side # 구조물 조합 층 쓰기 권한
|
||
assert "hopyo.paint();" in side.split("function paint(): void {")[1]
|
||
hopyo_side = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||
assert 'accept: ".xls,.xlsx"' in hopyo_side and "input.hidden = true;" in hopyo_side
|
||
assert "disabled: !opt.canWrite," in hopyo_side and "pageScope()" in hopyo_side
|
||
assert "buildHopyoLedger({ scope, canWrite: opt.canWrite })" in hopyo_side # [별칭 장부]
|
||
assert hopyo_side.index("list, ledger]") > 0
|
||
|
||
|
||
def test_로직_추가_한_곳과_파일_규칙():
|
||
detail = _text("M02_MasterTemplete_Labor_Detail.ts")
|
||
assert len(detail.splitlines()) == 693 # 줄 수 그대로
|
||
assert "withHopyo(library)," in detail
|
||
pick = _text("M02_MasterTemplete_Labor_Pick.ts")
|
||
assert "more?: () => Promise<PickExtra[]>;" in pick and "...later" in pick
|
||
assert "if (pre?.query) finder.search.value = pre.query;" in pick # 찾기 글 미리 넣음
|
||
for name in NEW:
|
||
text = _text(f"M02_MasterTemplete_{name}.ts")
|
||
assert len(text.splitlines()) <= 700, name
|
||
css = _text("M02_MasterTemplete_Hopyo.css")
|
||
assert "var(--color-" in css and not re.search(r"#[0-9a-fA-F]{3,6}\b", css)
|
||
for state in ("맞음", "별칭", "다른점", "후보"):
|
||
assert f".m02-hopyo__state.is-{state}" in css
|
||
assert '"./M02_MasterTemplete_Hopyo.css"' in _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||
|
||
|
||
def test_엑셀_보기_창_여는_자리와_늦게_불러옴():
|
||
lazy = 'import("./M02_MasterTemplete_Hopyo_Sheet")'
|
||
side = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||
assert lazy in side.split("await show(got);")[1].split("})")[0] # 올린 직후 자동
|
||
view = _text("M02_MasterTemplete_Hopyo_View.ts")
|
||
assert lazy in view and '"엑셀로 보기"' in view
|
||
check = _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||
assert lazy in check and '"엑셀에서 보기"' in check and "호표: opt.no" in check
|
||
assert "단산행: 행" in check
|
||
pick = _text("M02_MasterTemplete_Hopyo_Pick.ts")
|
||
assert lazy in pick
|
||
static = r'^import (?!type )[^(;]*"\./M02_MasterTemplete_Hopyo_Sheet"'
|
||
for text in (side, view, pick, check):
|
||
assert not re.search(static, text, re.M) # 정적 import 없음(타입만)
|
||
sheet = _text("M02_MasterTemplete_Hopyo_Sheet.ts")
|
||
assert 'import("../A00_Common/spreadsheet/spreadsheet")' in sheet
|
||
assert not re.search(
|
||
r'^import \{[^}]*\} from "\.\./A00_Common/spreadsheet/spreadsheet"', sheet, re.M
|
||
)
|
||
assert "readOnly: true" in sheet and "wide: true" in sheet
|
||
|
||
|
||
def test_실물_응답_대역_확인_창():
|
||
"""338-4 — 가 창 실물 응답(`fixtures/m02_hopyo_view_sample.json`)을 확인 창 · 고르기 창 도우미에
|
||
그대로 먹임 — 체크 = 서버 제안 · 몸 꼴 · 품 비교 · 옵션 출처 · 후보 판정 꼬리표."""
|
||
sample = json.loads((Path(HERE) / "fixtures" / "m02_hopyo_view_sample.json").read_bytes())
|
||
lines = sample["호표"]["호표"]["줄"]
|
||
got = _node("helper_m02_hopyo_check.cjs", {"real": lines})
|
||
by = {x["이름"]: x for x in got["lines"]}
|
||
remicon = by["레미콘타설(장비사용)"]
|
||
assert remicon["상태"] == "맞음" and remicon["checked"] and remicon["checkable"]
|
||
assert (
|
||
remicon["compare"]
|
||
== "콘크리트공 0.1091 → 0.1091 ✓ · 보통인부 0.0364 → 0.0364 ✓ · 굴착기 0.29 → 0.2909 ✓"
|
||
)
|
||
assert (
|
||
remicon["inputs"]
|
||
== "타설방법=장비사용타설(규격), 구조물=철근구조물(규격), 소량감률=50(블록)"
|
||
)
|
||
assert remicon["qty"] == "콘크리트공 0.1091 · 보통인부 0.0364 · 굴착기 0.29hr"
|
||
assert remicon["cands"][0][1] == "맞음" and remicon["cands"][0][2].startswith("조항 건설품셈")
|
||
rebar = by["철근가공조립(현장)"]
|
||
assert rebar["상태"] == "다른 점" and not rebar["checked"] and rebar["checkable"]
|
||
assert [x["차례"] for x in got["body"]] == [x["차례"] for x in lines if x["맞춤"]["체크"]]
|
||
assert all({"로직", "입력값", "배"} <= set(t) for x in got["body"] for t in x["대상"])
|
||
assert got["sure"] == [x["차례"] for x in lines if x["맞춤"]["상태"] == "맞음"]
|
||
|
||
|
||
def test_확인_창_손질_339():
|
||
"""339 덧고침 — ① 줄 표 꼴(한글 낱말 줄바꿈 · 옵션 · 품 비교 한 줄씩 · 단추 세로) · 장부 품명 · 규격 폭
|
||
② 만들기 뒤 좌측 사전조합 목록 다시 ③ 엑셀 보기 창 목록 셈 끝까지 다시 읽음 · 펼침 표 숫자 판정 칸."""
|
||
css = _text("M02_MasterTemplete_Hopyo.css")
|
||
assert "word-break: keep-all;" in css and "white-space: pre-line;" in css
|
||
assert (
|
||
".m02-hopyo__lines .m02-hopyo__acts > *" in css
|
||
and "text-overflow"
|
||
not in css.split(".m02-hopyo__lines td:is(:nth-child(9)")[-1].split("}")[0]
|
||
)
|
||
assert ".m02-hopyo-ledger__table td:nth-child(1)" in css
|
||
assert "m02-hopyo-ledger__table" in _text("M02_MasterTemplete_Hopyo_Ledger.ts")
|
||
check = _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||
assert r'td(opts.join("\n"))' in check and r'.split(" · ")' in check
|
||
assert r'.join("\n"),' in check # 품 비교 = 항목마다 한 줄(판정 글 함수는 그대로)
|
||
make = check.split("async function make(")[1].split("function builtBlock(")[0]
|
||
assert make.index("document.dispatchEvent(new Event(HOPYO_BUILT));") < make.index("opt.onBuilt")
|
||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||
assert "document.addEventListener(HOPYO_BUILT, onBuilt);" in side
|
||
assert "void precombo.refresh()" in side.split("const onBuilt")[1].split(";")[0]
|
||
sheet = _text("M02_MasterTemplete_Hopyo_Sheet.ts")
|
||
assert "poll(file);" in sheet and "poll(next);" in sheet and "POLL_MS" in sheet
|
||
assert "if (ok) document.dispatchEvent(new Event(HOPYO_BUILT));" in sheet
|
||
assert '`맞음 ${tally("맞음")} · 다른 점 ${tally("다른 점")}`' in sheet
|
||
|
||
|
||
def test_본_칸_호표_목록_셈_끝까지_다시_읽음_339():
|
||
"""339 덧고침 2 — 본 칸 호표 표 「제안 n / 줄 m」 · 머리 「셈 중」 — 셈 중이면 3 초마다 다시 읽음."""
|
||
view = _text("M02_MasterTemplete_Hopyo_View.ts")
|
||
assert "poll(file);" in view and "poll(next);" in view and "POLL_MS" in view
|
||
assert "hopyos = next.호표;" in view and "...hopyos" in view
|
||
assert (
|
||
"const live = (): boolean => my === nav && view.isConnected;" in view
|
||
) # 호표 하나로 가면 멈춤
|
||
assert "숫자 판정 셈 중" in view
|