penpot/common/test/common_tests/geom_modifiers_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

188 lines
7.6 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-modifiers-test
(:require
[app.common.geom.modifiers :as gm]
[app.common.geom.point :as gpt]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.types.modifiers :as ctm]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
;; ---- Helpers
(defn- add-flex-frame
"Create a flex layout frame"
[file frame-label & {:keys [width height] :as params}]
(ths/add-sample-shape file frame-label
(merge {:type :frame
:name "FlexFrame"
:layout-flex-dir :row
:width (or width 200)
:height (or height 200)}
params)))
(defn- add-rect-child
"Create a rectangle child inside a parent"
[file rect-label parent-label & {:keys [width height x y] :as params}]
(ths/add-sample-shape file rect-label
(merge {:type :rect
:name "Rect"
:parent-label parent-label
:width (or width 50)
:height (or height 50)
:x (or x 0)
:y (or y 0)}
params)))
(defn- add-ghost-child-id
"Add a non-existent child ID to a frame's shapes list.
This simulates data inconsistency where a child ID is referenced
but the child shape doesn't exist in objects."
[file frame-label ghost-id]
(let [page (thf/current-page file)
frame-id (thi/id frame-label)]
(update file :data
(fn [file-data]
(update-in file-data [:pages-index (:id page) :objects frame-id :shapes]
conj ghost-id)))))
;; ---- Tests
(t/deftest flex-layout-with-normal-children
(t/testing "set-objects-modifiers processes flex layout children correctly"
(let [file (-> (thf/sample-file :file1)
(add-flex-frame :frame1)
(add-rect-child :rect1 :frame1))
page (thf/current-page file)
objects (:objects page)
rect-id (thi/id :rect1)
;; Create a move modifier for the rectangle
modif-tree {rect-id {:modifiers (ctm/move-modifiers (gpt/point 10 20))}}
;; This should not crash
result (gm/set-objects-modifiers modif-tree objects)]
(t/is (some? result))
;; The rectangle should have modifiers
(t/is (contains? result rect-id)))))
(t/deftest flex-layout-with-nonexistent-child
(t/testing "set-objects-modifiers handles flex frame with non-existent child in shapes"
(let [ghost-id (thi/next-uuid)
file (-> (thf/sample-file :file1)
(add-flex-frame :frame1)
(add-rect-child :rect1 :frame1)
;; Add a non-existent child ID to the frame's shapes
(add-ghost-child-id :frame1 ghost-id))
page (thf/current-page file)
objects (:objects page)
rect-id (thi/id :rect1)
;; Create a move modifier for the existing rectangle
modif-tree {rect-id {:modifiers (ctm/move-modifiers (gpt/point 10 20))}}
;; This should NOT crash even though the flex frame has
;; a child ID (ghost-id) that doesn't exist in objects
result (gm/set-objects-modifiers modif-tree objects)]
(t/is (some? result))
(t/is (contains? result rect-id)))))
(t/deftest flex-layout-with-all-ghost-children
(t/testing "set-objects-modifiers handles flex frame with only non-existent children"
(let [ghost1 (thi/next-uuid)
ghost2 (thi/next-uuid)
file (-> (thf/sample-file :file1)
(add-flex-frame :frame1)
;; Add only non-existent children to the frame's shapes
(add-ghost-child-id :frame1 ghost1)
(add-ghost-child-id :frame1 ghost2))
page (thf/current-page file)
objects (:objects page)
frame-id (thi/id :frame1)
;; Create a move modifier for the frame itself
modif-tree {frame-id {:modifiers (ctm/move-modifiers (gpt/point 5 5))}}
;; Should not crash even though the flex frame has
;; no existing children in its shapes list
result (gm/set-objects-modifiers modif-tree objects)]
(t/is (some? result)))))
(t/deftest grid-layout-with-nonexistent-child
(t/testing "set-objects-modifiers handles grid frame with non-existent child in shapes"
(let [ghost-id (thi/next-uuid)
file (-> (thf/sample-file :file1)
(ths/add-sample-shape :frame1
{:type :frame
:name "GridFrame"
:layout-grid-dir :row
:width 200
:height 200})
(add-rect-child :rect1 :frame1)
(add-ghost-child-id :frame1 ghost-id))
page (thf/current-page file)
objects (:objects page)
rect-id (thi/id :rect1)
modif-tree {rect-id {:modifiers (ctm/move-modifiers (gpt/point 10 20))}}
;; Should not crash for grid layout with ghost child
result (gm/set-objects-modifiers modif-tree objects)]
(t/is (some? result))
(t/is (contains? result rect-id)))))
(t/deftest flex-layout-resize-with-nonexistent-child
(t/testing "resize modifier propagation handles non-existent children"
(let [ghost-id (thi/next-uuid)
file (-> (thf/sample-file :file1)
(add-flex-frame :frame1)
(add-rect-child :rect1 :frame1)
(add-ghost-child-id :frame1 ghost-id))
page (thf/current-page file)
objects (:objects page)
frame-id (thi/id :frame1)
;; Create a resize modifier for the frame itself
modif-tree {frame-id {:modifiers (ctm/resize-modifiers
(gpt/point 2 2)
(gpt/point 0 0))}}
;; Should not crash when propagating resize through flex layout
;; that has ghost children
result (gm/set-objects-modifiers modif-tree objects)]
(t/is (some? result))
;; The frame should have modifiers
(t/is (contains? result frame-id)))))
(t/deftest nested-flex-layout-with-nonexistent-child
(t/testing "nested flex layout handles non-existent children in outer frame"
(let [ghost-id (thi/next-uuid)
file (-> (thf/sample-file :file1)
(add-flex-frame :outer-frame)
(add-flex-frame :inner-frame :parent-label :outer-frame)
(add-rect-child :rect1 :inner-frame)
(add-ghost-child-id :outer-frame ghost-id))
page (thf/current-page file)
objects (:objects page)
rect-id (thi/id :rect1)
modif-tree {rect-id {:modifiers (ctm/move-modifiers (gpt/point 5 10))}}
result (gm/set-objects-modifiers modif-tree objects)]
(t/is (some? result))
(t/is (contains? result rect-id)))))