"""IMP-35 (#64) u10 — MDX preservation guard tests. Stage 2 binding contract (unit u10): After Step 17 POPUP gate (u5) stamps the unit, composition (u6) binds the strategy, pipeline (u7) wires the render context, and slide_base (u8) renders the ``
/`` wrapper, the end-to-end invariant the user lock requires is: MDX 원문 무손실 보존 (오답노트 #5 / IMPROVEMENT-REDESIGN.md §3.6 line 110, CLAUDE.md 자세히보기 원칙): - popup body == FULL ``raw_content`` (byte-for-byte verbatim) - body preview == SUBSET of ``raw_content`` (deterministic leading-substring CUT — never a rewrite, never a re-summary) - the original is ALWAYS reachable via the popup; the preview loses no information because the popup holds the full source - no structural element is dropped: text_block / table / image / ``
`` counts in popup body match the original u6 and u7 each lock pieces of this invariant on their own surface. u10 locks the END-TO-END no-content-drop guarantee on the rendered payload — the surface a downstream verifier (Selenium / vision gate) would inspect — so a future refactor on either u6 or u7 cannot silently degrade MDX preservation without this test failing first. Key invariants this file locks: 1. popup_html (full source) preserves every structural element from raw_content byte-for-byte: bullet lines, paragraph blocks, markdown table rows, image markdown, and nested ``
`` blocks. 2. preview_text is a deterministic leading-substring CUT of raw_content — ``raw_content.startswith(preview_text)`` holds when truncation happened. 3. Combined invariant: popup_html holds the FULL original even when preview_text is shorter, so no content is dropped — the full source is always reachable via the popup. 4. has_popup=False path: popup_html / preview_text are both None. There is no popup escalation, so by definition no escalation can drop content; the frame's partial_html (rendered separately by slide_base.html and not part of u7 popup wiring) holds the inline body. 5. AI isolation contract — pure deterministic preservation check; no anthropic import, no route_ai_fallback path. Cross-references: - u6 composition popup binding (popup_body_source = full raw_content): tests/phase_z2/test_composition_popup_strategy.py - u7 pipeline wiring (popup_html = popup_body_source verbatim; preview_text is a deterministic line-budget cut): tests/phase_z2/test_phase_z2_pipeline_popup_wiring.py - u8 slide_base.html render surface (autoescaped popup body): tests/phase_z2/test_slide_base_popup_render.py - u9 display_strategies.yaml catalog (preserves_original=True for the popup-bearing strategy): tests/phase_z2/test_display_strategies_popup.py """ from __future__ import annotations import re from dataclasses import dataclass from typing import Optional import pytest from src.phase_z2_composition import compose_zone_popup_payload # ─── Synthetic stubs ───────────────────────────────────────────────── @dataclass class _StubUnit: """Minimal duck-typed CompositionUnit for u10 preservation tests.""" raw_content: str = "MOCK_ORIGINAL_CONTENT" has_popup: bool = False popup_escalation_plan: Optional[dict] = None def _stub_popup_plan() -> dict: """Mirror the plan_details_popup_escalation feasible-escalation shape (u3). u10 only echoes the plan into the unit so the binder reaches the popup branch; no field is consumed here.""" return { "action": "details_popup_escalation", "stub": True, "feasible": True, "category": "structural_major_overflow", "needs_split_decision": True, "rationale": "MOCK_RATIONALE", "mapping_source": "IMP-35 u3 plan_details_popup_escalation stub", } # ─── Deterministic structural-element counters ────────────────────── def _count_markdown_bullet_lines(text: str) -> int: """Count leading-``-`` markdown bullet lines (- / * / + at line start).""" return sum( 1 for line in text.splitlines() if re.match(r"^\s*[-*+]\s+", line) ) def _count_markdown_table_rows(text: str) -> int: """Count markdown table rows (lines with ``|`` somewhere).""" return sum(1 for line in text.splitlines() if "|" in line) def _count_markdown_images(text: str) -> int: """Count markdown image references ``![...](...)``.""" return len(re.findall(r"!\[[^\]]*\]\([^)]+\)", text)) def _count_details_blocks(text: str) -> int: """Count nested ``
`` blocks in raw_content (rare — used to lock the invariant even when MDX already carries native popups).""" return len(re.findall(r"MOCK_NESTED_TRIGGER" "

MOCK_NESTED_BODY

\n" "\n" "Paragraph two — closing remarks for the MOCK topic.\n" ) # ─── Popup body = full raw_content (byte-for-byte) ─────────────────── def test_popup_body_byte_for_byte_equal_to_raw_content(): """u10 — the end-to-end invariant: popup_html on the rendered payload is byte-for-byte equal to the unit's raw_content. u6 + u7 already lock this on their own surface; u10 re-asserts on the payload a downstream verifier (Selenium / vision gate) would inspect.""" unit = _StubUnit( raw_content=_FULL_MDX_SAMPLE, has_popup=True, popup_escalation_plan=_stub_popup_plan(), ) payload = compose_zone_popup_payload(unit, container_height_px=200) assert payload["popup_html"] == _FULL_MDX_SAMPLE assert len(payload["popup_html"]) == len(_FULL_MDX_SAMPLE) def test_popup_body_preserves_bullet_line_count(): """u10 — text_block count equality. Every bullet line present in raw_content MUST also be present in popup_html. A future refactor that accidentally trims popup body to a summary would drop bullets and fail this guard.""" unit = _StubUnit( raw_content=_FULL_MDX_SAMPLE, has_popup=True, popup_escalation_plan=_stub_popup_plan(), ) payload = compose_zone_popup_payload(unit, container_height_px=200) assert _count_markdown_bullet_lines(payload["popup_html"]) == ( _count_markdown_bullet_lines(_FULL_MDX_SAMPLE) ) def test_popup_body_preserves_markdown_table_row_count(): """u10 — table count equality. Markdown table rows (header / divider / data) MUST all survive the popup wiring.""" unit = _StubUnit( raw_content=_FULL_MDX_SAMPLE, has_popup=True, popup_escalation_plan=_stub_popup_plan(), ) payload = compose_zone_popup_payload(unit, container_height_px=200) assert _count_markdown_table_rows(payload["popup_html"]) == ( _count_markdown_table_rows(_FULL_MDX_SAMPLE) ) def test_popup_body_preserves_image_reference_count(): """u10 — image count equality. Markdown ``![alt](src)`` references MUST all survive (CLAUDE.md: 이미지는 원본 그대로 사용, 크기만 조절 — popup escalation must not silently drop image refs).""" unit = _StubUnit( raw_content=_FULL_MDX_SAMPLE, has_popup=True, popup_escalation_plan=_stub_popup_plan(), ) payload = compose_zone_popup_payload(unit, container_height_px=200) assert _count_markdown_images(payload["popup_html"]) == ( _count_markdown_images(_FULL_MDX_SAMPLE) ) def test_popup_body_preserves_nested_details_block_count(): """u10 — nested ``
`` blocks. Even when MDX already carries a native popup, the u10 popup escalation MUST NOT collapse or drop nested ``
`` markers.""" unit = _StubUnit( raw_content=_FULL_MDX_SAMPLE, has_popup=True, popup_escalation_plan=_stub_popup_plan(), ) payload = compose_zone_popup_payload(unit, container_height_px=200) assert _count_details_blocks(payload["popup_html"]) == ( _count_details_blocks(_FULL_MDX_SAMPLE) ) # ─── Preview = deterministic leading-substring CUT ────────────────── def test_preview_text_is_a_leading_substring_of_raw_content_when_truncated(): """u10 — preview is a CUT, never a rewrite. When truncation happens, raw_content MUST start with preview_text verbatim (line-boundary cut semantics; popup body retains the FULL original).""" unit = _StubUnit( raw_content=_FULL_MDX_SAMPLE, has_popup=True, popup_escalation_plan=_stub_popup_plan(), ) # 2-line budget — far smaller than the multi-line sample. payload = compose_zone_popup_payload(unit, container_height_px=36) preview = payload["preview_text"] assert preview, "preview_text must be non-empty when truncation fires" assert _FULL_MDX_SAMPLE.startswith(preview), ( "preview_text must be a leading-substring of raw_content " "(MDX 원문 무손실 보존 — preview is a CUT, never a rewrite)." ) # The popup body still holds the FULL original — no information loss. assert payload["popup_html"] == _FULL_MDX_SAMPLE def test_no_content_drop_when_preview_is_shorter_than_popup_body(): """u10 — combined no-drop invariant. preview_text may be a strict prefix of popup_html (shorter), but the popup body always holds the full original. The user can always reach every line of the source via the popup, even when the inline preview shows only the head.""" unit = _StubUnit( raw_content=_FULL_MDX_SAMPLE, has_popup=True, popup_escalation_plan=_stub_popup_plan(), ) payload = compose_zone_popup_payload(unit, container_height_px=36) preview = payload["preview_text"] popup_body = payload["popup_html"] # preview is strictly shorter when truncation fires. assert len(preview) < len(popup_body) # popup_body is the FULL original — every line of raw_content is # present in popup_body regardless of the inline preview budget. for line in _FULL_MDX_SAMPLE.splitlines(): assert line in popup_body, ( f"MDX preservation guard violated — line {line!r} not present " f"in popup body." ) # ─── has_popup=False path: no popup, no escalation, no drop ───────── def test_no_popup_path_yields_no_popup_html_no_preview_text(): """u10 — when the Step 17 POPUP gate did not fire, no popup escalation happens. popup_html and preview_text are both None. By construction this branch cannot drop content (no escalation), and the frame's partial_html (rendered separately by slide_base and not part of u7 popup wiring) holds the inline body.""" unit = _StubUnit(raw_content=_FULL_MDX_SAMPLE, has_popup=False) payload = compose_zone_popup_payload(unit, container_height_px=200) assert payload["has_popup"] is False assert payload["popup_html"] is None assert payload["preview_text"] is None # ─── AI isolation contract (structural import lock) ───────────────── def test_popup_mdx_preservation_module_has_no_ai_imports(): """u10 — preservation guard MUST stay AI-free. Structural guard: composition module (where compose_zone_popup_payload lives) is allowed to consult the catalog and unit state, never the Anthropic SDK / route_ai_fallback path. Mirrors u6 / u7 import-isolation pattern (feedback_ai_isolation_contract).""" import src.phase_z2_composition as composition_module source = composition_module.__file__ assert source is not None with open(source, encoding="utf-8") as f: text = f.read() assert "import anthropic" not in text assert "from anthropic" not in text assert "route_ai_fallback" not in text