Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_3' and 'origin/sub_laptop_5' into main_laptop_1

This commit is contained in:
2026-10-26 21:58:45 +09:00
48 changed files with 727 additions and 1481 deletions
+59
View File
@@ -0,0 +1,59 @@
"""553-1 B02 · B03 · B04 · B07 페이지 부품 옮김 지킴 시험 — 자체 변종이 부품으로 바뀐 자리 · 가이드 촬영 선택자 · 날 색 없음."""
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
OLD_ALIAS = re.compile(r"--color-(royal-amethyst|deep-iris|mist-violet|plum-velvet|lavender-wash)")
RAW_COLOR = re.compile(r"#[0-9a-fA-F]{3,8}\b|\brgba?\(")
def _src(folder: str, suffix: str) -> dict[str, str]:
return {p.name: p.read_text(encoding="utf-8") for p in (ROOT / folder).glob(f"*{suffix}")}
def _guide_selectors(page: str, prefix: str) -> set[str]:
text = (ROOT / f"A05_EduDetail/guide/{page}/_scenes.json").read_text(encoding="utf-8")
return set(re.findall(rf"\.{re.escape(prefix)}[a-z0-9_-]+", text))
def _defined(folder: str, name: str) -> bool:
body = "".join(_src(folder, ".ts").values()) + "".join(_src(folder, ".css").values())
return name.lstrip(".") in body
def test_b03_guide_selectors_alive():
"""가이드 촬영 장면이 잡는 B03 클래스는 소스에 살아 있어야 함."""
for sel in _guide_selectors("B03", "b03-file__"):
assert _defined("B03_FileInput", sel), f"가이드 선택자 {sel} 가 B03 소스에 없음"
def test_b03_uses_parts():
ts = "".join(_src("B03_FileInput", ".ts").values())
for part in (
"createBanner",
"createProgressBar",
"createToggle",
"createPanelHead",
"createIconButton",
"createText",
):
assert part in ts, f"B03 이 부품 {part} 를 안 씀"
support = _src("B03_FileInput", ".ts")["B03_FileInput_UI_Support.ts"]
assert "<button" not in support # 날 단추 없음 — 단추는 부품이 만듦
assert 'class="ui-card b03-file__card' in support # 파일 카드 = 카드 부품
def test_b03_css_common_values_only():
for name, css in _src("B03_FileInput", ".css").items():
assert not OLD_ALIAS.search(css), f"{name}: 옛 보라 별칭"
body = re.sub(r"/\*.*?\*/", "", css, flags=re.S)
assert not RAW_COLOR.search(body), f"{name}: 날 색"
def test_b02_no_dead_select_and_no_raw_widgets():
css = "".join(_src("B02_ProjRegister", ".css").values())
assert "b02-proj__select" not in css # 안 쓰던 고르기 화살표 CSS — 고르기 부품이 냄
ts = "".join(_src("B02_ProjRegister", ".ts").values())
assert "<button" not in ts and 'createElement("button")' not in ts
+59
View File
@@ -0,0 +1,59 @@
"""553-4 예정노선(D01~D04) · 안정성평가(E01~E07) 부품 옮김 지킴 시험 — 서버 · 화면 없이 소스만 본다.
옮긴 페이지는 날 `<input>` · `<progress>` · 날 색 · 안 쓰는 변종 CSS 가 다시 생기면 안 됨."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def _src(path: str) -> str:
return (ROOT / path).read_text(encoding="utf-8")
def _no_hex(path: str) -> None:
css = re.sub(r"/\*.*?\*/", "", _src(path), flags=re.S)
assert not re.search(r"#[0-9a-fA-F]{3,8}\b", css), f"{path}: 날 색"
def test_d01_station_label_is_text_part():
ts = _src("D01_PlanRegister/D01_PlanRegister_UI_Page.ts")
assert "style.cssText" not in ts
assert 'createText("", "muted")' in ts
assert 'dataset.station = ""' in ts # 시험 · 촬영이 잡음
def test_d02_parts():
ts = _src("D02_PlanBase/D02_PlanBase_UI_Page.ts")
css = _src("D02_PlanBase/D02_PlanBase_UI_Style.css")
assert 'el("input"' not in ts
assert 'createInputField({ type: "number"' in ts
assert 'classList.add("d02-info")' in ts # 촬영 장면이 `p.d02-info` 로 잡음
for dead in (".d02-info", "is-invalid", "font-size: var(--text-caption);\n}\n\n.d02-info"):
assert dead not in css
assert "data-axis" in ts or "dataset.axis" in ts
def test_d03_parts():
ts = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Page.ts")
three = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_3D.ts")
css = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css")
assert 'createElement("progress")' not in ts and "createProgressBar()" in ts
assert "createCard({" in ts and "onSelect:" in ts
assert 'classList.toggle("is-picked"' in ts # 촬영 장면이 `.d03-plan__mode.is-picked` 로 잡음
assert "card.dataset.mode = mode" in ts
assert 'createElement("select")' not in three and "createSelectField" in three
assert 'createElement("input")' not in three and "createCheckbox" in three
for dead in (".d03-plan__bar", ".d03-plan__mode", "is-picked"):
assert dead not in css, dead
def test_d04_parts():
ts = _src("D04_PlanConfirm/D04_PlanConfirm_UI_Page.ts")
css = _src("D04_PlanConfirm/D04_PlanConfirm_UI_Style.css")
assert 'el("input"' not in ts
assert ts.count("createInputField(") == 2 and "createCheckbox(" in ts
assert 'createBanner({ tone: "warning"' in ts
assert "input:checked" in ts # [확정] 이 고른 연도를 읽는 길
assert "border:" not in css.split(".d04-confirm__input")[1].split("}")[0]
assert ".d04-confirm__dirty" not in css
+117
View File
@@ -0,0 +1,117 @@
"""553-7 공개 화면(A01~A09) · 로그인 · B10 · B11 부품 옮김 지킴 시험 — 서버 · 화면 없이 소스로.
· A01~A05 = 공개 화면 틀(createPublicFrame) 한 벌 — 페이지마다 hero · section 복제 CSS 없음
· 카드 = createCard · 알림 = createBanner · 배지 = createBadge · 여러 줄 = createInputField(multiline)
· 페이지 CSS 에 날 색(#hex · rgb · 옛 보라 이름) 없음 · 죽은 A07_Register_UI_Page 없음
"""
from __future__ import annotations
import re
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
PUBLIC_PAGES = {
"A01_Home/A01_Home_UI_Page.ts": ("A01_Home/A01_Home_UI_Style.css", "a01"),
"A02_ProgDetail/A02_ProgDetail_UI_Page.ts": (
"A02_ProgDetail/A02_ProgDetail_UI_Style.css",
"a02",
),
"A03_CompDetail/A03_CompDetail_UI_Page.ts": (
"A03_CompDetail/A03_CompDetail_UI_Style.css",
"a03",
),
"A04_NewsHistory/A04_NewsHistory_UI_Page.ts": (
"A04_NewsHistory/A04_NewsHistory_UI_Style.css",
"a04",
),
"A05_EduDetail/A05_EduDetail_UI_Page.ts": ("A05_EduDetail/A05_EduDetail_UI_Style.css", "a05"),
}
CARD_PAGES = [
"A06_Login/A06_Login_UI_Auth_Page.ts",
"A07_Register/A07_Register_UI_Auth_Page.ts",
"A08_Support/A08_Support_UI_Page.ts",
"A09_Security/A09_Security_UI_Page.ts",
]
CSS_FILES = [css for css, _ in PUBLIC_PAGES.values()] + [
"A06_Login/A06_Login_UI_Style.css",
"A07_Register/A07_Register_UI_Style.css",
"A08_Support/A08_Support_UI_Style.css",
"A09_Security/A09_Security_UI_Style.css",
"B10_Payment/B10_Payment_UI_Style.css",
"B11_Status/B11_Status_UI_Style.css",
]
RAW_COLOR = re.compile(
r"#[0-9a-fA-F]{3,8}\b|rgba?\(|hsla?\(|var\(--(?:b01|draw)-|"
r"var\(--color-(?:deep-iris|mist-violet|slate|charcoal|ash|plum-velvet)\)"
)
def _text(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8")
def _no_comments(css: str) -> str:
return re.sub(r"/\*.*?\*/", "", css, flags=re.S)
@pytest.mark.parametrize("page", PUBLIC_PAGES)
def test_public_page_uses_frame(page: str) -> None:
src = _text(page)
assert "createPublicFrame" in src
for tag in ("hero", "__inner", "__subtitle", "__ctas"):
assert tag not in src, f"{page}: 손 hero 조각 {tag} 남음"
@pytest.mark.parametrize("page", PUBLIC_PAGES)
def test_public_css_has_no_hero_copy(page: str) -> None:
css = _no_comments(_text(PUBLIC_PAGES[page][0]))
prefix = PUBLIC_PAGES[page][1]
assert f".{prefix}-hero" not in css
assert f".{prefix}-section" not in css
@pytest.mark.parametrize("page", CARD_PAGES)
def test_card_pages_use_card_part(page: str) -> None:
assert "createCard" in _text(page)
@pytest.mark.parametrize("css", CSS_FILES)
def test_page_css_no_raw_color(css: str) -> None:
assert not RAW_COLOR.search(_no_comments(_text(css)))
def test_a07_agreement_uses_checkbox_and_collapsible() -> None:
src = _text("A07_Register/A07_Register_UI_Auth_Page.ts")
assert "createCheckbox" in src and "attachCollapsible" in src
assert "<details" not in src and 'createElement("details")' not in src
assert 'type = "checkbox"' not in src
def test_a08_uses_banner_and_multiline_input() -> None:
src = _text("A08_Support/A08_Support_UI_Page.ts")
assert "createBanner" in src and "multiline" in src
assert "createTextAreaField" not in src and 'createElement("textarea")' not in src
def test_b10_b11_use_parts() -> None:
b10 = _text("B10_Payment/B10_Payment_UI_Page.ts")
b11 = _text("B11_Status/B11_Status_UI_Page.ts")
assert "createBanner" in b10 and "createBadge" in b10 and "createTag" not in b10
assert "createCard" in b11 and "createBadge" in b11 and "createTag" not in b11
assert ".b10-payment__note" not in _text("B10_Payment/B10_Payment_UI_Style.css")
assert ".b11-status__step {" not in _text("B11_Status/B11_Status_UI_Style.css")
def test_dead_register_page_gone() -> None:
assert not (ROOT / "A07_Register/A07_Register_UI_Page.ts").exists()
def test_public_frame_wide_option() -> None:
ts = _text("ui_template/ui_template_public_layout.ts")
css = _text("ui_template/ui_template_public_layout.css")
assert "wide?: boolean" in ts
assert "[data-wide] .ui-public-frame__title" in css