Merge remote-tracking branch 'origin/staging' into staging

This commit is contained in:
Andrey Antukh 2026-09-07 09:52:07 +02:00
commit 7ebd7cc0d5
4 changed files with 357 additions and 227 deletions

View File

@ -15,6 +15,7 @@
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.logging :as log]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
@ -579,44 +580,57 @@
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)] modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
(assoc state :workspace-modifiers modifiers)))))) (assoc state :workspace-modifiers modifiers))))))
(defn- without-nil-ids
"Drop nil-keyed entries from a modif-tree. A nil shape id (possible in
production builds, where the upstream asserts are elided) would crash
the WASM heap write with `uuid/get-u32` being called on nil."
[modif-tree]
(if (contains? modif-tree nil)
(do (log/warn :hint "modif-tree contains a nil shape id; ignoring entry")
(dissoc modif-tree nil))
modif-tree))
(defn- parse-structure-modifiers (defn- parse-structure-modifiers
[modif-tree] [modif-tree]
(into (into
[] []
(mapcat (comp
(fn [[parent-id data]] (mapcat
(when (ctm/has-structure? (:modifiers data)) (fn [[parent-id data]]
(->> (concat (when (ctm/has-structure? (:modifiers data))
(get-in data [:modifiers :structure-parent]) (->> (concat
(get-in data [:modifiers :structure-child])) (get-in data [:modifiers :structure-parent])
(mapcat (get-in data [:modifiers :structure-child]))
(fn [modifier] (mapcat
(case (:type modifier) (fn [modifier]
:remove-children (case (:type modifier)
(->> (:value modifier) :remove-children
(map (fn [child-id] (->> (:value modifier)
{:type :remove-children (map (fn [child-id]
:parent parent-id {:type :remove-children
:id child-id :parent parent-id
:index 0 :id child-id
:value 0}))) :index 0
:value 0})))
:add-children :add-children
(->> (:value modifier) (->> (:value modifier)
(map (fn [child-id] (map (fn [child-id]
{:type :add-children {:type :add-children
:parent parent-id :parent parent-id
:id child-id :id child-id
:index (:index modifier) :index (:index modifier)
:value 0}))) :value 0})))
:scale-content :scale-content
[{:type :scale-content [{:type :scale-content
:parent parent-id :parent parent-id
:id parent-id :id parent-id
:index 0 :index 0
:value (:value modifier)}] :value (:value modifier)}]
nil))))))) nil)))))))
(filter (fn [{:keys [id parent]}]
(and (some? id) (some? parent)))))
modif-tree)) modif-tree))
@ -624,7 +638,7 @@
(let [default-transform (gmt/matrix)] (let [default-transform (gmt/matrix)]
(keep (fn [[id data]] (keep (fn [[id data]]
(cond (cond
(= id uuid/zero) (or (nil? id) (= id uuid/zero))
nil nil
(ctm/has-geometry? (:modifiers data)) (ctm/has-geometry? (:modifiers data))
@ -693,65 +707,66 @@
subtree-ids-by-id selection-rect-cache] subtree-ids-by-id selection-rect-cache]
:or {ignore-constraints false ignore-snap-pixel false} :or {ignore-constraints false ignore-snap-pixel false}
:as params}] :as params}]
(ptk/reify ::set-wasm-modifiers (let [modif-tree (without-nil-ids modif-tree)]
ptk/UpdateEvent (ptk/reify ::set-wasm-modifiers
(update [_ state] ptk/UpdateEvent
(let [property-changes (extract-property-changes modif-tree)] (update [_ state]
(if (d/not-empty? property-changes) (let [property-changes (extract-property-changes modif-tree)]
(-> state (if (d/not-empty? property-changes)
(assoc :prev-wasm-props (:wasm-props state)) (-> state
(assoc :wasm-props property-changes)) (assoc :prev-wasm-props (:wasm-props state))
state))) (assoc :wasm-props property-changes))
state)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
;; Entering an interactive transform (drag/resize/rotate). Flip ;; Entering an interactive transform (drag/resize/rotate). Flip
;; the renderer into fast + atlas-backdrop mode so the live ;; the renderer into fast + atlas-backdrop mode so the live
;; preview is cheap, tiles never appear sequentially and the main ;; preview is cheap, tiles never appear sequentially and the main
;; thread is not blocked. The pair is closed in ;; thread is not blocked. The pair is closed in
;; `clear-local-transform`. ;; `clear-local-transform`.
(ensure-interactive-transform-start!) (ensure-interactive-transform-start!)
(let [snap-pixel? (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) (let [snap-pixel? (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
translation? (every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))] translation? (every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
(if translation? (if translation?
;; Pure translation: no structure changes needed. If structure ;; Pure translation: no structure changes needed. If structure
;; modifiers were active from a previous non-translation frame ;; modifiers were active from a previous non-translation frame
;; (e.g. shape hovered over a frame then dragged back out), ;; (e.g. shape hovered over a frame then dragged back out),
;; clear them now so the shape is not clipped by the old frame. ;; clear them now so the shape is not clipped by the old frame.
(when @wasm-structure-modifiers-active? (when @wasm-structure-modifiers-active?
(wasm.api/clean-modifiers) (wasm.api/clean-modifiers)
(vreset! wasm-structure-modifiers-active? false)) (vreset! wasm-structure-modifiers-active? false))
(let [objects (dsh/lookup-page-objects state)] (let [objects (dsh/lookup-page-objects state)]
(set-wasm-props! objects (:prev-wasm-props state) (:wasm-props state)) (set-wasm-props! objects (:prev-wasm-props state) (:wasm-props state))
(wasm.api/clean-modifiers) (wasm.api/clean-modifiers)
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)) (wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree))
(vreset! wasm-structure-modifiers-active? true))) (vreset! wasm-structure-modifiers-active? true)))
(let [geometry-entries (parse-geometry-modifiers modif-tree) (let [geometry-entries (parse-geometry-modifiers modif-tree)
root-modifiers (into [] (map (fn [[id data]] [id (:transform data)])) geometry-entries) root-modifiers (into [] (map (fn [[id data]] [id (:transform data)])) geometry-entries)
wasm-ready? (wasm.api/initialized?) wasm-ready? (wasm.api/initialized?)
;; While the GL context is down (lost / mid-reload), keep the ;; While the GL context is down (lost / mid-reload), keep the
;; root transforms so SVG selection/preview can still move. ;; root transforms so SVG selection/preview can still move.
;; `propagate-modifiers` returns [] when not ready, do not ;; `propagate-modifiers` returns [] when not ready, do not
;; treat that as "no modifiers". ;; treat that as "no modifiers".
modifiers modifiers
(cond (cond
(or (not wasm-ready?) (or (not wasm-ready?)
(and translation? (not snap-pixel?))) (and translation? (not snap-pixel?)))
root-modifiers root-modifiers
:else :else
(let [propagated (wasm.api/propagate-modifiers geometry-entries snap-pixel?)] (let [propagated (wasm.api/propagate-modifiers geometry-entries snap-pixel?)]
(if (seq propagated) propagated root-modifiers)))] (if (seq propagated) propagated root-modifiers)))]
(when wasm-ready? (when wasm-ready?
(wasm.api/set-modifiers modifiers)) (wasm.api/set-modifiers modifiers))
(let [ids (into [] xf:map-key geometry-entries) (let [ids (into [] xf:map-key geometry-entries)
selrect (when wasm-ready? selrect (when wasm-ready?
(if (and translation? (not snap-pixel?) selection-rect-cache (seq modifiers)) (if (and translation? (not snap-pixel?) selection-rect-cache (seq modifiers))
(cached-translation-selrect ids (second (first modifiers)) selection-rect-cache) (cached-translation-selrect ids (second (first modifiers)) selection-rect-cache)
(wasm.api/get-selection-rect ids)))] (wasm.api/get-selection-rect ids)))]
(rx/of (set-temporary-selrect selrect) (rx/of (set-temporary-selrect selrect)
(set-temporary-modifiers modifiers)))))))) (set-temporary-modifiers modifiers)))))))))
(defn propagate-structure-modifiers (defn propagate-structure-modifiers
[modif-tree objects] [modif-tree objects]
@ -782,58 +797,44 @@
subtree-ids-by-id] subtree-ids-by-id]
:or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-transation? true} :or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-transation? true}
:as params}] :as params}]
(ptk/reify ::apply-wasm-modifiers (let [modif-tree (without-nil-ids modif-tree)]
ptk/WatchEvent (ptk/reify ::apply-wasm-modifiers
(watch [_ state _] ptk/WatchEvent
(let [translation? (watch [_ state _]
(every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))] (let [translation?
(wasm.api/clean-modifiers) (every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
(when-not translation? (wasm.api/clean-modifiers)
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree))) (when-not translation?
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)))
;; Apply property changes (e.g. grow-type) to WASM shapes before ;; Apply property changes (e.g. grow-type) to WASM shapes before
;; propagating geometry, so propagate_modifiers sees the updated state. ;; propagating geometry, so propagate_modifiers sees the updated state.
(doseq [[id {:keys [property value]}] (extract-property-changes modif-tree)] (doseq [[id {:keys [property value]}] (extract-property-changes modif-tree)]
(when (= property :grow-type) (when (= property :grow-type)
(wasm.api/use-shape id) (wasm.api/use-shape id)
(wasm.api/set-shape-grow-type value))) (wasm.api/set-shape-grow-type value)))
(let [objects (dsh/lookup-page-objects state) (let [objects (dsh/lookup-page-objects state)
geometry-entries geometry-entries
(parse-geometry-modifiers modif-tree) (parse-geometry-modifiers modif-tree)
snap-pixel? snap-pixel?
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
transforms transforms
(cond (cond
(and translation? (not snap-pixel?)) (and translation? (not snap-pixel?))
;; Mirror WASM `propagate_modifiers` in CLJS: splat the ;; Mirror WASM `propagate_modifiers` in CLJS: splat the
;; translation matrix onto every descendant. Without ;; translation matrix onto every descendant. Without
;; this step the commit would only touch the dragged ;; this step the commit would only touch the dragged
;; primaries and descendants would snap back to their ;; primaries and descendants would snap back to their
;; pre-drag positions on drop. ;; pre-drag positions on drop.
;; ;;
;; Skipped when `snap-pixel?` is on: WASM applies ;; Skipped when `snap-pixel?` is on: WASM applies
;; per-shape pixel correction (different scale/translate ;; per-shape pixel correction (different scale/translate
;; per descendant) which we can't replicate cheaply on ;; per descendant) which we can't replicate cheaply on
;; the CLJS side. ;; the CLJS side.
(reduce
(fn [acc [id data]]
(let [t (:transform data)
subtree-ids
(or (get subtree-ids-by-id id)
(cfh/get-children-ids-with-self objects id))]
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
{}
geometry-entries)
;; Context lost / mid-reload: do not call into WASM. Use
;; root transforms (and splat translation onto descendants
;; when we can) so the commit still lands in file data.
(not (wasm.api/initialized?))
(if translation?
(reduce (reduce
(fn [acc [id data]] (fn [acc [id data]]
(let [t (:transform data) (let [t (:transform data)
@ -843,71 +844,87 @@
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids))) (reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
{} {}
geometry-entries) geometry-entries)
(into {}
(map (fn [[id data]] [id (:transform data)]))
geometry-entries))
:else ;; Context lost / mid-reload: do not call into WASM. Use
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?))) ;; root transforms (and splat translation onto descendants
;; when we can) so the commit still lands in file data.
(not (wasm.api/initialized?))
(if translation?
(reduce
(fn [acc [id data]]
(let [t (:transform data)
subtree-ids
(or (get subtree-ids-by-id id)
(cfh/get-children-ids-with-self objects id))]
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
{}
geometry-entries)
(into {}
(map (fn [[id data]] [id (:transform data)]))
geometry-entries))
ignore-tree :else
(calculate-ignore-tree-wasm transforms objects) (into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?)))
options ignore-tree
(-> params (calculate-ignore-tree-wasm transforms objects)
(assoc :reg-objects? true)
(assoc :ignore-tree ignore-tree)
(assoc :translation? translation?)
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
(assoc :attrs transform-attrs))
modif-tree options
(propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state)) (-> params
(assoc :reg-objects? true)
(assoc :ignore-tree ignore-tree)
(assoc :translation? translation?)
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
(assoc :attrs transform-attrs))
ids modif-tree
(into (set (keys modif-tree)) xf:without-uuid-zero (keys transforms)) (propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state))
update-shape ids
(fn [shape] (into (set (keys modif-tree)) xf:without-uuid-zero (keys transforms))
(let [shape-id (dm/get-prop shape :id)
transform (get transforms shape-id)
modifiers (dm/get-in modif-tree [shape-id :modifiers])]
(-> shape
(gsh/apply-transform transform)
(ctm/apply-structure-modifiers modifiers))))
bool-ids update-shape
(into #{} (fn [shape]
(comp (let [shape-id (dm/get-prop shape :id)
(mapcat (partial cfh/get-parents-with-self objects)) transform (get transforms shape-id)
(filter cfh/bool-shape?) modifiers (dm/get-in modif-tree [shape-id :modifiers])]
(map :id)) (-> shape
ids) (gsh/apply-transform transform)
(ctm/apply-structure-modifiers modifiers))))
undo-id (js/Symbol)] bool-ids
(rx/concat (into #{}
(if undo-transation? (comp
(rx/of (dwu/start-undo-transaction undo-id)) (mapcat (partial cfh/get-parents-with-self objects))
(rx/empty)) (filter cfh/bool-shape?)
(rx/of (map :id))
(clear-local-transform) ids)
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
(ptk/event ::dwcm/move-frame-comment-threads transforms)
(dwsh/update-shapes ids update-shape options)
;; The update to the bool path needs to be in a different operation because it undo-id (js/Symbol)]
;; needs to have the updated children info.
;; `update-layout? false`: recalculating a bool path can never change
;; `:hidden`, and the layout check would recompute the whole boolean
;; path in WASM once per bool shape just to find that out.
(dwsh/update-shapes bool-ids path/update-bool-shape (assoc options
:with-objects? true
:update-layout? false)))
(if undo-transation? (rx/concat
(rx/of (dwu/commit-undo-transaction undo-id)) (if undo-transation?
(rx/empty)))))))) (rx/of (dwu/start-undo-transaction undo-id))
(rx/empty))
(rx/of
(clear-local-transform)
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
(ptk/event ::dwcm/move-frame-comment-threads transforms)
(dwsh/update-shapes ids update-shape options)
;; The update to the bool path needs to be in a different operation because it
;; needs to have the updated children info.
;; `update-layout? false`: recalculating a bool path can never change
;; `:hidden`, and the layout check would recompute the whole boolean
;; path in WASM once per bool shape just to find that out.
(dwsh/update-shapes bool-ids path/update-bool-shape (assoc options
:with-objects? true
:update-layout? false)))
(if undo-transation?
(rx/of (dwu/commit-undo-transaction undo-id))
(rx/empty)))))))))
(def ^:private (def ^:private
xf-rotation-shape xf-rotation-shape

View File

@ -335,14 +335,15 @@
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id) objects (dsh/lookup-page-objects state page-id)
shape (get objects shape-id) shape (get objects shape-id)
container (get objects (:parent-id shape)) container (get objects (:parent-id shape))]
width (+ (:width container) (:width shape) 20) ;; 20 is the default gap for variants (when (and (some? shape) (some? container))
x (- width (+ (:width shape) 30))] ;; 30 is the default margin for variants (let [width (+ (:width container) (:width shape) 20) ;; 20 is the default gap for variants
(rx/of x (- width (+ (:width shape) 30))] ;; 30 is the default margin for variants
(dwt/update-dimensions [(:parent-id shape)] :width width) (rx/of
(dwt/update-position shape-id (dwt/update-dimensions [(:parent-id shape)] :width width)
{:x x} (dwt/update-position shape-id
{:absolute? false})))))) {:x x}
{:absolute? false}))))))))
(defn add-new-variant (defn add-new-variant
"Create a new variant and add it to the variant-container" "Create a new variant and add it to the variant-container"
@ -359,39 +360,40 @@
shape (get objects shape-id) shape (get objects shape-id)
shape (if (ctc/is-variant-container? shape) shape (if (ctc/is-variant-container? shape)
(get objects (last (:shapes shape))) (get objects (last (:shapes shape)))
shape) shape)]
component-id (:component-id shape) (when (some? shape)
component (ctkl/get-component data component-id) (let [component-id (:component-id shape)
component (ctkl/get-component data component-id)
container-id (:parent-id shape) container-id (:parent-id shape)
variant-container (get objects container-id) variant-container (get objects container-id)
has-layout? (ctsl/any-layout? variant-container) has-layout? (ctsl/any-layout? variant-container)
new-component-id (uuid/next) new-component-id (uuid/next)
new-shape-id (uuid/next) new-shape-id (uuid/next)
prop-num (dec (count (:variant-properties component))) prop-num (dec (count (:variant-properties component)))
changes (-> (pcb/empty-changes it page-id) changes (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data) (pcb/with-library-data data)
(pcb/with-objects objects) (pcb/with-objects objects)
(pcb/with-page-id page-id) (pcb/with-page-id page-id)
(clv/generate-add-new-variant shape (:variant-id component) new-component-id new-shape-id prop-num)) (clv/generate-add-new-variant shape (:variant-id component) new-component-id new-shape-id prop-num))
undo-id (js/Symbol)] undo-id (js/Symbol)]
(rx/concat (rx/concat
(rx/of (rx/of
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes) (dch/commit-changes changes)
(when-not has-layout? (when-not has-layout?
(resposition-and-resize-variant new-shape-id)) (resposition-and-resize-variant new-shape-id))
(dwu/commit-undo-transaction undo-id) (dwu/commit-undo-transaction undo-id)
(ptk/data-event :layout/update {:ids [(:parent-id shape)]}) (ptk/data-event :layout/update {:ids [(:parent-id shape)]})
(if multiselect? (if multiselect?
(dws/shift-select-shapes new-shape-id) (dws/shift-select-shapes new-shape-id)
(dws/select-shape new-shape-id))) (dws/select-shape new-shape-id)))
(->> (rx/of (focus-property (:id variant-container))) (->> (rx/of (focus-property (:id variant-container)))
(rx/delay 250)))))))) (rx/delay 250))))))))))
(defn transform-in-variant (defn transform-in-variant
"Given the id of a main shape of a component, creates a variant structure for "Given the id of a main shape of a component, creates a variant structure for

View File

@ -0,0 +1,109 @@
;; 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 frontend-tests.logic.wasm-modifiers-nil-id-test
"Reproduces the production crash \"Cannot read properties of null
(reading '__u32_buffer')\".
A modif-tree containing a nil shape id (production builds elide the
asserts that catch this upstream, e.g. `update-dimensions` called
with `[(:parent-id shape)]` when `shape` is missing) reached
`wasm.api/propagate-modifiers` / `wasm.api/set-structure-modifiers`,
and `mem.h32/write-uuid` crashed calling `uuid/get-u32` on nil while
writing to the WASM heap.
These tests assert that no nil id ever crosses the WASM boundary and
that valid shapes in the same modif-tree are still processed."
(:require
[app.common.geom.rect :as grc]
[app.common.math :as mth]
[app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]
[app.main.data.workspace.modifiers :as dwm]
[app.render-wasm.api :as wasm.api]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]))
(def ^:private captured-geometry-entries
"Entries passed to `wasm.api/propagate-modifiers` during a test."
(atom []))
(def ^:private captured-structure-entries
"Entries passed to `wasm.api/set-structure-modifiers` during a test."
(atom []))
(defn- install-capturing-spies!
"Replace the plain WASM mocks with variants that record their input.
Must run after `thw/setup-wasm-mocks!` so teardown still restores
the real implementations."
[]
(set! wasm.api/propagate-modifiers
(fn [entries _pixel-precision]
(swap! captured-geometry-entries into entries)
(into []
(map (fn [[id data]] [id (:transform data)]))
entries)))
(set! wasm.api/set-structure-modifiers
(fn [entries]
(swap! captured-structure-entries into entries)
nil)))
(t/use-fixtures :each
{:before (fn []
(cthi/reset-idmap!)
(reset! captured-geometry-entries [])
(reset! captured-structure-entries [])
(thw/setup-wasm-mocks!)
(install-capturing-spies!))
:after (fn []
(thw/teardown-wasm-mocks!))})
(t/deftest nil-id-does-not-reach-propagate-modifiers
;; A nil-keyed entry must be dropped before the WASM heap write while
;; the valid entry is still resized.
(t/async
done
(let [file (-> (cthf/sample-file :file1)
(ctho/add-rect :rect1 :x 10 :y 20 :width 100 :height 50))
store (ths/setup-store file)
rect (cths/get-shape file :rect1)
resize (ctm/change-dimensions-modifiers rect :width 200)
modif-tree {nil {:modifiers resize}
(:id rect) {:modifiers resize}}
events [(dwm/apply-wasm-modifiers modif-tree {:ignore-snap-pixel true})]]
(ths/run-store
store done events
(fn [new-state]
(let [entry-ids (into #{} (map first) @captured-geometry-entries)
file' (ths/get-file-from-state new-state)
rect' (cths/get-shape file' :rect1)
width (-> rect' :points grc/points->rect :width)]
(t/is (not (contains? entry-ids nil)))
(t/is (contains? entry-ids (:id rect)))
(t/is (mth/close? 200 width))))))))
(t/deftest nil-id-does-not-reach-set-structure-modifiers
;; A nil-keyed entry with structure modifiers must not produce
;; structure entries with a nil :parent or :id.
(t/async
done
(let [file (-> (cthf/sample-file :file1)
(ctho/add-rect :rect1 :x 10 :y 20 :width 100 :height 50))
store (ths/setup-store file)
rect (cths/get-shape file :rect1)
modif-tree {nil {:modifiers (ctm/add-children nil [(uuid/next)] 0)}
(:id rect) {:modifiers (ctm/change-dimensions-modifiers rect :width 200)}}
events [(dwm/apply-wasm-modifiers modif-tree {:ignore-snap-pixel true})]]
(ths/run-store
store done events
(fn [_new-state]
(t/is (every? #(some? (:parent %)) @captured-structure-entries))
(t/is (every? #(some? (:id %)) @captured-structure-entries)))))))

View File

@ -39,6 +39,7 @@
[frontend-tests.logic.pasting-in-containers-test] [frontend-tests.logic.pasting-in-containers-test]
[frontend-tests.logic.sidebar-transform-coalescing-test] [frontend-tests.logic.sidebar-transform-coalescing-test]
[frontend-tests.logic.update-position-test] [frontend-tests.logic.update-position-test]
[frontend-tests.logic.wasm-modifiers-nil-id-test]
[frontend-tests.main-errors-test] [frontend-tests.main-errors-test]
[frontend-tests.plugins.comments-test] [frontend-tests.plugins.comments-test]
[frontend-tests.plugins.context-shapes-test] [frontend-tests.plugins.context-shapes-test]
@ -138,6 +139,7 @@
'frontend-tests.main-errors-test 'frontend-tests.main-errors-test
'frontend-tests.logic.sidebar-transform-coalescing-test 'frontend-tests.logic.sidebar-transform-coalescing-test
'frontend-tests.logic.update-position-test 'frontend-tests.logic.update-position-test
'frontend-tests.logic.wasm-modifiers-nil-id-test
'frontend-tests.plugins.comments-test 'frontend-tests.plugins.comments-test
'frontend-tests.plugins.context-shapes-test 'frontend-tests.plugins.context-shapes-test
'frontend-tests.plugins.file-test 'frontend-tests.plugins.file-test