Files
C.E.L_Slide_test2/tests/phase_z2/test_parse_fr_string.py
kyeongmin 201099e53b feat(IMP-09): PR 1 — col-axis solver + per-zone geometry mapper + retry gate
Stage 3 round 4 lock implementation: extend build_layout_css beyond
the horizontal-2-only dynamic path. Every layout_css return now
carries length-locked col-axis keys (widths_px, width_ratios,
dynamic_cols) matching the parsed css_areas grid (R rows, C cols),
so 2-D layouts (T / 2x2 in PR 2) and the unified
_compute_per_zone_geometry mapper can plug in without further
contract churn.

PR 1 scope:
  - _parse_css_areas + _parse_fr_string + _compute_per_zone_geometry
    (unified — 1-D and 2-D from the same code path)
  - compute_zone_layout_cols (vertical-2 weight-only solver)
  - _build_fr_default / _build_rows_dynamic / _build_cols_dynamic
    (populate widths_px/heights_px on every return path)
  - build_layout_css override branch keeps the warn-and-fallthrough
    legacy for unsupported presets (PR 2 promotes to strict raise)
  - retry gate in _attempt_zone_ratio_retry skips when dynamic_cols=True
    or dynamic_rows=False, with explicit retry_skipped_reason
  - Step 8 artifact gains zone_widths_px_planned /
    zone_col_ratios_planned (top-level) + zone_width_px_planned /
    zone_col_ratio_planned (per-zone)
  - debug_zones width injection via _compute_per_zone_geometry
    (replaces the legacy row-only zip)

Tests: tests/phase_z2/ — 47 new cases (parse / fr-string / cols solver /
per-zone geometry / build_layout_css contract / retry gate +
6 build_layout_css YAML fixtures + 3 retry_gate fixtures).

Verification: python -m pytest -q tests = 89 passed (was 42).
horizontal-2 grid CSS strings (areas/cols/rows) byte-identical to
legacy; only additive col-axis keys are introduced.

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

50 lines
1.3 KiB
Python

"""IMP-09 PR 1 — _parse_fr_string tests.
Catalog presets only use `1fr` / `1fr 1fr` specs (verified
templates/phase_z2/layouts/layouts.yaml). The helper must reject
non-fr tokens and round to integer pixel sizes summing to `total`.
"""
from __future__ import annotations
import pytest
from src.phase_z2_pipeline import _parse_fr_string
def test_single_fr_returns_full_total():
assert _parse_fr_string("1fr", 585) == [585]
def test_two_equal_fr_splits_evenly():
result = _parse_fr_string("1fr 1fr", 1180)
assert result == [590, 590]
assert sum(result) == 1180
def test_unequal_fr_distributes_by_ratio():
result = _parse_fr_string("2fr 1fr", 300)
assert sum(result) == 300
assert result[0] > result[1]
def test_rounding_absorbed_by_last_track():
# 1fr 1fr 1fr / total=100 -> 33,33,33 + diff 1 absorbed by last.
result = _parse_fr_string("1fr 1fr 1fr", 100)
assert sum(result) == 100
assert result == [33, 33, 34]
def test_non_fr_token_raises():
with pytest.raises(ValueError, match="non-fr token"):
_parse_fr_string("200px 1fr", 1000)
def test_empty_spec_raises():
with pytest.raises(ValueError, match="empty spec"):
_parse_fr_string("", 1000)
def test_zero_fr_raises():
with pytest.raises(ValueError, match="total fr"):
_parse_fr_string("0fr 0fr", 1000)