penpot/common/test/common_tests/geom_shapes_common_test.cljc
Andrey Antukh 2ca7acfca6
Add tests for app.common.geom and descendant namespaces (#8768)
* 🎉 Add tests for app.common.geom.bounds-map

* 🎉 Add tests for app.common.geom and descendant namespaces

* 📎 Fix linting issues

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-04-02 09:50:34 +02:00

137 lines
5.7 KiB
Clojure

;; 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-common-test
(:require
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
[app.common.geom.shapes.common :as gco]
[app.common.math :as mth]
[clojure.test :as t]))
(t/deftest points->center-test
(t/testing "Center of a unit square"
(let [points [(gpt/point 0 0) (gpt/point 10 0)
(gpt/point 10 10) (gpt/point 0 10)]
center (gco/points->center points)]
(t/is (mth/close? 5.0 (:x center)))
(t/is (mth/close? 5.0 (:y center)))))
(t/testing "Center of a rectangle"
(let [points [(gpt/point 0 0) (gpt/point 20 0)
(gpt/point 20 10) (gpt/point 0 10)]
center (gco/points->center points)]
(t/is (mth/close? 10.0 (:x center)))
(t/is (mth/close? 5.0 (:y center)))))
(t/testing "Center of a translated square"
(let [points [(gpt/point 100 200) (gpt/point 150 200)
(gpt/point 150 250) (gpt/point 100 250)]
center (gco/points->center points)]
(t/is (mth/close? 125.0 (:x center)))
(t/is (mth/close? 225.0 (:y center))))))
(t/deftest shape->center-test
(t/testing "Center from shape selrect (proper rect record)"
(let [shape {:selrect (grc/make-rect 10 20 100 50)}
center (gco/shape->center shape)]
(t/is (mth/close? 60.0 (:x center)))
(t/is (mth/close? 45.0 (:y center))))))
(t/deftest transform-points-test
(t/testing "Transform with identity matrix leaves points unchanged"
(let [points [(gpt/point 0 0) (gpt/point 10 0)
(gpt/point 10 10) (gpt/point 0 10)]
result (gco/transform-points points (gmt/matrix))]
(doseq [[p r] (map vector points result)]
(t/is (mth/close? (:x p) (:x r)))
(t/is (mth/close? (:y p) (:y r))))))
(t/testing "Transform with translation matrix"
(let [points [(gpt/point 0 0) (gpt/point 10 0)
(gpt/point 10 10) (gpt/point 0 10)]
mtx (gmt/translate-matrix (gpt/point 5 10))
result (gco/transform-points points mtx)]
(t/is (mth/close? 5.0 (:x (first result))))
(t/is (mth/close? 10.0 (:y (first result))))))
(t/testing "Transform around a center point"
(let [points [(gpt/point 0 0) (gpt/point 10 0)
(gpt/point 10 10) (gpt/point 0 10)]
center (gco/points->center points)
mtx (gmt/scale-matrix (gpt/point 2 2))
result (gco/transform-points points center mtx)]
;; Scaling around center (5,5) by 2x: (0,0)→(-5,-5)
(t/is (mth/close? -5.0 (:x (first result))))
(t/is (mth/close? -5.0 (:y (first result))))))
(t/testing "Transform with nil matrix returns points unchanged"
(let [points [(gpt/point 1 2) (gpt/point 3 4)]
result (gco/transform-points points nil)]
(t/is (= points result))))
(t/testing "Transform empty points returns empty"
(let [result (gco/transform-points [] (gmt/matrix))]
(t/is (= [] result)))))
(t/deftest invalid-geometry?-test
(t/testing "Valid geometry is not invalid"
(let [shape {:selrect (grc/make-rect 0 0 100 50)
:points [(gpt/point 0 0) (gpt/point 100 0)
(gpt/point 100 50) (gpt/point 0 50)]}]
(t/is (not (gco/invalid-geometry? shape)))))
(t/testing "NaN x in selrect is invalid"
(let [selrect (grc/make-rect 0 0 100 50)
selrect (assoc selrect :x ##NaN)
shape {:selrect selrect
:points [(gpt/point 0 0) (gpt/point 100 0)
(gpt/point 100 50) (gpt/point 0 50)]}]
(t/is (true? (gco/invalid-geometry? shape)))))
(t/testing "NaN in points is invalid"
(let [shape {:selrect (grc/make-rect 0 0 100 50)
:points [(gpt/point ##NaN 0) (gpt/point 100 0)
(gpt/point 100 50) (gpt/point 0 50)]}]
(t/is (true? (gco/invalid-geometry? shape))))))
(t/deftest shape->points-test
(t/testing "Identity transform uses reconstructed points from corners"
(let [points [(gpt/point 10 20) (gpt/point 40 20)
(gpt/point 40 60) (gpt/point 10 60)]
shape {:transform (gmt/matrix) :points points}
result (gco/shape->points shape)]
(t/is (= 4 (count result)))
;; p0 and p2 are used to reconstruct p1 and p3
(t/is (mth/close? 10.0 (:x (nth result 0))))
(t/is (mth/close? 20.0 (:y (nth result 0))))
(t/is (mth/close? 40.0 (:x (nth result 2))))
(t/is (mth/close? 60.0 (:y (nth result 2))))))
(t/testing "Non-identity transform returns points as-is"
(let [points [(gpt/point 10 20) (gpt/point 40 20)
(gpt/point 40 60) (gpt/point 10 60)]
shape {:transform (gmt/translate-matrix (gpt/point 5 5)) :points points}
result (gco/shape->points shape)]
(t/is (= points result)))))
(t/deftest transform-selrect-test
(t/testing "Transform selrect with identity matrix"
(let [selrect (grc/make-rect 10 20 100 50)
result (gco/transform-selrect selrect (gmt/matrix))]
(t/is (mth/close? 10.0 (:x result)))
(t/is (mth/close? 20.0 (:y result)))
(t/is (mth/close? 100.0 (:width result)))
(t/is (mth/close? 50.0 (:height result)))))
(t/testing "Transform selrect with translation"
(let [selrect (grc/make-rect 0 0 100 50)
mtx (gmt/translate-matrix (gpt/point 10 20))
result (gco/transform-selrect selrect mtx)]
(t/is (mth/close? 10.0 (:x result)))
(t/is (mth/close? 20.0 (:y result))))))