mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
✨ Add tests for app.common.geom namespaces
This commit is contained in:
parent
fa89790fd6
commit
146219a439
106
common/test/common_tests/geom_flex_layout_test.cljc
Normal file
106
common/test/common_tests/geom_flex_layout_test.cljc
Normal file
@ -0,0 +1,106 @@
|
||||
;; 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))))))
|
||||
@ -289,3 +289,33 @@
|
||||
(t/is (mth/close? 1.2091818119288809 (:x rs)))
|
||||
(t/is (mth/close? 1.8275638211757912 (:y rs)))))
|
||||
|
||||
;; ---- gpt/abs ----
|
||||
|
||||
(t/deftest abs-point-returns-point-instance
|
||||
(t/testing "abs of a point with negative coordinates returns a Point record"
|
||||
(let [p (gpt/point -3 -4)
|
||||
rs (gpt/abs p)]
|
||||
(t/is (gpt/point? rs))
|
||||
(t/is (mth/close? 3 (:x rs)))
|
||||
(t/is (mth/close? 4 (:y rs)))))
|
||||
|
||||
(t/testing "abs of a point with mixed-sign coordinates"
|
||||
(let [p (gpt/point -5 7)
|
||||
rs (gpt/abs p)]
|
||||
(t/is (gpt/point? rs))
|
||||
(t/is (mth/close? 5 (:x rs)))
|
||||
(t/is (mth/close? 7 (:y rs)))))
|
||||
|
||||
(t/testing "abs of a point already positive is unchanged"
|
||||
(let [p (gpt/point 2 9)
|
||||
rs (gpt/abs p)]
|
||||
(t/is (gpt/point? rs))
|
||||
(t/is (mth/close? 2 (:x rs)))
|
||||
(t/is (mth/close? 9 (:y rs)))))
|
||||
|
||||
(t/testing "abs of a zero point stays zero"
|
||||
(let [rs (gpt/abs (gpt/point 0 0))]
|
||||
(t/is (gpt/point? rs))
|
||||
(t/is (mth/close? 0 (:x rs)))
|
||||
(t/is (mth/close? 0 (:y rs))))))
|
||||
|
||||
|
||||
94
common/test/common_tests/geom_rect_test.cljc
Normal file
94
common/test/common_tests/geom_rect_test.cljc
Normal file
@ -0,0 +1,94 @@
|
||||
;; 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-rect-test
|
||||
(:require
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.math :as mth]
|
||||
[clojure.test :as t]))
|
||||
|
||||
;; ---- update-rect :size ----
|
||||
|
||||
(t/deftest update-rect-size-sets-all-corners
|
||||
(t/testing ":size updates x1/y1 as well as x2/y2 from x/y/width/height"
|
||||
(let [r (grc/make-rect 10 20 30 40)
|
||||
r' (grc/update-rect r :size)]
|
||||
;; x1/y1 must mirror x/y
|
||||
(t/is (mth/close? (:x r) (:x1 r')))
|
||||
(t/is (mth/close? (:y r) (:y1 r')))
|
||||
;; x2/y2 must be x+width / y+height
|
||||
(t/is (mth/close? (+ (:x r) (:width r)) (:x2 r')))
|
||||
(t/is (mth/close? (+ (:y r) (:height r)) (:y2 r')))))
|
||||
|
||||
(t/testing ":size is consistent with :corners round-trip"
|
||||
;; Applying :size then :corners should recover the original x/y/w/h
|
||||
(let [r (grc/make-rect 5 15 100 50)
|
||||
r' (-> r (grc/update-rect :size) (grc/update-rect :corners))]
|
||||
(t/is (mth/close? (:x r) (:x r')))
|
||||
(t/is (mth/close? (:y r) (:y r')))
|
||||
(t/is (mth/close? (:width r) (:width r')))
|
||||
(t/is (mth/close? (:height r) (:height r')))))
|
||||
|
||||
(t/testing ":size works for a rect at the origin"
|
||||
(let [r (grc/make-rect 0 0 200 100)
|
||||
r' (grc/update-rect r :size)]
|
||||
(t/is (mth/close? 0 (:x1 r')))
|
||||
(t/is (mth/close? 0 (:y1 r')))
|
||||
(t/is (mth/close? 200 (:x2 r')))
|
||||
(t/is (mth/close? 100 (:y2 r'))))))
|
||||
|
||||
;; ---- corners->rect ----
|
||||
|
||||
(t/deftest corners->rect-normal-order
|
||||
(t/testing "p1 top-left, p2 bottom-right yields a valid rect"
|
||||
(let [r (grc/corners->rect 0 0 10 20)]
|
||||
(t/is (grc/rect? r))
|
||||
(t/is (mth/close? 0 (:x r)))
|
||||
(t/is (mth/close? 0 (:y r)))
|
||||
(t/is (mth/close? 10 (:width r)))
|
||||
(t/is (mth/close? 20 (:height r))))))
|
||||
|
||||
(t/deftest corners->rect-reversed-corners
|
||||
(t/testing "reversed x-coordinates still produce a positive-width rect"
|
||||
(let [r (grc/corners->rect 10 0 0 20)]
|
||||
(t/is (grc/rect? r))
|
||||
(t/is (mth/close? 0 (:x r)))
|
||||
(t/is (mth/close? 10 (:width r)))))
|
||||
|
||||
(t/testing "reversed y-coordinates still produce a positive-height rect"
|
||||
(let [r (grc/corners->rect 0 20 10 0)]
|
||||
(t/is (grc/rect? r))
|
||||
(t/is (mth/close? 0 (:y r)))
|
||||
(t/is (mth/close? 20 (:height r)))))
|
||||
|
||||
(t/testing "both axes reversed yield the same rect as normal order"
|
||||
(let [r-normal (grc/corners->rect 0 0 10 20)
|
||||
r-reversed (grc/corners->rect 10 20 0 0)]
|
||||
(t/is (mth/close? (:x r-normal) (:x r-reversed)))
|
||||
(t/is (mth/close? (:y r-normal) (:y r-reversed)))
|
||||
(t/is (mth/close? (:width r-normal) (:width r-reversed)))
|
||||
(t/is (mth/close? (:height r-normal) (:height r-reversed))))))
|
||||
|
||||
(t/deftest corners->rect-from-points
|
||||
(t/testing "two-arity overload taking point maps works identically"
|
||||
(let [p1 {:x 5 :y 10}
|
||||
p2 {:x 15 :y 30}
|
||||
r (grc/corners->rect p1 p2)]
|
||||
(t/is (grc/rect? r))
|
||||
(t/is (mth/close? 5 (:x r)))
|
||||
(t/is (mth/close? 10 (:y r)))
|
||||
(t/is (mth/close? 10 (:width r)))
|
||||
(t/is (mth/close? 20 (:height r)))))
|
||||
|
||||
(t/testing "two-arity overload with reversed points"
|
||||
(let [p1 {:x 15 :y 30}
|
||||
p2 {:x 5 :y 10}
|
||||
r (grc/corners->rect p1 p2)]
|
||||
(t/is (grc/rect? r))
|
||||
(t/is (mth/close? 5 (:x r)))
|
||||
(t/is (mth/close? 10 (:y r)))
|
||||
(t/is (mth/close? 10 (:width r)))
|
||||
(t/is (mth/close? 20 (:height r))))))
|
||||
27
common/test/common_tests/geom_shapes_constraints_test.cljc
Normal file
27
common/test/common_tests/geom_shapes_constraints_test.cljc
Normal file
@ -0,0 +1,27 @@
|
||||
;; 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-shapes-constraints-test
|
||||
(:require
|
||||
[app.common.geom.shapes.constraints :as gsc]
|
||||
[clojure.test :as t]))
|
||||
|
||||
;; ---- constraint-modifier :default ----
|
||||
|
||||
(t/deftest constraint-modifier-default-returns-empty-vector
|
||||
(t/testing ":default method accepts 6 args and returns an empty vector"
|
||||
;; Before the fix the :default method only accepted 5 positional args
|
||||
;; (plus the dispatch value), so calling it with 6 args would throw an
|
||||
;; arity error. After the fix it takes [_ _ _ _ _ _] and returns [].
|
||||
(let [result (gsc/constraint-modifier :unknown-constraint-type
|
||||
:x nil nil nil nil)]
|
||||
(t/is (vector? result))
|
||||
(t/is (empty? result))))
|
||||
|
||||
(t/testing ":default method returns [] for :scale-like unknown type on :y axis"
|
||||
(let [result (gsc/constraint-modifier :some-other-unknown
|
||||
:y nil nil nil nil)]
|
||||
(t/is (= [] result)))))
|
||||
@ -230,3 +230,44 @@
|
||||
(t/is (true? (gsin/slow-has-point? shape point1)))
|
||||
(t/is (false? (gsin/fast-has-point? shape point2)))
|
||||
(t/is (false? (gsin/fast-has-point? shape point2)))))
|
||||
|
||||
;; ---- adjust-shape-flips (via apply-transform / transform-shape) ----
|
||||
|
||||
(t/deftest flip-x-only-toggles-flip-x-and-negates-rotation
|
||||
(t/testing "Flipping only X axis toggles flip-x and negates rotation"
|
||||
;; Build a rect with a known rotation, then apply a scale(-1, 1)
|
||||
;; from the left edge to simulate an X-axis flip.
|
||||
(let [shape (create-test-shape :rect {:rotation 30})
|
||||
;; Flip horizontally about x=0 (left edge of shape)
|
||||
origin (gpt/point (get-in shape [:selrect :x]) (get-in shape [:selrect :y]))
|
||||
mods (ctm/resize-modifiers (gpt/point -1 1) origin)
|
||||
result (gsh/transform-shape shape mods)]
|
||||
;; flip-x should have been toggled (from nil/false to true)
|
||||
(t/is (true? (:flip-x result)))
|
||||
;; flip-y should NOT be set
|
||||
(t/is (not (true? (:flip-y result))))
|
||||
;; rotation is negated then normalised into [0,360): -30 mod 360 = 330
|
||||
(t/is (mth/close? 330 (:rotation result))))))
|
||||
|
||||
(t/deftest flip-y-only-toggles-flip-y-and-negates-rotation
|
||||
(t/testing "Flipping only Y axis toggles flip-y and negates rotation"
|
||||
(let [shape (create-test-shape :rect {:rotation 45})
|
||||
origin (gpt/point (get-in shape [:selrect :x]) (get-in shape [:selrect :y]))
|
||||
mods (ctm/resize-modifiers (gpt/point 1 -1) origin)
|
||||
result (gsh/transform-shape shape mods)]
|
||||
(t/is (not (true? (:flip-x result))))
|
||||
(t/is (true? (:flip-y result)))
|
||||
;; -45 mod 360 = 315
|
||||
(t/is (mth/close? 315 (:rotation result))))))
|
||||
|
||||
(t/deftest flip-both-axes-toggles-both-flags-but-preserves-rotation
|
||||
(t/testing "Flipping both axes toggles flip-x and flip-y, but does NOT negate rotation"
|
||||
;; Two simultaneous axis flips = 180° rotation, so stored rotation is unchanged.
|
||||
(let [shape (create-test-shape :rect {:rotation 30})
|
||||
origin (gpt/point (get-in shape [:selrect :x]) (get-in shape [:selrect :y]))
|
||||
mods (ctm/resize-modifiers (gpt/point -1 -1) origin)
|
||||
result (gsh/transform-shape shape mods)]
|
||||
(t/is (true? (:flip-x result)))
|
||||
(t/is (true? (:flip-y result)))
|
||||
;; rotation must not be negated when both axes are flipped
|
||||
(t/is (mth/close? 30 (:rotation result))))))
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.math :as mth]
|
||||
[app.common.schema :as sm]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest point-constructors-test
|
||||
@ -100,3 +101,28 @@
|
||||
(let [m (-> (gmt/matrix)
|
||||
(gmt/rotate 10))]
|
||||
(t/is (= m (gmt/matrix 0.984807753012208 0.17364817766693033 -0.17364817766693033 0.984807753012208 0 0)))))
|
||||
|
||||
;; ---- matrix->str (no trailing comma) ----
|
||||
|
||||
(t/deftest matrix-str-roundtrip-test
|
||||
(t/testing "Identity matrix encodes and decodes back to equal matrix"
|
||||
(let [m (gmt/matrix)
|
||||
enc (sm/encode gmt/schema:matrix m (sm/string-transformer))
|
||||
dec (sm/decode gmt/schema:matrix enc (sm/string-transformer))]
|
||||
(t/is (string? enc))
|
||||
;; Must not end with a comma
|
||||
(t/is (not= \, (last enc)))
|
||||
(t/is (gmt/close? m dec))))
|
||||
|
||||
(t/testing "Arbitrary matrix encodes without trailing comma and round-trips"
|
||||
(let [m (gmt/matrix 2 0.5 -0.5 3 10 20)
|
||||
enc (sm/encode gmt/schema:matrix m (sm/string-transformer))
|
||||
dec (sm/decode gmt/schema:matrix enc (sm/string-transformer))]
|
||||
(t/is (string? enc))
|
||||
(t/is (not= \, (last enc)))
|
||||
(t/is (gmt/close? m dec))))
|
||||
|
||||
(t/testing "Encoded string contains exactly 5 commas (6 fields)"
|
||||
(let [m (gmt/matrix 1 0 0 1 0 0)
|
||||
enc (sm/encode gmt/schema:matrix m (sm/string-transformer))]
|
||||
(t/is (= 5 (count (filter #(= \, %) enc)))))))
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
[common-tests.files-migrations-test]
|
||||
[common-tests.geom-align-test]
|
||||
[common-tests.geom-bounds-map-test]
|
||||
[common-tests.geom-flex-layout-test]
|
||||
[common-tests.geom-grid-layout-test]
|
||||
[common-tests.geom-grid-test]
|
||||
[common-tests.geom-line-test]
|
||||
@ -22,7 +23,9 @@
|
||||
[common-tests.geom-modifiers-test]
|
||||
[common-tests.geom-point-test]
|
||||
[common-tests.geom-proportions-test]
|
||||
[common-tests.geom-rect-test]
|
||||
[common-tests.geom-shapes-common-test]
|
||||
[common-tests.geom-shapes-constraints-test]
|
||||
[common-tests.geom-shapes-corners-test]
|
||||
[common-tests.geom-shapes-effects-test]
|
||||
[common-tests.geom-shapes-intersect-test]
|
||||
@ -88,13 +91,17 @@
|
||||
'common-tests.files-migrations-test
|
||||
'common-tests.geom-align-test
|
||||
'common-tests.geom-bounds-map-test
|
||||
'common-tests.geom-flex-layout-test
|
||||
'common-tests.geom-grid-layout-test
|
||||
'common-tests.geom-grid-test
|
||||
'common-tests.geom-line-test
|
||||
'common-tests.geom-modif-tree-test
|
||||
'common-tests.geom-modifiers-test
|
||||
'common-tests.geom-point-test
|
||||
'common-tests.geom-proportions-test
|
||||
'common-tests.geom-rect-test
|
||||
'common-tests.geom-shapes-common-test
|
||||
'common-tests.geom-shapes-constraints-test
|
||||
'common-tests.geom-shapes-corners-test
|
||||
'common-tests.geom-shapes-effects-test
|
||||
'common-tests.geom-shapes-intersect-test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user