🐛 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
This commit is contained in:
Eva Marco 2026-10-01 13:37:46 +02:00 committed by Andrés Moya
parent e702b44121
commit 743431bd42
2 changed files with 73 additions and 4 deletions

View File

@ -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

View File

@ -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]))))