penpot/common/test/common_tests/files_shape_compact_test.cljc
Andrey Antukh c5a29e27a6 Add shape compact/expand module for binfile-v3 format
Create common/src/app/common/files/shape_compact.cljc with:
- compact-shape: prune derivable geometry, identity transforms,
  nil values, defaults, and empty collections
- expand-shape: restore omitted fields via existing setup-rect/setup-path
- round-values: round float32 artifacts to 4 decimal places

Includes 24 tests (unit + generative) over schema:shape.

AI-assisted-by: deepseek-v4-pro
2026-07-31 13:28:22 +02:00

251 lines
9.1 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 Sucursal en España SL
(ns common-tests.files-shape-compact-test
(:require
[app.common.files.shape-compact :as sc]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
[app.common.math :as mth]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.schema.test :as smt]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[clojure.test :as t]))
;; --- Helpers
(defn- make-rect
[props]
(cts/setup-shape (merge {:type :rect :x 0 :y 0 :width 10 :height 10} props)))
(defn- make-path
[props]
(cts/setup-shape
(merge {:type :path
:content [{:command :move-to :params {:x 0 :y 0}}
{:command :line-to :params {:x 10 :y 10}}]}
props)))
;; --- round-values
(t/deftest round-values-integers-unchanged
(t/is (= 5 (sc/round-values 5)))
(t/is (= 0 (sc/round-values 0))))
(t/deftest round-values-floats-rounded
(t/is (= 0.6 (sc/round-values 0.6000000238418579)))
(t/is (= 4213.6 (sc/round-values 4213.60009765625)))
(t/is (= 0.1235 (sc/round-values 0.12345678))))
(t/deftest round-values-nested-maps
(let [data {:x 100.1234567 :y 200.9876543
:meta {:opacity 0.6000000238418579}}]
(t/is (= {:x 100.1235 :y 200.9877
:meta {:opacity 0.6}}
(sc/round-values data)))))
(t/deftest round-values-vectors
(let [data [1.2345678 2.00001 3]]
(t/is (= [1.2346 2.0 3] (sc/round-values data)))))
(t/deftest round-values-non-numeric-unchanged
(t/is (= "hello" (sc/round-values "hello")))
(t/is (= :foo (sc/round-values :foo)))
(t/is (= true (sc/round-values true)))
(t/is (nil? (sc/round-values nil)))
(let [id (uuid/next)]
(t/is (= id (sc/round-values id)))))
;; --- compact-shape
(t/deftest compact-shape-removes-nils
(let [shape {:id (uuid/next) :type :rect :name "test"
:flip-x nil :flip-y nil
:fills [] :strokes []}
c (sc/compact-shape shape)]
(t/is (not (contains? c :flip-x)))
(t/is (not (contains? c :flip-y)))
(t/is (not (contains? c :fills)))
(t/is (not (contains? c :strokes)))))
(t/deftest compact-shape-removes-identity-transform
(let [shape (make-rect {:x 100 :y 200 :width 50 :height 30})
c (sc/compact-shape shape)]
(t/is (not (contains? c :transform)))
(t/is (not (contains? c :transform-inverse)))))
(t/deftest compact-shape-keeps-non-identity-transform
(let [shape (-> (make-rect {:x 100 :y 200 :width 50 :height 30})
(assoc :transform (gmt/matrix 1 0.5 0 1 0 0)))
c (sc/compact-shape shape)]
(t/is (contains? c :transform))
(t/is (contains? c :transform-inverse))))
(t/deftest compact-shape-removes-derivable-geometry-rect
(let [shape (make-rect {:x 100 :y 200 :width 50 :height 30})
c (sc/compact-shape shape)]
(t/is (not (contains? c :selrect)))
(t/is (not (contains? c :points)))))
(t/deftest compact-shape-keeps-geometry-with-rotation
(let [shape (-> (make-rect {:x 100 :y 200 :width 50 :height 30})
(assoc :rotation 45))
c (sc/compact-shape shape)]
(t/is (contains? c :selrect))
(t/is (contains? c :points))))
(t/deftest compact-shape-path-keeps-selrect
(let [shape (make-path {})
c (sc/compact-shape shape)]
(t/is (contains? c :selrect))
(t/is (not (contains? c :points)))
(t/is (contains? c :content))))
(t/deftest compact-shape-removes-page-id
(let [shape (assoc (make-rect {}) :page-id (uuid/next))
c (sc/compact-shape shape)]
(t/is (not (contains? c :page-id)))))
(t/deftest compact-shape-removes-defaults
(let [shape (make-rect {:rotation 0 :proportion-lock false :blocked false})
c (sc/compact-shape shape)]
(t/is (not (contains? c :rotation)))
(t/is (not (contains? c :proportion-lock)))
(t/is (not (contains? c :blocked)))))
(t/deftest compact-shape-keeps-non-defaults
(let [shape (make-rect {:rotation 30 :blocked true :opacity 0.5})
c (sc/compact-shape shape)]
(t/is (= 30 (:rotation c)))
(t/is (true? (:blocked c)))
(t/is (= 0.5 (:opacity c)))))
(t/deftest compact-shape-preserves-component-attrs
(let [comp-id (uuid/next)
ref-id (uuid/next)
shape (-> (make-rect {})
(assoc :component-id comp-id
:component-file (uuid/next)
:shape-ref ref-id
:touched #{:geometry-group}))
c (sc/compact-shape shape)]
(t/is (= comp-id (:component-id c)))
(t/is (= ref-id (:shape-ref c)))
(t/is (= #{:geometry-group} (:touched c)))))
;; --- expand-shape
(t/deftest expand-shape-restores-transform
(let [compact {:id (uuid/next) :type :rect :name "test"
:x 100 :y 200 :width 50 :height 30
:frame-id uuid/zero :parent-id uuid/zero}
expanded (sc/expand-shape compact)]
(t/is (gmt/matrix? (:transform expanded)))
(t/is (gmt/matrix? (:transform-inverse expanded)))
(t/is (true? (gmt/unit? (:transform expanded))))))
(t/deftest expand-shape-restores-selrect-rect
(let [compact {:id (uuid/next) :type :rect :name "test"
:x 100 :y 200 :width 50 :height 30
:frame-id uuid/zero :parent-id uuid/zero}
expanded (sc/expand-shape compact)]
(t/is (grc/rect? (:selrect expanded)))
(t/is (= 100 (:x (:selrect expanded))))
(t/is (= 200 (:y (:selrect expanded))))
(t/is (= 50 (:width (:selrect expanded))))
(t/is (= 30 (:height (:selrect expanded))))))
(t/deftest expand-shape-restores-points
(let [compact {:id (uuid/next) :type :rect :name "test"
:x 100 :y 200 :width 50 :height 30
:frame-id uuid/zero :parent-id uuid/zero}
expanded (sc/expand-shape compact)
points (:points expanded)]
(t/is (vector? points))
(t/is (= 4 (count points)))
(t/is (every? gpt/point? points))))
(t/deftest expand-shape-path
(let [compact {:id (uuid/next) :type :path :name "test"
:selrect (grc/make-rect 0 0 10 10)
:content [{:command :move-to :params {:x 0 :y 0}}
{:command :line-to :params {:x 10 :y 10}}]
:frame-id uuid/zero :parent-id uuid/zero}
expanded (sc/expand-shape compact)]
(t/is (grc/rect? (:selrect expanded)))
(t/is (vector? (:points expanded)))
(t/is (= 4 (count (:points expanded))))))
;; --- Round-trip
(t/deftest compact-expand-roundtrip-rect
(let [shape (make-rect {:x 123 :y 456 :width 78 :height 90})
compact (sc/compact-shape shape)
result (sc/expand-shape compact)]
(t/is (= (:id shape) (:id result)))
(t/is (= (:type shape) (:type result)))
(t/is (= (:name shape) (:name result)))
(t/is (= (:x shape) (:x result)))
(t/is (= (:y shape) (:y result)))
(t/is (= (:width shape) (:width result)))
(t/is (= (:height shape) (:height result)))
(t/is (grc/rect? (:selrect result)))
(t/is (gmt/matrix? (:transform result)))
(t/is (sm/validate cts/schema:shape result))))
(t/deftest compact-expand-roundtrip-preserves-ids
(let [id (uuid/next)
frame-id (uuid/next)
parent-id (uuid/next)
shape (-> (make-rect {:x 10 :y 20 :width 30 :height 40})
(assoc :id id :frame-id frame-id :parent-id parent-id))
compact (sc/compact-shape shape)
result (sc/expand-shape compact)]
(t/is (= id (:id result)))
(t/is (= frame-id (:frame-id result)))
(t/is (= parent-id (:parent-id result)))))
;; --- Generative
(t/deftest compact-expand-schema-valid
(smt/check!
(smt/for [shape (sg/generator cts/schema:shape)]
(let [compact (sc/compact-shape shape)
expanded (sc/expand-shape compact)]
(sm/validate cts/schema:shape expanded)))
{:num 200}))
(t/deftest compact-expand-preserves-geometry
(smt/check!
(smt/for [shape (sg/generator cts/schema:shape)]
(let [compact (sc/compact-shape shape)
expanded (sc/expand-shape compact)]
(and (= (:id shape) (:id expanded))
(= (:type shape) (:type expanded))
(= (:name shape) (:name expanded))
(= (:frame-id shape) (:frame-id expanded))
(= (:parent-id shape) (:parent-id expanded))
(= (:component-id shape) (:component-id expanded))
(= (:component-file shape) (:component-file expanded))
(= (:shape-ref shape) (:shape-ref expanded))
(= (:touched shape) (:touched expanded)))))
{:num 200}))
(t/deftest compact-expand-preserves-content
(smt/check!
(smt/for [shape (sg/generator cts/schema:shape)]
(let [compact (sc/compact-shape shape)
expanded (sc/expand-shape compact)]
(or (not= :path (:type shape))
(and (= (:id shape) (:id expanded))
(= (:content shape) (:content expanded))
(some? (:selrect expanded))
(vector? (:points expanded))))))
{:num 50}))