Buckets:
sabaridsnfuji/bco-repro-artifacts / repro-bundle /scripts /posterly /tests /test_clip_integration.py
| """Chromium-gated integration test for the content-clipping gate in | |
| ``measure`` (``_MEASURE_JS`` + the scroll-vs-client check in ``cmd_measure``). | |
| ``measure`` reads each element's border-box bottom to judge alignment. A | |
| flex card/column with ``overflow: hidden`` (or ``clip``/``scroll``/``auto``) | |
| decouples that box from the real content extent: the spec flips its | |
| ``min-height: auto`` to ``0``, so flexbox shrinks the over-full item back | |
| inside the column and clips the overflow. The box then looks perfectly | |
| aligned, ``measure`` reports a clean gap, and ``polish``'s CARD/TRAILING | |
| check sees no blank space -- yet the content past the edge is silently lost | |
| in print. This is the bug the gate exists to catch. | |
| The check is render-time (computed ``overflow`` + ``scrollHeight`` vs | |
| ``clientHeight``), so it can only be exercised against a real headless | |
| Chromium, not the mocked unit suite. | |
| Verifies the gate FIRES on the clipped case, does NOT fire when the same | |
| ``overflow: hidden`` card's content actually fits (no false positive), and | |
| does NOT fire on a visible overflow -- that path is left to the existing | |
| gap gate, which the test confirms still catches it. | |
| Skipped when Playwright/Chromium isn't installed. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import pytest | |
| from _posterly import measure as _measure | |
| def _chromium_available() -> bool: | |
| try: | |
| from playwright.sync_api import sync_playwright | |
| with sync_playwright() as p: | |
| browser = p.chromium.launch() | |
| browser.close() | |
| return True | |
| except Exception: | |
| return False | |
| pytestmark = pytest.mark.skipif( | |
| not _chromium_available(), | |
| reason="playwright + chromium not available", | |
| ) | |
| # Full-bleed 24x36in poster (viewport 2304x3456 px at 96 ppi) so it clears | |
| # measure's canvas-fill + position gates; a single flex column + flex card | |
| # keeps spread = 0; the footer-strip's 40 px top margin puts the gap in the | |
| # [30, 50] band. The card's `overflow` rule and content length are the only | |
| # variables -- everything else is held constant so the clip gate is what the | |
| # assertions isolate. | |
| def _poster(overflow_css: str, paragraphs: int) -> str: | |
| para = ("<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, " | |
| "sed do eiusmod tempor incididunt ut labore.</p>") | |
| content = para * paragraphs | |
| return f"""<!DOCTYPE html> | |
| <html><head><meta charset="utf-8"> | |
| <style> | |
| @page {{ size: 24in 36in; margin: 0; }} | |
| * {{ margin: 0; padding: 0; box-sizing: border-box; }} | |
| html, body {{ width: 100%; height: 100%; background: #fff; }} | |
| .poster {{ width: 24in; height: 36in; background: #fff; | |
| display: flex; flex-direction: column; padding: 60px; }} | |
| .column {{ flex: 1; display: flex; flex-direction: column; min-height: 0; }} | |
| .card {{ flex: 1; border: 2px solid #888; padding: 20px; | |
| display: flex; flex-direction: column; {overflow_css} }} | |
| .footer-strip {{ height: 160px; margin-top: 40px; background: #233; }} | |
| p {{ font-size: 28px; line-height: 1.4; }} | |
| </style></head> | |
| <body> | |
| <div class="poster" data-measure-role="poster"> | |
| <div class="column" data-measure-role="column"> | |
| <div class="card" data-measure-role="card"> | |
| {content} | |
| </div> | |
| </div> | |
| <div class="footer-strip" data-measure-role="footer-strip"></div> | |
| </div> | |
| </body></html> | |
| """ | |
| def _args(html) -> argparse.Namespace: | |
| return argparse.Namespace( | |
| html=str(html), canvas=None, | |
| max_spread=5.0, min_gap=30.0, max_gap=50.0, | |
| allow_empty_column=False, allow_no_footer_gap=False, | |
| settle_ms=200, mathjax_timeout_ms=5000, | |
| min_canvas_fill=0.95, max_canvas_fill=1.01, | |
| position_tol_px=2.0, max_clip_px=2.0, json_out=None, | |
| ) | |
| def _run(tmp_path, capsys, overflow_css: str, paragraphs: int): | |
| poster = tmp_path / "poster.html" | |
| poster.write_text(_poster(overflow_css, paragraphs), encoding="utf-8") | |
| rc = _measure.cmd_measure(_args(poster)) | |
| return rc, "".join(capsys.readouterr()) | |
| def test_overflow_hidden_clip_fires(tmp_path, capsys) -> None: | |
| # overflow:hidden + far too much content -> flexbox shrinks the card and | |
| # clips ~thousands of px; the gate must hard-fail and name the clip. | |
| rc, out = _run(tmp_path, capsys, "overflow: hidden;", paragraphs=80) | |
| assert rc == 1 | |
| assert "CLIPPED" in out | |
| assert "overflow-y: hidden" in out | |
| def test_content_fits_no_false_positive(tmp_path, capsys) -> None: | |
| # Same overflow:hidden card, but the content easily fits -> scrollHeight | |
| # <= clientHeight, so the clip gate must stay silent and measure PASSes. | |
| rc, out = _run(tmp_path, capsys, "overflow: hidden;", paragraphs=1) | |
| assert rc == 0 | |
| assert "PASS" in out | |
| assert "CLIPPED" not in out | |
| def test_visible_overflow_is_gap_failure_not_clip(tmp_path, capsys) -> None: | |
| # Same over-full content but overflow:visible -> the card spills VISIBLY, | |
| # so the clip gate must NOT fire; the existing gap gate catches it via a | |
| # negative gap. This pins the boundary between the two gates. | |
| rc, out = _run(tmp_path, capsys, "", paragraphs=80) | |
| assert rc == 1 | |
| assert "CLIPPED" not in out | |
| assert "min gap" in out | |
| # A hero panel feeds spread/gap just like a card/column, and the shipped | |
| # hero template's `.hero` is itself `overflow: hidden` -- so the same clip | |
| # trap applies and the gate must cover the `hero` role too. | |
| def _hero_poster(paragraphs: int) -> str: | |
| para = ("<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, " | |
| "sed do eiusmod tempor incididunt ut labore.</p>") | |
| return f"""<!DOCTYPE html> | |
| <html><head><meta charset="utf-8"> | |
| <style> | |
| @page {{ size: 24in 36in; margin: 0; }} | |
| * {{ margin: 0; padding: 0; box-sizing: border-box; }} | |
| html, body {{ width: 100%; height: 100%; background: #fff; }} | |
| .poster {{ width: 24in; height: 36in; background: #fff; | |
| display: flex; flex-direction: column; padding: 60px; }} | |
| .hero {{ flex: 1; border: 2px solid #888; padding: 20px; | |
| display: flex; flex-direction: column; overflow: hidden; }} | |
| .footer-strip {{ height: 160px; margin-top: 40px; background: #233; }} | |
| p {{ font-size: 28px; line-height: 1.4; }} | |
| </style></head> | |
| <body> | |
| <div class="poster" data-measure-role="poster"> | |
| <section class="hero" data-measure-role="hero"> | |
| {para * paragraphs} | |
| </section> | |
| <div class="footer-strip" data-measure-role="footer-strip"></div> | |
| </div> | |
| </body></html> | |
| """ | |
| def test_hero_overflow_hidden_clip_fires(tmp_path, capsys) -> None: | |
| # A flex:1 hero with overflow:hidden + far too much content clips just | |
| # like a card -- the gate must fire (hero is in scope). | |
| poster = tmp_path / "poster.html" | |
| poster.write_text(_hero_poster(80), encoding="utf-8") | |
| rc = _measure.cmd_measure(_args(poster)) | |
| out = "".join(capsys.readouterr()) | |
| assert rc == 1 | |
| assert "CLIPPED" in out | |
| assert "hero" in out | |
Xet Storage Details
- Size:
- 6.92 kB
- Xet hash:
- 05203c437d5805692d7edfd1098cd67f01d4d97a78980adc3b1c645e59b5142e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.