From 743431bd4218cbc37254c01cdb023a916a821685 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Thu, 1 Oct 2026 13:37:46 +0200 Subject: [PATCH] :bug: Validate pasted shapes where they are pasted generate-duplicate-changes looked for the instance roots of the copied shapes in the page they were copied from, and mapped them to the new ids. Pasting a main into another main creates a nested copy, but the copy was validated on its own and reported :nested-copy-not-allowed. Shapes copied from inside a component and pasted outside were not validated at all: the unchanged source root was validated instead. Look for the instance roots from the new shapes, in the objects of the changes builder, where they already are in their new place. This fixes three tests in frontend pasting_in_containers_test. AI-assisted-by: claude-opus-5-5 --- common/src/app/common/logic/libraries.cljc | 10 +-- .../logic/comp_creation_test.cljc | 67 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 157bfde441..4ffdf693da 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -3230,11 +3230,13 @@ (map #(vector (:old-id %) (-> % :obj :id)))) (:redo-changes changes)) - copied-components - (ctn/get-all-instance-roots (:objects page) ids) - + ;; Look for the instance roots from the new shapes, where they have + ;; been pasted. A copy pasted inside another component is a nested + ;; copy, so the root of the enclosing instance is validated instead + ;; of the copy alone ids-to-validate - (map #(get ids-map % %) copied-components) + (ctn/get-all-instance-roots (pcb/get-objects changes) + (keep ids-map ids)) changes (if (seq ids-to-validate) (pcb/validate-shapes changes diff --git a/common/test/common_tests/logic/comp_creation_test.cljc b/common/test/common_tests/logic/comp_creation_test.cljc index 1a24bab0fb..4eb98a2b7f 100644 --- a/common/test/common_tests/logic/comp_creation_test.cljc +++ b/common/test/common_tests/logic/comp_creation_test.cljc @@ -819,3 +819,70 @@ ;; ==== Check (t/is (= 3 (count (:shapes v02')))) (t/is (validates-shapes? changes [v02-id])))) + +(defn- paste-changes + "Build the changes of pasting a copy of the shape into the parent, as the + workspace does." + [file shape-label parent-label] + (let [page (thf/current-page file) + shape-id (thi/id shape-label) + parent-id (thi/id parent-label) + objects (update (:objects page) shape-id assoc :parent-id parent-id :frame-id parent-id)] + (-> (pcb/empty-changes nil) + (pcb/with-page-id (:id page)) + (pcb/with-library-data (:data file)) + (pcb/with-objects (:objects page)) + (cll/generate-duplicate-changes objects page #{shape-id} (gpt/point 0 0) + {(:id file) file} (:data file) (:id file))))) + +(defn- pasted-id + [changes shape-label] + (->> (:redo-changes changes) + (filter #(and (= :add-obj (:type %)) + (= (thi/id shape-label) (:old-id %)))) + first + :obj + :id)) + +(t/deftest test-paste-main-into-main-validates-enclosing-root + ;; Pasting a main into another main creates a nested copy, that can't be + ;; validated on its own: the root of the enclosing main is validated + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component :component1 :main1-root :main1-child) + (tho/add-simple-component :component2 :main2-root :main2-child)) + + ;; ==== Action + changes (paste-changes file :main2-root :main1-root) + file' (thf/apply-changes file changes) + copy' (ths/get-shape-by-id file' (pasted-id changes :main2-root))] + + ;; ==== Check + (t/is (= (thi/id :main1-root) (:parent-id copy'))) + (t/is (not (ctk/instance-root? copy'))) + (t/is (validates-shapes? changes [(thi/id :main1-root)])))) + +(t/deftest test-paste-copy-at-root-validates-new-copy + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy :component1 + :main1-root + :main1-child + :copy1-root)) + page (thf/current-page file) + + ;; ==== Action + changes (-> (pcb/empty-changes nil) + (pcb/with-page-id (:id page)) + (pcb/with-library-data (:data file)) + (pcb/with-objects (:objects page)) + (cll/generate-duplicate-changes (:objects page) page #{(thi/id :copy1-root)} + (gpt/point 0 0) + {(:id file) file} (:data file) (:id file))) + new-id (pasted-id changes :copy1-root) + file' (thf/apply-changes file changes)] + + ;; ==== Check + (t/is (ctk/instance-root? (ths/get-shape-by-id file' new-id))) + (t/is (validates-shapes? changes [new-id])))) +