From 5611fcfc2c36bfac1c7d8d081c159950329462c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Moya?= <andres.moya@kaleidos.net>
Date: Mon, 29 Apr 2024 15:12:43 +0200
Subject: [PATCH 1/4] :wrench: Add generator function for update-shapes

---
 .../app/common/files/libraries_helpers.cljc   | 17 ++++++++++
 .../src/app/main/data/workspace/changes.cljs  | 31 +++++++++----------
 2 files changed, 31 insertions(+), 17 deletions(-)

diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc
index 5aa612a52..dc4c152ec 100644
--- a/common/src/app/common/files/libraries_helpers.cljc
+++ b/common/src/app/common/files/libraries_helpers.cljc
@@ -34,6 +34,23 @@
 ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
 (log/set-level! :warn)
 
+(defn generate-update-shapes
+[changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}]
+  (let [changes   (reduce
+                   (fn [changes id]
+                     (let [opts {:attrs attrs
+                                 :ignore-geometry? (get ignore-tree id)
+                                 :ignore-touched ignore-touched
+                                 :with-objects? with-objects?}]
+                       (pcb/update-shapes changes [id] update-fn (d/without-nils opts))))
+                   (-> changes
+                       (pcb/with-objects objects))
+                   ids)
+        grid-ids (->> ids (filter (partial ctl/grid-layout? objects)))
+        changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true})
+        changes (pcb/reorder-grid-children changes ids)]
+    changes))
+
 (declare generate-sync-container)
 (declare generate-sync-shape)
 (declare generate-sync-text-shape)
diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs
index b2d595086..c1e0bb04f 100644
--- a/frontend/src/app/main/data/workspace/changes.cljs
+++ b/frontend/src/app/main/data/workspace/changes.cljs
@@ -12,6 +12,7 @@
    [app.common.files.changes :as cpc]
    [app.common.files.changes-builder :as pcb]
    [app.common.files.helpers :as cph]
+   [app.common.files.libraries-helpers :as cflh]
    [app.common.logging :as log]
    [app.common.schema :as sm]
    [app.common.types.shape-tree :as ctst]
@@ -74,23 +75,19 @@
                   (filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?})))
                   (map :id))
 
-             changes   (reduce
-                        (fn [changes id]
-                          (let [opts {:attrs attrs
-                                      :ignore-geometry? (get ignore-tree id)
-                                      :ignore-touched ignore-touched
-                                      :with-objects? with-objects?}]
-                            (pcb/update-shapes changes [id] update-fn (d/without-nils opts))))
-                        (-> (pcb/empty-changes it page-id)
-                            (pcb/set-save-undo? save-undo?)
-                            (pcb/set-stack-undo? stack-undo?)
-                            (pcb/with-objects objects)
-                            (cond-> undo-group
-                              (pcb/set-undo-group undo-group)))
-                        ids)
-             grid-ids (->> ids (filter (partial ctl/grid-layout? objects)))
-             changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true})
-             changes (pcb/reorder-grid-children changes ids)
+             changes (-> (pcb/empty-changes it page-id)
+                         (pcb/set-save-undo? save-undo?)
+                         (pcb/set-stack-undo? stack-undo?)
+                         (cflh/generate-update-shapes ids
+                                                      update-fn
+                                                      objects
+                                                      {:attrs attrs
+                                                       :ignore-tree ignore-tree
+                                                       :ignore-touched ignore-touched
+                                                       :with-objects? with-objects?})
+                         (cond-> undo-group
+                           (pcb/set-undo-group undo-group)))
+
              changes (add-undo-group changes state)]
          (rx/concat
           (if (seq (:redo-changes changes))

From a40afd5b638c505cb9c22127fc0c9addcac89f15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Moya?= <andres.moya@kaleidos.net>
Date: Mon, 29 Apr 2024 15:15:10 +0200
Subject: [PATCH 2/4] :white_check_mark: Add test for touched shapes

---
 .../app/common/files/libraries_helpers.cljc   |  2 +-
 .../common_tests/helpers/compositions.cljc    | 47 ++++++++------
 common/test/common_tests/helpers/files.cljc   | 31 ++++++---
 ...test.cljc => component_creation_test.cljc} | 10 +--
 .../logic/components_touched_test.cljc        | 51 +++++++++++++++
 .../types/types_libraries_test.cljc           | 27 ++++----
 .../src/app/main/data/workspace/changes.cljs  |  1 -
 .../state_components_sync_test.cljs           | 64 -------------------
 8 files changed, 122 insertions(+), 111 deletions(-)
 rename common/test/common_tests/logic/{logic_comp_creation_test.cljc => component_creation_test.cljc} (92%)
 create mode 100644 common/test/common_tests/logic/components_touched_test.cljc

diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc
index dc4c152ec..617643b61 100644
--- a/common/src/app/common/files/libraries_helpers.cljc
+++ b/common/src/app/common/files/libraries_helpers.cljc
@@ -35,7 +35,7 @@
 (log/set-level! :warn)
 
 (defn generate-update-shapes
-[changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}]
+  [changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}]
   (let [changes   (reduce
                    (fn [changes id]
                      (let [opts {:attrs attrs
diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc
index e28526ee6..48e4fff7c 100644
--- a/common/test/common_tests/helpers/compositions.cljc
+++ b/common/test/common_tests/helpers/compositions.cljc
@@ -10,35 +10,44 @@
    [common-tests.helpers.ids-map :as thi]))
 
 (defn add-rect
-  [file rect-label]
+  [file rect-label & {:keys [] :as params}]
   (thf/add-sample-shape file rect-label
-                        :type :rect
-                        :name "Rect1"))
+                        (merge {:type :rect
+                                :name "Rect1"}
+                               params)))
 
 (defn add-frame
-  ([file frame-label & {:keys [parent-label]}]
-   (thf/add-sample-shape file frame-label
-                         :type :frame
-                         :name "Frame1"
-                         :parent-label parent-label)))
+  [file frame-label & {:keys [] :as params}]
+  (thf/add-sample-shape file frame-label
+                        (merge {:type :frame
+                                :name "Frame1"}
+                               params)))
 
 (defn add-frame-with-child
-  [file frame-label child-label]
+  [file frame-label child-label & {:keys [frame-params child-params]}]
   (-> file
-      (add-frame frame-label)
+      (add-frame frame-label frame-params)
       (thf/add-sample-shape child-label
-                            :type :rect
-                            :name "Rect1"
-                            :parent-label frame-label)))
+                            (merge {:type :rect
+                                    :name "Rect1"
+                                    :parent-label frame-label}
+                                   child-params))))
 
 (defn add-simple-component
-  [file component-label root-label child-label]
+  [file component-label root-label child-label
+   & {:keys [component-params root-params child-params]}]
   (-> file
-      (add-frame-with-child root-label child-label)
-      (thf/make-component component-label root-label)))
+      (add-frame-with-child root-label child-label :frame-params root-params :child-params child-params)
+      (thf/make-component component-label root-label component-params)))
 
 (defn add-simple-component-with-copy
-  [file component-label main-root-label main-child-label copy-root-label]
+  [file component-label main-root-label main-child-label copy-root-label
+   & {:keys [component-params main-root-params main-child-params copy-root-params]}]
   (-> file
-      (add-simple-component component-label main-root-label main-child-label)
-      (thf/instantiate-component component-label copy-root-label)))
+      (add-simple-component component-label
+                            main-root-label
+                            main-child-label
+                            :component-params component-params
+                            :root-params main-root-params
+                            :child-params main-child-params)
+      (thf/instantiate-component component-label copy-root-label copy-root-params)))
diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc
index bf605f5dc..b6349636b 100644
--- a/common/test/common_tests/helpers/files.cljc
+++ b/common/test/common_tests/helpers/files.cljc
@@ -6,6 +6,7 @@
 
 (ns common-tests.helpers.files
   (:require
+   [app.common.colors :as clr]
    [app.common.data.macros :as dm]
    [app.common.features :as ffeat]
    [app.common.files.changes :as cfc]
@@ -169,7 +170,7 @@
 ;; ----- Components
 
 (defn make-component
-  [file label root-label]
+  [file label root-label & {:keys [] :as params}]
   (let [page (current-page file)
         root (get-shape file root-label)]
 
@@ -194,12 +195,12 @@
                                        #(update % :objects assoc (:id shape) shape)))
                    $
                    updated-shapes)
-           (ctkl/add-component $
-                               {:id (:component-id updated-root)
-                                :name (:name updated-root)
-                                :main-instance-id (:id updated-root)
-                                :main-instance-page (:id page)
-                                :shapes updated-shapes})))))))
+           (ctkl/add-component $ (assoc params
+                                        :id (:component-id updated-root)
+                                        :name (:name updated-root)
+                                        :main-instance-id (:id updated-root)
+                                        :main-instance-page (:id page)
+                                        :shapes updated-shapes))))))))
 
 (defn get-component
   [file label]
@@ -306,7 +307,21 @@
   [label & {:keys [] :as params}]
   (ctc/make-color (assoc params :id (thi/new-id! label))))
 
-(defn add-sample-color
+(defn sample-fill-color
+  [& {:keys [fill-color fill-opacity] :as params}]
+  (let [params (cond-> params
+                 (nil? fill-color)
+                 (assoc :fill-color clr/black)
+
+                 (nil? fill-opacity)
+                 (assoc :fill-opacity 1))]
+    params))
+
+(defn sample-fills-color
+  [& {:keys [] :as params}]
+  [(sample-fill-color params)])
+
+(defn add-sample-library-color
   [file label & {:keys [] :as params}]
   (let [color (sample-color label params)]
     (ctf/update-file-data file #(ctcl/add-color % color))))
diff --git a/common/test/common_tests/logic/logic_comp_creation_test.cljc b/common/test/common_tests/logic/component_creation_test.cljc
similarity index 92%
rename from common/test/common_tests/logic/logic_comp_creation_test.cljc
rename to common/test/common_tests/logic/component_creation_test.cljc
index 577bef06f..3e6499fb2 100644
--- a/common/test/common_tests/logic/logic_comp_creation_test.cljc
+++ b/common/test/common_tests/logic/component_creation_test.cljc
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) KALEIDOS INC
 
-(ns common-tests.logic.logic-comp-creation-test
+(ns common-tests.logic.component-creation-test
   (:require
    [app.common.files.changes-builder :as pcb]
    [app.common.files.libraries-helpers :as cflh]
@@ -15,14 +15,14 @@
 (t/use-fixtures :each thi/test-fixture)
 
 (t/deftest test-add-component-from-single-shape
-  (let [; Setup
+  (let [;; Setup
         file   (-> (thf/sample-file :file1)
                    (thf/add-sample-shape :shape1 :type :frame))
 
         page   (thf/current-page file)
         shape1 (thf/get-shape file :shape1)
 
-        ; Action
+        ;; Action
         [_ component-id changes]
         (cflh/generate-add-component (pcb/empty-changes)
                                      [shape1]
@@ -35,11 +35,11 @@
 
         file' (thf/apply-changes file changes)
 
-        ; Get
+        ;; Get
         component (thf/get-component-by-id file' component-id)
         root      (thf/get-shape-by-id file' (:main-instance-id component))]
 
-    ; Check
+    ;; Check
     (t/is (some? component))
     (t/is (some? root))
     (t/is (= (:component-id root) (:id component)))))
diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc
new file mode 100644
index 000000000..eedd84fd9
--- /dev/null
+++ b/common/test/common_tests/logic/components_touched_test.cljc
@@ -0,0 +1,51 @@
+;; 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.logic.components-touched-test
+  (:require
+   [app.common.files.changes-builder :as pcb]
+   [app.common.files.libraries-helpers :as cflh]
+   [clojure.test :as t]
+   [common-tests.helpers.compositions :as tho]
+   [common-tests.helpers.files :as thf]
+   [common-tests.helpers.ids-map :as thi]))
+
+(t/use-fixtures :each thi/test-fixture)
+
+(t/deftest test-touched-when-changing-attribute
+  (let [;; Setup
+        file (-> (thf/sample-file :file1)
+                 (tho/add-simple-component-with-copy :component1
+                                                     :main-root
+                                                     :main-child
+                                                     :copy-root
+                                                     :main-child-params {:fills (thf/sample-fills-color
+                                                                                 :fill-color "#abcdef")}))
+        page (thf/current-page file)
+        copy-root (thf/get-shape file :copy-root)
+
+        ;; Action
+        changes (cflh/generate-update-shapes (pcb/empty-changes nil (:id page))
+                                             (:shapes copy-root)
+                                             #(assoc % :fills (thf/sample-fills-color
+                                                               :fill-color "#fabada"))
+                                             (:objects page)
+                                             {})
+
+        file' (thf/apply-changes file changes)
+
+        ;; Get
+        copy-root' (thf/get-shape file' :copy-root)
+        copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))
+        fills' (:fills copy-child')
+        fill' (first fills')]
+
+    ;; Check
+    (t/is (= (count fills') 1))
+    (t/is (= (:fill-color fill') "#fabada"))
+    (t/is (= (:fill-opacity fill') 1))
+    (t/is (= (:touched copy-root') nil))
+    (t/is (= (:touched copy-child') #{:fill-group}))))
diff --git a/common/test/common_tests/types/types_libraries_test.cljc b/common/test/common_tests/types/types_libraries_test.cljc
index 0eab0db07..d95232824 100644
--- a/common/test/common_tests/types/types_libraries_test.cljc
+++ b/common/test/common_tests/types/types_libraries_test.cljc
@@ -70,21 +70,21 @@
     (t/is (= (:name f1) "Test file"))))
 
 (t/deftest test-absorb-components
-  (let [; Setup
+  (let [;; Setup
         library (-> (thf/sample-file :library :is-shared true)
                     (tho/add-simple-component :component1 :main-root :rect1))
 
         file (-> (thf/sample-file :file)
                  (thf/instantiate-component :component1 :copy-root :library library))
 
-        ; Action
+        ;; Action
         file' (ctf/update-file-data
                file
                #(ctf/absorb-assets % (:data library)))
 
         _ (thf/validate-file! file')
 
-        ; Get
+        ;; Get
         pages'      (ctpl/pages-seq (ctf/file-data file'))
         components' (ctkl/components-seq (ctf/file-data file'))
         component' (first components')
@@ -92,7 +92,7 @@
         copy-root' (thf/get-shape file' :copy-root)
         main-root' (ctf/get-ref-shape (ctf/file-data file') component' copy-root')]
 
-    ; Check
+    ;; Check
     (t/is (= (count pages') 2))
     (t/is (= (:name (first pages')) "Page 1"))
     (t/is (= (:name (second pages')) "Main components"))
@@ -104,10 +104,10 @@
     (t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component'))))
 
 (t/deftest test-absorb-colors
-  (let [; Setup
+  (let [;; Setup
         library (-> (thf/sample-file :library :is-shared true)
-                    (thf/add-sample-color :color1 {:name "Test color"
-                                                   :color "#abcdef"}))
+                    (thf/add-sample-library-color :color1 {:name "Test color"
+                                                           :color "#abcdef"}))
 
         file    (-> (thf/sample-file :file)
                     (thf/add-sample-shape :shape1
@@ -118,19 +118,19 @@
                                                    :fill-color-ref-id (thi/id :color1)
                                                    :fill-color-ref-file (thi/id :library)}]))
 
-        ; Action
+        ;; Action
         file' (ctf/update-file-data
                file
                #(ctf/absorb-assets % (:data library)))
 
         _ (thf/validate-file! file')
 
-        ; Get
+        ;; Get
         colors' (ctcl/colors-seq (ctf/file-data file'))
         shape1' (thf/get-shape file' :shape1)
         fill'   (first (:fills shape1'))]
 
-    ; Check
+    ;; Check
     (t/is (= (count colors') 1))
     (t/is (= (:id (first colors')) (thi/id :color1)))
     (t/is (= (:name (first colors')) "Test color"))
@@ -141,7 +141,7 @@
     (t/is (= (:fill-color-ref-file fill') (:id file')))))
 
 (t/deftest test-absorb-typographies
-  (let [; Setup
+  (let [;; Setup
         library (-> (thf/sample-file :library :is-shared true)
                     (thf/add-sample-typography :typography1 {:name "Test typography"}))
 
@@ -169,18 +169,19 @@
                                                                                         :letter-spacing "0"
                                                                                         :fills [{:fill-color "#000000"
                                                                                                  :fill-opacity 1}]}]}]}]}))
-        ; Action
+        ;; Action
         file' (ctf/update-file-data
                file
                #(ctf/absorb-assets % (:data library)))
 
         _ (thf/validate-file! file')
 
-        ; Get
+        ;; Get
         typographies' (ctyl/typographies-seq (ctf/file-data file'))
         shape1'       (thf/get-shape file' :shape1)
         text-node'    (d/seek #(some? (:text %)) (txt/node-seq (:content shape1')))]
 
+    ;; Check
     (t/is (= (count typographies') 1))
     (t/is (= (:id (first typographies')) (thi/id :typography1)))
     (t/is (= (:name (first typographies')) "Test typography"))
diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs
index c1e0bb04f..87dec4048 100644
--- a/frontend/src/app/main/data/workspace/changes.cljs
+++ b/frontend/src/app/main/data/workspace/changes.cljs
@@ -16,7 +16,6 @@
    [app.common.logging :as log]
    [app.common.schema :as sm]
    [app.common.types.shape-tree :as ctst]
-   [app.common.types.shape.layout :as ctl]
    [app.common.uuid :as uuid]
    [app.main.data.workspace.state-helpers :as wsh]
    [app.main.data.workspace.undo :as dwu]
diff --git a/frontend/test/frontend_tests/state_components_sync_test.cljs b/frontend/test/frontend_tests/state_components_sync_test.cljs
index 4928ad74c..56581b29e 100644
--- a/frontend/test/frontend_tests/state_components_sync_test.cljs
+++ b/frontend/test/frontend_tests/state_components_sync_test.cljs
@@ -7,7 +7,6 @@
 (ns frontend-tests.state-components-sync-test
   (:require
    [app.common.colors :as clr]
-   [app.common.types.file :as ctf]
    [app.main.data.workspace :as dw]
    [app.main.data.workspace.changes :as dch]
    [app.main.data.workspace.libraries :as dwl]
@@ -24,69 +23,6 @@
 
 ;; === Test touched ======================
 
-(t/deftest test-touched
-  (t/async done
-    (let [state (-> thp/initial-state
-                    (thp/sample-page)
-                    (thp/sample-shape :shape1 :rect
-                                      {:name "Rect 1"
-                                       :fill-color clr/white
-                                       :fill-opacity 1})
-                    (thp/make-component :main1 :component1
-                                        [(thp/id :shape1)])
-                    (thp/instantiate-component :instance1
-                                               (thp/id :component1)))
-
-          [_group1 shape1']
-          (thl/resolve-instance state (thp/id :instance1))
-
-          store (the/prepare-store state done
-                                   (fn [new-state]
-                                     ;; Uncomment to debug
-                                     ;; (ctf/dump-tree (get new-state :workspace-data)
-                                     ;;                (get new-state :current-page-id)
-                                     ;;                (get new-state :workspace-libraries)
-                                     ;;                false true)
-                                     ;; Expected shape tree:
-                                     ;;;
-                                     ;; [Page]
-                                     ;; Root Frame
-                                     ;;   Rect 1
-                                     ;;     Rect 1
-                                     ;;   Rect 1              #--> Rect 1
-                                     ;;     Rect 1*           ---> Rect 1
-                                     ;;         #{:fill-group}
-                                     ;;;
-                                     ;; [Rect 1]
-                                     ;;   page1 / Rect 1
-                                     ;;;
-                                     (let [[[group shape1] [c-group c-shape1] _component]
-                                           (thl/resolve-instance-and-main
-                                            new-state
-                                            (thp/id :instance1))]
-
-                                       (t/is (= (:name group) "Rect 1"))
-                                       (t/is (= (:touched group) nil))
-                                       (t/is (= (:name shape1) "Rect 1"))
-                                       (t/is (= (:touched shape1) #{:fill-group}))
-                                       (t/is (= (:fill-color shape1) clr/test))
-                                       (t/is (= (:fill-opacity shape1) 0.5))
-
-                                       (t/is (= (:name c-group) "Rect 1"))
-                                       (t/is (= (:touched c-group) nil))
-                                       (t/is (= (:name c-shape1) "Rect 1"))
-                                       (t/is (= (:touched c-shape1) nil))
-                                       (t/is (= (:fill-color c-shape1) clr/white))
-                                       (t/is (= (:fill-opacity c-shape1) 1)))))]
-
-      (ptk/emit!
-       store
-       (dch/update-shapes [(:id shape1')]
-                          (fn [shape]
-                            (merge shape {:fill-color clr/test
-                                          :fill-opacity 0.5})))
-       :the/end))))
-
 (t/deftest test-touched-children-add
   (t/async done
     (let [state (-> thp/initial-state

From 77d4901db10522d3d5fa2224838be15984709652 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Moya?= <andres.moya@kaleidos.net>
Date: Mon, 29 Apr 2024 18:40:09 +0200
Subject: [PATCH 3/4] :white_check_mark: Add more tests for touched

---
 .../common_tests/helpers/compositions.cljc    |  32 ++-
 .../logic/components_touched_test.cljc        | 136 ++++++++++--
 .../state_components_sync_test.cljs           | 195 ------------------
 3 files changed, 148 insertions(+), 215 deletions(-)

diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc
index 48e4fff7c..d8b9cbe8b 100644
--- a/common/test/common_tests/helpers/compositions.cljc
+++ b/common/test/common_tests/helpers/compositions.cljc
@@ -6,8 +6,8 @@
 
 (ns common-tests.helpers.compositions
   (:require
-   [common-tests.helpers.files :as thf]
-   [common-tests.helpers.ids-map :as thi]))
+   [app.common.data :as d]
+   [common-tests.helpers.files :as thf]))
 
 (defn add-rect
   [file rect-label & {:keys [] :as params}]
@@ -51,3 +51,31 @@
                             :root-params main-root-params
                             :child-params main-child-params)
       (thf/instantiate-component component-label copy-root-label copy-root-params)))
+
+(defn add-component-with-many-children
+  [file component-label root-label child-labels
+   & {:keys [component-params root-params child-params-list]}]
+  (as-> file $
+    (add-frame $ root-label root-params)
+    (reduce (fn [file [label params]]
+              (thf/add-sample-shape file
+                                    label
+                                    (merge {:type :rect
+                                            :name "Rect1"
+                                            :parent-label root-label}
+                                           params)))
+            $
+            (d/zip-all child-labels child-params-list))
+    (thf/make-component $ component-label root-label component-params)))
+
+(defn add-component-with-many-children-and-copy
+  [file component-label root-label child-labels copy-root-label
+   & {:keys [component-params root-params child-params-list copy-root-params]}]
+  (-> file
+      (add-component-with-many-children component-label
+                                        root-label
+                                        child-labels
+                                        :component-params component-params
+                                        :root-params root-params
+                                        :child-params-list child-params-list)
+      (thf/instantiate-component component-label copy-root-label copy-root-params)))
diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc
index eedd84fd9..45b11b59f 100644
--- a/common/test/common_tests/logic/components_touched_test.cljc
+++ b/common/test/common_tests/logic/components_touched_test.cljc
@@ -17,31 +17,33 @@
 
 (t/deftest test-touched-when-changing-attribute
   (let [;; Setup
-        file (-> (thf/sample-file :file1)
-                 (tho/add-simple-component-with-copy :component1
-                                                     :main-root
-                                                     :main-child
-                                                     :copy-root
-                                                     :main-child-params {:fills (thf/sample-fills-color
-                                                                                 :fill-color "#abcdef")}))
-        page (thf/current-page file)
+        file  (-> (thf/sample-file :file1)
+                  (tho/add-simple-component-with-copy :component1
+                                                      :main-root
+                                                      :main-child
+                                                      :copy-root
+                                                      :main-child-params {:fills (thf/sample-fills-color
+                                                                                  :fill-color "#abcdef")}))
+        page      (thf/current-page file)
         copy-root (thf/get-shape file :copy-root)
 
         ;; Action
-        changes (cflh/generate-update-shapes (pcb/empty-changes nil (:id page))
-                                             (:shapes copy-root)
-                                             #(assoc % :fills (thf/sample-fills-color
-                                                               :fill-color "#fabada"))
-                                             (:objects page)
-                                             {})
+        update-fn (fn [shape]
+                    (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada")))
 
-        file' (thf/apply-changes file changes)
+        changes   (cflh/generate-update-shapes (pcb/empty-changes nil (:id page))
+                                               (:shapes copy-root)
+                                               update-fn
+                                               (:objects page)
+                                               {})
+
+        file'     (thf/apply-changes file changes)
 
         ;; Get
-        copy-root' (thf/get-shape file' :copy-root)
+        copy-root'  (thf/get-shape file' :copy-root)
         copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))
-        fills' (:fills copy-child')
-        fill' (first fills')]
+        fills'      (:fills copy-child')
+        fill'       (first fills')]
 
     ;; Check
     (t/is (= (count fills') 1))
@@ -49,3 +51,101 @@
     (t/is (= (:fill-opacity fill') 1))
     (t/is (= (:touched copy-root') nil))
     (t/is (= (:touched copy-child') #{:fill-group}))))
+
+(t/deftest test-not-touched-when-adding-shape
+  (let [;; Setup
+        file (-> (thf/sample-file :file1)
+                 (tho/add-simple-component-with-copy :component1
+                                                     :main-root
+                                                     :main-child
+                                                     :copy-root)
+                 (thf/add-sample-shape :free-shape))
+
+        page (thf/current-page file)
+
+        ;; Action
+        ;; IMPORTANT: as modifying copies structure is now forbidden, this action
+        ;; will not have any effect, and so the parent shape won't also be touched.
+        changes (cflh/generate-relocate-shapes (pcb/empty-changes)
+                                               (:objects page)
+                                               #{(thi/id :copy-root)}   ; parents
+                                               (thi/id :copy-root)      ; parent-id
+                                               (:id page)               ; page-id
+                                               0                        ; to-index
+                                               #{(thi/id :free-shape)}) ; ids
+
+        file'   (thf/apply-changes file changes)
+
+        ;; Get
+        copy-root'  (thf/get-shape file' :copy-root)
+        copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))]
+
+    ;; Check
+    (t/is (= (:touched copy-root') nil))
+    (t/is (= (:touched copy-child') nil))))
+
+(t/deftest test-touched-when-deleting-shape
+  (let [;; Setup
+        file       (-> (thf/sample-file :file1)
+                       (tho/add-simple-component-with-copy :component1
+                                                           :main-root
+                                                           :main-child
+                                                           :copy-root))
+
+        page       (thf/current-page file)
+        copy-root  (thf/get-shape file :copy-root)
+
+        ;; Action
+        ;; IMPORTANT: as modifying copies structure is now forbidden, this action will not
+        ;; delete the child shape, but hide it (thus setting the visibility group).
+        [_all-parents changes]
+        (cflh/generate-delete-shapes (pcb/empty-changes)
+                                     file
+                                     page
+                                     (:objects page)
+                                     (set (:shapes copy-root))
+                                     {:components-v2 true})
+
+        file'   (thf/apply-changes file changes)
+
+        ;; Get
+        copy-root'  (thf/get-shape file' :copy-root)
+        copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))]
+
+    ;; Check
+    (t/is (= (:touched copy-root') nil))
+    (t/is (= (:touched copy-child') #{:visibility-group}))))
+
+(t/deftest test-not-touched-when-moving-shape
+  (let [;; Setup
+        file (-> (thf/sample-file :file1)
+                 (tho/add-component-with-many-children-and-copy :component1
+                                                                :main-root
+                                                                [:main-child1 :main-child2 :main-child3]
+                                                                :copy-root)
+                 (thf/add-sample-shape :free-shape))
+
+        page (thf/current-page file)
+        copy-root (thf/get-shape file :copy-root)
+        copy-child1 (thf/get-shape-by-id file (first (:shapes copy-root)))
+
+        ;; Action
+        ;; IMPORTANT: as modifying copies structure is now forbidden, this action
+        ;; will not have any effect, and so the parent shape won't also be touched.
+        changes (cflh/generate-relocate-shapes (pcb/empty-changes)
+                                               (:objects page)
+                                               #{(thi/id :copy-root)}   ; parents
+                                               (thi/id :copy-root)      ; parent-id
+                                               (:id page)               ; page-id
+                                               2                        ; to-index
+                                               #{(:id copy-child1)}) ; ids
+
+        file'   (thf/apply-changes file changes)
+
+        ;; Get
+        copy-root'  (thf/get-shape file' :copy-root)
+        copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))]
+
+    ;; Check
+    (t/is (= (:touched copy-root') nil))
+    (t/is (= (:touched copy-child') nil))))
diff --git a/frontend/test/frontend_tests/state_components_sync_test.cljs b/frontend/test/frontend_tests/state_components_sync_test.cljs
index 56581b29e..8ae88562a 100644
--- a/frontend/test/frontend_tests/state_components_sync_test.cljs
+++ b/frontend/test/frontend_tests/state_components_sync_test.cljs
@@ -23,201 +23,6 @@
 
 ;; === Test touched ======================
 
-(t/deftest test-touched-children-add
-  (t/async done
-    (let [state (-> thp/initial-state
-                    (thp/sample-page)
-                    (thp/sample-shape :shape1 :rect
-                                      {:name "Rect 1"
-                                       :fill-color clr/white
-                                       :fill-opacity 1})
-                    (thp/make-component :main1 :component1
-                                        [(thp/id :shape1)])
-                    (thp/instantiate-component :instance1
-                                               (thp/id :component1))
-                    (thp/sample-shape :shape2 :circle
-                                      {:name "Circle 1"}))
-
-          instance1 (thp/get-shape state :instance1)
-          shape2    (thp/get-shape state :shape2)
-
-          store (the/prepare-store state done
-                                   (fn [new-state]
-                                     ;; Expected shape tree:
-                                     ;; [Page: Page 1]
-                                     ;;   Root Frame
-                                     ;;     {Rect 1}
-                                     ;;       Rect1
-                                     ;;     Rect 1            #--> Rect 1
-                                     ;;       Rect 1          ---> Rect 1
-                                     ;;     Circle 1
-                                     ;;
-                                     ;; [Component: Rect 1] core.cljs:200:23
-                                     ;;   --> [Page 1] Rect 1
-
-                                     (let [[[group shape1] [c-group c-shape1] _component]
-                                           (thl/resolve-instance-and-main-allow-dangling
-                                            new-state
-                                            (thp/id :instance1))]
-
-                                       (t/is (= (:name group) "Rect 1"))
-                                       (t/is (nil? (:touched group)))
-                                       (t/is (= (:name shape1) "Rect 1"))
-                                       (t/is (= (:touched shape1) nil))
-                                       (t/is (not= (:shape-ref shape1) nil))
-
-                                       (t/is (= (:name c-group) "Rect 1"))
-                                       (t/is (= (:touched c-group) nil))
-                                       (t/is (= (:shape-ref c-group) nil))
-                                       (t/is (= (:name c-shape1) "Rect 1"))
-                                       (t/is (= (:touched c-shape1) nil))
-                                       (t/is (= (:shape-ref c-shape1) nil)))))]
-
-      (ptk/emit!
-       store
-       (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) ;; We cant't change the structure of component copies, so this operation will do nothing
-       :the/end))))
-
-(t/deftest test-touched-children-delete
-  (t/async done
-    (let [state (-> thp/initial-state
-                    (thp/sample-page)
-                    (thp/sample-shape :shape1 :rect
-                                      {:name "Rect 1"})
-                    (thp/sample-shape :shape2 :rect
-                                      {:name "Rect 2"})
-                    (thp/make-component :main1 :component1
-                                        [(thp/id :shape1)
-                                         (thp/id :shape2)])
-                    (thp/instantiate-component :instance1
-                                               (thp/id :component1)))
-
-          [_group1 shape1']
-          (thl/resolve-instance state (thp/id :instance1))
-
-          store (the/prepare-store state done
-                                   (fn [new-state]
-                                     ;; Expected shape tree:
-                                     ;;;
-                                     ;; [Page]
-                                     ;; Root Frame
-                                     ;;   Component 1
-                                     ;;     Rect 1
-                                     ;;     Rect 2
-                                     ;;   Component 1         #--> Component 1
-                                     ;;     Rect 1*           ---> Rect 1
-                                     ;;         #{:visibility-group}
-                                     ;;     Rect 2            ---> Rect 2
-                                     ;;;
-                                     ;; [Component 1]
-                                     ;;   page1 / Component 1
-                                     ;;
-                                     (let [[[group shape1 shape2] [c-group c-shape1 c-shape2] _component]
-                                           (thl/resolve-instance-and-main-allow-dangling
-                                            new-state
-                                            (thp/id :instance1))]
-
-                                       (t/is (= (:name group) "Component 1"))
-                                       (t/is (= (:touched group) nil))
-                                       (t/is (not= (:shape-ref group) nil))
-                                       (t/is (= (:name shape1) "Rect 1"))
-                                       (t/is (= (:hidden shape1) true))  ; Instance shapes are not deleted but hidden
-                                       (t/is (= (:touched shape1) #{:visibility-group}))
-                                       (t/is (not= (:shape-ref shape1) nil))
-                                       (t/is (= (:name shape2) "Rect 2"))
-                                       (t/is (= (:touched shape2) nil))
-                                       (t/is (not= (:shape-ref shape2) nil))
-
-                                       (t/is (= (:name c-group) "Component 1"))
-                                       (t/is (= (:touched c-group) nil))
-                                       (t/is (= (:shape-ref c-group) nil))
-                                       (t/is (= (:name c-shape1) "Rect 1"))
-                                       (t/is (= (:touched c-shape1) nil))
-                                       (t/is (= (:shape-ref c-shape1) nil))
-                                       (t/is (= (:name c-shape2) "Rect 2"))
-                                       (t/is (= (:touched c-shape2) nil))
-                                       (t/is (= (:shape-ref c-shape2) nil)))))]
-
-      (ptk/emit!
-       store
-       (dwsh/delete-shapes #{(:id shape1')})
-       :the/end))))
-
-(t/deftest test-touched-children-move
-  (t/async done
-    (let [state (-> thp/initial-state
-                    (thp/sample-page)
-                    (thp/sample-shape :shape1 :rect
-                                      {:name "Rect 1"})
-                    (thp/sample-shape :shape2 :rect
-                                      {:name "Rect 2"})
-                    (thp/sample-shape :shape3 :rect
-                                      {:name "Rect 3"})
-                    (thp/make-component :main1 :component1
-                                        [(thp/id :shape1)
-                                         (thp/id :shape2)
-                                         (thp/id :shape3)])
-                    (thp/instantiate-component :instance1
-                                               (thp/id :component1)))
-
-          [group1' shape1']
-          (thl/resolve-instance state (thp/id :instance1))
-
-          store (the/prepare-store state done
-                                   (fn [new-state]
-                                     ;; Expected shape tree:
-                                     ;; [Page: Page 1]
-                                            ;;   Root Frame
-                                            ;;     {Component 1}     #
-                                            ;;       Rect 1
-                                            ;;       Rect 2
-                                            ;;       Rect 3
-                                            ;;     Component 1       #--> Component 1
-                                            ;;       Rect 1          ---> Rect 1
-                                            ;;       Rect 2          ---> Rect 2
-                                            ;;       Rect 3          ---> Rect 3
-                                            ;;
-                                            ;; ========= Local library
-                                            ;;
-                                            ;; [Component: Component 1]
-                                            ;;   --> [Page 1] Component 1
-
-                                     (let [[[group shape1 shape2 shape3]
-                                            [c-group c-shape1 c-shape2 c-shape3] _component]
-                                           (thl/resolve-instance-and-main-allow-dangling
-                                            new-state
-                                            (thp/id :instance1))]
-
-                                       (t/is (= (:name group) "Component 1"))
-                                       (t/is (nil? (:touched group)))
-                                       (t/is (= (:name shape1) "Rect 1"))
-                                       (t/is (= (:touched shape1) nil))
-                                       (t/is (not= (:shape-ref shape1) nil))
-                                       (t/is (= (:name shape2) "Rect 2"))
-                                       (t/is (= (:touched shape2) nil))
-                                       (t/is (not= (:shape-ref shape2) nil))
-                                       (t/is (= (:name shape3) "Rect 3"))
-                                       (t/is (= (:touched shape3) nil))
-                                       (t/is (not= (:shape-ref shape3) nil))
-
-                                       (t/is (= (:name c-group) "Component 1"))
-                                       (t/is (= (:touched c-group) nil))
-                                       (t/is (= (:shape-ref c-group) nil))
-                                       (t/is (= (:name c-shape1) "Rect 1"))
-                                       (t/is (= (:touched c-shape1) nil))
-                                       (t/is (= (:shape-ref c-shape1) nil))
-                                       (t/is (= (:name c-shape2) "Rect 2"))
-                                       (t/is (= (:touched c-shape2) nil))
-                                       (t/is (= (:shape-ref c-shape2) nil))
-                                       (t/is (= (:name c-shape3) "Rect 3"))
-                                       (t/is (= (:touched c-shape3) nil))
-                                       (t/is (= (:shape-ref c-shape3) nil)))))]
-
-      (ptk/emit!
-       store
-       (dw/relocate-shapes #{(:id shape1')} (:id group1') 2) ;; We cant't change the structure of component copies, so this operation will do nothing
-       :the/end))))
-
 (t/deftest test-touched-from-lib
   (t/async
     done

From de6d8ccbf981539fd61d2f110fafb368ccf0673c Mon Sep 17 00:00:00 2001
From: Pablo Alba <pablo.alba@kaleidos.net>
Date: Tue, 30 Apr 2024 19:23:58 +0200
Subject: [PATCH 4/4] :white_check_mark: Small fix on components touched test

---
 .../test/common_tests/logic/components_touched_test.cljc   | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc
index 45b11b59f..4d912a641 100644
--- a/common/test/common_tests/logic/components_touched_test.cljc
+++ b/common/test/common_tests/logic/components_touched_test.cljc
@@ -61,14 +61,15 @@
                                                      :copy-root)
                  (thf/add-sample-shape :free-shape))
 
-        page (thf/current-page file)
+        page      (thf/current-page file)
+        copy-root (thf/get-shape file :copy-root)
 
         ;; Action
         ;; IMPORTANT: as modifying copies structure is now forbidden, this action
         ;; will not have any effect, and so the parent shape won't also be touched.
         changes (cflh/generate-relocate-shapes (pcb/empty-changes)
                                                (:objects page)
-                                               #{(thi/id :copy-root)}   ; parents
+                                               #{(:parent-id copy-root)}   ; parents
                                                (thi/id :copy-root)      ; parent-id
                                                (:id page)               ; page-id
                                                0                        ; to-index
@@ -134,7 +135,7 @@
         ;; will not have any effect, and so the parent shape won't also be touched.
         changes (cflh/generate-relocate-shapes (pcb/empty-changes)
                                                (:objects page)
-                                               #{(thi/id :copy-root)}   ; parents
+                                               #{(:parent-id copy-child1)}   ; parents
                                                (thi/id :copy-root)      ; parent-id
                                                (:id page)               ; page-id
                                                2                        ; to-index