Layout prop tests - #213
Merged
Merged
Conversation
Add ScalaCheck + scalatest bridge to the termflow-screen test classpath and a LayoutPropSpec with 5 invariants over arbitrary Layout trees: - measure is total and non-negative - resolve/resolveTo/resolveTracked never throw for any tree/budget - flow layouts (Row/Column/Fill/Zone) never place a node before the origin - sum of Fill children's main-axis sizes <= parent main-axis budget - Zone wrapping is transparent to measure and resolve Includes a Gen[Layout] over the real DSL and a custom Shrink[Layout] for minimal failing trees. P3 is scoped to flow primitives: Grid (no column compaction for spanning cells) and Border (right/bottom-edge pinning) can emit coordinates before the origin under a starving budget, which is documented out-of-scope overflow territory (see .multidev/DECISIONS.md).
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DECISIONS — layout-prop-tests (#142)
Test-only change. No production behaviour touched.
What changed
project/Dependencies.scala: addedscalacheck1.18.1 and thescalatest+scalacheck bridge
scalacheck-1-183.2.19.0 (version tracksscalatest 3.2.19).
build.sbt: added both as% Testdeps ontermflowScreenonly (themodule that owns
Layout). Matches the existing per-module test-dep style.modules/termflow-screen/src/test/scala/termflow/tui/LayoutPropSpec.scala:Gen[Layout]over the real DSL (Elem/Row/Column/Spacer/Fill/Zone/ Grid/Border), a customShrink[Layout](replace container with a child,then drop one child) for minimal failing trees, and 5 properties
(minSuccessful = 300).
Brief vs reality
The brief's invariant names referenced constructors that don't exist in the
v1 DSL (
Pad/Flex/Sized/Clip/Scroll/Overlay). I readLayout.scalaandmapped each idea to a real, holding invariant rather than inventing API:
measuretotal + non-negative (brief: "measure is total").resolve/resolveTo/resolveTrackednever throw for any tree/budget(incl. zero/negative). (brief: totality)
(brief: "bounds contained within requested bounds", scoped to where it
actually holds; see below).
Fillchildren's main-axis sizes ≤ parent main-axis budget(brief: "sum of flex children's main-axis sizes ≤ parent main-axis").
Tested with all-
FillRow/Column so every resolvedBoxNode's main sizeis an observable flex allocation.
Zonewrapping is transparent tomeasureandresolve— thewrapper-identity analogue of the brief's "nested Pad inverts" (Pad doesn't
exist; Zone is the only pure pass-through wrapper).
Key finding: containment does NOT hold universally (P3 scope)
P3 initially asserted containment for any generated tree. ScalaCheck
shrank two counterexamples:
places its right zone at
leftX + budgetW - rightActualW. When the widthbudget is smaller than the right zone's natural width, this is negative
relative to the origin (node drawn left of where it was placed).
A
colSpan > 1cell contributes 0 to per-column natural widths (Grid doesno column compaction — explicitly documented out of scope), so it can be
allotted width ≈
colGap. That starving budget flows into a nestedBorder and reproduces (1) — e.g. a
TextNoderesolved at x = -7 fromorigin 0.
Both stem from documented v1 limitations (Layout.scala: "Clipping / overflow
handling" and "automatic column-width compaction" are out of scope). So
asserting containment there is a test-side over-reach, not a resolver bug —
I scoped P3 to the flow primitives (
Row/Column/Fill/Zone/Spacer/Elem), whichonly ever advance cursors forward and genuinely guarantee containment under
any budget. Did not weaken a property to hide a real bug, and did not park
NEEDS_HUMAN, because the behaviour is documented-out-of-scope, not a
regression.
Risk / follow-up: the Grid+Border combo emitting negative coordinates is
arguably worth a real fix (right-pin should clamp to
>= leftX; spanningcells could get a min width). Out of scope for this test-only task — flagged
here for a maintainer to triage. A reviewer who disagrees can promote P3 back
to all trees to make the failure reproduce.
Rejected
Shrinkvia deprecatedStream— usedShrink.withLazyList(LazyList) instead to avoid deprecation warnings.
BoxNodes tothe flex subset, so used all-
Fillcontainers for a clean, honest check.