diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index ca92c93cc7..325a6d3069 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -235,7 +235,7 @@ (ctm/move-modifiers (displacement center-before center-after before-side-vector after-side-vector)))) (defmethod constraint-modifier :default [_ _ _ _ _ _] - []) + (ctm/empty)) (def const->type+axis {:left :start diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index b360942448..f7f96bac1c 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -18,6 +18,7 @@ [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.geom.matrix :as gmt] + [app.common.geom.modifiers :as gm] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] @@ -309,6 +310,51 @@ (-> (set-touched shape-inst container nil) (change-remote-synced shape-inst container nil))))) +(defn- in-main-frame + "The copy shape `shape-inst` expressed in the frame of the main, as + `reposition-shape` would place it from there: turned back by the roots' + relative rotation and flips when the copy root has them, and moved by the + roots' offset." + [shape-inst root-inst root-main] + (let [turn (when (ctk/touched-group? root-inst :geometry-group) + (orient-like root-inst root-main (grc/rect->center (:selrect root-inst)))) + shape (cond-> shape-inst (some? turn) turn) + delta (gpt/subtract (gpt/point (-> root-main :selrect :x) (-> root-main :selrect :y)) + (gpt/point (-> root-inst :selrect :x) (-> root-inst :selrect :y)))] + (gsh/move shape delta))) + +(defn- close-geometry? + [shape other] + (and (gsh/close-attrs? :selrect (:selrect shape) (:selrect other)) + (gsh/close-attrs? :points (:points shape) (:points other)))) + +(defn- placed-like + "The main `objects` with the subtree of `shape-main` placed like `placed`, + a copy shape expressed in the main's frame: resized to its size, its + children following through their constraints and layouts, then turned + and moved with it as one piece." + [objects shape-main placed] + (let [width (-> placed :selrect :width) + height (-> placed :selrect :height) + objects (if (and (mth/close? width (-> shape-main :selrect :width)) + (mth/close? height (-> shape-main :selrect :height))) + objects + (let [modif-tree (gm/set-objects-modifiers + {(:id shape-main) {:modifiers (ctm/change-size shape-main width height)}} + objects)] + (reduce-kv (fn [objects id {:keys [modifiers]}] + (d/update-when objects id gsh/transform-shape modifiers)) + objects + modif-tree))) + ids (cfh/get-children-ids-with-self objects (:id shape-main)) + resized (get objects (:id shape-main)) + turn (orient-like resized placed (grc/rect->center (:selrect resized))) + objects (cond-> objects + (some? turn) (as-> objects (reduce #(update %1 %2 turn) objects ids))) + delta (gpt/subtract (grc/rect->center (:selrect placed)) + (grc/rect->center (:selrect (get objects (:id shape-main)))))] + (reduce #(update %1 %2 gsh/move delta) objects ids))) + (defn- sync-copy-recursive "Sync the copy shape `shape-inst` from `shape-main`, the main shape it pairs with, then pair their children (`compare-children`) and walk each @@ -317,7 +363,9 @@ overrides and clears them. `ctx` holds what stays the same along the walk: the copy's `:container`, the `:main-container` of the main shapes, the `:component` and its `:library`, the roots `:root-inst` and - `:root-main`, and `:reset?`." + `:root-main`, and `:reset?`. The main shapes of the children of a copy + shape that keeps its own geometry are placed like it first, so they get + their expected geometry." [changes {:keys [container main-container library component reset?] :as ctx} shape-inst shape-main] (shape-log :debug (:id shape-inst) container :msg "Sync shape direct recursive" @@ -326,8 +374,16 @@ (if (nil? shape-main) ;; This should not occur, but protect against it in any case changes - (let [children-inst (vec (ctn/get-direct-children container shape-inst)) - children-main (vec (ctn/get-direct-children main-container shape-main))] + (let [;; a copy shape that keeps its own geometry passes it on to its + ;; children, which follow it as they follow their main's + placed (when (and (not reset?) (ctk/touched-group? shape-inst :geometry-group)) + (in-main-frame shape-inst (:root-inst ctx) (:root-main ctx))) + main-container (cond-> main-container + (and (some? placed) (not (close-geometry? placed shape-main))) + (update :objects placed-like shape-main placed)) + ctx (assoc ctx :main-container main-container) + children-inst (vec (ctn/get-direct-children container shape-inst)) + children-main (vec (ctn/get-direct-children main-container shape-main))] (cond-> (-> changes (sync-copy-shape ctx shape-inst shape-main) (compare-children diff --git a/common/src/app/common/logic/placement.cljc b/common/src/app/common/logic/placement.cljc index fd33f21dfd..82eb5ac99e 100644 --- a/common/src/app/common/logic/placement.cljc +++ b/common/src/app/common/logic/placement.cljc @@ -148,63 +148,69 @@ relative to its copy root (or, for the root, its orientation) is the same after `transform` (shape -> transformed shape). Each shape is compared with the root of the copy of the transformed shape latest in - `ids` among itself and its ancestors." - [objects ids transform] - (let [order (into {} (map-indexed (fn [i id] [id i])) ids) + `ids` among itself and its ancestors. With `user-ids`, the shapes the + user transformed, every other shape changes only as a consequence of + theirs (a child through its constraints, a board that hugs them), a + derived change that leaves it untouched." + ([objects ids transform] + (calculate-ignore-tree objects ids transform nil)) + ([objects ids transform user-ids] + (let [order (into {} (map-indexed (fn [i id] [id i])) ids) - component-shape - (fn [shape parent parent-component] - (cond - (cfh/root? shape) nil - (ctk/instance-root? shape) shape - (and (ctk/instance-head? shape) (not (ctk/in-component-copy? parent))) shape - :else parent-component)) + component-shape + (fn [shape parent parent-component] + (cond + (cfh/root? shape) nil + (ctk/instance-root? shape) shape + (and (ctk/instance-head? shape) (not (ctk/in-component-copy? parent))) shape + :else parent-component)) - walk - (fn walk [ignore-tree shape component latest] - (let [index (get order (dm/get-prop shape :id)) - latest (if (and (some? index) (or (nil? latest) (> index (nth latest 0)))) - (if (some? component) - [index component (transform component)] - latest) - latest) - ignore-tree - (if (and (some? latest) (ctk/in-component-copy? shape)) - (let [[_ root transformed-root] latest] - (assoc ignore-tree - (dm/get-prop shape :id) - (check-delta shape root (transform shape) transformed-root))) - ignore-tree)] - (reduce (fn [ignore-tree child] - (walk ignore-tree child (component-shape child shape component) latest)) - ignore-tree - (map (d/getf objects) (:shapes shape))))) + walk + (fn walk [ignore-tree shape component latest] + (let [index (get order (dm/get-prop shape :id)) + latest (if (and (some? index) (or (nil? latest) (> index (nth latest 0)))) + (if (some? component) + [index component (transform component)] + latest) + latest) + ignore-tree + (if (and (some? latest) (ctk/in-component-copy? shape)) + (let [[_ root transformed-root] latest + id (dm/get-prop shape :id)] + (assoc ignore-tree id + (or (and (some? user-ids) (not (contains? user-ids id))) + (check-delta shape root (transform shape) transformed-root)))) + ignore-tree)] + (reduce (fn [ignore-tree child] + (walk ignore-tree child (component-shape child shape component) latest)) + ignore-tree + (map (d/getf objects) (:shapes shape))))) - under-transform (volatile! {}) + under-transform (volatile! {}) - under-transform? - (fn under-transform? [id] - (let [parent-id (dm/get-in objects [id :parent-id])] - (cond - (or (nil? parent-id) (= parent-id id)) false - (contains? order parent-id) true - :else - (let [cached (get @under-transform parent-id ::none)] - (if (= cached ::none) - (let [result (under-transform? parent-id)] - (vswap! under-transform assoc parent-id result) - result) - cached)))))] - (->> ids - (remove under-transform?) - (keep #(get objects %)) - (reduce (fn [ignore-tree shape] - (let [parent (get objects (:parent-id shape))] - (walk ignore-tree - shape - (component-shape shape parent (ctn/get-component-shape objects parent {:allow-main? true})) - nil))) - nil)))) + under-transform? + (fn under-transform? [id] + (let [parent-id (dm/get-in objects [id :parent-id])] + (cond + (or (nil? parent-id) (= parent-id id)) false + (contains? order parent-id) true + :else + (let [cached (get @under-transform parent-id ::none)] + (if (= cached ::none) + (let [result (under-transform? parent-id)] + (vswap! under-transform assoc parent-id result) + result) + cached)))))] + (->> ids + (remove under-transform?) + (keep #(get objects %)) + (reduce (fn [ignore-tree shape] + (let [parent (get objects (:parent-id shape))] + (walk ignore-tree + shape + (component-shape shape parent (ctn/get-component-shape objects parent {:allow-main? true})) + nil))) + nil))))) (defn modifiers-transform "A cached transform (see `cached-transform`) that applies to each shape its diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc index 2b9c697ac1..121dd4fa22 100644 --- a/common/src/app/common/logic/shapes.cljc +++ b/common/src/app/common/logic/shapes.cljc @@ -89,9 +89,11 @@ [changes ids update-fn objects {:keys [attrs changed-sub-attr changed-item-index ignore-tree ignore-touched with-objects? translation? skip-grid-reassignment?]}] (let [changes (reduce (fn [changes id] + ;; a shape the transform leaves untouched changes only + ;; as a consequence of it: no attribute touches (let [opts {:attrs attrs :ignore-geometry? (get ignore-tree id) - :ignore-touched ignore-touched + :ignore-touched (or (true? (get ignore-tree id)) ignore-touched) :with-objects? with-objects?}] (pcb/update-shapes changes [id] update-fn (d/without-nils opts)))) (cond-> changes diff --git a/common/test/common_tests/geom_shapes_constraints_test.cljc b/common/test/common_tests/geom_shapes_constraints_test.cljc index 4f6cd3ce4c..0dd38b5f33 100644 --- a/common/test/common_tests/geom_shapes_constraints_test.cljc +++ b/common/test/common_tests/geom_shapes_constraints_test.cljc @@ -7,21 +7,19 @@ (ns common-tests.geom-shapes-constraints-test (:require [app.common.geom.shapes.constraints :as gsc] + [app.common.types.modifiers :as ctm] [clojure.test :as t])) ;; ---- constraint-modifier :default ---- -(t/deftest constraint-modifier-default-returns-empty-vector - (t/testing ":default method accepts 6 args and returns an empty vector" - ;; Before the fix the :default method only accepted 5 positional args - ;; (plus the dispatch value), so calling it with 6 args would throw an - ;; arity error. After the fix it takes [_ _ _ _ _ _] and returns []. +(t/deftest constraint-modifier-default-returns-empty-modifiers + (t/testing ":default method accepts 6 args and returns empty modifiers" + ;; :scale and any other type with no method of its own add nothing; the + ;; caller adds the result to the child's modifiers (`ctm/add-modifiers`) (let [result (gsc/constraint-modifier :unknown-constraint-type :x nil nil nil nil)] - (t/is (vector? result)) - (t/is (empty? result)))) + (t/is (ctm/empty? result)))) - (t/testing ":default method returns [] for :scale-like unknown type on :y axis" - (let [result (gsc/constraint-modifier :some-other-unknown - :y nil nil nil nil)] - (t/is (= [] result))))) + (t/testing ":default method can be added to modifiers" + (let [result (gsc/constraint-modifier :scale :y nil nil nil nil)] + (t/is (ctm/empty? (ctm/add-modifiers (ctm/empty) result)))))) diff --git a/common/test/common_tests/logic/comp_expected_geometry_test.cljc b/common/test/common_tests/logic/comp_expected_geometry_test.cljc new file mode 100644 index 0000000000..8250bac61a --- /dev/null +++ b/common/test/common_tests/logic/comp_expected_geometry_test.cljc @@ -0,0 +1,114 @@ +;; 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 common-tests.logic.comp-expected-geometry-test + "A copy whose root (or another shape) keeps a geometry of its own, and the + children of that shape: a transform of the shape leaves them untouched, + and sync gives them their main's geometry placed against it, resized + through their constraints (decision (f) of the component contract)." + (:require + [app.common.files.changes-builder :as pcb] + [app.common.geom.modifiers :as gm] + [app.common.logic.libraries :as cll] + [app.common.logic.placement :as clp] + [app.common.logic.shapes :as cls] + [app.common.math :as mth] + [app.common.test-helpers.components :as thc] + [app.common.test-helpers.compositions :as tho] + [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) + +(defn- component-with-child + "A 100x100 main board with a 20x20 rect at (70, 10) of it, constrained + `constraints-h`, and a copy of it at (300, 0)." + [constraints-h] + (-> (thf/sample-file :file1) + (tho/add-frame :main-root {:x 0 :y 0 :width 100 :height 100}) + (tho/add-rect :main-child {:parent-label :main-root :x 70 :y 10 :width 20 :height 20 + :constraints-h constraints-h :constraints-v :top}) + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root :children-labels [:copy-child]))) + +(defn- resize + "Resize the shape `label` to `width` x `height`, as the editor does: the + modifiers propagate to its children through their constraints, and the + placement step decides what the transform touches. With `scale-content`, + strokes, radii and the like scale by it too." + [file label width height & {:keys [scale-content]}] + (let [page (thf/current-page file) + objects (:objects page) + shape (ths/get-shape file label) + modifiers (cond-> (ctm/change-size shape width height) + (some? scale-content) (ctm/scale-content scale-content)) + modif-tree (gm/set-objects-modifiers {(:id shape) {:modifiers modifiers}} + objects) + ids (keys modif-tree) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + ids + (clp/modifiers-transform modif-tree) + objects + {:ignore-tree (clp/calculate-ignore-tree objects ids + (clp/modifiers-transform modif-tree) + #{(:id shape)}) + :attrs clp/transform-attrs})] + (thf/apply-changes file changes))) + +(defn- edit-and-sync + "Change the fill of the main child and sync the copy." + [file] + (let [page (thf/current-page file) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(thi/id :main-child)} + #(assoc % :fills (ths/sample-fills-color :fill-color "#ff0000")) + (:objects page) + {}) + file (thf/apply-changes file changes)] + (thf/apply-changes file (cll/generate-sync-file-changes (pcb/empty-changes) nil :components (:id file) + (thi/id :component1) (:id file) + {(:id file) file} (:id file))))) + +(defn- child-box + "The position of the copy child relative to its root, and its size." + [file] + (let [root (ths/get-shape file :copy-root) + child (ths/get-shape file :copy-child)] + (mapv #(mth/round (double %)) + [(- (-> child :selrect :x) (-> root :selrect :x)) + (- (-> child :selrect :y) (-> root :selrect :y)) + (-> child :selrect :width) + (-> child :selrect :height)]))) + +(t/deftest a-resized-copy-root-keeps-its-children-placed-through-their-constraints + (doseq [[constraints-h box] [[:left [70 10 20 20]] + [:right [170 10 20 20]] + [:center [120 10 20 20]] + [:scale [140 10 40 20]] + [:leftright [70 10 120 20]]]] + (t/testing (name constraints-h) + (let [resized (resize (component-with-child constraints-h) :copy-root 200 100) + synced (edit-and-sync resized)] + (t/is (= box (child-box resized)) "the resize places the child") + (t/is (contains? (:touched (ths/get-shape resized :copy-root)) :geometry-group)) + (t/is (nil? (:touched (ths/get-shape resized :copy-child))) "the child is not touched") + (t/is (= box (child-box synced)) "the sync keeps it there") + (t/is (= "#ff0000" (-> (ths/get-shape synced :copy-child) :fills first :fill-color))))))) + +(t/deftest a-copy-root-resized-with-its-content-touches-only-the-root + (let [file (-> (component-with-child :scale) + (ths/update-shape :main-child :strokes [{:stroke-color "#000000" :stroke-opacity 1 + :stroke-width 2 :stroke-alignment :center + :stroke-style :solid}]) + (thc/instantiate-component :component1 :copy2-root :children-labels [:copy2-child])) + resized (resize file :copy2-root 200 200 :scale-content 2) + child (ths/get-shape resized :copy2-child)] + (t/is (mth/close? 4 (-> child :strokes first :stroke-width))) + (t/is (nil? (:touched child))))) + diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index a1c795d236..a5f5d7c118 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -140,7 +140,7 @@ ptk/UpdateEvent (update [_ state] (-> state - (dissoc :workspace-modifiers :wasm-props :prev-wasm-props) + (dissoc :workspace-modifiers :workspace-modifiers-ids :wasm-props :prev-wasm-props) (dissoc :app.main.data.workspace.transforms/current-move-selected))))) (defn create-modif-tree @@ -356,7 +356,9 @@ (ptk/reify ::update-modifiers ptk/UpdateEvent (update [_ state] - (update state :workspace-modifiers calculate-update-modifiers state ignore-constraints ignore-snap-pixel modif-tree))))) + (-> state + (update :workspace-modifiers calculate-update-modifiers state ignore-constraints ignore-snap-pixel modif-tree) + (update :workspace-modifiers-ids (fnil into #{}) (keys modif-tree))))))) (defn set-modifiers @@ -375,7 +377,9 @@ (update [_ state] (let [page-id (:current-page-id state) modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)] - (assoc state :workspace-modifiers modifiers)))))) + (assoc state + :workspace-modifiers modifiers + :workspace-modifiers-ids (set (keys modif-tree)))))))) (defn- without-nil-ids "Drop nil-keyed entries from a modif-tree. A nil shape id (possible in @@ -682,7 +686,7 @@ (clp/matrices-transform transforms) ignore-tree - (clp/calculate-ignore-tree objects (keys transforms) transform-shape) + (clp/calculate-ignore-tree objects (keys transforms) transform-shape (set (keys modif-tree))) ;; the guides move in a commit of their own, in the undo ;; group of the shapes, so the copies' sync joins them both @@ -793,7 +797,9 @@ (-> (build-modif-tree ids objects get-modifier) (gm/set-objects-modifiers objects))] - (assoc state :workspace-modifiers modif-tree)))))) + (assoc state + :workspace-modifiers modif-tree + :workspace-modifiers-ids (set ids))))))) ;; This function is similar to set-rotation-modifiers but: ;; - It consideres the center for everyshape instead of the center of the total selrect @@ -821,7 +827,9 @@ (-> (build-modif-tree ids objects get-modifier) (gm/set-objects-modifiers objects))] - (assoc state :workspace-modifiers modif-tree))))) + (assoc state + :workspace-modifiers modif-tree + :workspace-modifiers-ids (set ids)))))) (defn apply-modifiers* "A lower-level version of apply-modifiers, that expects receive ready @@ -840,7 +848,8 @@ ignore-tree (clp/calculate-ignore-tree objects (keys object-modifiers) - (clp/modifiers-transform object-modifiers)) + (clp/modifiers-transform object-modifiers) + (:user-ids options)) ;; the guides move in a commit of their own, in the undo group of ;; the shapes, so the copies' sync joins them both @@ -894,6 +903,13 @@ (calculate-modifiers state ignore-constraints ignore-snap-pixel modifiers page-id) (get state :workspace-modifiers)) + ;; the shapes the user transformed: those of the given + ;; modifiers, or those the stored ones were set for + user-ids + (if (some? modifiers) + (set (keys modifiers)) + (get state :workspace-modifiers-ids)) + undo-id (js/Symbol)] @@ -901,7 +917,7 @@ (if undo-transation? (rx/of (dwu/start-undo-transaction undo-id)) (rx/empty)) - (rx/of (apply-modifiers* objects object-modifiers text-modifiers options) + (rx/of (apply-modifiers* objects object-modifiers text-modifiers (assoc options :user-ids user-ids)) (fn [state] (let [ids (into [] xf:without-uuid-zero (keys object-modifiers))] (update state :workspace-text-modifier #(apply dissoc % ids))))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index e5bcaa1c8a..a1b79fe5b5 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -563,7 +563,7 @@ (rx/of (dwm/apply-wasm-modifiers modif-tree (assoc options :ignore-snap-pixel true))) (let [modif-tree (gm/set-objects-modifiers modif-tree objects)] - (rx/of (dwm/apply-modifiers* objects modif-tree nil options))))))))) + (rx/of (dwm/apply-modifiers* objects modif-tree nil (assoc options :user-ids (set ids))))))))))) (defn change-orientation "Change orientation of shapes, from the sidebar options form. @@ -590,7 +590,9 @@ (-> (dwm/build-modif-tree ids objects get-modifier) (gm/set-objects-modifiers objects))] - (assoc state :workspace-modifiers modif-tree)))) + (assoc state + :workspace-modifiers modif-tree + :workspace-modifiers-ids (set ids))))) ptk/WatchEvent (watch [_ state _] diff --git a/frontend/test/frontend_tests/composable_tests/golden_master.edn b/frontend/test/frontend_tests/composable_tests/golden_master.edn index 09adef69e1..e19b6891dc 100644 --- a/frontend/test/frontend_tests/composable_tests/golden_master.edn +++ b/frontend/test/frontend_tests/composable_tests/golden_master.edn @@ -27,7 +27,7 @@ {:transcript "one-of -> \n {:target :copy-sub1}\n one-of -> ", :states ["fff37cf722a40e61" "6fd3a8bad999968b"]} {:transcript "one-of -> \n {:target :copy-sub3}\n {:target :copy-root, :value 500}", :states ["efada86db353920f" "701edc575ad9c0c6" "91db7f5306659911"]} {:transcript "one-of -> \n {:target :copy-sub3}\n one-of -> ", :states ["efada86db353920f" "3364b0fbfb76067a"]} - {:transcript "one-of -> \n {:target :copy-root, :value 500}", :states ["df9a66ef7c8ee469" "c4f0c92f92575cda"]} + {:transcript "one-of -> \n {:target :copy-root, :value 500}", :states ["000943ecb6c266ee" "209fe67d76a27a9b"]} {:transcript "one-of -> \n one-of -> ", :states ["94318a628db305b6"]}] :case-aj-reflow-after-nested-sync-is-its-own-undo-step [{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:target #fn, :property :fills, :value \"#0000ff\"}\n {:times 1}", :states ["43ca8b2de80a51fc" "32b45a5ac7536b08" "7104bfaccd42cd3b"]}] @@ -194,7 +194,7 @@ {:transcript " {:component \"main\", :id sync-main-component}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:target #fn, :property :fills, :value \"#00ff00\"}\n {:target #fn, :new-head 8}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["170f05fe60d794fb" "6f95a0e52dbc58f3" "1682e155232c79ff" "7e41824902c17537"]} {:transcript " {:component \"main\", :id sync-main-component}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :copy sync-main-copyhead-0}\n one-of -> \n {:target #fn, :new-head 8}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["e09ce2b9958878d1" "59617d71e336c70c" "4f37d9c4a6c72163"]}] :case-touched-copy-child-survives-main-rotation - [{:transcript " {:target :copy-root, :angle 30}\n {:target :copy-instance, :value 60}\n \n {:target :main-root, :angle 40}", :states ["6b63805de28b6fa4" "cfb4d7484a4abd78" "f4cf8a16c5457089" "fa0aab3b284e3448"]}] + [{:transcript " {:target :copy-root, :angle 30}\n {:target :copy-instance, :value 60}\n \n {:target :main-root, :angle 40}", :states ["4b1199b00726b9ad" "cfb4d7484a4abd78" "f4cf8a16c5457089" "fa0aab3b284e3448"]}] :case-u-nested-copy-reaches-copies-of-the-receiving-main [{:transcript " {:component \"outer\", :id sync-outer-component}\n {:component \"inner\", :id sync-inner-component}\n {:component \"outer\", :copy sync-outer-copyhead-0}\n {:component \"inner\", :copy sync-inner-copyhead-0}\n {:copy #fn, :into #fn, :cell nil}\n {:target #fn, :property :fills, :value \"#00ff00\"}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["3e741919b165a9b1" "3b36d2a788757ccf" "13cc7bde13f6db61" "d2df2363c6fd87ee"]} {:transcript " {:component \"outer\", :id sync-outer-component}\n {:component \"inner\", :id sync-inner-component}\n {:component \"outer\", :copy sync-outer-copyhead-0}\n {:component \"inner\", :copy sync-inner-copyhead-0}\n {:copy #fn, :into #fn, :cell nil}\n one-of -> \n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["3e741919b165a9b1" "20ac8b615e23f0ea" "3b80647668a43256"]}] diff --git a/frontend/test/frontend_tests/data/workspace_layout_owned_sizes_test.cljs b/frontend/test/frontend_tests/data/workspace_layout_owned_sizes_test.cljs new file mode 100644 index 0000000000..f9713f22a2 --- /dev/null +++ b/frontend/test/frontend_tests/data/workspace_layout_owned_sizes_test.cljs @@ -0,0 +1,46 @@ +;; 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 frontend-tests.data.workspace-layout-owned-sizes-test + "The size of a board that hugs its content follows the layout: a resize + of a child in a copy touches the child, not the board." + (:require + [app.common.test-helpers.composable.core :as tm] + [app.common.test-helpers.compositions :as tho] + [app.common.test-helpers.files :as thf] + [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.shapes :as ths] + [app.main.data.workspace.transforms :as dwt] + [cljs.test :as t :include-macros true] + [frontend-tests.composable-tests.interpreter :as ftm])) + +(t/use-fixtures :each ftm/fixture) + +(defn- hug-component-with-copy + "A component whose root has a flex layout (row) and hugs its 50x50 + child, and a copy of it." + [] + (let [file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy + :component1 :main-root :main-child :copy-root + :main-root-params {:layout :flex :layout-flex-dir :row + :layout-item-h-sizing :auto + :layout-item-v-sizing :auto + :width 50 :height 50} + :main-child-params {:width 50 :height 50} + :copy-root-params {:children-labels [:copy-child]}))] + (tm/make-situation file))) + +(t/deftest ^:async a-resize-of-a-copy-child-leaves-its-hugging-board-untouched + (ftm/install! (hug-component-with-copy)) + (await (ftm/await-step [(dwt/update-dimensions [(thi/id :copy-child)] :width 80)])) + (let [file (ftm/current-file) + child (ths/get-shape file :copy-child) + root (ths/get-shape file :copy-root)] + (t/is (= 80 (:width child))) + (t/is (= 80 (:width root))) + (t/is (contains? (:touched child) :geometry-group)) + (t/is (not (contains? (:touched root) :geometry-group))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index ee9afbd663..4fc6968d78 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -35,6 +35,7 @@ [frontend-tests.data.workspace-font-variants-test] [frontend-tests.data.workspace-frame-guides-undo-test] [frontend-tests.data.workspace-interactions-test] + [frontend-tests.data.workspace-layout-owned-sizes-test] [frontend-tests.data.workspace-libraries-test] [frontend-tests.data.workspace-library-links-test] [frontend-tests.data.workspace-library-updates-test] @@ -205,6 +206,7 @@ 'frontend-tests.data.workspace-context-menu-test 'frontend-tests.data.workspace-edit-watchers-test 'frontend-tests.data.workspace-interactions-test + 'frontend-tests.data.workspace-layout-owned-sizes-test 'frontend-tests.data.workspace-font-variants-test 'frontend-tests.data.workspace-frame-guides-undo-test 'frontend-tests.data.workspace-text-modifier-test