feat(IMP-14): A-4 — slide_base embedded vs standalone mode contract

Step 13 owns iframe-vs-standalone CSS contract in slide_base.html via
3-valued embedded_mode enum (auto / embedded / standalone). Removes
SlideCanvas.tsx runtime CSS injection workaround; frontend now passes
?embedded=1 query so auto-mode script attaches html.embedded class and
scopes the standalone body centering/min-height/padding reset.

- templates/phase_z2/slide_base.html: conditional html.embedded class +
  CSP-safe auto-mode <script> + additive html.embedded body/.slide rules
- src/phase_z2_pipeline.py: render_slide gains keyword-only embedded_mode
  ("auto" default) + ValueError guard; 3 existing call sites unchanged
- Front/client/src/components/SlideCanvas.tsx: derive embeddedSrc with
  ?embedded=1 (query-preserving), drop reset CSS injection block
- tests/phase_z2/test_slide_base_embedded_mode.py: 6 cases — auto script,
  CSS rules, embedded/standalone explicit modes, byte-determinism,
  invalid-mode guard
This commit is contained in:
2026-05-18 07:21:31 +09:00
parent 7d5639ad72
commit 7a52cebfaa
4 changed files with 120 additions and 24 deletions

View File

@@ -0,0 +1,69 @@
"""IMP-14 A-4 — slide_base.html embedded_mode contract tests.
Asserts the three-valued enum (auto / embedded / standalone) round-trips
through render_slide -> slide_base.html, that the additive html.embedded
CSS reset and the auto-mode detection <script> are emitted under the
correct modes, that the invalid-mode guard raises ValueError, and that
Jinja2 rendering is byte-deterministic across calls.
"""
from __future__ import annotations
import pytest
from src.phase_z2_pipeline import render_slide
def _zone() -> dict:
return {"position": "primary", "template_id": "__empty__", "slot_payload": {}}
def _layout_css() -> dict:
return {"areas": '"primary"', "cols": "1fr", "rows": "1fr"}
def _render(embedded_mode: str = "auto") -> str:
return render_slide(
slide_title="t",
slide_footer=None,
zones_data=[_zone()],
layout_preset="single",
layout_css=_layout_css(),
gap_px=14,
embedded_mode=embedded_mode,
)
def test_auto_script_present():
html = _render("auto")
assert "params.get('embedded')" in html
assert "window.self !== window.top" in html
assert "classList.add('embedded')" in html
def test_css_rules_present():
html = _render("auto")
assert "html.embedded body" in html
assert "html.embedded .slide" in html
def test_embedded_mode_explicit():
html = _render("embedded")
assert '<html lang="ko" class="embedded">' in html
assert "params.get('embedded')" not in html
def test_standalone_mode_explicit():
html = _render("standalone")
assert '<html lang="ko">' in html
assert 'class="embedded"' not in html.split("</head>")[0]
assert "params.get('embedded')" not in html
def test_deterministic():
assert _render("embedded") == _render("embedded")
assert _render("auto") == _render("auto")
def test_invalid_mode_raises():
with pytest.raises(ValueError, match="invalid embedded_mode"):
_render("bogus")