From ca56e08459eb99c3033d86e5e56ad9fc5a4c5018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 16 Mar 2022 16:07:38 +0100 Subject: [PATCH] :tada: Add more test cases, and some fixes --- .../src/app/common/pages/changes_builder.cljc | 92 +- common/src/app/common/pages/helpers.cljc | 1 - frontend/src/app/main/data/workspace.cljs | 5 +- .../app/main/data/workspace/libraries.cljs | 81 +- .../data/workspace/libraries_helpers.cljs | 83 +- .../main/data/workspace/state_helpers.cljs | 22 +- frontend/test/app/components_basic_test.cljs | 685 +++++++++------ frontend/test/app/components_sync_test.cljs | 817 ++++++++++++++++-- frontend/test/app/test_helpers/libraries.cljs | 38 +- frontend/test/app/test_helpers/pages.cljs | 46 +- 10 files changed, 1386 insertions(+), 484 deletions(-) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index cf023be1e..b0cf90e84 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -7,6 +7,7 @@ (ns app.common.pages.changes-builder (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.bool :as gshb] [app.common.pages :as cp] @@ -94,6 +95,11 @@ [changes] (assert (contains? (meta changes) ::library-data) "Call (with-library-data) before using this function")) +(defn- lookup-objects + [changes] + (let [data (::file-data (meta changes))] + (dm/get-in data [:pages-index uuid/zero :objects]))) + (defn- apply-changes-local [changes] (if-let [file-data (::file-data (meta changes))] @@ -221,7 +227,7 @@ ([changes parent-id shapes index] (assert-page-id changes) (assert-objects changes) - (let [objects (get-in (meta changes) [::file-data :pages-index uuid/zero :objects]) + (let [objects (lookup-objects changes) set-parent-change (cond-> {:type :mov-objects @@ -253,12 +259,13 @@ ([changes ids update-fn] (update-shapes changes ids update-fn nil)) - ([changes ids update-fn {:keys [attrs ignore-geometry?] :or {attrs nil ignore-geometry? false}}] + ([changes ids update-fn {:keys [attrs ignore-geometry? ignore-touched] + :or {ignore-geometry? false ignore-touched false}}] (assert-container-id changes) (assert-objects changes) (let [page-id (::page-id (meta changes)) component-id (::component-id (meta changes)) - objects (get-in (meta changes) [::file-data :pages-index uuid/zero :objects]) + objects (lookup-objects changes) generate-operation (fn [operations attr old new ignore-geometry?] @@ -267,8 +274,11 @@ (if (= old-val new-val) operations (-> operations - (update :rops conj {:type :set :attr attr :val new-val :ignore-geometry ignore-geometry?}) - (update :uops conj {:type :set :attr attr :val old-val :ignore-touched true}))))) + (update :rops conj {:type :set :attr attr :val new-val + :ignore-geometry ignore-geometry? + :ignore-touched ignore-touched}) + (update :uops conj {:type :set :attr attr :val old-val + :ignore-touched true}))))) update-shape (fn [changes id] @@ -310,7 +320,7 @@ (assert-page-id changes) (assert-objects changes) (let [page-id (::page-id (meta changes)) - objects (get-in (meta changes) [::file-data :pages-index uuid/zero :objects]) + objects (lookup-objects changes) add-redo-change (fn [change-set id] @@ -358,7 +368,7 @@ (assert-page-id changes) (assert-objects changes) (let [page-id (::page-id (meta changes)) - objects (get-in (meta changes) [::file-data :pages-index uuid/zero :objects]) + objects (lookup-objects changes) xform (comp (mapcat #(cons % (cph/get-parent-ids objects %))) @@ -504,8 +514,28 @@ (assert-page-id changes) (assert-objects changes) (let [page-id (::page-id (meta changes)) - objects (-> changes meta ::file-data (get-in [:pages-index uuid/zero :objects]))] + objects (lookup-objects changes) + lookupf (d/getf objects) + mk-change (fn [shape] + {:type :mod-obj + :page-id page-id + :id (:id shape) + :operations [{:type :set + :attr :component-id + :val (:component-id shape)} + {:type :set + :attr :component-file + :val (:component-file shape)} + {:type :set + :attr :component-root? + :val (:component-root? shape)} + {:type :set + :attr :shape-ref + :val (:shape-ref shape)} + {:type :set + :attr :touched + :val (:touched shape)}]}) ] (-> changes (update :redo-changes (fn [redo-changes] @@ -515,52 +545,16 @@ :path path :name name :shapes new-shapes}) - (into (map (fn [updated-shape] - {:type :mod-obj - :page-id page-id - :id (:id updated-shape) - :operations [{:type :set - :attr :component-id - :val (:component-id updated-shape)} - {:type :set - :attr :component-file - :val (:component-file updated-shape)} - {:type :set - :attr :component-root? - :val (:component-root? updated-shape)} - {:type :set - :attr :shape-ref - :val (:shape-ref updated-shape)} - {:type :set - :attr :touched - :val (:touched updated-shape)}]}) - updated-shapes))))) + (into (map mk-change) updated-shapes)))) (update :undo-changes (fn [undo-changes] (-> undo-changes (conj {:type :del-component :id id}) - (into (map (fn [updated-shape] - (let [original-shape (get objects (:id updated-shape))] - {:type :mod-obj - :page-id page-id - :id (:id updated-shape) - :operations [{:type :set - :attr :component-id - :val (:component-id original-shape)} - {:type :set - :attr :component-file - :val (:component-file original-shape)} - {:type :set - :attr :component-root? - :val (:component-root? original-shape)} - {:type :set - :attr :shape-ref - :val (:shape-ref original-shape)} - {:type :set - :attr :touched - :val (:touched original-shape)}]})) - updated-shapes))))) + (into (comp (map :id) + (map lookupf) + (map mk-change)) + updated-shapes)))) (apply-changes-local)))) (defn update-component diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 19e6e792b..ce5dd18a4 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -307,7 +307,6 @@ (defn clean-loops "Clean a list of ids from circular references." [objects ids] - (let [parent-selected? (fn [id] (let [parents (get-parent-ids objects id)] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 8f5f823eb..a5c387a04 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -660,8 +660,9 @@ moved-shape (assoc shape :parent-id parent-id :frame-id frame-id)] - (assoc shape :constraints-h (gsh/default-constraints-h moved-shape) - :constraints-v (gsh/default-constraints-v moved-shape)))) + (assoc shape + :constraints-h (gsh/default-constraints-h moved-shape) + :constraints-v (gsh/default-constraints-v moved-shape)))) {:ignore-touched true}) ; Resize parent containers that need to diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 6b30243a7..9e275bbbf 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -250,7 +250,7 @@ objects (wsh/lookup-page-objects state page-id) shapes (dwg/shapes-for-grouping objects selected)] (when-not (empty? shapes) - (let [[group changes] + (let [[group _ changes] (dwlh/generate-add-component it shapes objects page-id file-id)] (when-not (empty? (:redo-changes changes)) (rx/of (dch/commit-changes changes) @@ -306,7 +306,7 @@ (ptk/reify ::duplicate-component ptk/WatchEvent (watch [it state _] - (let [libraries (dwlh/get-libraries state) + (let [libraries (wsh/get-libraries state) component (cph/get-component libraries id) all-components (-> state :workspace-data :components vals) unames (into #{} (map :name) all-components) @@ -349,57 +349,16 @@ (ptk/reify ::instantiate-component ptk/WatchEvent (watch [it state _] - (let [libraries (dwlh/get-libraries state) - component (cph/get-component libraries file-id component-id) - component-shape (cph/get-shape component component-id) - - orig-pos (gpt/point (:x component-shape) (:y component-shape)) - delta (gpt/subtract position orig-pos) - - page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - unames (volatile! (dwc/retrieve-used-names objects)) - - frame-id (cph/frame-id-by-position objects (gpt/add orig-pos delta)) - - update-new-shape - (fn [new-shape original-shape] - (let [new-name (dwc/generate-unique-name @unames (:name new-shape))] - - (when (nil? (:parent-id original-shape)) - (vswap! unames conj new-name)) - - (cond-> new-shape - true - (as-> $ - (geom/move $ delta) - (assoc $ :frame-id frame-id) - (assoc $ :parent-id - (or (:parent-id $) (:frame-id $))) - (dissoc $ :touched)) - - (nil? (:shape-ref original-shape)) - (assoc :shape-ref (:id original-shape)) - - (nil? (:parent-id original-shape)) - (assoc :component-id (:id original-shape) - :component-file file-id - :component-root? true - :name new-name) - - (some? (:parent-id original-shape)) - (dissoc :component-root?)))) - - [new-shape new-shapes _] - (cph/clone-object component-shape - nil - (get component :objects) - update-new-shape) - - changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) - (pcb/empty-changes it page-id) - new-shapes)] + (let [page (wsh/lookup-page state) + libraries (wsh/get-libraries state) + [new-shape changes] + (dwlh/generate-instantiate-component it + file-id + component-id + position + page + libraries)] (rx/of (dch/commit-changes changes) (dwc/select-shapes (d/ordered-set (:id new-shape)))))))) @@ -411,7 +370,7 @@ (ptk/reify ::detach-component ptk/WatchEvent (watch [it state _] - (let [file (dwlh/get-local-file state) + (let [file (wsh/get-local-file state) page-id (get state :current-page-id) container (cph/get-container file :page page-id) @@ -428,7 +387,7 @@ (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - file (dwlh/get-local-file state) + file (wsh/get-local-file state) container (cph/get-container file :page page-id) selected (->> state (wsh/lookup-selected) @@ -482,8 +441,8 @@ ptk/WatchEvent (watch [it state _] (log/info :msg "RESET-COMPONENT of shape" :id (str id)) - (let [file (dwlh/get-local-file state) - libraries (dwlh/get-libraries state) + (let [file (wsh/get-local-file state) + libraries (wsh/get-libraries state) page-id (:current-page-id state) container (cph/get-container file :page page-id) @@ -516,8 +475,8 @@ (log/info :msg "UPDATE-COMPONENT of shape" :id (str id)) (let [page-id (get state :current-page-id) - local-file (dwlh/get-local-file state) - libraries (dwlh/get-libraries state) + local-file (wsh/get-local-file state) + libraries (wsh/get-libraries state) container (cph/get-container local-file :page page-id) shape (cph/get-shape container id) @@ -528,7 +487,7 @@ (dwlh/generate-sync-shape-inverse libraries container id)) file-id (:component-file shape) - file (dwlh/get-file state file-id) + file (wsh/get-file state file-id) xf-filter (comp (filter :local-change?) @@ -608,7 +567,7 @@ (log/info :msg "SYNC-FILE" :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state)) - (let [file (dwlh/get-file state file-id) + (let [file (wsh/get-file state file-id) library-changes (reduce pcb/concat-changes @@ -665,7 +624,7 @@ (log/info :msg "SYNC-FILE (2nd stage)" :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state)) - (let [file (dwlh/get-file state file-id) + (let [file (wsh/get-file state file-id) changes (reduce pcb/concat-changes (pcb/empty-changes it) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 8ce0233d5..f0ee45cd9 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -15,7 +15,9 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.text :as txt] + [app.main.data.workspace.common :as dwc] [app.main.data.workspace.groups :as dwg] + [app.main.data.workspace.state-helpers :as wsh] [cljs.spec.alpha :as s] [clojure.set :as set])) @@ -52,31 +54,12 @@ (declare reposition-shape) (declare make-change) -(defn get-local-file - [state] - (get state :workspace-data)) - -(defn get-file - [state file-id] - (if (= file-id (:current-file-id state)) - (get state :workspace-data) - (get-in state [:workspace-libraries file-id :data]))) - -(defn get-libraries - "Retrieve all libraries, including the local file." - [state] - (let [{:keys [id] :as local} (:workspace-data state)] - (-> (:workspace-libraries state) - (assoc id {:id id - :data local})))) - (defn pretty-file [file-id state] (if (= file-id (:current-file-id state)) "" (str "<" (get-in state [:workspace-libraries file-id :name]) ">"))) - ;; ---- Create a new component ---- (defn make-component-shape @@ -146,7 +129,7 @@ name new-shapes updated-shapes))] - [group changes]))) + [group new-shape changes]))) (defn duplicate-component "Clone the root shape of the component and all children. Generate new @@ -158,6 +141,60 @@ (get component :objects) identity))) +(defn generate-instantiate-component + "Generate changes to create a new instance from a component." + [it file-id component-id position page libraries] + (let [component (cph/get-component libraries file-id component-id) + component-shape (cph/get-shape component component-id) + + orig-pos (gpt/point (:x component-shape) (:y component-shape)) + delta (gpt/subtract position orig-pos) + + objects (:objects page) + unames (volatile! (dwc/retrieve-used-names objects)) + + frame-id (cph/frame-id-by-position objects (gpt/add orig-pos delta)) + + update-new-shape + (fn [new-shape original-shape] + (let [new-name (dwc/generate-unique-name @unames (:name new-shape))] + + (when (nil? (:parent-id original-shape)) + (vswap! unames conj new-name)) + + (cond-> new-shape + true + (as-> $ + (geom/move $ delta) + (assoc $ :frame-id frame-id) + (assoc $ :parent-id + (or (:parent-id $) (:frame-id $))) + (dissoc $ :touched)) + + (nil? (:shape-ref original-shape)) + (assoc :shape-ref (:id original-shape)) + + (nil? (:parent-id original-shape)) + (assoc :component-id (:id original-shape) + :component-file file-id + :component-root? true + :name new-name) + + (some? (:parent-id original-shape)) + (dissoc :component-root?)))) + + [new-shape new-shapes _] + (cph/clone-object component-shape + nil + (get component :objects) + update-new-shape) + + changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) + (pcb/empty-changes it (:id page)) + new-shapes)] + + [new-shape changes])) + (defn generate-detach-instance "Generate changes to remove the links between a shape and all its children with a component." @@ -193,7 +230,7 @@ :file (pretty-file file-id state) :library (pretty-file library-id state)) - (let [file (get-file state file-id)] + (let [file (wsh/get-file state file-id)] (loop [pages (vals (get file :pages-index)) changes (pcb/empty-changes it)] (if-let [page (first pages)] @@ -218,7 +255,7 @@ :file (pretty-file file-id state) :library (pretty-file library-id state)) - (let [file (get-file state file-id)] + (let [file (wsh/get-file state file-id)] (loop [local-components (vals (get file :components)) changes (pcb/empty-changes it)] (if-let [local-component (first local-components)] @@ -305,7 +342,7 @@ (defmethod generate-sync-shape :components [_ changes _library-id state container shape] (let [shape-id (:id shape) - libraries (get-libraries state)] + libraries (wsh/get-libraries state)] (generate-sync-shape-direct changes libraries container shape-id false))) (defn- generate-sync-text-shape diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index 049c8c704..708c3d1d3 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -37,7 +37,6 @@ (get-in state [:workspace-data :components]))) ;; TODO: improve performance of this - (defn lookup-selected ([state] (lookup-selected state nil)) @@ -68,3 +67,24 @@ ([state page-id filter-fn] (let [objects (lookup-page-objects state page-id)] (into [] (filter filter-fn) (vals objects))))) + +(defn get-local-file + "Get the data content of the file you are currently working with." + [state] + (get state :workspace-data)) + +(defn get-file + "Get the data content of the given file (it may be the current file + or one library)." + [state file-id] + (if (= file-id (:current-file-id state)) + (get state :workspace-data) + (get-in state [:workspace-libraries file-id :data]))) + +(defn get-libraries + "Retrieve all libraries, including the local file." + [state] + (let [{:keys [id] :as local} (:workspace-data state)] + (-> (:workspace-libraries state) + (assoc id {:id id + :data local})))) diff --git a/frontend/test/app/components_basic_test.cljs b/frontend/test/app/components_basic_test.cljs index a6d154a9f..591b353d2 100644 --- a/frontend/test/app/components_basic_test.cljs +++ b/frontend/test/app/components_basic_test.cljs @@ -31,34 +31,34 @@ {:name "Rect-1"})) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 #--> Rect-2 - ; Rect-1 ---> Rect-1 - ; - ; [Rect-1] - ; Rect-2 - ; Rect-1 - ; - (let [shape1 (thp/get-shape new-state :shape1) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 #--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + ; [Rect-1] + ; Rect-2 + ; 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)) + [[group shape1] [c-group c-shape1] component] + (thl/resolve-instance-and-main + new-state + (:parent-id shape1)) - file (dwlh/get-local-file new-state)] + file (wsh/get-local-file new-state)] - (t/is (= (:name shape1) "Rect-1")) - (t/is (= (:name group) "Rect-2")) - (t/is (= (:name component) "Rect-1")) - (t/is (= (:name c-shape1) "Rect-1")) - (t/is (= (:name c-group) "Rect-2")) + (t/is (= (:name shape1) "Rect-1")) + (t/is (= (:name group) "Rect-2")) + (t/is (= (:name component) "Rect-1")) + (t/is (= (:name c-shape1) "Rect-1")) + (t/is (= (:name c-group) "Rect-2")) - (thl/is-from-file group file))))] + (thl/is-from-file group file))))] (ptk/emit! store @@ -88,7 +88,7 @@ new-state (:parent-id shape1)) - file (dwlh/get-local-file new-state)] + file (wsh/get-local-file new-state)] (t/is (= (:name shape1) "Rect-1")) (t/is (= (:name group) "Component-1")) @@ -109,41 +109,42 @@ {: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 #--> Component-1 - ; Rect-1 ---> Rect-1 - ; Rect-2 ---> Rect-2 - ; - ; [Component-1] - ; Component-1 - ; Rect-1 - ; Rect-2 - ; - (let [shape1 (thp/get-shape new-state :shape1) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component-1 #--> Component-1 + ; Rect-1 ---> Rect-1 + ; Rect-2 ---> Rect-2 + ; + ; [Component-1] + ; Component-1 + ; Rect-1 + ; Rect-2 + ; + (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)) + [[group shape1 shape2] + [c-group c-shape1 c-shape2] + component] + (thl/resolve-instance-and-main + new-state + (:parent-id shape1)) - file (dwlh/get-local-file new-state)] + 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")) + (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))))] + (thl/is-from-file group file))))] (ptk/emit! store @@ -164,39 +165,40 @@ (thp/group-shapes :group1 [(thp/id :shape1) (thp/id :shape2)])) + store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group-1 #--> Group-1 - ; Rect-1 ---> Rect-1 - ; Rect-2 ---> Rect-2 - ; - ; [Group-1] - ; Group-1 - ; Rect-1 - ; Rect-2 - ; - (let [[[group shape1 shape2] - [c-group c-shape1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - (thp/id :group1)) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Group-1 #--> Group-1 + ; Rect-1 ---> Rect-1 + ; Rect-2 ---> Rect-2 + ; + ; [Group-1] + ; Group-1 + ; Rect-1 + ; Rect-2 + ; + (let [[[group shape1 shape2] + [c-group c-shape1 c-shape2] + component] + (thl/resolve-instance-and-main + new-state + (thp/id :group1)) - file (dwlh/get-local-file new-state)] + file (wsh/get-local-file new-state)] - (t/is (= (:name shape1) "Rect-1")) - (t/is (= (:name shape2) "Rect-2")) - (t/is (= (:name group) "Group-1")) - (t/is (= (:name component) "Group-1")) - (t/is (= (:name c-shape1) "Rect-1")) - (t/is (= (:name c-shape2) "Rect-2")) - (t/is (= (:name c-group) "Group-1")) + (t/is (= (:name shape1) "Rect-1")) + (t/is (= (:name shape2) "Rect-2")) + (t/is (= (:name group) "Group-1")) + (t/is (= (:name component) "Group-1")) + (t/is (= (:name c-shape1) "Rect-1")) + (t/is (= (:name c-shape2) "Rect-2")) + (t/is (= (:name c-group) "Group-1")) - (thl/is-from-file group file))))] + (thl/is-from-file group file))))] (ptk/emit! store @@ -211,29 +213,29 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect-1"}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)])) instance1 (thp/get-shape state :instance1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 #--> Renamed component - ; Rect-1 ---> Rect-1 - ; - ; [Renamed] - ; Renamed component - ; Rect-1 - (let [libs (dwlh/get-libraries new-state) - component (cph/get-component libs - (:component-file instance1) - (:component-id instance1))] - (t/is (= (:name component) - "Renamed component")))))] + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 #--> Renamed component + ; Rect-1 ---> Rect-1 + ; + ; [Renamed] + ; Renamed component + ; Rect-1 + (let [libs (wsh/get-libraries new-state) + component (cph/get-component libs + (:component-file instance1) + (:component-id instance1))] + (t/is (= (:name component) + "Renamed component")))))] (ptk/emit! store @@ -247,50 +249,50 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect-1"}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)])) instance1 (thp/get-shape state :instance1) component-id (:component-id instance1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 #--> Rect-2 - ; Rect-1 ---> Rect-1 - ; - ; [Rect-1] - ; Rect-2 - ; Rect-1 - ; - ; [Rect-2] - ; Rect-2 - ; Rect-1 - ; - (let [new-component-id (->> (get-in new-state - [:workspace-data - :components]) - (keys) - (filter #(not= % component-id)) - (first)) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 #--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + ; [Rect-1] + ; Rect-2 + ; Rect-1 + ; + ; [Rect-2] + ; Rect-2 + ; 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 instance1)) + [[instance1 shape1] + [c-instance1 c-shape1] + component1] + (thl/resolve-instance-and-main + new-state + (:id instance1)) - [[c-component2 c-shape2] - component2] - (thl/resolve-component - new-state - new-component-id)] + [[c-component2 c-shape2] + component2] + (thl/resolve-component + new-state + new-component-id)] - (t/is (= (:name component2) "Rect-2")))))] + (t/is (= (:name component2) "Rect-2")))))] (ptk/emit! store @@ -304,36 +306,36 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect-1"}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)])) - file (dwlh/get-local-file state) + file (wsh/get-local-file state) instance1 (thp/get-shape state :instance1) component-id (:component-id instance1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 - ; Rect-1 - ; - (let [[instance1 shape1] - (thl/resolve-noninstance - new-state - (:id instance1)) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 + ; Rect-1 + ; + (let [[instance1 shape1] + (thl/resolve-noninstance + new-state + (:id instance1)) - libs (dwlh/get-libraries new-state) - component (cph/get-component libs - (:component-file instance1) - (:component-id instance1))] + libs (wsh/get-libraries new-state) + component (cph/get-component libs + (:component-file instance1) + (:component-id instance1))] - (t/is (some? instance1)) - (t/is (some? shape1)) - (t/is (nil? component)))))] + (t/is (some? instance1)) + (t/is (some? shape1)) + (t/is (nil? component)))))] (ptk/emit! store @@ -348,50 +350,101 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect-1"}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)])) - file (dwlh/get-local-file state) + file (wsh/get-local-file state) + component-id (thp/id :component-1) instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 #--> Rect-2 - ; Rect-1 ---> Rect-1 - ; Rect-3 #--> Rect-2 - ; Rect-1 ---> Rect-1 - ; - ; [Rect-2] - ; Rect-2 - ; Rect-1 - ; - (let [new-instance-id (-> new-state - wsh/lookup-selected - first) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 #--> Rect-2 + ; Rect-1 ---> Rect-1 + ; Rect-3 #--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + ; [Rect-2] + ; Rect-2 + ; Rect-1 + ; + (let [new-instance-id (-> new-state + wsh/lookup-selected + first) - [[instance2 shape2] - [c-instance2 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - new-instance-id)] + [[instance2 shape2] + [c-instance2 c-shape2] + component] + (thl/resolve-instance-and-main + new-state + new-instance-id)] - (t/is (not= (:id instance1) (:id instance2))) - (t/is (= (:id component) component-id)) - (t/is (= (:name instance2) "Rect-3")) - (t/is (= (:name shape2) "Rect-1")) - (t/is (= (:name c-instance2) "Rect-2")) - (t/is (= (:name c-shape2) "Rect-1")))))] + (t/is (not= (:id instance1) (:id instance2))) + (t/is (= (:id component) component-id)) + (t/is (= (:name instance2) "Rect-3")) + (t/is (= (:name shape2) "Rect-1")) + (t/is (= (:name c-instance2) "Rect-2")) + (t/is (= (:name c-shape2) "Rect-1")) + (t/is (= (:component-file instance2) + thp/current-file-id)))))] (ptk/emit! store (dwl/instantiate-component (:id file) - (:component-id instance1) + 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 :instance1 :component-1 + [(thp/id :shape1)]) + (thp/move-to-library :lib1 "Library 1") + (thp/sample-page)) + + library-id (thp/id :lib1) + component-id (thp/id :component-1) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 #--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + (let [new-instance-id (-> new-state + wsh/lookup-selected + first) + + [[instance2 shape2] + [c-instance2 c-shape2] + component] + (thl/resolve-instance-and-main + new-state + new-instance-id)] + + (t/is (= (:id component) component-id)) + (t/is (= (:name instance2) "Rect-2")) + (t/is (= (:name shape2) "Rect-1")) + (t/is (= (:name c-instance2) "Rect-2")) + (t/is (= (:name c-shape2) "Rect-1")) + (t/is (= (:component-file instance2) library-id)))))] + + (ptk/emit! + store + (dwl/instantiate-component library-id + component-id (gpt/point 100 100)) :the/end)))) @@ -402,32 +455,32 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect-1"}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)])) instance1 (thp/get-shape state :instance1) component-id (:component-id instance1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 - ; Rect-1 - ; - ; [Rect-2] - ; Rect-2 - ; Rect-1 - ; - (let [[instance1 shape1] - (thl/resolve-noninstance - new-state - (:id instance1))] + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 + ; Rect-1 + ; + ; [Rect-2] + ; Rect-2 + ; Rect-1 + ; + (let [[instance1 shape1] + (thl/resolve-noninstance + new-state + (:id instance1))] - (t/is (some? instance1)) - (t/is (some? shape1)))))] + (t/is (some? instance1)) + (t/is (some? shape1)))))] (ptk/emit! store @@ -442,47 +495,47 @@ (thp/sample-shape :shape1 :rect {:name "Rect-1"})) - file (dwlh/get-local-file state) + file (wsh/get-local-file state) instance1 (thp/get-shape state :instance1) component-id (:component-id instance1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group-1 #--> Group-1 - ; Rect-2 @--> Rect-2 - ; Rect-1 ---> Rect-1 - ; - ; [Rect-1] - ; Rect-2 - ; Rect-1 - ; - ; [Group-1] - ; Group-1 - ; Rect-2 @--> Rect-2 - ; Rect-1 ---> Rect-1 - ; - (let [page (thp/current-page new-state) - shape1 (thp/get-shape new-state :shape1) - parent1 (cph/get-shape page (:parent-id shape1)) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Group-1 #--> Group-1 + ; Rect-2 @--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + ; [Rect-1] + ; Rect-2 + ; Rect-1 + ; + ; [Group-1] + ; Group-1 + ; Rect-2 @--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + (let [page (thp/current-page new-state) + shape1 (thp/get-shape new-state :shape1) + parent1 (cph/get-shape page (:parent-id shape1)) - [[group shape1 shape2] - [c-group c-shape1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - (:parent-id parent1))] + [[group shape1 shape2] + [c-group c-shape1 c-shape2] + component] + (thl/resolve-instance-and-main + new-state + (:parent-id parent1))] - (t/is (= (:name group) "Group-1")) - (t/is (= (:name shape1) "Rect-2")) - (t/is (= (:name shape2) "Rect-1")) - (t/is (= (:name component) "Group-1")) - (t/is (= (:name c-group) "Group-1")) - (t/is (= (:name c-shape1) "Rect-2")) - (t/is (= (:name c-shape2) "Rect-1")))))] + (t/is (= (:name group) "Group-1")) + (t/is (= (:name shape1) "Rect-2")) + (t/is (= (:name shape2) "Rect-1")) + (t/is (= (:name component) "Group-1")) + (t/is (= (:name c-group) "Group-1")) + (t/is (= (:name c-shape1) "Rect-2")) + (t/is (= (:name c-shape2) "Rect-1")))))] (ptk/emit! store @@ -499,59 +552,59 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect-1"}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)]) (thp/group-shapes :group1 [(thp/id :instance1)]) - (thp/make-component :instance2 + (thp/make-component :instance2 :component-2 [(thp/id :group1)])) - file (dwlh/get-local-file state) + file (wsh/get-local-file state) instance1 (thp/get-shape state :instance1) instance2 (thp/get-shape state :instance2) component-id (:component-id instance2) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 #--> Rect-2 - ; Rect-2 @--> Rect-2 - ; Rect-1 ---> Rect-1 - ; Rect-3 #--> Rect-2 - ; Rect-2 @--> Rect-2 - ; Rect-1 ---> Rect-1 - ; - ; [Rect-1] - ; Rect-2 - ; Rect-1 - ; - ; [Rect-2] - ; Rect-2 - ; Rect-2 @--> Rect-2 - ; Rect-1 ---> Rect-1 - ; - (let [new-instance-id (-> new-state - wsh/lookup-selected - first) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect-2 #--> Rect-2 + ; Rect-2 @--> Rect-2 + ; Rect-1 ---> Rect-1 + ; Rect-3 #--> Rect-2 + ; Rect-2 @--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + ; [Rect-1] + ; Rect-2 + ; Rect-1 + ; + ; [Rect-2] + ; Rect-2 + ; Rect-2 @--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + (let [new-instance-id (-> new-state + wsh/lookup-selected + first) - [[instance3 shape3 shape4] - [c-instance3 c-shape3 c-shape4] - component] - (thl/resolve-instance-and-main - new-state - new-instance-id)] + [[instance3 shape3 shape4] + [c-instance3 c-shape3 c-shape4] + component] + (thl/resolve-instance-and-main + new-state + new-instance-id)] - (t/is (not= (:id instance1) (:id instance3))) - (t/is (= (:id component) component-id)) - (t/is (= (:name instance3) "Rect-3")) - (t/is (= (:name shape3) "Rect-2")) - (t/is (= (:name shape4) "Rect-1")) - (t/is (= (:name c-instance3) "Rect-2")) - (t/is (= (:name c-shape3) "Rect-2")) - (t/is (= (:name c-shape4) "Rect-1")))))] + (t/is (not= (:id instance1) (:id instance3))) + (t/is (= (:id component) component-id)) + (t/is (= (:name instance3) "Rect-3")) + (t/is (= (:name shape3) "Rect-2")) + (t/is (= (:name shape4) "Rect-1")) + (t/is (= (:name c-instance3) "Rect-2")) + (t/is (= (:name c-shape3) "Rect-2")) + (t/is (= (:name c-shape4) "Rect-1")))))] (ptk/emit! store @@ -560,3 +613,63 @@ (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 :instance1 :component-1 + [(thp/id :shape1)]) + (thp/move-to-library :lib1 "Library 1") + (thp/sample-page) + (thp/instantiate-component :instance2 + (thp/id :component-1) + (thp/id :lib1))) + + library-id (thp/id :lib1) + component-id (thp/id :component-1) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Group-1 #--> Group-1 + ; Rect-2 @--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + ; [Group-1] + ; Group-1 + ; Rect-2 @--> Rect-2 + ; Rect-1 ---> Rect-1 + ; + (let [instance2 (thp/get-shape new-state :instance2) + + [[group1 shape1 shape2] [c-group1 c-shape1 c-shape2] component] + (thl/resolve-instance-and-main + new-state + (:parent-id instance2))] + + (t/is (= (:name group1) "Group-1")) + (t/is (= (:name shape1) "Rect-2")) + (t/is (= (:name shape2) "Rect-1")) + (t/is (= (:name c-group1) "Group-1")) + (t/is (= (:name c-shape1) "Rect-2")) + (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) nil)) + (t/is (= (:component-file c-shape1) library-id)) + (t/is (= (:component-file c-shape2) nil)))))] + + (ptk/emit! + store + (dw/select-shape (thp/id :instance2)) + dwg/group-selected + (dwl/add-component) + :the/end)))) + diff --git a/frontend/test/app/components_sync_test.cljs b/frontend/test/app/components_sync_test.cljs index 4e45238dc..2b2c79c95 100644 --- a/frontend/test/app/components_sync_test.cljs +++ b/frontend/test/app/components_sync_test.cljs @@ -1,19 +1,23 @@ (ns app.components-sync-test (:require - [app.common.colors :as clr] - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.main.data.workspace.changes :as dwc] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.libraries-helpers :as dwlh] - [app.test-helpers.events :as the] - [app.test-helpers.libraries :as thl] - [app.test-helpers.pages :as thp] - [beicon.core :as rx] - [cljs.pprint :refer [pprint]] - [cljs.test :as t :include-macros true] - [linked.core :as lks] - [potok.core :as ptk])) + [app.common.colors :as clr] + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.pages.helpers :as cph] + [app.main.data.workspace :as dw] + [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.libraries-helpers :as dwlh] + [app.main.data.workspace.state-helpers :as wsh] + [app.test-helpers.events :as the] + [app.test-helpers.libraries :as thl] + [app.test-helpers.pages :as thp] + [beicon.core :as rx] + [cljs.pprint :refer [pprint]] + [cljs.test :as t :include-macros true] + [linked.core :as lks] + [potok.core :as ptk])) (t/use-fixtures :each {:before thp/reset-idmap!}) @@ -27,37 +31,220 @@ {:name "Rect 1" :fill-color clr/white :fill-opacity 1}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)])) + shape1 (thp/get-shape state :shape1) + + update-fn (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5})) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Rect 1* ---> Rect 1 + ; #{:fill-group} + ; + ; [Rect 1] + ; Rect 1-1 + ; Rect 1 + ; + (let [instance1 (thp/get-shape new-state :instance1) + shape1 (thp/get-shape new-state :shape1) + + [[group shape1] [c-group c-shape1] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] + + (t/is (= (:touched instance1) nil)) + (t/is (= (:touched shape1) #{:fill-group})) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:touched c-group) nil)) + (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)] update-fn) + :the/end))))) + +(t/deftest test-touched-children-add + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1)]) + (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] + ; Root Frame + ; Rect 1-1* #--> Rect 1-1 + ; #{:shapes-group} + ; Circle 1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; Rect 1-1 + ; Rect 1 + ; + (let [instance1 (thp/get-shape new-state :instance1) + + [[group shape1 shape2] [c-group c-shape1] component] + (thl/resolve-instance-and-main-allow-dangling + new-state + (:id instance1))] + + (t/is (= (:touched group) #{:shapes-group})) + (t/is (nil? (:touched shape1))) + (t/is (= (:name shape1) "Circle 1")) + (t/is (nil? (:shape-ref shape1))) + (t/is (nil? (:touched shape2))) + (t/is (= (:name shape2) "Rect 1")) + (t/is (some? (:shape-ref shape2))) + (t/is (nil? (:touched c-group))) + (t/is (nil? (:touched c-shape1))))))] + + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) + :the/end))))) + +(t/deftest test-touched-children-delete + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1) + (thp/id :shape2)])) + + shape1 (thp/get-shape state :shape1) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component-1* #--> Component-1 + ; #{:shapes-group} + ; Rect 2 ---> Rect 2 + ; + ; [Component-1] + ; Component-1 + ; Rect 1 + ; Rect 2 + ; + (let [instance1 (thp/get-shape new-state :instance1) + + [[group shape2] [c-group c-shape2] component] + (thl/resolve-instance-and-main-allow-dangling + new-state + (:id instance1))] + + (t/is (= (:touched group) #{:shapes-group})) + (t/is (nil? (:touched shape2))) + (t/is (= (:name shape2) "Rect 2")) + (t/is (some? (:shape-ref shape2))) + (t/is (nil? (:touched c-group))) + (t/is (nil? (:touched c-shape2))))))] + + (ptk/emit! + store + (dwc/delete-shapes #{(:id shape1)}) + :the/end))))) + +(t/deftest test-touched-children-move + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1) + (thp/id :shape2) + (thp/id :shape3)])) + shape1 (thp/get-shape state :shape1) instance1 (thp/get-shape state :instance1) - update-shape (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) - store (the/prepare-store state done - (fn [new-state] - (let [shape1 (thp/get-shape new-state :shape1) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component-1* #--> Component-1 + ; #{:shapes-group} + ; Rect 2 ---> Rect 2 + ; Rect 1 ---> Rect 1 + ; Rect 3 ---> Rect 3 + ; + ; [Component-1] + ; Component-1 + ; Rect 1 + ; Rect 2 + ; Rect 3 + ; + (let [instance1 (thp/get-shape new-state :instance1) - [[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (:id instance1)) + [[group shape1 shape2 shape3] + [c-group c-shape1 c-shape2 c-shape3] component] + (thl/resolve-instance-and-main-allow-dangling + new-state + (:id instance1))] - file (dwlh/get-local-file new-state)] - - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:touched shape1) #{:fill-group})) - (t/is (= (:fill-color c-shape1) clr/white)) - (t/is (= (:fill-opacity c-shape1) 1)) - (t/is (= (:touched c-shape1) nil)))))] + (t/is (= (:touched group) #{:shapes-group})) + (t/is (nil? (:touched shape1))) + (t/is (some? (:shape-ref shape1))) + (t/is (= (:name shape1) "Rect 2")) + (t/is (nil? (:touched shape2))) + (t/is (some? (:shape-ref shape2))) + (t/is (= (:name shape2) "Rect 1")) + (t/is (nil? (:touched shape3))) + (t/is (some? (:shape-ref shape3))) + (t/is (= (:name shape3) "Rect 3")) + (t/is (nil? (:touched c-group))) + (t/is (nil? (:touched c-shape1))) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (nil? (:touched c-shape2))) + (t/is (= (:name c-shape2) "Rect 2")) + (t/is (nil? (:touched c-shape3))) + (t/is (= (:name c-shape3) "Rect 3")))))] (ptk/emit! store - (dwc/update-shapes [(:id shape1)] update-shape) + (dw/relocate-shapes #{(:id shape1)} (:id instance1) 2) :the/end))))) (t/deftest test-reset-changes @@ -69,37 +256,561 @@ {:name "Rect 1" :fill-color clr/white :fill-opacity 1}) - (thp/make-component :instance1 + (thp/make-component :instance1 :component-1 [(thp/id :shape1)])) shape1 (thp/get-shape state :shape1) instance1 (thp/get-shape state :instance1) - update-shape (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + update-fn (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5})) store (the/prepare-store state done - (fn [new-state] - (let [shape1 (thp/get-shape new-state :shape1) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; Rect 1-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 - (:id instance1)) + [[group shape1] [c-group c-shape1] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] - file (dwlh/get-local-file new-state)] - - (t/is (= (:fill-color shape1) clr/white)) - (t/is (= (:fill-opacity shape1) 1)) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/white)) - (t/is (= (:fill-opacity c-shape1) 1)) - (t/is (= (:touched c-shape1) nil)))))] + (t/is (= (:fill-color shape1) clr/white)) + (t/is (= (:fill-opacity shape1) 1)) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/white)) + (t/is (= (:fill-opacity c-shape1) 1)) + (t/is (= (:touched c-shape1) nil)))))] (ptk/emit! store - (dwc/update-shapes [(:id shape1)] update-shape) + (dch/update-shapes [(:id shape1)] update-fn) (dwl/reset-component (:id instance1)) :the/end))))) +(t/deftest test-reset-children-add + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1)]) + (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] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; Rect 1-1 + ; Rect 1 + ; + (let [instance1 (thp/get-shape new-state :instance1) + + [[group shape1] [c-group c-shape1] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] + + (t/is (nil? (:touched instance1))) + (t/is (= (:name shape1) "Rect 1")) + (t/is (some? (:shape-ref shape1))))))] + + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) + (dwl/reset-component (:id instance1)) + :the/end))))) + +(t/deftest test-reset-children-delete + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1) + (thp/id :shape2)])) + + instance1 (thp/get-shape state :instance1) + shape1 (thp/get-shape state :shape1) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; Rect 1-1 + ; Rect 1 + ; + (let [instance1 (thp/get-shape new-state :instance1) + + [[group shape1] [c-group c-shape1] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] + + (t/is (nil? (:touched instance1))) + (t/is (= (:name shape1) "Rect 1")) + (t/is (some? (:shape-ref shape1))))))] + + (ptk/emit! + store + (dwc/delete-shapes #{(:id shape1)}) + (dwl/reset-component (:id instance1)) + :the/end))))) + +(t/deftest test-reset-children-move + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1) + (thp/id :shape2) + (thp/id :shape3)])) + + shape1 (thp/get-shape state :shape1) + instance1 (thp/get-shape state :instance1) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component-1 #--> Component-1 + ; Rect 1 ---> Rect 1 + ; Rect 2 ---> Rect 2 + ; Rect 3 ---> Rect 3 + ; + ; [Component-1] + ; Component-1 + ; Rect 1 + ; Rect 2 + ; Rect 3 + ; + (let [instance1 (thp/get-shape new-state :instance1) + + [[group shape1 shape2 shape3] [c-group c-shape1 c-shape2 c-shape3] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] + + (t/is (nil? (:touched group))) + (t/is (nil? (:touched shape1))) + (t/is (some? (:shape-ref shape1))) + (t/is (= (:name shape1) "Rect 1")) + (t/is (nil? (:touched shape2))) + (t/is (some? (:shape-ref shape2))) + (t/is (= (:name shape2) "Rect 2")) + (t/is (nil? (:touched shape3))) + (t/is (some? (:shape-ref shape3))) + (t/is (= (:name shape3) "Rect 3")) + (t/is (nil? (:touched c-group))) + (t/is (nil? (:touched c-shape1))) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (nil? (:touched c-shape2))) + (t/is (= (:name c-shape2) "Rect 2")) + (t/is (nil? (:touched c-shape3))) + (t/is (= (:name c-shape3) "Rect 3")))))] + + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape1)} (:id instance1) 2) + (dwl/reset-component (:id instance1)) + :the/end))))) + +(t/deftest test-update-component + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1)])) + + shape1 (thp/get-shape state :shape1) + instance1 (thp/get-shape state :instance1) + + update-fn (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5})) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; Rect 1-1 + ; Rect 1 + ; + (let [[[group shape1] [c-group c-shape1] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] + + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/test)) + (t/is (= (:fill-opacity c-shape1) 0.5)) + (t/is (= (:touched c-shape1) nil)))))] + + (ptk/emit! + store + (dch/update-shapes [(:id shape1)] update-fn) + (dwl/update-component (:id instance1)) + :the/end))))) + +(t/deftest test-update-component-and-sync + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance2 + (thp/id :component-1))) + + file (wsh/get-local-file state) + + shape1 (thp/get-shape state :shape1) + instance1 (thp/get-shape state :instance1) + + update-fn (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5})) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Rect 1 ---> Rect 1 + ; Rect 1-2 #--> Rect 1-1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; Rect 1-1 + ; Rect 1 + ; + (let [instance2 (thp/get-shape state :instance2) + + [[group shape2] [c-group c-shape2] component] + (thl/resolve-instance-and-main + new-state + (:id instance2))] + + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/test)) + (t/is (= (:fill-opacity c-shape2) 0.5)) + (t/is (= (:touched c-shape2) nil)))))] + + (ptk/emit! + store + (dch/update-shapes [(:id shape1)] update-fn) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end))))) + +(t/deftest test-update-preserve-touched + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance2 + (thp/id :component-1))) + + file (wsh/get-local-file state) + + shape1 (thp/get-shape state :shape1) + instance1 (thp/get-shape state :instance1) + instance2 (thp/get-shape state :instance2) + + shape2 (cph/get-shape (wsh/lookup-page state) + (first (:shapes instance2))) + + update-fn1 (fn [shape] + (merge shape {:fill-color clr/test + :stroke-width 0.5})) + + update-fn2 (fn [shape] + (merge shape {:stroke-width 0.2})) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Rect 1 ---> Rect 1 + ; Rect 1-2 #--> Rect 1-1 + ; Rect 1* ---> Rect 1 + ; #{:stroke-group} + ; + ; [Rect 1] + ; Rect 1-1 + ; Rect 1 + ; + (let [instance2 (thp/get-shape state :instance2) + + [[group shape2] [c-group c-shape2] component] + (thl/resolve-instance-and-main + new-state + (:id instance2))] + + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:stroke-width shape2) 0.2)) + (t/is (= (:touched shape2 #{:stroke-group}))) + (t/is (= (:fill-color c-shape2) clr/test)) + (t/is (= (:stroke-width c-shape2) 0.5)) + (t/is (= (:touched c-shape2) nil)))))] + + (ptk/emit! + store + (dch/update-shapes [(:id shape1)] update-fn1) + (dch/update-shapes [(:id shape2)] update-fn2) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end))))) + +(t/deftest test-update-children-add + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1)]) + (thp/sample-shape :shape2 :circle + {:name "Circle 1"})) + + file (wsh/get-local-file state) + + 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] + ; Root Frame + ; Rect 1-1 #--> Rect 1-1 + ; Circle 1 ---> Circle 1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; Rect 1-1 + ; Circle 1 + ; Rect 1 + ; + (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 + (:id instance1))] + + (t/is (nil? (:touched group))) + (t/is (nil? (:touched shape1))) + (t/is (= (:name shape1) "Circle 1")) + (t/is (some? (:shape-ref shape1))) + (t/is (nil? (:touched shape2))) + (t/is (= (:name shape2) "Rect 1")) + (t/is (some? (:shape-ref shape2))) + (t/is (nil? (:touched c-group))) + (t/is (nil? (:touched c-shape1))) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (nil? (:touched c-shape2))) + (t/is (= (:name c-shape2) "Rect 1")))))] + + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end))))) + +(t/deftest test-update-children-delete + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1) + (thp/id :shape2)])) + + file (wsh/get-local-file state) + + instance1 (thp/get-shape state :instance1) + shape1 (thp/get-shape state :shape1) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component-1 #--> Component-1 + ; Rect 2 ---> Rect 2 + ; + ; [Component-1] + ; Component-1 + ; Rect 2 + ; + (let [instance1 (thp/get-shape new-state :instance1) + + [[group shape2] [c-group c-shape2] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] + + (t/is (nil? (:touched group))) + (t/is (nil? (:touched shape2))) + (t/is (= (:name shape2) "Rect 2")) + (t/is (some? (:shape-ref shape2))) + (t/is (nil? (:touched c-group))) + (t/is (nil? (:touched c-shape2))) + (t/is (= (:name c-shape2) "Rect 2")))))] + + (ptk/emit! + store + (dwc/delete-shapes #{(:id shape1)}) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end))))) + +(t/deftest test-update-children-move + (t/async done + (try + (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 :instance1 :component-1 + [(thp/id :shape1) + (thp/id :shape2) + (thp/id :shape3)])) + + file (wsh/get-local-file state) + + shape1 (thp/get-shape state :shape1) + instance1 (thp/get-shape state :instance1) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component-1 #--> Component-1 + ; Rect 2 ---> Rect 2 + ; Rect 1 ---> Rect 1 + ; Rect 3 ---> Rect 3 + ; + ; [Component-1] + ; Component-1 + ; Rect 2 + ; Rect 1 + ; Rect 3 + ; + (let [instance1 (thp/get-shape new-state :instance1) + + [[group shape1 shape2 shape3] [c-group c-shape1 c-shape2 c-shape3] component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] + + (t/is (nil? (:touched group))) + (t/is (nil? (:touched shape1))) + (t/is (some? (:shape-ref shape1))) + (t/is (= (:name shape1) "Rect 2")) + (t/is (nil? (:touched shape2))) + (t/is (some? (:shape-ref shape2))) + (t/is (= (:name shape2) "Rect 1")) + (t/is (nil? (:touched shape3))) + (t/is (some? (:shape-ref shape3))) + (t/is (= (:name shape3) "Rect 3")) + (t/is (nil? (:touched c-group))) + (t/is (nil? (:touched c-shape1))) + (t/is (= (:name c-shape1) "Rect 2")) + (t/is (nil? (:touched c-shape2))) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (nil? (:touched c-shape3))) + (t/is (= (:name c-shape3) "Rect 3")))))] + + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape1)} (:id instance1) 2) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end))))) diff --git a/frontend/test/app/test_helpers/libraries.cljs b/frontend/test/app/test_helpers/libraries.cljs index 55ab69cb4..94fbfa8e0 100644 --- a/frontend/test/app/test_helpers/libraries.cljs +++ b/frontend/test/app/test_helpers/libraries.cljs @@ -10,6 +10,7 @@ [app.common.pages.helpers :as cph] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries-helpers :as dwlh] + [app.main.data.workspace.state-helpers :as wsh] [app.test-helpers.pages :as thp])) ;; ---- Helpers to manage libraries and synchronization @@ -85,7 +86,7 @@ (let [page (thp/current-page state) root-inst (cph/get-shape page root-inst-id) - libs (dwlh/get-libraries state) + libs (wsh/get-libraries state) component (cph/get-component libs (:component-id root-inst)) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id) @@ -115,11 +116,44 @@ [shapes-inst shapes-main component])) +(defn resolve-instance-and-main-allow-dangling + "Get the shape with the given id and all its children, and also + the main component and all its shapes. Allows shapes with the + corresponding component shape missing." + [state root-inst-id] + (let [page (thp/current-page state) + root-inst (cph/get-shape page root-inst-id) + + libs (wsh/get-libraries state) + component (cph/get-component libs (:component-id root-inst)) + + shapes-inst (cph/get-children-with-self (:objects page) root-inst-id) + shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst)) + + unique-refs (into #{} (map :shape-ref) shapes-inst) + + main-exists? (fn [shape] + (let [component-shape + (cph/get-component-shape (:objects page) shape) + + component + (cph/get-component libs (:component-id component-shape)) + + main-shape + (cph/get-shape component (:shape-ref shape))] + + (t/is (some? main-shape))))] + + ;; Validate that the instance tree is well constructed + (is-instance-root (first shapes-inst)) + + [shapes-inst shapes-main component])) + (defn resolve-component "Get the component with the given id and all its shapes." [state component-id] (let [page (thp/current-page state) - libs (dwlh/get-libraries state) + libs (wsh/get-libraries state) component (cph/get-component libs component-id) root-main (cph/get-component-root component) shapes-main (cph/get-children-with-self (:objects component) (:id root-main))] diff --git a/frontend/test/app/test_helpers/pages.cljs b/frontend/test/app/test_helpers/pages.cljs index 324d969a2..6156a02ce 100644 --- a/frontend/test/app/test_helpers/pages.cljs +++ b/frontend/test/app/test_helpers/pages.cljs @@ -10,10 +10,10 @@ [app.common.pages :as cp] [app.common.pages.helpers :as cph] [app.main.data.workspace :as dw] - [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.layout :as layout] - [app.main.data.workspace.libraries-helpers :as dwlh])) + [app.main.data.workspace.libraries-helpers :as dwlh] + [app.main.data.workspace.state-helpers :as wsh])) ;; ---- Helpers to manage pages and objects @@ -96,19 +96,53 @@ cp/process-changes (:redo-changes changes))))))) (defn make-component - [state label ids] + [state instance-label component-label shape-ids] (let [page (current-page state) objects (wsh/lookup-page-objects state (:id page)) - shapes (dwg/shapes-for-grouping objects ids) + shapes (dwg/shapes-for-grouping objects shape-ids) - [group changes] + [group component-root changes] (dwlh/generate-add-component nil shapes (:objects page) (:id page) current-file-id)] - (swap! idmap assoc label (:id group)) + (swap! idmap assoc instance-label (:id group) + component-label (:id component-root)) (update state :workspace-data cp/process-changes (:redo-changes changes)))) +(defn instantiate-component + ([state label component-id] + (instantiate-component state label component-id current-file-id)) + ([state label component-id file-id] + (let [page (current-page state) + libraries (wsh/get-libraries state) + + [new-shape changes] + (dwlh/generate-instantiate-component nil + file-id + component-id + (gpt/point 100 100) + page + libraries)] + + (swap! idmap assoc label (:id new-shape)) + (update state :workspace-data + cp/process-changes (:redo-changes changes))))) + +(defn move-to-library + [state label name] + (let [library-id (uuid/next) + data (get state :workspace-data)] + (swap! idmap assoc label library-id) + (-> state + (update :workspace-libraries + assoc library-id {:id library-id + :name name + :data {:id library-id + :components (:components data)}}) + (update :workspace-data + assoc :components {} :pages [] :pages-index {})))) +