penpot/common/test/common_tests/geom_flex_layout_test.cljc
2026-04-15 23:37:53 +02:00

107 lines
4.7 KiB
Clojure
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;; 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-flex-layout-test
(:require
[app.common.geom.rect :as grc]
[app.common.geom.shapes.flex-layout.positions :as flp]
[app.common.math :as mth]
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[clojure.test :as t]))
;; ---- helpers ----
(defn- make-col-frame
"Minimal col? flex frame with wrap enabled.
wrap is required for the content-around? predicate to activate."
[& {:as opts}]
(cts/setup-shape (merge {:type :frame
:layout :flex
:layout-flex-dir :column
:layout-wrap-type :wrap
:x 0 :y 0 :width 200 :height 200}
opts)))
(defn- rect->bounds
"Convert a rect to the 4-point layout-bounds vector expected by gpo/*."
[rect]
(grc/rect->points rect))
;; ---- get-base-line (around? branch) ----
;;
;; Bug: in positions.cljc the col? + around? branch had a mis-parenthesised
;; expression `(/ free-width num-lines) 2`, which was parsed as three
;; arguments to `max`:
;; (max lines-gap-col (/ free-width num-lines) 2)
;; instead of the intended two-argument max with a nested division:
;; (max lines-gap-col (/ free-width num-lines 2))
;;
;; For a col? layout the cross-axis is horizontal (hv), so the around? offset
;; is applied as hv(delta) — i.e. the delta ends up in (:x base-p).
(t/deftest get-base-line-around-uses-half-per-line-free-width
(t/testing "col? + content-around? offset is free-width / num-lines / 2"
;; Layout: col? wrap, width=200, 3 lines each 20px wide → free-width=140
;; lines-gap-col = 0 (no gap defined)
;; Expected horizontal offset = max(0, 140/3/2) ≈ 23.33
;; Before the bug fix the formula was (max ... (/ 140 3) 2) ≈ 46.67.
(let [frame (make-col-frame :layout-align-content :space-around)
bounds (rect->bounds (grc/make-rect 0 0 200 200))
;; 3 lines of 20px each (widths); no row gap
num-lines 3
total-width 60
total-height 0
base-p (flp/get-base-line frame bounds total-width total-height num-lines)
free-width (- 200 total-width)
;; lines-gap-col = (dec 3) * 0 = 0; max(0, free-width/num-lines/2)
expected-x (/ free-width num-lines 2)]
;; The base point x-coordinate (hv offset) should equal half per-line free space.
(t/is (mth/close? expected-x (:x base-p) 0.01))))
(t/testing "col? + content-around? offset respects lines-gap-col minimum"
;; When the accumulated column gap exceeds the computed half-per-line value
;; max(lines-gap-col, free-width/num-lines/2) returns the gap.
(let [frame (make-col-frame :layout-align-content :space-around
:layout-gap {:column-gap 50 :row-gap 0})
bounds (rect->bounds (grc/make-rect 0 0 200 200))
;; 4 lines × 20px = 80px used; free-width=120; half-per-line = 120/4/2 = 15
;; lines-gap-col = (dec 4)*50 = 150 → max(150, 15) = 150
num-lines 4
total-width 80
total-height 0
base-p (flp/get-base-line frame bounds total-width total-height num-lines)
lines-gap-col (* (dec num-lines) 50)]
(t/is (mth/close? lines-gap-col (:x base-p) 0.01)))))
;; ---- v-end? guard (drop-line-area) ----
;;
;; Bug: `v-end?` inside `drop-line-area` was guarded by `row?` instead of
;; `col?`, so vertical-end alignment in a column layout was never triggered.
;; We verify the predicate behaviour directly via ctl/v-end?.
(t/deftest v-end-guard-uses-col-not-row
(t/testing "v-end? is true for col? frame with justify-content :end"
;; col? + justify-content=:end → ctl/v-end? must be true
(let [frame (cts/setup-shape {:type :frame
:layout :flex
:layout-flex-dir :column
:layout-justify-content :end
:x 0 :y 0 :width 100 :height 100})]
(t/is (true? (ctl/v-end? frame)))))
(t/testing "v-end? is false for row? frame with only justify-content :end"
;; row? + justify-content=:end alone does NOT set v-end?; for row layouts
;; v-end? checks align-items, not justify-content.
(let [frame (cts/setup-shape {:type :frame
:layout :flex
:layout-flex-dir :row
:layout-justify-content :end
:x 0 :y 0 :width 100 :height 100})]
(t/is (not (ctl/v-end? frame))))))