mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🐛 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:
parent
71904c9ab6
commit
fa89790fd6
@ -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
|
||||
|
||||
410
common/test/common_tests/geom_grid_layout_test.cljc
Normal file
410
common/test/common_tests/geom_grid_layout_test.cljc
Normal 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)))))
|
||||
@ -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]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user