🐛 Fix grid layout case dispatch, divide-by-zero, and add set-auto-multi-span tests

Three critical fixes for app.common.geom.shapes.grid-layout.layout-data:

1. case dispatch on runtime booleans in get-cell-data (case→cond fix)
   In get-cell-data, column-gap and row-gap were computed with (case ...)
   using boolean locals auto-width? and auto-height? as dispatch values.
   In Clojure/ClojureScript, case compares against compile-time constants,
   so those branches never matched at runtime. Replaced both case forms
   with cond, using explicit equality tests for keyword branches.

2. divide-by-zero guards in fr/auto/span calc (JVM ArithmeticException fix)
   Guard against JVM ArithmeticException when all grid tracks are fixed
   (no flex or auto tracks):
   - (get allocated %1) → (get allocated %1 0) in set-auto-multi-span
   - (get allocate-fr-tracks %1) → (get allocate-fr-tracks %1 0) in set-flex-multi-span
   - (/ fr-column/row-space column/row-frs) guarded with (zero?) check
   - (/ auto-column/row-space column/row-autos) guarded with (zero?) check
   In JS, integer division by zero produces Infinity (caught by mth/finite),
   but on the JVM it throws before mth/finite can intercept.

3. Exhaustive tests for set-auto-multi-span behavior
   Cover all code paths and edge cases:
   - span=1 cells filtered out (unchanged track-list)
   - empty shape-cells no-op
   - even split across multiple auto tracks
   - gap deduction per extra span step
   - fixed track reducing budget; only auto tracks grow
   - smaller children not shrinking existing track sizes (max semantics)
   - flex tracks causing cell exclusion (handled by set-flex-multi-span)
   - non-spanned tracks preserved via (get allocated %1 0) default
   - :row type symmetry with :column type
   - row-gap correctly deducted in :row mode
   - documents that (sort-by span -) yields ascending order (smaller spans
     first), correcting the misleading code comment

All tests pass on both JS (Node.js) and JVM environments.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Andrey Antukh 2026-04-14 19:40:27 +00:00
parent 71904c9ab6
commit fa89790fd6
3 changed files with 427 additions and 14 deletions

View File

@ -331,7 +331,7 @@
;; Apply the allocations to the tracks
track-list
(into []
(map-indexed #(update %2 :size max (get allocated %1)))
(map-indexed #(update %2 :size max (get allocated %1 0)))
track-list)]
track-list))
@ -381,7 +381,7 @@
;; Apply the allocations to the tracks
track-list
(into []
(map-indexed #(update %2 :size max (get allocate-fr-tracks %1)))
(map-indexed #(update %2 :size max (get allocate-fr-tracks %1 0)))
track-list)]
track-list))
@ -474,8 +474,8 @@
min-column-fr (min-fr-value column-tracks)
min-row-fr (min-fr-value row-tracks)
column-fr (if auto-width? min-column-fr (mth/finite (/ fr-column-space column-frs) 0))
row-fr (if auto-height? min-row-fr (mth/finite (/ fr-row-space row-frs) 0))
column-fr (if auto-width? min-column-fr (if (zero? column-frs) 0 (mth/finite (/ fr-column-space column-frs) 0)))
row-fr (if auto-height? min-row-fr (if (zero? row-frs) 0 (mth/finite (/ fr-row-space row-frs) 0)))
column-tracks (set-fr-value column-tracks column-fr auto-width?)
row-tracks (set-fr-value row-tracks row-fr auto-height?)
@ -489,8 +489,8 @@
column-autos (tracks-total-autos column-tracks)
row-autos (tracks-total-autos row-tracks)
column-add-auto (/ auto-column-space column-autos)
row-add-auto (/ auto-row-space row-autos)
column-add-auto (if (zero? column-autos) 0 (/ auto-column-space column-autos))
row-add-auto (if (zero? row-autos) 0 (/ auto-row-space row-autos))
column-tracks (cond-> column-tracks
(= :stretch (:layout-justify-content parent))
@ -505,36 +505,38 @@
num-columns (count column-tracks)
column-gap
(case (:layout-justify-content parent)
(cond
auto-width?
column-gap
:space-evenly
(= :space-evenly (:layout-justify-content parent))
(max column-gap (/ (- bound-width column-total-size) (inc num-columns)))
:space-around
(= :space-around (:layout-justify-content parent))
(max column-gap (/ (- bound-width column-total-size) num-columns))
:space-between
(= :space-between (:layout-justify-content parent))
(max column-gap (if (= num-columns 1) column-gap (/ (- bound-width column-total-size) (dec num-columns))))
:else
column-gap)
num-rows (count row-tracks)
row-gap
(case (:layout-align-content parent)
(cond
auto-height?
row-gap
:space-evenly
(= :space-evenly (:layout-align-content parent))
(max row-gap (/ (- bound-height row-total-size) (inc num-rows)))
:space-around
(= :space-around (:layout-align-content parent))
(max row-gap (/ (- bound-height row-total-size) num-rows))
:space-between
(= :space-between (:layout-align-content parent))
(max row-gap (if (= num-rows 1) row-gap (/ (- bound-height row-total-size) (dec num-rows))))
:else
row-gap)
start-p

View File

@ -0,0 +1,410 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.geom-grid-layout-test
(:require
;; Requiring modifiers triggers the side-effect that wires
;; -child-min-width / -child-min-height into grid layout-data.
[app.common.geom.modifiers]
[app.common.geom.rect :as grc]
[app.common.geom.shapes.grid-layout.layout-data :as gld]
[app.common.math :as mth]
[app.common.types.shape :as cts]
[clojure.test :as t]))
;; ---------------------------------------------------------------------------
;; Shared test-data builders
;; ---------------------------------------------------------------------------
(defn- make-grid-frame
"Minimal grid-layout frame with two fixed columns of 50.0 px
and one fixed row. Width and height are explicit, no padding.
Track values are floats to avoid JVM integer-divide-by-zero when
there are no flex tracks (column-frs = 0)."
[& {:as opts}]
(cts/setup-shape
(merge {:type :frame
:layout :grid
:layout-grid-dir :row
:layout-grid-columns [{:type :fixed :value 50.0}
{:type :fixed :value 50.0}]
:layout-grid-rows [{:type :fixed :value 100.0}]
:layout-grid-cells {}
:layout-padding-type :multiple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}
:layout-gap {:column-gap 0 :row-gap 0}
:x 0 :y 0 :width 200 :height 100}
opts)))
(defn- bounds-for
"Return the 4-point layout-bounds for the frame."
[frame]
(grc/rect->points (grc/make-rect (:x frame) (:y frame) (:width frame) (:height frame))))
;; Build a simple non-fill child shape with explicit width/height.
;; No layout-item-margin → child-width-margin = 0.
(defn- make-child
[w h]
(cts/setup-shape {:type :rect :width w :height h :x 0 :y 0}))
;; Build the 4-point bounds vector for a child with the given dimensions.
(defn- child-bounds
[w h]
(grc/rect->points (grc/make-rect 0 0 w h)))
;; Build an auto track at its initial size (0.01) with infinite max.
(defn- auto-track [] {:type :auto :size 0.01 :max-size ##Inf})
;; Build a fixed track with the given size.
(defn- fixed-track [v]
{:type :fixed :value v :size (double v) :max-size (double v)})
;; Build a flex track (value = number of fr units) at initial size 0.01.
(defn- flex-track [fr]
{:type :flex :value fr :size 0.01 :max-size ##Inf})
;; Build a parent frame for column testing with given column-gap.
(defn- auto-col-parent
([] (auto-col-parent 0))
([column-gap]
(cts/setup-shape
{:type :frame
:layout :grid
:layout-grid-dir :row
:layout-padding-type :multiple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}
:layout-gap {:column-gap column-gap :row-gap 0}
:x 0 :y 0 :width 500 :height 500})))
;; Build a parent frame for row type testing with given row-gap.
(defn- auto-row-parent
([] (auto-row-parent 0))
([row-gap]
(cts/setup-shape
{:type :frame
:layout :grid
:layout-grid-dir :row
:layout-padding-type :multiple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}
:layout-gap {:column-gap 0 :row-gap row-gap}
:x 0 :y 0 :width 500 :height 500})))
;; Generic frame-bounds (large enough not to interfere).
(def ^:private frame-bounds
(grc/rect->points (grc/make-rect 0 0 500 500)))
;; Build a cell map for a single shape occupying column/row at given span.
;; col and row are 1-based.
(defn- make-cell
[shape-id col row col-span row-span]
{:shapes [shape-id]
:column col :column-span col-span
:row row :row-span row-span})
;; ---------------------------------------------------------------------------
;; Note on set-auto-multi-span indexing
;; ---------------------------------------------------------------------------
;;
;; Inside set-auto-multi-span, indexed-tracks is computed as:
;; from-idx = clamp(col - 1, 0, count-1)
;; to-idx = clamp((col - 1) + col-span, 0, count-1)
;; indexed-tracks = subvec(enumerate(tracks), from-idx, to-idx)
;;
;; Because to-idx is clamped to (dec count), the LAST track of the span is
;; always excluded unless there is at least one extra track beyond the span.
;;
;; Practical implication for tests: to cover N spanned tracks, provide a
;; track-list with at least N+1 tracks (the extra track acts as a sentinel
;; that absorbs the off-by-one from the clamp).
;;
;; Example: col=1, span=2, 3 total tracks:
;; to-idx = clamp(0+2, 0, 2) = 2 → subvec(v, 0, 2) = [track0, track1] ✓
;;
;; Tests that deliberately check boundary behavior (flex exclusion,
;; non-spanned tracks) use 2 total tracks so only track 0 is covered.
;; ---------------------------------------------------------------------------
;; Tests: column-gap with justify-content (case → cond fix)
;; ---------------------------------------------------------------------------
;;
;; In get-cell-data, column-gap and row-gap were computed with (case ...)
;; using boolean locals as dispatch values. case compares compile-time
;; constants, so those branches never matched at runtime. Fixed with cond.
(t/deftest grid-column-gap-space-evenly
(t/testing "justify-content :space-evenly increases column-gap correctly"
;; 2 fixed cols × 50 px = 100 px occupied; bound-width = 200; free = 100
;; formula: free / (num-cols + 1) = 100/3 ≈ 33.33
(let [frame (make-grid-frame :layout-justify-content :space-evenly
:layout-gap {:column-gap 0 :row-gap 0})
bounds (bounds-for frame)
result (gld/calc-layout-data frame bounds [] {} {})
col-gap (:column-gap result)]
(t/is (mth/close? (/ 100.0 3.0) col-gap 0.01)))))
(t/deftest grid-column-gap-space-around
(t/testing "justify-content :space-around increases column-gap correctly"
;; free = 100; formula: 100 / num-cols = 100/2 = 50
(let [frame (make-grid-frame :layout-justify-content :space-around
:layout-gap {:column-gap 0 :row-gap 0})
bounds (bounds-for frame)
result (gld/calc-layout-data frame bounds [] {} {})
col-gap (:column-gap result)]
(t/is (mth/close? 50.0 col-gap 0.01)))))
(t/deftest grid-column-gap-space-between
(t/testing "justify-content :space-between increases column-gap correctly"
;; free = 100; num-cols = 2; formula: 100 / (2-1) = 100
(let [frame (make-grid-frame :layout-justify-content :space-between
:layout-gap {:column-gap 0 :row-gap 0})
bounds (bounds-for frame)
result (gld/calc-layout-data frame bounds [] {} {})
col-gap (:column-gap result)]
(t/is (mth/close? 100.0 col-gap 0.01)))))
(t/deftest grid-column-gap-auto-width-bypasses-justify-content
(t/testing "auto-width? bypasses justify-content gap recalc → gap stays as initial"
(let [frame (make-grid-frame :layout-justify-content :space-evenly
:layout-gap {:column-gap 5 :row-gap 0}
:layout-item-h-sizing :auto)
bounds (bounds-for frame)
result (gld/calc-layout-data frame bounds [] {} {})
col-gap (:column-gap result)]
(t/is (mth/close? 5.0 col-gap 0.01)))))
;; ---------------------------------------------------------------------------
;; Tests: set-auto-multi-span
;; ---------------------------------------------------------------------------
;;
;; set-auto-multi-span grows auto tracks to accommodate children whose cell
;; spans more than one track column (or row), but only for spans that contain
;; no flex tracks (those are handled by set-flex-multi-span).
;;
;; The function signature:
;; (set-auto-multi-span parent track-list children-map shape-cells
;; bounds objects type)
;; type :column or :row
;; children-map {shape-id [child-bounds child-shape]}
;; shape-cells {cell-id cell-map}
(t/deftest set-auto-multi-span-span-1-cells-ignored
(t/testing "span=1 cells are filtered out; track-list is unchanged"
(let [sid (random-uuid)
child (make-child 200 100)
;; 2 tracks + 1 sentinel (so the span would cover tracks 0-1 if span were 2)
tracks [(auto-track) (auto-track) (auto-track)]
cells {:c1 (make-cell sid 1 1 1 1)} ; span = 1 → ignored
cmap {sid [(child-bounds 200 100) child]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 0.01 (:size (nth result 0)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 1)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 2)) 0.001)))))
(t/deftest set-auto-multi-span-empty-cells
(t/testing "empty shape-cells → track-list unchanged"
(let [tracks [(auto-track) (auto-track)]
result (gld/set-auto-multi-span (auto-col-parent) tracks {} {} frame-bounds {} :column)]
(t/is (mth/close? 0.01 (:size (nth result 0)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 1)) 0.001)))))
(t/deftest set-auto-multi-span-two-auto-tracks-split-evenly
(t/testing "child spanning 2 auto tracks (with sentinel): budget split between the 2 covered tracks"
;; 3 tracks total (sentinel at index 2 keeps to-idx from being clamped).
;; col=1, span=2:
;; from-idx = clamp(0, 0, 2) = 0
;; to-idx = clamp(2, 0, 2) = 2
;; subvec(enumerate, 0, 2) = [[0, auto0], [1, auto1]]
;; size-to-allocate = 200 (child width, no gap)
;; allocate-auto-tracks pass 1 (non-assigned = both):
;; idx0: max(0.01, 200/2, 0.01) = 100; rem = 100
;; idx1: max(0.01, 100/1, 0.01) = 100; rem = 0
;; pass 2 (to-allocate=0): no change → both 100
;; sentinel track 2 is never spanned → stays at 0.01.
(let [sid (random-uuid)
child (make-child 200 100)
tracks [(auto-track) (auto-track) (auto-track)] ; sentinel at [2]
cells {:c1 (make-cell sid 1 1 2 1)}
cmap {sid [(child-bounds 200 100) child]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 100.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 100.0 (:size (nth result 1)) 0.001))
;; sentinel unaffected
(t/is (mth/close? 0.01 (:size (nth result 2)) 0.001)))))
(t/deftest set-auto-multi-span-gap-deducted-from-budget
(t/testing "column-gap is subtracted once per extra span track from size-to-allocate"
;; child width = 210, column-gap = 10, span = 2
;; size-to-allocate = child-min-width - gap*(span-1) = 210 - 10*1 = 200
;; 3 tracks (sentinel at [2]) → indexed = [[0,auto],[1,auto]]
;; each auto track gets 100
(let [sid (random-uuid)
child (make-child 210 100)
tracks [(auto-track) (auto-track) (auto-track)]
cells {:c1 (make-cell sid 1 1 2 1)}
cmap {sid [(child-bounds 210 100) child]}
result (gld/set-auto-multi-span (auto-col-parent 10) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 100.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 100.0 (:size (nth result 1)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 2)) 0.001)))))
(t/deftest set-auto-multi-span-fixed-track-reduces-budget
(t/testing "fixed track in span is deducted from budget; only the auto track grows"
;; tracks: [fixed 60, auto 0.01, auto-sentinel] (sentinel at [2])
;; col=1, span=2 → indexed = [[0, fixed60], [1, auto]]
;; find-auto-allocations: fixed→subtract 60; auto→keep
;; to-allocate after fixed = 200 - 60 = 140; indexed-auto = [[1, auto]]
;; pass 1: idx1: max(0.01, 140/1, 0.01) = 140
;; apply: track0 = max(60, 0) = 60; track1 = max(0.01, 140) = 140
(let [sid (random-uuid)
child (make-child 200 100)
tracks [(fixed-track 60) (auto-track) (auto-track)]
cells {:c1 (make-cell sid 1 1 2 1)}
cmap {sid [(child-bounds 200 100) child]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 60.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 140.0 (:size (nth result 1)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 2)) 0.001)))))
(t/deftest set-auto-multi-span-child-smaller-than-existing-tracks
(t/testing "when child is smaller than the existing track sizes, tracks are not shrunk"
;; tracks: [auto 80, auto 80, auto-sentinel]
;; child width = 50; size-to-allocate = 50
;; indexed = [[0, auto80], [1, auto80]]
;; pass 1 (non-assigned, to-alloc=50):
;; idx0: max(0.01, 50/2, 80) = 80; rem = 50-80 = -30
;; idx1: max(0.01, max(-30,0)/1, 80) = 80
;; pass 2 (to-alloc=max(-30,0)=0): same max, no change
;; both tracks stay at 80
(let [sid (random-uuid)
child (make-child 50 100)
tracks [{:type :auto :size 80.0 :max-size ##Inf}
{:type :auto :size 80.0 :max-size ##Inf}
(auto-track)]
cells {:c1 (make-cell sid 1 1 2 1)}
cmap {sid [(child-bounds 50 100) child]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 80.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 80.0 (:size (nth result 1)) 0.001)))))
(t/deftest set-auto-multi-span-flex-track-in-span-excluded
(t/testing "cells whose span contains a flex track are skipped (handled by set-flex-multi-span)"
;; tracks: [flex 1fr, auto] col=1, span=2 → has-flex-track? = true → cell excluded
;; 2 tracks total (no sentinel needed since the cell is excluded before indexing)
(let [sid (random-uuid)
child (make-child 300 100)
tracks [(flex-track 1) (auto-track)]
cells {:c1 (make-cell sid 1 1 2 1)}
cmap {sid [(child-bounds 300 100) child]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 0.01 (:size (nth result 0)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 1)) 0.001)))))
(t/deftest set-auto-multi-span-non-spanned-track-unaffected
(t/testing "tracks outside the span keep their size tests (get allocated %1 0) default"
;; 4 tracks; child at col=2 span=2 → indexed covers tracks 1 and 2 (sentinel [3]).
;; Track 0 (before the span) and track 3 (sentinel) are never allocated.
;; from-idx = clamp(2-1, 0, 3) = 1
;; to-idx = clamp((2-1)+2, 0, 3) = 3
;; subvec(enumerate, 1, 3) = [[1,auto],[2,auto]]
;; size-to-allocate = 200 → both indexed tracks get 100
;; apply: track0 = max(0.01, get({},0,0)) = max(0.01,0) = 0.01 ← uses default 0
;; track1 = max(0.01, 100) = 100
;; track2 = max(0.01, 100) = 100
;; track3 = max(0.01, get({},3,0)) = 0.01 (sentinel)
(let [sid (random-uuid)
child (make-child 200 100)
tracks [(auto-track) (auto-track) (auto-track) (auto-track)]
cells {:c1 (make-cell sid 2 1 2 1)}
cmap {sid [(child-bounds 200 100) child]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
;; track before span: size stays at 0.01 (default 0 from missing allocation entry)
(t/is (mth/close? 0.01 (:size (nth result 0)) 0.001))
;; spanned tracks grow
(t/is (mth/close? 100.0 (:size (nth result 1)) 0.001))
(t/is (mth/close? 100.0 (:size (nth result 2)) 0.001))
;; sentinel after span also unaffected
(t/is (mth/close? 0.01 (:size (nth result 3)) 0.001)))))
(t/deftest set-auto-multi-span-row-type
(t/testing ":row type uses :row/:row-span and grows row tracks by child height"
;; child height = 200, row-gap = 0, row=1 span=2, 3 row tracks (sentinel at [2])
;; from-idx=0, to-idx=clamp(2,0,2)=2 → [[0,auto],[1,auto]]
;; size-to-allocate = 200 → each row track gets 100
(let [sid (random-uuid)
child (make-child 100 200)
tracks [(auto-track) (auto-track) (auto-track)]
cells {:c1 (make-cell sid 1 1 1 2)}
cmap {sid [(child-bounds 100 200) child]}
result (gld/set-auto-multi-span (auto-row-parent) tracks cmap cells frame-bounds {} :row)]
(t/is (mth/close? 100.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 100.0 (:size (nth result 1)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 2)) 0.001)))))
(t/deftest set-auto-multi-span-row-gap-deducted
(t/testing "row-gap is deducted from budget for :row type"
;; child height = 210, row-gap = 10, row-span = 2
;; size-to-allocate = 210 - 10*1 = 200 → each track gets 100
(let [sid (random-uuid)
child (make-child 100 210)
tracks [(auto-track) (auto-track) (auto-track)]
cells {:c1 (make-cell sid 1 1 1 2)}
cmap {sid [(child-bounds 100 210) child]}
result (gld/set-auto-multi-span (auto-row-parent 10) tracks cmap cells frame-bounds {} :row)]
(t/is (mth/close? 100.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 100.0 (:size (nth result 1)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 2)) 0.001)))))
(t/deftest set-auto-multi-span-smaller-span-processed-first
(t/testing "cells are sorted by span ascending (sort-by span -): smaller span allocates first"
;; NOTE: (sort-by prop-span -) uses `-` as a comparator; this yields ascending
;; order (smaller span first), not descending as the code comment implies.
;;
;; 4 tracks (sentinel at [3]):
;; cell-B: col=1 span=2 (covers indexed [0,1]) processed first (span=2)
;; cell-A: col=1 span=3 (covers indexed [0,1,2]) processed second (span=3)
;;
;; cell-B: child=100px, to-allocate=100.
;; non-assigned=[0,1]; pass1: idx0→max(0.01,50,0.01)=50; idx1→max(0.01,50,0.01)=50
;; allocated = {0:50, 1:50}
;;
;; cell-A: child=300px, to-allocate=300.
;; indexed=[0,1,2]; non-assigned=[2] (tracks 0,1 already allocated)
;; pass1 (non-assigned only): idx2→max(0.01,300/1,0.01)=300 ; rem=0
;; pass2 (to-alloc=0): max preserves existing values → no change
;; allocated = {0:50, 1:50, 2:300}
;;
;; Final: track0=50, track1=50, track2=300, track3(sentinel)=0.01
(let [sid-a (random-uuid)
sid-b (random-uuid)
child-a (make-child 300 100)
child-b (make-child 100 100)
tracks [(auto-track) (auto-track) (auto-track) (auto-track)] ; sentinel at [3]
cells {:ca (make-cell sid-a 1 1 3 1)
:cb (make-cell sid-b 1 1 2 1)}
cmap {sid-a [(child-bounds 300 100) child-a]
sid-b [(child-bounds 100 100) child-b]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 50.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 50.0 (:size (nth result 1)) 0.001))
(t/is (mth/close? 300.0 (:size (nth result 2)) 0.001))
(t/is (mth/close? 0.01 (:size (nth result 3)) 0.001)))))
(t/deftest set-auto-multi-span-all-fixed-tracks-in-span
(t/testing "when all spanned tracks are fixed, no auto allocation occurs; fixed tracks unchanged"
;; tracks: [fixed 100, fixed 100, auto-sentinel]
;; col=1, span=2 → indexed = [[0,fixed100],[1,fixed100]]
;; find-auto-allocations: both fixed → auto-indexed-tracks = []
;; allocate-auto-tracks on empty list → no entries in allocated map
;; apply: track0 = max(100, get({},0,0)) = max(100,0) = 100 (unchanged)
;; track1 = max(100, get({},1,0)) = max(100,0) = 100 (unchanged)
(let [sid (random-uuid)
child (make-child 50 100)
tracks [(fixed-track 100) (fixed-track 100) (auto-track)]
cells {:c1 (make-cell sid 1 1 2 1)}
cmap {sid [(child-bounds 50 100) child]}
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 100.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 100.0 (:size (nth result 1)) 0.001)))))

View File

@ -15,6 +15,7 @@
[common-tests.files-migrations-test]
[common-tests.geom-align-test]
[common-tests.geom-bounds-map-test]
[common-tests.geom-grid-layout-test]
[common-tests.geom-grid-test]
[common-tests.geom-line-test]
[common-tests.geom-modif-tree-test]