Skip to content

Commit 1b9657f

Browse files
committed
Three more forward-locking contracts for the figure system
Continuing the audit pass, three more invariants currently hold but aren't asserted. Lock them in so a future change can't silently break the registry without CI catching it. Contract 6 — anchor coverage (FigureAnchorContract): every cell-N attachment anchor in ATTACHMENTS resolves to a real cell in the example's walkthrough. Catches typos and stale attachments left behind after a markdown example loses a cell. Contract 7 — score validity (FigureScoreContract): every SCORES entry is a (number-in-[0,10], non-empty commentary) tuple. Locks the rubric range and catches type drift if the dict shape changes. Contract 8 — banner-fit (FigureSizeContract): every figure's intrinsic width (Canvas.w + 2 * PAD_X) fits the 440px ceiling that .cell-banner--1 enforces in public/site.css. A figure that exceeds the ceiling renders scaled-down on every page, magnifying or shrinking text in ways the paint code didn't plan for. All three pass on the current codebase; they exist to prevent regressions, not to fix anything today. Suite is now 52 tests. Sweeps that found nothing actionable (no contract added) - prose duplication: 3 hits were diagrammatic labels (`__getattr__`, `yield from inner`, `execution continues`) that naturally appear in the caption too; not the prose-in-SVG anti-pattern the v2 rubric bans. - text crossing lines: 8 hits were intentional (dashed strikes through `.append`, signature dividers, struck-through erased names, type-alias old name). - text overlapping circles: 10 hits were node labels (the `?` in branch-fork, `in`/`out` in context-bowtie, operators in expression-tree) — labels belong inside their circles. - placeholder captions: 13 false positives — all "placeholders" were dunder names (__init__, __dict__, __cause__, etc.).
1 parent 8470b42 commit 1b9657f

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

tests/test_marginalia_geometry.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,5 +328,91 @@ def test_every_stroke_width_is_from_the_locked_set(self):
328328
self.assertEqual(failures, [], "\n " + "\n ".join(failures))
329329

330330

331+
class FigureAnchorContract(unittest.TestCase):
332+
"""Contract 6: every cell-N attachment anchor points to a real cell.
333+
334+
Adding an attachment that points past the end of an example's
335+
walkthrough leaves the figure orphan — `render_for_anchor` returns
336+
empty and the figure never ships. Catches a typo or a stale
337+
attachment after a markdown example loses a cell.
338+
"""
339+
340+
def test_every_attachment_anchor_resolves_to_a_real_cell(self):
341+
# Imported lazily to avoid pulling the example loader for every
342+
# test in this file.
343+
import re as _re
344+
from src.example_loader import load_examples
345+
346+
_, examples = load_examples()
347+
by_slug = {ex["slug"]: ex for ex in examples}
348+
349+
failures: list[str] = []
350+
for slug, items in ATTACHMENTS.items():
351+
ex = by_slug.get(slug)
352+
if ex is None:
353+
failures.append(f"{slug}: ATTACHMENT exists but no markdown example matches")
354+
continue
355+
cells = ex.get("walkthrough", [])
356+
for anchor, name, _ in items:
357+
match = _re.match(r"cell-(\d+)$", anchor)
358+
if not match:
359+
failures.append(f"{slug}: anchor {anchor!r} is not cell-N")
360+
continue
361+
if int(match.group(1)) >= len(cells):
362+
failures.append(
363+
f"{slug}: anchor {anchor!r} → figure {name!r}, "
364+
f"but example has only {len(cells)} cell(s)"
365+
)
366+
self.assertEqual(failures, [], "\n " + "\n ".join(failures))
367+
368+
369+
class FigureScoreContract(unittest.TestCase):
370+
"""Contract 7: every SCORES entry is a real assessment.
371+
372+
A score must be a number in [0, 10] (the rubric's range) with a
373+
non-empty short commentary. Catches typos where a comma slips and
374+
a tuple turns into something else.
375+
"""
376+
377+
def test_every_score_is_in_range_with_commentary(self):
378+
failures: list[str] = []
379+
for slug, entry in SCORES.items():
380+
if not isinstance(entry, tuple) or len(entry) != 2:
381+
failures.append(f"{slug}: not a (score, commentary) tuple")
382+
continue
383+
score, commentary = entry
384+
if not isinstance(score, (int, float)) or not 0 <= score <= 10:
385+
failures.append(f"{slug}: score {score!r} outside [0, 10]")
386+
if not isinstance(commentary, str) or not commentary.strip():
387+
failures.append(f"{slug}: empty commentary")
388+
self.assertEqual(failures, [], "\n " + "\n ".join(failures))
389+
390+
391+
class FigureSizeContract(unittest.TestCase):
392+
"""Contract 8: every figure's intrinsic width fits the banner.
393+
394+
.cell-banner figure { max-width: 360px } and
395+
.cell-banner--1 figure { max-width: 440px } in public/site.css.
396+
A figure whose intrinsic width exceeds 440px will scale down on
397+
every page, magnifying or shrinking text in a way the paint code
398+
didn't plan for. Better to flag and adjust the canvas.
399+
400+
The "intrinsic width" is Canvas.w plus the to_svg padding (2 * PAD_X).
401+
"""
402+
403+
BANNER_MAX_WIDTH = 440 # cell-banner--1 max-width, the larger of the two
404+
405+
def test_every_figure_fits_the_banner(self):
406+
failures: list[str] = []
407+
for name, (_, w, _) in FIGURES.items():
408+
intrinsic = w + 2 * PAD_X
409+
if intrinsic > self.BANNER_MAX_WIDTH:
410+
failures.append(
411+
f"{name}: intrinsic width {intrinsic}px exceeds "
412+
f"{self.BANNER_MAX_WIDTH}px banner ceiling"
413+
)
414+
self.assertEqual(failures, [], "\n " + "\n ".join(failures))
415+
416+
331417
if __name__ == "__main__":
332418
unittest.main()

0 commit comments

Comments
 (0)