diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 7d693c370..865ed2fa7 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -720,6 +720,7 @@ (map lookupf) (map mk-change)) updated-shapes)))) + (apply-changes-local))))) (defn update-component diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 5566ab449..ccc2f5d34 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -150,8 +150,6 @@ [new-component-shape new-component-shapes nil nil])))) - - (defn generate-duplicate-component "Create a new component copied from the one with the given id." [changes library component-id components-v2] diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index a76189f71..c7a301ca4 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -61,6 +61,10 @@ (update container :objects update-objects parent-id))) +(defn parent-of? + [parent child] + (= (:id parent) (:parent-id child))) + (defn get-shape "Get a shape identified by id" [container id] diff --git a/common/test/common_tests/helpers/components.cljc b/common/test/common_tests/helpers/components.cljc index a8657c325..81d331660 100644 --- a/common/test/common_tests/helpers/components.cljc +++ b/common/test/common_tests/helpers/components.cljc @@ -33,8 +33,9 @@ (let [[_new-root _new-shapes updated-shapes] (ctn/convert-shape-in-component root (:objects page) (:id file)) - updated-root (first updated-shapes)] ; Can't use new-root because it has a new id + updated-root (first updated-shapes) ; Can't use new-root because it has a new id + [path name] (cfh/parse-path-name (:name updated-root))] (thi/set-id! label (:component-id updated-root)) (ctf/update-file-data @@ -49,14 +50,15 @@ updated-shapes) (ctkl/add-component $ (assoc params :id (:component-id updated-root) - :name (:name updated-root) + :name name + :path path :main-instance-id (:id updated-root) :main-instance-page (:id page) :shapes updated-shapes)))))))) (defn get-component - [file label] - (ctkl/get-component (:data file) (thi/id label))) + [file label & {:keys [include-deleted?] :or {include-deleted? false}}] + (ctkl/get-component (:data file) (thi/id label) include-deleted?)) (defn get-component-by-id [file id] diff --git a/common/test/common_tests/logic/comp_creation_test.cljc b/common/test/common_tests/logic/comp_creation_test.cljc index bed320a0a..6d3280533 100644 --- a/common/test/common_tests/logic/comp_creation_test.cljc +++ b/common/test/common_tests/logic/comp_creation_test.cljc @@ -6,20 +6,62 @@ (ns common-tests.logic.comp-creation-test (:require + [app.common.data :as d] [app.common.files.changes-builder :as pcb] + [app.common.files.shapes-helpers :as cfsh] + [app.common.geom.point :as gpt] [app.common.logic.libraries :as cll] + [app.common.logic.shapes :as cls] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.shape-tree :as ctst] [clojure.test :as t] [common-tests.helpers.components :as thc] + [common-tests.helpers.compositions :as tho] [common-tests.helpers.files :as thf] [common-tests.helpers.ids-map :as thi] [common-tests.helpers.shapes :as ths])) (t/use-fixtures :each thi/test-fixture) +(t/deftest test-add-component-from-single-frame + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (ths/add-sample-shape :frame1 :type :frame)) + + page (thf/current-page file) + frame1 (ths/get-shape file :frame1) + + ;; ==== Action + [_ component-id changes] + (cll/generate-add-component (pcb/empty-changes) + [frame1] + (:objects page) + (:id page) + (:id file) + true + nil + nil) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component (thc/get-component-by-id file' component-id) + root (ths/get-shape-by-id file' (:main-instance-id component)) + frame1' (ths/get-shape file' :frame1)] + + ;; ==== Check + (t/is (some? component)) + (t/is (some? root)) + (t/is (some? frame1')) + (t/is (= (:id root) (:id frame1'))) + (t/is (ctk/main-instance? root)) + (t/is (ctk/main-instance-of? (:id root) (:id page) component)))) + (t/deftest test-add-component-from-single-shape (let [;; ==== Setup file (-> (thf/sample-file :file1) - (ths/add-sample-shape :shape1 :type :frame)) + (ths/add-sample-shape :shape1 :type :rect)) page (thf/current-page file) shape1 (ths/get-shape file :shape1) @@ -33,15 +75,536 @@ (:id file) true nil + cfsh/prepare-create-artboard-from-selection) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component (thc/get-component-by-id file' component-id) + root (ths/get-shape-by-id file' (:main-instance-id component)) + shape1' (ths/get-shape file' :shape1)] + + ;; ==== Check + (t/is (some? component)) + (t/is (some? root)) + (t/is (some? shape1')) + (t/is (ctst/parent-of? root shape1')) + (t/is (= (:type root) :frame)) + (t/is (ctk/main-instance? root)) + (t/is (ctk/main-instance-of? (:id root) (:id page) component)))) + +(t/deftest test-add-component-from-several-shapes + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (ths/add-sample-shape :shape1 :type :rect) + (ths/add-sample-shape :shape2 :type :rect)) + + page (thf/current-page file) + shape1 (ths/get-shape file :shape1) + shape2 (ths/get-shape file :shape2) + + ;; ==== Action + [_ component-id changes] + (cll/generate-add-component (pcb/empty-changes) + [shape1 shape2] + (:objects page) + (:id page) + (:id file) + true + nil + cfsh/prepare-create-artboard-from-selection) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component (thc/get-component-by-id file' component-id) + root (ths/get-shape-by-id file' (:main-instance-id component)) + shape1' (ths/get-shape file' :shape1) + shape2' (ths/get-shape file' :shape2)] + + ;; ==== Check + (t/is (some? component)) + (t/is (some? root)) + (t/is (some? shape1')) + (t/is (some? shape2')) + (t/is (ctst/parent-of? root shape1')) + (t/is (ctst/parent-of? root shape2')) + (t/is (= (:type root) :frame)) + (t/is (ctk/main-instance? root)) + (t/is (ctk/main-instance-of? (:id root) (:id page) component)))) + +(t/deftest test-add-component-from-several-frames + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (ths/add-sample-shape :frame1 :type :frame) + (ths/add-sample-shape :frame2 :type :frame)) + + page (thf/current-page file) + frame1 (ths/get-shape file :frame1) + frame2 (ths/get-shape file :frame2) + + ;; ==== Action + [_ component-id changes] + (cll/generate-add-component (pcb/empty-changes) + [frame1 frame2] + (:objects page) + (:id page) + (:id file) + true + nil + cfsh/prepare-create-artboard-from-selection) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component (thc/get-component-by-id file' component-id) + root (ths/get-shape-by-id file' (:main-instance-id component)) + frame1' (ths/get-shape file' :frame1) + frame2' (ths/get-shape file' :frame2)] + + ;; ==== Check + (t/is (some? component)) + (t/is (some? root)) + (t/is (some? frame1')) + (t/is (some? frame2')) + (t/is (ctst/parent-of? root frame1')) + (t/is (ctst/parent-of? root frame2')) + (t/is (= (:type root) :frame)) + (t/is (ctk/main-instance? root)) + (t/is (ctk/main-instance-of? (:id root) (:id page) component)))) + +(t/deftest test-add-component-from-frame-with-children + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (ths/add-sample-shape :frame1 :type :frame) + (ths/add-sample-shape :shape1 :type :rect :parent-label :frame1) + (ths/add-sample-shape :shape2 :type :rect :parent-label :frame1)) + + page (thf/current-page file) + frame1 (ths/get-shape file :frame1) + + ;; ==== Action + [_ component-id changes] + (cll/generate-add-component (pcb/empty-changes) + [frame1] + (:objects page) + (:id page) + (:id file) + true + nil nil) file' (thf/apply-changes file changes) ;; ==== Get component (thc/get-component-by-id file' component-id) - root (ths/get-shape-by-id file' (:main-instance-id component))] + root (ths/get-shape-by-id file' (:main-instance-id component)) + frame1' (ths/get-shape file' :frame1) + shape1' (ths/get-shape file' :shape1) + shape2' (ths/get-shape file' :shape2)] ;; ==== Check (t/is (some? component)) (t/is (some? root)) - (t/is (= (:component-id root) (:id component))))) + (t/is (some? frame1')) + (t/is (= (:id root) (:id frame1'))) + (t/is (ctst/parent-of? frame1' shape1')) + (t/is (ctst/parent-of? frame1' shape2')) + (t/is (ctk/main-instance? root)) + (t/is (ctk/main-instance-of? (:id root) (:id page) component)))) + +(t/deftest test-add-component-from-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) + copy1-root (ths/get-shape file :copy1-root) + + ;; ==== Action + [_ component2-id changes] + (cll/generate-add-component (pcb/empty-changes) + [copy1-root] + (:objects page) + (:id page) + (:id file) + true + nil + cfsh/prepare-create-artboard-from-selection) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component2' (thc/get-component-by-id file' component2-id) + root2' (ths/get-shape-by-id file' (:main-instance-id component2')) + copy1-root' (ths/get-shape file' :copy1-root)] + + ;; ==== Check + (t/is (some? component2')) + (t/is (some? root2')) + (t/is (some? copy1-root')) + (t/is (ctst/parent-of? root2' copy1-root')) + (t/is (ctk/main-instance? root2')) + (t/is (ctk/main-instance-of? (:id root2') (:id page) component2')))) + +(t/deftest test-rename-component + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component :component1 + :main1-root + :main1-child + :name "Test component before")) + + component (thc/get-component file :component1) + + ;; ==== Action + changes (cll/generate-rename-component (pcb/empty-changes) + (:id component) + "Test component after" + (:data file) + true) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component' (thc/get-component file' :component1)] + + ;; ==== Check + (t/is (= (:name component') "Test component after")))) + +(t/deftest test-duplicate-component + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component :component1 + :main1-root + :main1-child)) + + component (thc/get-component file :component1) + + ;; ==== Action + changes (cll/generate-duplicate-component (pcb/empty-changes) + file + (:id component) + true) + + file' (thf/apply-changes file changes) + + ;; ==== Get + components' (ctkl/components-seq (:data file')) + component1' (d/seek #(= (:id %) (thi/id :component1)) components') + component2' (d/seek #(not= (:id %) (thi/id :component1)) components') + root1' (ths/get-shape-by-id file' (:main-instance-id component1')) + root2' (ths/get-shape-by-id file' (:main-instance-id component2')) + child1' (ths/get-shape-by-id file' (first (:shapes root1'))) + child2' (ths/get-shape-by-id file' (first (:shapes root2')))] + + ;; ==== Check + (t/is (= 2 (count components'))) + (t/is (some? component1')) + (t/is (some? component2')) + (t/is (some? root1')) + (t/is (some? root2')) + (t/is (= (thi/id :main1-root) (:id root1'))) + (t/is (not= (thi/id :main1-root) (:id root2'))) + (t/is (some? child1')) + (t/is (some? child2')) + (t/is (= (thi/id :main1-child) (:id child1'))) + (t/is (not= (thi/id :main1-child) (:id child2'))))) + +(t/deftest test-delete-component + (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) + root (ths/get-shape file :main1-root) + + ;; ==== Action + [_ changes] + (cls/generate-delete-shapes (pcb/empty-changes) + file + page + (:objects page) + #{(:id root)} + {:components-v2 true}) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component1' (thc/get-component file' :component1 :include-deleted? true) + copy1-root' (ths/get-shape file' :copy1-root) + + main1-root' (ths/get-shape file' :main1-root) + main1-child' (ths/get-shape file' :main1-child) + + saved-objects (:objects component1') + saved-main1-root' (get saved-objects (thi/id :main1-root)) + saved-main1-child' (get saved-objects (thi/id :main1-child))] + + ;; ==== Check + (t/is (true? (:deleted component1'))) + (t/is (some? copy1-root')) + (t/is (nil? main1-root')) + (t/is (nil? main1-child')) + (t/is (some? saved-main1-root')) + (t/is (some? saved-main1-child')))) + +(t/deftest test-restore-component + (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) + root (ths/get-shape file :main1-root) + + ;; ==== Action + [_ changes] + (cls/generate-delete-shapes (pcb/empty-changes) + file + page + (:objects page) + #{(:id root)} + {:components-v2 true}) + + file-deleted (thf/apply-changes file changes) + page-deleted (thf/current-page file-deleted) + + changes (cll/generate-restore-component (pcb/empty-changes) + (:data file-deleted) + (thi/id :component1) + (:id file-deleted) + page-deleted + (:objects page-deleted)) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component1' (thc/get-component file' :component1 :include-deleted? false) + copy1-root' (ths/get-shape file' :copy1-root) + + main1-root' (ths/get-shape file' :main1-root) + main1-child' (ths/get-shape file' :main1-child) + + saved-objects' (:objects component1')] + + ;; ==== Check + (t/is (nil? (:deleted component1'))) + (t/is (some? copy1-root')) + (t/is (some? main1-root')) + (t/is (some? main1-child')) + (t/is (ctk/main-instance? main1-root')) + (t/is (ctk/main-instance-of? (:id main1-root') (:id page) component1')) + (t/is (nil? saved-objects')))) + +(t/deftest test-instantiate-component + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component :component1 + :main1-root + :main1-child)) + + page (thf/current-page file) + component (thc/get-component file :component1) + + ;; ==== Action + [new-shape changes] + (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) ;; This may not be moved to generate + (pcb/with-objects (:objects page))) ;; because in some cases the objects + (:objects page) ;; not the same as those on the page + (:id file) + (:id component) + (gpt/point 1000 1000) + page + {(:id file) file}) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component' (thc/get-component file' :component1) + main1-root' (ths/get-shape file' :main1-root) + main1-child' (ths/get-shape file' :main1-child) + copy1-root' (ths/get-shape-by-id file' (:id new-shape)) + copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))] + + ;; ==== Check + (t/is (some? main1-root')) + (t/is (some? main1-child')) + (t/is (some? copy1-root')) + (t/is (some? copy1-child')) + (t/is (ctk/instance-root? copy1-root')) + (t/is (ctk/instance-of? copy1-root' (:id file') (:id component'))) + (t/is (ctk/is-main-of? main1-root' copy1-root' true)) + (t/is (ctk/is-main-of? main1-child' copy1-child' true)) + (t/is (ctst/parent-of? copy1-root' copy1-child')))) + +(t/deftest test-instantiate-component-from-lib + (let [;; ==== Setup + library (-> (thf/sample-file :library1) + (tho/add-simple-component :component1 + :main1-root + :main1-child)) + + file (thf/sample-file :file1) + + page (thf/current-page file) + component (thc/get-component library :component1) + + ;; ==== Action + [new-shape changes] + (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) + (pcb/with-objects (:objects page))) + (:objects page) + (:id library) + (:id component) + (gpt/point 1000 1000) + page + {(:id file) file + (:id library) library}) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component' (thc/get-component library :component1) + main1-root' (ths/get-shape library :main1-root) + main1-child' (ths/get-shape library :main1-child) + copy1-root' (ths/get-shape-by-id file' (:id new-shape)) + copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))] + + ;; ==== Check + (t/is (some? main1-root')) + (t/is (some? main1-child')) + (t/is (some? copy1-root')) + (t/is (some? copy1-child')) + (t/is (ctk/instance-root? copy1-root')) + (t/is (ctk/instance-of? copy1-root' (:id library) (:id component'))) + (t/is (ctk/is-main-of? main1-root' copy1-root' true)) + (t/is (ctk/is-main-of? main1-child' copy1-child' true)) + (t/is (ctst/parent-of? copy1-root' copy1-child')))) + +(t/deftest test-instantiate-nested-component + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-nested-component :component1 + :main1-root + :main1-child + :component2 + :main2-root + :main2-nested-head)) + + page (thf/current-page file) + component (thc/get-component file :component1) + + ;; ==== Action + [new-shape changes] + (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) + (pcb/with-objects (:objects page))) + (:objects page) + (:id file) + (:id component) + (gpt/point 1000 1000) + page + {(:id file) file}) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component' (thc/get-component file' :component1) + main1-root' (ths/get-shape file' :main1-root) + main1-child' (ths/get-shape file' :main1-child) + copy1-root' (ths/get-shape-by-id file' (:id new-shape)) + copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))] + + ;; ==== Check + (t/is (some? main1-root')) + (t/is (some? main1-child')) + (t/is (some? copy1-root')) + (t/is (some? copy1-child')) + (t/is (ctk/instance-root? copy1-root')) + (t/is (ctk/instance-of? copy1-root' (:id file') (:id component'))) + (t/is (ctk/is-main-of? main1-root' copy1-root' true)) + (t/is (ctk/is-main-of? main1-child' copy1-child' true)) + (t/is (ctst/parent-of? copy1-root' copy1-child')))) + +(t/deftest test-instantiate-nested-component-from-lib + (let [;; ==== Setup + library (-> (thf/sample-file :file1) + (tho/add-nested-component :component1 + :main1-root + :main1-child + :component2 + :main2-root + :main2-nested-head)) + + file (thf/sample-file :file1) + + page (thf/current-page file) + component (thc/get-component library :component1) + + ;; ==== Action + [new-shape changes] + (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) + (pcb/with-objects (:objects page))) + (:objects page) + (:id library) + (:id component) + (gpt/point 1000 1000) + page + {(:id file) file + (:id library) library}) + + file' (thf/apply-changes file changes) + + ;; ==== Get + component' (thc/get-component library :component1) + main1-root' (ths/get-shape library :main1-root) + main1-child' (ths/get-shape library :main1-child) + copy1-root' (ths/get-shape-by-id file' (:id new-shape)) + copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))] + + ;; ==== Check + (t/is (some? main1-root')) + (t/is (some? main1-child')) + (t/is (some? copy1-root')) + (t/is (some? copy1-child')) + (t/is (ctk/instance-root? copy1-root')) + (t/is (ctk/instance-of? copy1-root' (:id library) (:id component'))) + (t/is (ctk/is-main-of? main1-root' copy1-root' true)) + (t/is (ctk/is-main-of? main1-child' copy1-child' true)) + (t/is (ctst/parent-of? copy1-root' copy1-child')))) + +(t/deftest test-detach-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) + copy1-root (ths/get-shape file :copy1-root) + + ;; ==== Action + changes (cll/generate-detach-component (pcb/empty-changes) + (:id copy1-root) + (:data file) + (:id page) + {(:id file) file}) + + file' (thf/apply-changes file changes) + + ;; ==== Get + copy1-root' (ths/get-shape file' :copy1-root)] + + ;; ==== Check + (t/is (some? copy1-root')) + (t/is (not (ctk/instance-head? copy1-root'))) + (t/is (not (ctk/in-component-copy? copy1-root'))))) diff --git a/frontend/test/frontend_tests/state_components_test.cljs b/frontend/test/frontend_tests/state_components_test.cljs deleted file mode 100644 index 3a8751019..000000000 --- a/frontend/test/frontend_tests/state_components_test.cljs +++ /dev/null @@ -1,860 +0,0 @@ -(ns frontend-tests.state-components-test - (:require - [app.common.geom.point :as gpt] - [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [app.main.data.workspace :as dw] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.shapes :as dwsh] - [app.main.data.workspace.state-helpers :as wsh] - [cljs.test :as t :include-macros true] - [frontend-tests.helpers.events :as the] - [frontend-tests.helpers.libraries :as thl] - [frontend-tests.helpers.pages :as thp] - [linked.core :as lks] - [potok.v2.core :as ptk])) - -(t/use-fixtures :each - {:before thp/reset-idmap!}) - -(t/deftest test-add-component-from-single-shape - (t/testing "test-add-component-from-single-shape" - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"})) - - 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 - ;; Rect 1 - ;; - (let [shape1 (thp/get-shape new-state :shape1) - - [[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (:parent-id shape1)) - - file (wsh/get-local-file new-state)] - - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name group) "Rect 1")) - (t/is (= (:name component) "Rect 1")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-group) "Rect 1")) - - (thl/is-from-file group file))))] - - (ptk/emit! - store - (dw/select-shape (thp/id :shape1)) - (dwl/add-component) - :the/end))))) - -(t/deftest test-add-component-from-several-shapes - (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"})) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; [Page] - ;; Root Frame - ;; Component 1 - ;; Rect 1 - ;; Rect-2 - ;; - ;; [Component 1] - ;; page1 / Component 1 - ;; - (let [shape1 (thp/get-shape new-state :shape1) - - [[group shape1 shape2] - [c-group c-shape1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - (:parent-id shape1)) - - file (wsh/get-local-file new-state)] - - (t/is (= (:name group) "Component 1")) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name shape2) "Rect-2")) - (t/is (= (:name component) "Component 1")) - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect-2")) - - (thl/is-from-file group file))))] - - (ptk/emit! - store - (dw/select-shapes (lks/set (thp/id :shape1) - (thp/id :shape2))) - (dwl/add-component) - :the/end)))) - -(t/deftest test-add-component-from-frame - (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/frame-shapes :frame1 - [(thp/id :shape1) - (thp/id :shape2)])) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Group - ;; Rect 1 - ;; Rect-2 - ;; - ;; [Group] - ;; page1 / Group - ;; - (let [[[group shape1 shape2] - [c-group c-shape1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - (thp/id :frame1)) - - file (wsh/get-local-file new-state)] - - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name shape2) "Rect-2")) - (t/is (= (:name group) "Board")) - (t/is (= (:name component) "Board")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect-2")) - (t/is (= (:name c-group) "Board")) - - (thl/is-from-file group file))))] - - (ptk/emit! - store - (dw/select-shape (thp/id :frame1)) - (dwl/add-component) - :the/end)))) - -(t/deftest test-add-component-from-component-instance - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 (thp/id :component1))) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page: Page] - ;; Root Frame - ;; Rect 1 # - ;; Rect 1 - ;; Rect 1 # - ;; Rect 1* @--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; - (let [[[instance1 shape1] - [c-instance1 c-shape1] - component1] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1) - true) - - [[instance2 instance1' shape1'] - [c-instance2 c-instance1' c-shape1'] - component2] - (thl/resolve-instance-and-main - new-state - (:parent-id instance1))] - - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:name component1) "Rect 1")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-instance1) "Rect 1")) - - (t/is (= (:name shape1') "Rect 1")) - (t/is (= (:name instance1') "Rect 1")) - (t/is (= (:name instance2) "Rect 1")) - (t/is (= (:name component2) "Rect 1")) - (t/is (= (:name c-shape1') "Rect 1")) - (t/is (= (:name c-instance1') "Rect 1")) - (t/is (= (:name c-instance2) "Rect 1")))))] - - (ptk/emit! - store - (dw/select-shape (thp/id :instance1)) - (dwl/add-component) - :the/end)))) - - -(t/deftest test-add-component-from-component-main - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)])) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - (let [file (wsh/get-local-file new-state) - components (ctkl/components file) - page (thp/current-page new-state) - shape1 (thp/get-shape new-state :shape1) - parent1 (ctn/get-shape page (:parent-id shape1)) - main1 (thp/get-shape state :main1) - [[instance1 shape1] - [c-instance1 c-shape1] - component1] - (thl/resolve-instance-and-main - new-state - (:id main1))] - ;; Creating a component from a main doesn't generate a new component - (t/is (= (count components) 1)) - - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:name component1) "Rect 1")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-instance1) "Rect 1")))))] - - (ptk/emit! - store - (dw/select-shape (thp/id :main1)) - (dwl/add-component) - :the/end)))) - -(t/deftest test-rename-component - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)])) - - main1 (thp/get-shape state :main1) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; - ;; [Renamed component] - ;; page1 / Rect 1 - ;; - (let [libs (wsh/get-libraries new-state) - component (ctf/get-component libs - (:component-file main1) - (:component-id main1))] - (t/is (= (:name component) - "Renamed component")))))] - - (ptk/emit! - store - (dwl/rename-component (:component-id main1) "Renamed component") - :the/end)))) - -(t/deftest test-duplicate-component - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)])) - - main1 (thp/get-shape state :main1) - component-id (:component-id main1) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - (let [new-component-id (->> (get-in new-state - [:workspace-data - :components]) - (keys) - (filter #(not= % component-id)) - (first)) - - [[_instance1 _shape1] - [_c-instance1 _c-shape1] - _component1] - (thl/resolve-instance-and-main - new-state - (:id main1)) - - [[_c-component2 _c-shape2] - component2] - (thl/resolve-component - new-state - (:current-file-id new-state) - new-component-id)] - - (t/is (= (:name component2) "Rect 1")))))] - - (ptk/emit! - store - (dwl/duplicate-component thp/current-file-id component-id) - :the/end)))) - -(t/deftest test-delete-component - (t/async done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :main1 :component1 [(thp/id :shape1)]) - (thp/instantiate-component :instance1 (thp/id :component1))) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;;; - ;; [Page] - ;; Root Frame - ;; Rect 1 #--> ? - ;; Rect 1 ---> ? - ;;; - (let [[main1 shape1] - (thl/resolve-noninstance - new-state - (thp/id :main1)) - - [[instance1 shape2] [c-instance1 c-shape2] component1] - (thl/resolve-instance-and-main-allow-dangling - new-state - (thp/id :instance1)) - - file (wsh/get-local-file new-state) - component2 (ctkl/get-component file (thp/id :component1)) - component3 (ctkl/get-deleted-component file (thp/id :component1)) - - saved-objects (:objects component3) - saved-main1 (get saved-objects (:shape-ref instance1)) - saved-shape2 (get saved-objects (:shape-ref shape2))] - - (t/is (nil? main1)) - (t/is (nil? shape1)) - - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (not= (:shape-ref instance1) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) - (t/is (nil? c-instance1)) - (t/is (nil? c-shape2)) - (t/is (nil? component1)) - - (t/is (nil? component2)) - - (t/is (= (:name component3) "Rect 1")) - (t/is (= (:deleted component3) true)) - (t/is (some? (:objects component3))) - - (t/is (= (:name saved-main1) "Rect 1")) - (t/is (= (:name saved-shape2) "Rect 1")))))] - (ptk/emit! store - (dwl/delete-component {:id (thp/id :component1)}) - :the/end)))) - -(t/deftest test-restore-component - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 - (thp/id :component1))) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; Rect 1 - ;; Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - (let [[[instance1 shape2] [c-instance1 c-shape2] component1] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1)) - - file (wsh/get-local-file new-state) - component2 (ctkl/get-component file (thp/id :component1)) - - saved-objects (:objects component2)] - - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect 1")) - - (t/is (some? component1)) - (t/is (some? component2)) - (t/is (nil? saved-objects)))))] - - (ptk/emit! - store - (dwl/delete-component {:id (thp/id :component1)}) - (dwl/restore-component thp/current-file-id (thp/id :component1)) - :the/end)))) - -(t/deftest test-instantiate-component - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)])) - - file (wsh/get-local-file state) - component-id (thp/id :component1) - main1 (thp/get-shape state :main1) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - (let [new-instance-id (-> new-state - wsh/lookup-selected - first) - - [[instance1 shape2] - [c-instance1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - new-instance-id)] - - (t/is (not= (:id main1) (:id instance1))) - (t/is (= (:id component) component-id)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:component-file instance1) - thp/current-file-id)))))] - - (ptk/emit! - store - (dwl/instantiate-component (:id file) - component-id - (gpt/point 100 100)) - :the/end)))) - -(t/deftest test-instantiate-component-from-lib - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/move-to-library :lib1 "Library 1") - (thp/sample-page)) - - library-id (thp/id :lib1) - component-id (thp/id :component1) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; - (let [new-instance-id (-> new-state - wsh/lookup-selected - first) - - [[instance1 shape2] - [c-instance1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - new-instance-id)] - - (t/is (= (:id component) component-id)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:component-file instance1) library-id)))))] - - (ptk/emit! - store - (dwl/instantiate-component library-id - component-id - (gpt/point 100 100)) - :the/end)))) - -(t/deftest test-detach-component - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 - (thp/id :component1))) - - instance1 (thp/get-shape state :instance1) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Rect 1 - ;; Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - (let [[instance2 shape1] - (thl/resolve-noninstance - new-state - (:id instance1))] - - (t/is (some? instance2)) - (t/is (some? shape1)))))] - - (ptk/emit! - store - (dwl/detach-component (:id instance1)) - :the/end)))) - - - -(t/deftest test-add-nested-component-instance - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 (thp/id :component1))) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Board - ;; Rect 1 - ;; Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - ;; [Board] - ;; page1 / Board - ;; - (let [instance1 (thp/get-shape new-state :instance1) - - [[group shape1 shape2] - [c-group c-shape1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - (:parent-id instance1))] - - (t/is (= (:name group) "Board")) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name component) "Board")) - (t/is (= (:name c-group) "Board")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect 1")))))] - - (ptk/emit! - store - (dw/select-shape (thp/id :instance1)) - (dwsh/create-artboard-from-selection) - (dwl/add-component) - :the/end)))) - -(t/deftest test-add-nested-component-main - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"})) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Board - ;; Rect 1 - ;; Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - ;; - (let [file (wsh/get-local-file new-state) - components (ctkl/components file) - page (thp/current-page new-state) - - shape1 (thp/get-shape new-state :shape1) - parent1 (ctn/get-shape page (:parent-id shape1)) - - [[group shape1] - [c-group c-shape1] - component] - (thl/resolve-instance-and-main - new-state - (:parent-id shape1))] - - ;; Creating a component from something containing a main doesn't generate a new component - (t/is (= (count components) 1)) - - (t/is (= (:name group) "Rect 1")) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name component) "Rect 1")) - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:name c-shape1) "Rect 1")))))] - - (ptk/emit! - store - (dw/select-shape (thp/id :shape1)) - (dwl/add-component) - (dwsh/create-artboard-from-selection) - (dwl/add-component) - :the/end)))) - -(t/deftest test-instantiate-nested-component - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/make-component :main2 :component-2 - [(thp/id :main1)])) - - file (wsh/get-local-file state) - main1 (thp/get-shape state :main1) - main2 (thp/get-shape state :main2) - component-id (:component-id main2) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Rect 1 - ;; Rect 1 #--> Rect 1 - ;; Rect 1 @--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - (let [new-instance-id (-> new-state - wsh/lookup-selected - first) - - [[instance1 shape1 shape2] - [c-instance1 c-shape1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - new-instance-id)] - - ;; TODO: get and check the instance inside component [Rect-2] - - (t/is (not= (:id main1) (:id instance1))) - (t/is (= (:id component) component-id)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect 1")))))] - - (ptk/emit! - store - (dwl/instantiate-component (:id file) - (:component-id main2) - (gpt/point 100 100)) - :the/end)))) - -(t/deftest test-instantiate-nested-component-from-lib - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/move-to-library :lib1 "Library 1") - (thp/sample-page) - (thp/instantiate-component :instance1 - (thp/id :component1) - (thp/id :lib1))) - - file (wsh/get-local-file state) - library-id (thp/id :lib1) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Group - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; - ;; [Group] - ;; page1 / Group - ;; - (let [instance1 (thp/get-shape new-state :instance1) - - [[group1 shape1 shape2] [c-group1 c-shape1 c-shape2] _component] - (thl/resolve-instance-and-main - new-state - (:parent-id instance1))] - - (t/is (= (:name group1) "Board")) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name c-group1) "Board")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:component-file group1) thp/current-file-id)) - (t/is (= (:component-file shape1) library-id)) - (t/is (= (:component-file shape2) nil)) - (t/is (= (:component-file c-group1) (:id file))) - (t/is (= (:component-file c-shape1) library-id)) - (t/is (= (:component-file c-shape2) nil)))))] - - (ptk/emit! - store - (dw/select-shape (thp/id :instance1)) - (dwsh/create-artboard-from-selection) - (dwl/add-component) - :the/end))))