Files
C.E.L_Slide_test2/tests/test_phase_z2_slide_css_smoke.py
kyeongmin 9062931863
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 22s
feat(#74): IMP-45 u1~u8 slide-level CSS override (frontmatter slide_overrides.css + --override-slide-css/--slide-css-file + idempotent Step 13 injector)
u1 KNOWN_AXES tuple gains slide_css entry in src/user_overrides_io.py
(snake_case parity with image_overrides); round-trip test extends
to 6 axes.
u2 src/mdx_normalizer.py surfaces nested slide_overrides.css from the
MDX frontmatter into the normalize_mdx_content return dict; absent
key -> {}, non-string css drops. 4 unit cases in tests/test_mdx_normalizer.py
(present / absent / non-string / title-only).
u3 src/slide_css_injector.py NEW (88 lines) mirrors the
inject_image_overrides_style contract from src/image_id_stamper.py:
marker pair <!--IMP45-SLIDE-CSS:OPEN--> / <!--IMP45-SLIDE-CSS:CLOSE-->,
idempotent re-injection, </head> > <body> > document-start three-tier
fallback, empty/None -> unchanged. 8 fixtures in
tests/test_slide_css_injector.py mirror test_image_id_stamper.py.
u4 run_phase_z2_mvp1 accepts override_slide_css: Optional[str] = None;
None -> frontmatter slide_overrides.css fallback. Step 13 calls
inject_slide_css after image override injection and before the
final.html disk write, so CLI/CI/regression renders observe the same
backend artifact.
u5 argparse adds mutually-exclusive --override-slide-css TEXT (inline
CSS, <style> wrapper optional) and --slide-css-file PATH (UTF-8 read,
fail-closed sys.exit(2) on missing path / decode error / both flags
present). Resolved string is forwarded as override_slide_css kwarg.
6 cases in tests/test_phase_z2_cli_overrides.py (inline / file / both
/ missing / non-utf8 / neither).
u6 samples/mdx_batch/04.mdx frontmatter gains slide_overrides.css
block (verbatim of the former MDX04_DEFAULT_OVERRIDE_CSS constant,
no sample/frame gate). Subprocess smoke in
tests/test_phase_z2_slide_css_smoke.py verifies the marker pair and
CSS substring land in final.html.
u7 Front/client removes the sample/frame-gated frontend-only injection:
Home.tsx drops the MDX04_DEFAULT_OVERRIDE_CSS constant and the
sample==="04"+frame==="process_product_two_way" branch (-28 lines);
SlideCanvas.tsx drops the iframe contentDocument.head injection of
that prop (-14 lines). Live preview now reads backend final.html only.
u8 tests/regression/fixtures/89a_pre_baseline_sha.json 04.mdx entry
resyncs to the live SHA ddb6bf2f... / 28042 bytes (overwrites the
earlier 5-byte-drift d02c76fd... / 28047). Other entries untouched.
Note: 01.mdx baseline drift (ad6f16a3... / 29089 -> live f26a7fac...
/ 29084) predates this branch and is split to a follow-up issue per
the closed-issue fresh validation rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:26:03 +09:00

102 lines
4.1 KiB
Python

"""IMP-45 (#74) u6 — subprocess smoke for the slide-level CSS override axis.
End-to-end guard that the ``slide_overrides.css`` frontmatter axis added
in u2 propagates through the pipeline (u4) and lands in ``final.html``
via :func:`src.slide_css_injector.inject_slide_css` (u3).
The fixture is the new ``slide_overrides.css`` frontmatter block in
``samples/mdx_batch/04.mdx`` (u6 migration of the legacy frontend-only
``MDX04_DEFAULT_OVERRIDE_CSS`` constant). The pipeline is spawned via
``python -m src.phase_z2_pipeline 04.mdx <run_id>`` so the assertion
applies to the on-disk artifact CI / CLI / regression all observe, not
to a live iframe view.
The subprocess returncode is intentionally NOT asserted: mdx04 has known
downstream issues (see ``test_pipeline_smoke_imp85.py`` —
``test_mdx04_no_longer_emits_imp85_crash_signature``) that are tracked
on a separate axis. Step 13 runs before the downstream failure surface,
so ``final.html`` is written with the injected slide CSS marker
regardless. The test asserts ``final.html`` exists and contains both
the IMP-45 sentinel marker and a distinctive CSS substring from the
migrated frontmatter block.
"""
from __future__ import annotations
import subprocess
import sys
import uuid
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SAMPLES_DIR = REPO_ROOT / "samples" / "mdx_batch"
RUNS_DIR = REPO_ROOT / "data" / "runs"
# IMP-45 (#74) u3 marker sentinel emitted by
# :func:`src.slide_css_injector.inject_slide_css` around the injected
# ``<style>`` block. Match the ``_IMP45_STYLE_MARKER_OPEN`` constant in
# ``src/slide_css_injector.py`` byte-for-byte.
IMP45_OPEN_MARKER = "<!--IMP45-SLIDE-CSS:OPEN-->"
IMP45_CLOSE_MARKER = "<!--IMP45-SLIDE-CSS:CLOSE-->"
# Distinctive substring from the migrated frontmatter block in
# ``samples/mdx_batch/04.mdx``. ``f29b__cell:nth-child(n+3)`` is unique
# to the MDX04 slide-level override CSS and does not appear elsewhere in
# the slide_base / partial templates, so its presence in ``final.html``
# is sufficient evidence that the frontmatter axis fed the injector.
MDX04_DISTINCTIVE_CSS_SUBSTRING = ".f29b__cell:nth-child(n+3)"
def _run_pipeline(mdx_name: str, run_id: str, timeout: int = 240) -> subprocess.CompletedProcess:
"""Spawn ``python -m src.phase_z2_pipeline <mdx> <run_id>``."""
return subprocess.run(
[
sys.executable,
"-m",
"src.phase_z2_pipeline",
str(SAMPLES_DIR / mdx_name),
run_id,
],
capture_output=True,
text=True,
timeout=timeout,
cwd=str(REPO_ROOT),
)
def _unique_run_id(prefix: str) -> str:
return f"{prefix}_imp45_slide_css_smoke_{uuid.uuid4().hex[:8]}"
def test_mdx04_slide_overrides_css_lands_in_final_html() -> None:
"""mdx04 ``slide_overrides.css`` frontmatter must reach ``final.html``.
Contract pins both the IMP-45 marker sentinel and a distinctive CSS
substring from the migrated frontmatter block so a regression in
either the frontmatter extractor (u2), the kwarg forwarding (u4),
or the injector (u3) is caught by this single smoke.
"""
run_id = _unique_run_id("mdx04")
cp = _run_pipeline("04.mdx", run_id)
final_html_path = RUNS_DIR / run_id / "phase_z2" / "final.html"
assert final_html_path.is_file(), (
f"final.html not found at {final_html_path}\n"
f"--- stderr tail ---\n{cp.stderr[-1500:]}\n"
f"--- stdout tail ---\n{cp.stdout[-1500:]}"
)
html = final_html_path.read_text(encoding="utf-8")
assert IMP45_OPEN_MARKER in html, (
f"IMP-45 open marker missing from mdx04 final.html ({final_html_path}).\n"
f"slide_overrides.css frontmatter axis did not reach the injector."
)
assert IMP45_CLOSE_MARKER in html, (
f"IMP-45 close marker missing from mdx04 final.html ({final_html_path}).\n"
f"Marker wrap appears unbalanced — check inject_slide_css() output."
)
assert MDX04_DISTINCTIVE_CSS_SUBSTRING in html, (
f"Migrated frontmatter CSS substring "
f"{MDX04_DISTINCTIVE_CSS_SUBSTRING!r} missing from mdx04 final.html "
f"({final_html_path}). The slide_overrides.css block did not propagate."
)