sabaridsnfuji/bco-repro-artifacts / repro-bundle /scripts /posterly /tests /test_intercard_gap_integration.py
sabaridsnfuji's picture
download
raw
7.96 kB
"""Chromium-gated integration tests for the intra-column whitespace gate
in ``measure``.
Real-world failure this pins (Paper2Poster pilot, 2026-06-04): an agent
hit the column-bottom spread gate by setting ``justify-content:
space-between`` on under-filled columns -- the first card pins to the
top, the last card pins to the bottom, spread reads 0.00 px, the
footer gap lands in band, and measure PASSes while a 98-135 px void sits
mid-column (design row-gap was 22.7 px). polish's space-between check is
a SOFT warn with a relative threshold (5% of column height ~= 117 px on
a 36in poster), so it stayed silent too. The void is plainly visible in
print; a human caught it, the gates did not.
The fix: measure hard-fails when the gap between consecutive stacked
cards (after grouping side-by-side cards into rows) exceeds
``--max-intercard-gap`` (default 50 px, same ceiling as the footer gap).
Verifies the gate FIRES on the space-between void, does NOT fire on a
normally-flowed column with design-sized gaps, and does NOT false-fire
on side-by-side cards sharing a row.
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), one flex
# column, two fixed-height cards that together under-fill the column.
# `justify_css` is the only variable: space-between stretches the slack
# into a mid-column void; normal flow + flex-grow on the last card fills
# the column with content instead. The footer-strip's 40 px margin keeps
# the footer gap in band either way, so the new gate is what the
# assertions isolate.
def _poster(justify_css: str, grow_last: bool) -> str:
grow = "flex-grow: 1;" if grow_last else ""
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;
gap: 24px; {justify_css} }}
.card {{ border: 2px solid #888; padding: 20px; height: 1200px; }}
.card.last {{ {grow} }}
.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"><p>Card one.</p></div>
<div class="card last" data-measure-role="card"><p>Card two.</p></div>
</div>
<div class="footer-strip" data-measure-role="footer-strip"></div>
</div>
</body></html>
"""
# Same canvas, one column whose first "row" is two half-width cards side
# by side (a row wrapper WITHOUT a measure role, so both cards map to the
# column) followed by a full-width card. Vertical-overlap grouping must
# treat the pair as ONE row: the only inter-row gaps are the design 24 px,
# so the gate must stay silent.
_SIDE_BY_SIDE = """<!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; gap: 24px; }
.row { display: flex; gap: 24px; }
.row .card { flex: 1; height: 900px; }
.card { border: 2px solid #888; padding: 20px; }
.card.wide { flex-grow: 1; }
.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="row">
<div class="card" data-measure-role="card"><p>Left.</p></div>
<div class="card" data-measure-role="card"><p>Right.</p></div>
</div>
<div class="card wide" data-measure-role="card"><p>Below.</p></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,
max_intercard_gap=50.0, min_intercard_gap=12.0, json_out=None,
)
def _run(tmp_path, capsys, html: str):
poster = tmp_path / "poster.html"
poster.write_text(html, encoding="utf-8")
rc = _measure.cmd_measure(_args(poster))
return rc, "".join(capsys.readouterr())
def test_space_between_void_fires(tmp_path, capsys) -> None:
# Two 1200 px cards in a ~3296 px column with space-between -> a
# ~700-900 px mid-column void while spread = 0 and the footer gap is
# in band. The gate must hard-fail and tell the author to fill the
# slack with content, not stretched whitespace.
rc, out = _run(
tmp_path, capsys,
_poster("justify-content: space-between;", grow_last=False),
)
assert rc == 1
assert "intercard" in out or "between stacked cards" in out
assert "space-between" in out # the fix hint names the culprit
def test_filled_column_passes(tmp_path, capsys) -> None:
# Same two cards, normal flow, last card flex-grows to absorb the
# slack -> the only inter-card gap is the design 24 px. PASS, and no
# intercard complaint.
rc, out = _run(tmp_path, capsys, _poster("", grow_last=True))
assert rc == 0
assert "PASS" in out
assert "between stacked cards" not in out
def test_side_by_side_cards_no_false_positive(tmp_path, capsys) -> None:
# Two cards sharing a row must be grouped as one row, not read as a
# negative/huge "gap" pair; the column's real inter-row gap is 24 px.
rc, out = _run(tmp_path, capsys, _SIDE_BY_SIDE)
assert rc == 0
assert "between stacked cards" not in out
def test_cards_too_tight_fires(tmp_path, capsys) -> None:
# The flip side of the void: cards nearly touching. The shipped card
# shadow is `0 2u 6u` (offset ~7.6 px + blur ~22.7 px in print), so a
# gap under ~12 px buries the shadow core under the next card and the
# stack reads as one fused slab. Two cards at a 4 px gap (the second
# flex-grows so the footer gap stays in band) must hard-fail with the
# shadow rationale.
html = _poster("", grow_last=True).replace("gap: 24px;", "gap: 4px;")
rc, out = _run(tmp_path, capsys, html)
assert rc == 1
assert "too tight" in out
assert "shadow" in out
def test_intercard_gaps_row_grouping_pure() -> None:
# Pure-function check (no Chromium): side-by-side cards chain into one
# row; gaps are measured between row bands.
cards = [
{"y": 0.0, "bottom": 900.0}, # row 1, left
{"y": 0.0, "bottom": 880.0}, # row 1, right (shorter)
{"y": 924.0, "bottom": 1800.0}, # row 2 -> gap 24 from row1 max
{"y": 1922.0, "bottom": 2400.0}, # row 3 -> gap 122 (the void)
]
gaps = _measure.intercard_gaps(cards)
assert [round(g, 1) for g in gaps] == [24.0, 122.0]

Xet Storage Details

Size:
7.96 kB
·
Xet hash:
a5a42f7fb123775cee877f4dee3341baaae505f0be8d8c39d55799395e4c6f5a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.