diff --git a/common/src/app/common/test_helpers/ids_map.cljc b/common/src/app/common/test_helpers/ids_map.cljc index 603229d9e..b24996287 100644 --- a/common/src/app/common/test_helpers/ids_map.cljc +++ b/common/src/app/common/test_helpers/ids_map.cljc @@ -11,9 +11,11 @@ ;; ---- Helpers to manage ids as known identifiers (def ^:private idmap (atom {})) +(def ^:private next-uuid-val (atom 1)) (defn reset-idmap! [] - (reset! idmap {})) + (reset! idmap {}) + (reset! next-uuid-val 1)) (defn set-id! [label id] @@ -41,3 +43,8 @@ (map key) (first)) (str ""))) + +(defn next-uuid [] + (let [current (uuid/custom @next-uuid-val)] + (swap! next-uuid-val inc) + current)) diff --git a/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs b/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs index 87ec95532..cb8f467bb 100644 --- a/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs +++ b/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs @@ -19,6 +19,9 @@ [frontend-tests.helpers.pages :as thp] [frontend-tests.helpers.state :as ths])) +(t/use-fixtures :each + {:before thp/reset-idmap!}) + ;; Related .penpot file: common/test/cases/remove-swap-slots.penpot (defn- setup-file [] diff --git a/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs new file mode 100644 index 000000000..7cf889849 --- /dev/null +++ b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs @@ -0,0 +1,396 @@ +;; 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 frontend-tests.logic.copying-and-duplicating-test + (:require + [app.common.test-helpers.components :as cthc] + [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.uuid :as uuid] + [app.main.data.workspace :as dw] + [app.main.data.workspace.colors :as dc] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.selection :as dws] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.pages :as thp] + [frontend-tests.helpers.state :as ths])) + +(t/use-fixtures :each + {:before thp/reset-idmap!}) + +;; Related .penpot file: common/test/cases/copying-and-duplicating.penpot +(defn- setup-file [] + (-> (cthf/sample-file :file1 :page-label :page-1) + (ctho/add-simple-component :simple-1 :frame-simple-1 :rect-simple-1 :child-params {:type :rect :fills (cths/sample-fills-color :fill-color "#2152e5") :name "rect-simple-1"}) + + (ctho/add-frame :frame-composed-1 :name "frame-composed-1") + (cthc/instantiate-component :simple-1 :copy-simple-1 :parent-label :frame-composed-1 :children-labels [:composed-1-simple-1]) + (cths/add-sample-shape :rect-composed-1 :parent-label :frame-composed-1 :fills (cths/sample-fills-color :fill-color "#B1B2B5")) + (cthc/make-component :composed-1 :frame-composed-1) + + (ctho/add-frame :frame-composed-2 :name "frame-composed-2") + (cthc/instantiate-component :composed-1 :copy-composed-1-composed-2 :parent-label :frame-composed-2 :children-labels [:composed-1-composed-2]) + (cthc/make-component :composed-2 :frame-composed-2) + + (cthc/instantiate-component :composed-2 :copy-composed-2) + + (ctho/add-frame :frame-composed-3 :name "frame-composed-3") + (ctho/add-group :group-3 :parent-label :frame-composed-3) + (cthc/instantiate-component :composed-2 :copy-composed-1-composed-3 :parent-label :group-3 :children-labels [:composed-1-composed-2]) + (cths/add-sample-shape :circle-composed-3 :parent-label :group-3 :fills (cths/sample-fills-color :fill-color "#B1B2B5")) + (cthc/make-component :composed-3 :frame-composed-3) + + (cthc/instantiate-component :composed-3 :copy-composed-3 :children-labels [:composed-2-composed-3]) + (cthf/add-sample-page :page-2) + (cthf/switch-to-page :page-1))) + + +(defn- copy-paste-shape + [id file & {:keys [target-page-label target-container-id]}] + (let [features #{"components/v2"} + version 46 + page (cthf/current-page file) + target-page-id (cthi/id target-page-label) + shape (if (keyword? id) + (cths/get-shape file id) + (cths/get-shape-by-id file id)) + pdata (thp/simulate-copy-shape #{(:id shape)} (:objects page) {(:id file) file} page file features version) + target-container-id (or target-container-id (:parent-id shape))] + + (filter some? + [(when target-page-id (dw/initialize-page target-page-id)) + (dws/select-shape target-container-id) + (dw/paste-shapes pdata) + (when target-page-id (dw/initialize-page (:id page)))]))) + +(defn- sync-file [file] + (map (fn [component-tag] + (->> component-tag + (cthc/get-component file) + :component-id + (dwl/sync-file (:id file) (:id file) :components))) + [:simple-1 :composed-1 :composed-2 :composed-3])) + +(defn- set-color-bottom-shape [label file color] + (let [shape (ctho/bottom-shape file label)] + (concat + [(dws/select-shape (:id shape)) + (dc/apply-color-from-palette color false)] + (sync-file file)))) + +(defn- count-shapes [file name color] + (let [page (cthf/current-page file)] + (->> (vals (:objects page)) + (filter #(and + (= (:name %) name) + (-> (cths/get-shape-by-id file (:id %)) + :fills + first + :fill-color + (= color)))) + (count)))) + +(defn- duplicate-each-main-and-first-level-copy [file] + (concat (copy-paste-shape :frame-simple-1 file) + (copy-paste-shape :frame-simple-1 file) + (copy-paste-shape :frame-composed-1 file) + (copy-paste-shape :frame-composed-1 file) + (copy-paste-shape :frame-composed-2 file) + (copy-paste-shape :frame-composed-2 file) + (copy-paste-shape :frame-composed-3 file) + (copy-paste-shape :frame-composed-3 file) + (copy-paste-shape :copy-composed-2 file) + (copy-paste-shape :copy-composed-2 file) + (copy-paste-shape :copy-composed-3 file) + (copy-paste-shape :copy-composed-3 file))) + +(defn- duplicate-simple-nested-in-main-and-group [file] + (concat (copy-paste-shape :copy-simple-1 file) + (copy-paste-shape :copy-simple-1 file) + (copy-paste-shape :group-3 file) + (copy-paste-shape :group-3 file))) + +(defn- duplicate-copy-nested-and-group-out-of-the-main + [file & {:keys [target-page-label]}] + (let [page (cthf/current-page file) + frame-1-instance-ids (->> (vals (:objects page)) + (filter #(and + (or + (= (:name %) "Frame1") + (= (:name %) "Group1")) + (not (:component-root %)))) + (map :id))] + (concat + (apply concat (mapv #(copy-paste-shape % file :target-page-label target-page-label :target-container-id uuid/zero) frame-1-instance-ids)) + (apply concat (mapv #(copy-paste-shape % file :target-page-label target-page-label :target-container-id uuid/zero) frame-1-instance-ids))))) + +(t/deftest main-and-first-level-copy-1 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + ;; ==== Action + + + ;; For each main and first level copy: + ;; - Duplicate it two times with copy-paste. + events + (concat + (duplicate-each-main-and-first-level-copy file) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + (t/is (= (count-shapes file' "rect-simple-1" "#111111") 18))))))))) + +(t/deftest main-and-first-level-copy-2 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + ;; ==== Action + + + ;; For each main and first level copy: + ;; - Duplicate it two times with copy-paste. + events + (concat + (duplicate-each-main-and-first-level-copy file) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}) + ;; - Change color of the nearest main and check propagation to duplicated. + (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + (t/is (= (count-shapes file' "rect-simple-1" "#222222") 15))))))))) + +(t/deftest main-and-first-level-copy-3 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + ;; ==== Action + + + ;; For each main and first level copy: + ;; - Duplicate it two times with copy-paste. + events + (concat + (duplicate-each-main-and-first-level-copy file) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}) + ;; - Change color of the nearest main and check propagation to duplicated. + (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}) + (set-color-bottom-shape :frame-composed-2 file {:color "#333333"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + (t/is (= (count-shapes file' "rect-simple-1" "#333333") 12))))))))) + + +(t/deftest main-and-first-level-copy-4 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + ;; ==== Action + + + ;; For each main and first level copy: + ;; - Duplicate it two times with copy-paste. + events + (concat + (duplicate-each-main-and-first-level-copy file) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}) + ;; - Change color of the nearest main and check propagation to duplicated. + (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}) + (set-color-bottom-shape :frame-composed-2 file {:color "#333333"}) + (set-color-bottom-shape :frame-composed-3 file {:color "#444444"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + (t/is (= (count-shapes file' "rect-simple-1" "#444444") 6))))))))) + +(t/deftest copy-nested-in-main-1 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + + ;; ==== Action + ;; For each copy of Simple1 nested in a main, and the group inside Composed3 main: + ;; - Duplicate it two times, keeping the duplicated inside the same main. + events + (concat + (duplicate-simple-nested-in-main-and-group file) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + ;; Check propagation to all copies. + (t/is (= (count-shapes file' "rect-simple-1" "#111111") 28))))))))) + +(t/deftest copy-nested-in-main-2 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + + ;; ==== Action + ;; For each copy of Simple1 nested in a main, and the group inside Composed3 main: + ;; - Duplicate it two times, keeping the duplicated inside the same main. + events + (concat + (duplicate-simple-nested-in-main-and-group file) + ;; - Change color of the nearest main + (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + ;; Check propagation to duplicated. + (t/is (= (count-shapes file' "rect-simple-1" "#222222") 9))))))))) + +(t/deftest copy-nested-in-main-3 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + + ;; ==== Action + ;; For each copy of Simple1 nested in a main, and the group inside Composed3 main: + ;; - Duplicate it two times, keeping the duplicated inside the same main. + events + (concat + (duplicate-simple-nested-in-main-and-group file) + ;; - Change color of the copy you duplicated from. + (set-color-bottom-shape :group-3 file {:color "#333333"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + ;; Check that it's NOT PROPAGATED. + (t/is (= (count-shapes file' "rect-simple-1" "#333333") 2))))))))) + +(t/deftest copy-nested-1 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + + ;; ==== Action + ;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3 + ;; main and copy: + ;; - Duplicate it two times, moving the duplicates out of the main. + events + (concat + (duplicate-copy-nested-and-group-out-of-the-main file) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + ;; Check propagation to all copies. + (t/is (= (count-shapes file' "rect-simple-1" "#111111") 20))))))))) + + +(t/deftest copy-nested-2 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + + ;; ==== Action + ;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3 + ;; main and copy: + ;; - Duplicate it two times, moving the duplicates out of the main. + events + (concat + (duplicate-copy-nested-and-group-out-of-the-main file) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}) + ;; - Change color of the previous main + (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}) + (set-color-bottom-shape :group-3 file {:color "#333333"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state)] + ;; Check that it's NOT PROPAGATED. + (t/is (= (count-shapes file' "rect-simple-1" "#111111") 11)) + (t/is (= (count-shapes file' "rect-simple-1" "#222222") 7)) + (t/is (= (count-shapes file' "rect-simple-1" "#333333") 2))))))))) + + +(t/deftest copy-nested-3 + (t/async + done + (with-redefs [uuid/next cthi/next-uuid] + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) + + ;; ==== Action + ;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3 + ;; main and copy: + ;; - Duplicate it two times, moving the duplicates to another page + events + (concat + (duplicate-copy-nested-and-group-out-of-the-main file :target-page-label :page-2) + ;; - Change color of Simple1 + (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}) + ;; - Change color of the previous main + (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}) + (set-color-bottom-shape :group-3 file {:color "#333333"}))] + + (ths/run-store + store done events + (fn [new-state] + (let [file' (-> (ths/get-file-from-store new-state) + (cthf/switch-to-page :page-2))] + ;; Check that it's NOT PROPAGATED. + (t/is (= (count-shapes file' "rect-simple-1" "#111111") 10)) + (t/is (= (count-shapes file' "rect-simple-1" "#222222") 4)) + (t/is (= (count-shapes file' "rect-simple-1" "#333333") 0))))))))) \ No newline at end of file