2026-10-28 19:17:39 +01:00

215 lines
11 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 SUBSIDIARY SL
(ns performance-tests.render-wasm.boolean-test
(:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes.intersect :as gint]
[app.common.types.path :as path]
[app.common.types.path.segment :as segm]
[app.common.uuid :as uuid]
[benches.render-wasm.builder :as b :include-macros true]
[benches.render-wasm.snapshot :as snapshot]
[cljs.test :as t :include-macros true]
[performance-tests.render-wasm.test-helpers :as helpers]))
(t/deftest difference-of-overlapping-rects-keeps-hierarchy-and-bounds
(let [red {:fill-color "#ff0000" :fill-opacity 1}
blue {:fill-color "#0000ff" :fill-opacity 1}
instance (b/scene {:seed 1}
(b/bool :l-shape {:bool-type :difference :name "L"}
(b/rect {:x 0 :y 0 :width 100 :height 100 :fills [red]})
(b/rect {:x 50 :y 50 :width 100 :height 100 :fills [blue]})))
bool (first (helpers/children-of instance uuid/zero))
children (helpers/children-of instance (:id bool))]
(t/is (= :bool (:type bool)))
(t/is (= :difference (:bool-type bool)))
(t/is (= (:id bool) (snapshot/ref-id instance :l-shape)))
(t/is (= 2 (count children)))
(t/is (= [(:id (first children)) (:id (second children))] (:shapes bool)))
(t/is (= [(:id bool) (:id bool)]
[(:parent-id (first children)) (:parent-id (second children))]))
(t/is (= [uuid/zero uuid/zero]
[(:frame-id (first children)) (:frame-id (second children))]))
(t/is (= [0 0 100 100] (helpers/selrect bool)))))
(t/deftest difference-excludes-points-in-removed-overlap
(let [instance (b/scene {:seed 1}
(b/bool {:bool-type :difference}
(b/rect {:x 0 :y 0 :width 100 :height 100})
(b/rect {:x 50 :y 50 :width 100 :height 100})))
bool (first (helpers/children-of instance uuid/zero))
lines (segm/path->lines bool)]
(t/is (true? (gint/is-point-inside-nonzero? (gpt/point 25 25) lines))
"a point in the kept region stays inside the content")
(t/is (false? (gint/is-point-inside-nonzero? (gpt/point 75 75) lines))
"a point in the subtracted overlap is excluded from the content")))
(t/deftest union-derives-canonical-bounds
(let [instance (b/scene {:seed 1}
(b/bool {:bool-type :union}
(b/rect {:x 0 :y 0 :width 100 :height 100})
(b/rect {:x 50 :y 50 :width 100 :height 100})))
bool (first (helpers/children-of instance uuid/zero))]
(t/is (= :union (:bool-type bool)))
(t/is (= [0 0 150 150] (helpers/selrect bool)))))
(t/deftest difference-picks-the-first-child-as-style-head
(let [red {:fill-color "#ff0000" :fill-opacity 1}
blue {:fill-color "#0000ff" :fill-opacity 1}
blur {:id (uuid/custom 3 3) :type :layer-blur :value 4 :hidden false}
difference (b/scene {:seed 1}
(b/bool {:bool-type :difference}
(b/rect {:x 0 :y 0 :width 10 :height 10
:fills [red] :blur blur})
(b/rect {:x 5 :y 0 :width 10 :height 10
:fills [blue]})))
diff-bool (first (helpers/children-of difference uuid/zero))]
(t/is (= [red] (:fills diff-bool)))
(t/is (= blur (:blur diff-bool)))))
(t/deftest supplied-styles-override-inherited
(let [instance (b/scene {:seed 1}
(b/bool {:bool-type :union :fills []}
(b/rect {:x 0 :y 0 :width 10 :height 10
:fills [{:fill-color "#ff0000" :fill-opacity 1}]})
(b/rect {:x 5 :y 0 :width 10 :height 10
:fills [{:fill-color "#0000ff" :fill-opacity 1}]})))
bool (first (helpers/children-of instance uuid/zero))]
(t/is (= [] (:fills bool)))))
(t/deftest nil-styles-do-not-count-as-supplied
(let [red {:fill-color "#ff0000" :fill-opacity 1}
blue {:fill-color "#0000ff" :fill-opacity 1}
instance (b/scene {:seed 1}
(b/bool {:bool-type :union :fills nil}
(b/rect {:x 0 :y 0 :width 10 :height 10 :fills [red]})
(b/rect {:x 5 :y 0 :width 10 :height 10 :fills [blue]})))
bool (first (helpers/children-of instance uuid/zero))]
(t/is (= [blue] (:fills bool)))))
(t/deftest single-child-bool-uses-the-child-path
(let [instance (b/scene {:seed 1}
(b/bool {:bool-type :difference}
(b/rect {:x 10 :y 20 :width 30 :height 40})))
bool (first (helpers/children-of instance uuid/zero))]
(t/is (= 1 (count (:shapes bool))))
(t/is (= [10 20 30 40] (helpers/selrect bool)))
(t/is (= (:selrect bool) (path/calc-selrect (:content bool))))))
(t/deftest nested-bools-finalize-inside-out
(let [instance (b/scene {:seed 1}
(b/bool {:bool-type :union}
(b/bool {:bool-type :difference}
(b/rect {:x 0 :y 0 :width 100 :height 100})
(b/rect {:x 50 :y 50 :width 100 :height 100}))
(b/rect {:x 200 :y 0 :width 50 :height 50})))
outer (first (helpers/children-of instance uuid/zero))
inner (first (helpers/children-of instance (:id outer)))]
(t/is (= :bool (:type inner)))
(t/is (= [0 0 100 100] (helpers/selrect inner)))
(t/is (= [0 0 250 100] (helpers/selrect outer)))
(t/is (= (:selrect outer) (path/calc-selrect (:content outer))))))
(t/deftest bool-inside-frame-keeps-frame-ids
(let [instance (b/scene {:seed 1}
(b/frame {:x 0 :y 0 :width 200 :height 200}
(b/bool {:bool-type :union}
(b/rect {:x 10 :y 10 :width 10 :height 10})
(b/rect {:x 30 :y 10 :width 10 :height 10}))))
frame (first (helpers/children-of instance uuid/zero))
bool (first (helpers/children-of instance (:id frame)))
rects (helpers/children-of instance (:id bool))]
(t/is (= (:id frame) (:parent-id bool)))
(t/is (= (:id frame) (:frame-id bool)))
(t/is (= [(:id frame) (:id frame)] (mapv :frame-id rects)))
(t/is (= [(:id bool) (:id bool)] (mapv :parent-id rects)))))
(t/deftest bool-inside-group-derives-group-bounds
(let [instance (b/scene {:seed 1}
(b/group {}
(b/bool {:bool-type :union}
(b/rect {:x 10 :y 20 :width 30 :height 40})
(b/rect {:x 50 :y 80 :width 10 :height 10}))))
group (first (helpers/children-of instance uuid/zero))
bool (first (helpers/children-of instance (:id group)))]
(t/is (= :bool (:type bool)))
(t/is (= [10 20 50 70] (helpers/selrect group)))
(t/is (= uuid/zero (:frame-id bool)))))
(t/deftest empty-bool-is-rejected
(let [data (helpers/failure-data #(b/scene {:seed 1} (b/bool {:bool-type :union})))]
(t/is (= ::b/empty-bool (:type data)))))
(t/deftest caught-empty-bool-still-fails-finish
(let [state (helpers/run-scope {:seed 1}
(fn []
(try
(b/bool {:bool-type :union})
(catch :default _ nil))))]
(t/is (= ::b/unfinished-container
(:type (helpers/failure-data #(b/finish! state)))))))
(t/deftest bool-geometry-attrs-are-rejected
(doseq [attrs [{:bool-type :union :x 1}
{:bool-type :union :selrect {:x 0 :y 0 :width 1 :height 1}}
{:bool-type :union :content []}]]
(let [data (helpers/failure-data #(b/scene {:seed 1} (b/bool attrs (b/rect))))]
(t/is (= ::b/bool-geometry (:type data)) (str attrs))
(t/is (contains? #{:x :selrect :content} (:key data)) (str attrs)))))
(t/deftest bool-transform-attrs-are-rejected
(doseq [attrs [{:bool-type :union :rotation 45}
{:bool-type :union :transform "m"}
{:bool-type :union :transform-inverse "m"}
{:bool-type :union :flip-x true}
{:bool-type :union :flip-y true}]]
(let [data (helpers/failure-data #(b/scene {:seed 1} (b/bool attrs (b/rect))))]
(t/is (= ::b/bool-transform (:type data)) (str attrs)))))
(t/deftest bool-type-is-required-and-validated
(t/is (= ::b/bool-type
(:type (helpers/failure-data #(b/scene {:seed 1} (b/bool {:name "B"} (b/rect)))))))
(let [data (helpers/failure-data #(b/scene {:seed 1} (b/bool {:bool-type :merge} (b/rect))))]
(t/is (= ::b/bool-type (:type data)))
(t/is (= :merge (:bool-type data)))
(t/is (= #{:union :difference :intersection :exclude} (set (:supported data))))))
(t/deftest frame-child-is-rejected
(let [data (helpers/failure-data
#(b/scene {:seed 1}
(b/bool {:bool-type :union}
(b/frame {:x 0 :y 0 :width 10 :height 10}
(b/rect)))))]
(t/is (= ::b/frame-in-bool (:type data)))
(t/is (uuid? (:child data)))))
(t/deftest bool-generation-ignores-the-renderer-override
(let [original path/wasm:calc-bool-content]
(try
(set! path/wasm:calc-bool-content
(fn [& _]
(throw (ex-info "renderer override ran during generation"
{:type ::unexpected-renderer-call}))))
(let [instance (b/scene {:seed 1}
(b/bool {:bool-type :difference}
(b/rect {:x 0 :y 0 :width 100 :height 100})
(b/rect {:x 50 :y 50 :width 100 :height 100})))
bool (first (helpers/children-of instance uuid/zero))]
(t/is (= [0 0 100 100] (helpers/selrect bool))))
(finally
(set! path/wasm:calc-bool-content original)))))
(t/deftest non-map-bool-attrs-are-rejected
(t/is (= ::b/invalid-attrs
(:type (helpers/failure-data #(b/scene {:seed 1} (b/bool :hero (b/rect)))))))
(let [state (helpers/run-scope {:seed 1}
(fn []
(try
(b/bool :hero (b/rect))
(catch :default _ nil))))]
(t/is (= 1 (count (get-in @state [:objects uuid/zero :shapes]))))))