diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 9f2839757..6b30243a7 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -34,7 +34,7 @@ [potok.core :as ptk])) ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default -(log/set-level! :debug) +(log/set-level! :warn) (defn- log-changes [changes file] @@ -610,15 +610,18 @@ :library (dwlh/pretty-file library-id state)) (let [file (dwlh/get-file state file-id) - changes (-> (pcb/empty-changes it)) - library-changes (-> changes - (dwlh/generate-sync-library file-id :components library-id state) - (dwlh/generate-sync-library file-id :colors library-id state) - (dwlh/generate-sync-library file-id :typographies library-id state)) - file-changes (-> library-changes - (dwlh/generate-sync-file file-id :components library-id state) - (dwlh/generate-sync-file file-id :colors library-id state) - (dwlh/generate-sync-file file-id :typographies library-id state)) + library-changes (reduce + pcb/concat-changes + (pcb/empty-changes it) + [(dwlh/generate-sync-library it file-id :components library-id state) + (dwlh/generate-sync-library it file-id :colors library-id state) + (dwlh/generate-sync-library it file-id :typographies library-id state)]) + file-changes (reduce + pcb/concat-changes + (pcb/empty-changes it) + [(dwlh/generate-sync-file it file-id :components library-id state) + (dwlh/generate-sync-file it file-id :colors library-id state) + (dwlh/generate-sync-file it file-id :typographies library-id state)]) changes (pcb/concat-changes library-changes file-changes)] @@ -663,9 +666,11 @@ :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state)) (let [file (dwlh/get-file state file-id) - changes (-> (pcb/empty-changes it) - (dwlh/generate-sync-file file-id :components library-id state) - (dwlh/generate-sync-library file-id :components library-id state))] + changes (reduce + pcb/concat-changes + (pcb/empty-changes it) + [(dwlh/generate-sync-file it file-id :components library-id state) + (dwlh/generate-sync-library it file-id :components library-id state)])] (when (seq (:redo-changes changes)) (log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes (:redo-changes changes) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index ac8127744..8ce0233d5 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -22,8 +22,6 @@ ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default (log/set-level! :warn) -;; (defonce empty-changes [[] []]) - (defonce color-sync-attrs [[:fill-color-ref-id :fill-color-ref-file :color :fill-color] [:fill-color-ref-id :fill-color-ref-file :gradient :fill-color-gradient] @@ -128,7 +126,7 @@ [it shapes objects page-id file-id] (if (and (= (count shapes) 1) (:component-id (first shapes))) - (pcb/empty-changes it) + [(first shapes) (pcb/empty-changes it)] (let [name (if (= 1 (count shapes)) (:name (first shapes)) "Component-1") [path name] (cph/parse-path-name name) @@ -185,7 +183,7 @@ (defn generate-sync-file "Generate changes to synchronize all shapes in all pages of the given file, that use assets of the given type in the given library." - [changes file-id asset-type library-id state] + [it file-id asset-type library-id state] (s/assert #{:colors :components :typographies} asset-type) (s/assert ::us/uuid file-id) (s/assert ::us/uuid library-id) @@ -197,21 +195,23 @@ (let [file (get-file state file-id)] (loop [pages (vals (get file :pages-index)) - changes changes] + changes (pcb/empty-changes it)] (if-let [page (first pages)] (recur (next pages) - (generate-sync-container changes - asset-type - library-id - state - (cph/make-container page :page))) + (pcb/concat-changes + changes + (generate-sync-container it + asset-type + library-id + state + (cph/make-container page :page)))) changes)))) (defn generate-sync-library "Generate changes to synchronize all shapes in all components of the local library of the given file, that use assets of the given type in the given library." - [changes file-id asset-type library-id state] + [it file-id asset-type library-id state] (log/info :msg "Sync local components with library" :asset-type asset-type @@ -220,20 +220,22 @@ (let [file (get-file state file-id)] (loop [local-components (vals (get file :components)) - changes changes] + changes (pcb/empty-changes it)] (if-let [local-component (first local-components)] (recur (next local-components) - (generate-sync-container changes - asset-type - library-id - state - (cph/make-container local-component :component))) + (pcb/concat-changes + changes + (generate-sync-container it + asset-type + library-id + state + (cph/make-container local-component :component)))) changes)))) (defn- generate-sync-container "Generate changes to synchronize all shapes in a particular container (a page or a component) that use assets of the given type in the given library." - [changes asset-type library-id state container] + [it asset-type library-id state container] (if (cph/page? container) (log/debug :msg "Sync page in local file" :page-id (:id container)) @@ -243,7 +245,9 @@ linked-shapes (->> (vals (:objects container)) (filter has-asset-reference?))] (loop [shapes (seq linked-shapes) - changes changes] + changes (-> (pcb/empty-changes it) + (pcb/with-container container) + (pcb/with-objects (:objects container)))] (if-let [shape (first shapes)] (recur (next shapes) (generate-sync-shape asset-type @@ -530,12 +534,14 @@ component (cph/get-component libraries (:component-file shape-inst) (:component-id shape-inst)) - shape-main (cph/get-shape component (:shape-ref shape-inst)) + shape-main (when component + (cph/get-shape component (:shape-ref shape-inst))) initial-root? (:component-root? shape-inst) root-inst shape-inst - root-main (cph/get-component-root component)] + root-main (when component + (cph/get-component-root component))] (if component (generate-sync-shape-direct-recursive changes @@ -549,7 +555,7 @@ initial-root?) ; If the component is not found, because the master component has been ; deleted or the library unlinked, detach the instance. - (generate-detach-instance changes shape-id container)))) + (generate-detach-instance changes container shape-id)))) (defn- generate-sync-shape-direct-recursive [changes container shape-inst component shape-main root-inst root-main reset? initial-root?] @@ -559,7 +565,7 @@ (if (nil? shape-main) ;; This should not occur, but protect against it in any case - (generate-detach-instance changes (:id shape-inst) container) + (generate-detach-instance changes container (:id shape-inst)) (let [omit-touched? (not reset?) clear-remote-synced? (and initial-root? reset?) set-remote-synced? (and (not initial-root?) reset?) diff --git a/frontend/test/app/components_basic_test.cljs b/frontend/test/app/components_basic_test.cljs index 7b8138e25..a6d154a9f 100644 --- a/frontend/test/app/components_basic_test.cljs +++ b/frontend/test/app/components_basic_test.cljs @@ -1,76 +1,80 @@ (ns app.components-basic-test (:require - [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.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] - [clojure.stacktrace :as stk] - [linked.core :as lks])) + [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.groups :as dwg] + [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] + [clojure.stacktrace :as stk] + [linked.core :as lks] + [potok.core :as ptk])) (t/use-fixtures :each {:before thp/reset-idmap!}) -;; Test using potok -#_(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 (ptk/store {:state state}) - stream (ptk/input-stream store) - end? (->> stream (rx/filter #(= ::end %)))] +(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"})) - (->> stream - (rx/take-until end?) - (rx/last) - (rx/do - (fn [] - (let [new-state @store - shape1 (thp/get-shape new-state :shape1) + 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) - [[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 (dwlh/get-local-file new-state)] - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name group) "Component-1")) - (t/is (= (:name component) "Component-1")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-group) "Component-1")) + (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))))] - (rx/subs done #(throw %))) - - (ptk/emit! + (ptk/emit! store (dw/select-shape (thp/id :shape1)) (dwl/add-component) - ::end))))) + :the/end))))) -;; FAILING +;; Remove definitely when we ensure that the other method works +;; well in more advanced tests. #_(t/deftest 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"}))] + {:name "Rect-1"}))] (->> state (the/do-update (dw/select-shape (thp/id :shape1))) @@ -86,98 +90,119 @@ file (dwlh/get-local-file new-state)] - (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:name shape1) "Rect-1")) (t/is (= (:name group) "Component-1")) (t/is (= (:name component) "Component-1")) - (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:name c-shape1) "Rect-1")) (t/is (= (:name c-group) "Component-1")) (thl/is-from-file group file)))) (rx/subs done #(throw %)))))) -;; FAILING -#_(t/deftest test-add-component-from-several-shapes +(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"}) + {:name "Rect-1"}) (thp/sample-shape :shape2 :rect - {:name "Rect 2"}))] - - (->> state - (the/do-update (dw/select-shapes (lks/set - (thp/id :shape1) - (thp/id :shape2)))) - (the/do-watch-update dwl/add-component) - (rx/do - (fn [new-state] + {: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) [[group shape1 shape2] [c-group c-shape1 c-shape2] component] (thl/resolve-instance-and-main - new-state - (:parent-id shape1)) + new-state + (:parent-id shape1)) file (dwlh/get-local-file new-state)] - ;; NOTE: the group name depends on having executed - ;; the previous test. (t/is (= (:name group) "Component-1")) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name shape2) "Rect 2")) + (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 c-shape1) "Rect-1")) + (t/is (= (:name c-shape2) "Rect-2")) - (thl/is-from-file group file)))) + (thl/is-from-file group file))))] - (rx/subs done #(throw %)))))) + (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-group +(t/deftest test-add-component-from-group (t/async done (let [state (-> thp/initial-state (thp/sample-page) (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) + {:name "Rect-1"}) (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) + {:name "Rect-2"}) (thp/group-shapes :group1 [(thp/id :shape1) - (thp/id :shape2)]))] - - (->> state - (the/do-update (dw/select-shape (thp/id :group1))) - (the/do-watch-update dwl/add-component) - (rx/do - (fn [new-state] + (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 + [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 (dwlh/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))))] - (rx/subs done #(throw %)))))) + (ptk/emit! + store + (dw/select-shape (thp/id :group1)) + (dwl/add-component) + :the/end)))) (t/deftest test-rename-component (t/async @@ -185,26 +210,35 @@ (let [state (-> thp/initial-state (thp/sample-page) (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) + {:name "Rect-1"}) (thp/make-component :instance1 [(thp/id :shape1)])) - instance1 (thp/get-shape state :instance1)] + instance1 (thp/get-shape state :instance1) - (->> state - (the/do-watch-update (dwl/rename-component - (:component-id instance1) - "Renamed component")) - (rx/do - (fn [new-state] - (let [libs (dwlh/get-libraries new-state) - component (cph/get-component libs - (:component-file instance1) - (:component-id instance1))] - (t/is (= (:name component) - "Renamed component"))))) + 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")))))] - (rx/subs done #(throw %)))))) + (ptk/emit! + store + (dwl/rename-component (:component-id instance1) "Renamed component") + :the/end)))) (t/deftest test-duplicate-component (t/async @@ -217,36 +251,51 @@ [(thp/id :shape1)])) instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1)] + component-id (:component-id instance1) - (->> state - (the/do-watch-update (dwl/duplicate-component - {:id component-id})) - (rx/do - (fn [new-state] - (let [new-component-id (->> (get-in new-state - [:workspace-data - :components]) - (keys) - (filter #(not= % component-id)) - (first)) + 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)) - [[instance1 shape1] - [c-instance1 c-shape1] - component1] - (thl/resolve-instance-and-main + [[instance1 shape1] + [c-instance1 c-shape1] + component1] + (thl/resolve-instance-and-main new-state (:id instance1)) - [[c-component2 c-shape2] - component2] - (thl/resolve-component + [[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")))))] - (rx/subs done #(throw %)))))) + (ptk/emit! + store + (dwl/duplicate-component {:id component-id}) + :the/end)))) (t/deftest test-delete-component (t/async @@ -254,30 +303,43 @@ (let [state (-> thp/initial-state (thp/sample-page) (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) + {:name "Rect-1"}) (thp/make-component :instance1 [(thp/id :shape1)])) - instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1)] + file (dwlh/get-local-file state) - (->> state - (the/do-watch-update (dwl/delete-component - {:id component-id})) - (rx/do - (fn [new-state] - (let [[instance1 shape1] - (thl/resolve-instance + 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)) - libs (dwlh/get-libraries new-state) - component (cph/get-component libs - (:component-file instance1) - (:component-id instance1))] - (t/is (nil? component))))) + libs (dwlh/get-libraries new-state) + component (cph/get-component libs + (:component-file instance1) + (:component-id instance1))] - (rx/subs done #(throw %)))))) + (t/is (some? instance1)) + (t/is (some? shape1)) + (t/is (nil? component)))))] + + (ptk/emit! + store + (dwl/delete-component {:id component-id}) + (dwl/sync-file (:id file) (:id file)) + :the/end)))) (t/deftest test-instantiate-component (t/async @@ -291,34 +353,47 @@ file (dwlh/get-local-file state) instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1)] + component-id (:component-id instance1) - (->> state - (the/do-watch-update (dwl/instantiate-component - (:id file) - (:component-id instance1) - (gpt/point 100 100))) - (rx/do - (fn [new-state] - (let [new-instance-id (-> new-state - wsh/lookup-selected - first) + 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) - [[instance2 shape2] - [c-instance2 c-shape2] - component] - (thl/resolve-instance-and-main + [[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")))))] - (rx/subs done #(throw %)))))) + (ptk/emit! + store + (dwl/instantiate-component (:id file) + (:component-id instance1) + (gpt/point 100 100)) + :the/end)))) (t/deftest test-detach-component (t/async @@ -326,24 +401,162 @@ (let [state (-> thp/initial-state (thp/sample-page) (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) + {:name "Rect-1"}) (thp/make-component :instance1 [(thp/id :shape1)])) instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1)] + component-id (:component-id instance1) - (->> state - (the/do-watch-update (dwl/detach-component - (:id instance1))) - (rx/do - (fn [new-state] - (let [[instance1 shape1] - (thl/resolve-noninstance + 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))] - (t/is (= (:name "Rect 1")))))) + (t/is (some? instance1)) + (t/is (some? shape1)))))] - (rx/subs done #(throw %)))))) + (ptk/emit! + store + (dwl/detach-component (:id instance1)) + :the/end)))) + +(t/deftest test-add-nested-component + (t/async + done + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect-1"})) + + file (dwlh/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)) + + [[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")))))] + + (ptk/emit! + store + (dw/select-shape (thp/id :shape1)) + (dwl/add-component) + dwg/group-selected + (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 :instance1 + [(thp/id :shape1)]) + (thp/group-shapes :group1 + [(thp/id :instance1)]) + (thp/make-component :instance2 + [(thp/id :group1)])) + + file (dwlh/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) + + [[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")))))] + + (ptk/emit! + store + (dwl/instantiate-component (:id file) + (:component-id instance2) + (gpt/point 100 100)) + :the/end)))) diff --git a/frontend/test/app/components_sync_test.cljs b/frontend/test/app/components_sync_test.cljs index 29ea86996..4e45238dc 100644 --- a/frontend/test/app/components_sync_test.cljs +++ b/frontend/test/app/components_sync_test.cljs @@ -12,7 +12,8 @@ [beicon.core :as rx] [cljs.pprint :refer [pprint]] [cljs.test :as t :include-macros true] - [linked.core :as lks])) + [linked.core :as lks] + [potok.core :as ptk])) (t/use-fixtures :each {:before thp/reset-idmap!}) @@ -34,38 +35,30 @@ update-shape (fn [shape] (merge shape {:fill-color clr/test - :fill-opacity 0.5}))] + :fill-opacity 0.5})) - (->> state - (the/do-watch-update (dwc/update-shapes [(:id shape1)] - update-shape)) - (rx/do - (fn [new-state] - (let [shape1 (thp/get-shape new-state :shape1) + store (the/prepare-store state done + (fn [new-state] + (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)] + 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 (= (: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)))))] - (rx/subs - done - #(do - (println (.-stack %)) - (done))))) - - (catch :default e - (println (.-stack e)) - (done))))) + (ptk/emit! + store + (dwc/update-shapes [(:id shape1)] update-shape) + :the/end))))) (t/deftest test-reset-changes (t/async done @@ -84,40 +77,29 @@ update-shape (fn [shape] (merge shape {:fill-color clr/test - :fill-opacity 0.5}))] + :fill-opacity 0.5})) - (->> state - (the/do-watch-update (dwc/update-shapes [(:id shape1)] - update-shape)) + store (the/prepare-store state done + (fn [new-state] + (let [shape1 (thp/get-shape new-state :shape1) - (rx/mapcat #(the/do-watch-update - (dwl/reset-component (:id instance1)) %)) + [[group shape1] [c-group c-shape1] component] + (thl/resolve-instance-and-main + new-state + (:id instance1)) - (rx/do - (fn [new-state] - (let [shape1 (thp/get-shape new-state :shape1) + file (dwlh/get-local-file new-state)] - [[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (:id instance1)) + (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)))))] - 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))))) - - (rx/subs - done - #(do - (println (.-stack %)) - (done))))) - - (catch :default e - (println (.-stack e)) - (done))))) + (ptk/emit! + store + (dwc/update-shapes [(:id shape1)] update-shape) + (dwl/reset-component (:id instance1)) + :the/end))))) diff --git a/frontend/test/app/shapes_test.cljs b/frontend/test/app/shapes_test.cljs index 977ec64e8..cb3564c07 100644 --- a/frontend/test/app/shapes_test.cljs +++ b/frontend/test/app/shapes_test.cljs @@ -12,7 +12,8 @@ [cljs.pprint :refer [pprint]] [cljs.test :as t :include-macros true] [clojure.stacktrace :as stk] - [linked.core :as lks])) + [linked.core :as lks] + [potok.core :as ptk])) (t/use-fixtures :each {:before thp/reset-idmap!}) @@ -33,27 +34,20 @@ shape (thp/get-shape state :shape1)] (t/is (= (:name shape) "Rect 1"))))) -(t/deftest synctest - (t/testing "synctest" - (let [state {:workspace-local {:color-for-rename "something"}} - new-state (->> state - (the/do-update - dwl/clear-color-for-rename))] - (t/is (= (get-in new-state [:workspace-local :color-for-rename]) - nil))))) - (t/deftest asynctest (t/testing "asynctest" (t/async done (let [state {} - color {:color clr/white}] - (->> state - (the/do-watch-update - (dwl/add-recent-color color)) - (rx/map - (fn [new-state] - (t/is (= (get-in new-state [:workspace-data - :recent-colors]) - [color])))) - (rx/subs done done)))))) + color {:color clr/white} + + store (the/prepare-store state done + (fn [new-state] + (t/is (= (get-in new-state [:workspace-data + :recent-colors]) + [color]))))] + + (ptk/emit! + store + (dwl/add-recent-color color) + :the/end))))) diff --git a/frontend/test/app/test_helpers/events.cljs b/frontend/test/app/test_helpers/events.cljs index 85c6488ad..82857a30e 100644 --- a/frontend/test/app/test_helpers/events.cljs +++ b/frontend/test/app/test_helpers/events.cljs @@ -1,31 +1,50 @@ (ns app.test-helpers.events (:require - [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] - [beicon.core :as rx] - [potok.core :as ptk] [app.common.uuid :as uuid] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.helpers :as cph] - [app.main.data.workspace :as dw])) + [app.main.data.workspace :as dw] + [cljs.test :as t :include-macros true] + [cljs.pprint :refer [pprint]] + [beicon.core :as rx] + [potok.core :as ptk])) ;; ---- Helpers to manage global events -(defn do-update + +(defn prepare-store + "Create a store with the given initial state. Wait until + a :the/end event occurs, and then call the function with + the final state at this point." + [state done completed-cb] + (let [store (ptk/store {:state state}) + stream (ptk/input-stream store) + stream (->> stream + (rx/take-until (rx/filter #(= :the/end %) stream)) + (rx/last) + (rx/do + (fn [] + (completed-cb @store))) + (rx/subs done #(throw %)))] + store)) + +;; Remove definitely when we ensure that the above method works +;; well in more advanced tests. +#_(defn do-update "Execute an update event and returns the new state." [event state] (ptk/update event state)) -(defn do-watch +#_(defn do-watch "Execute a watch event and return an observable, that emits once a list with all new events." [event state] (->> (ptk/watch event state nil) (rx/reduce conj []))) -(defn do-watch-update +#_(defn do-watch-update "Execute a watch event and return an observable, that emits once the new state, after all new events applied in sequence (considering they are all update events)." diff --git a/frontend/test/app/test_helpers/libraries.cljs b/frontend/test/app/test_helpers/libraries.cljs index a49faa518..55ab69cb4 100644 --- a/frontend/test/app/test_helpers/libraries.cljs +++ b/frontend/test/app/test_helpers/libraries.cljs @@ -26,11 +26,6 @@ (t/is (some? (:component-id shape))) (t/is (nil? (:component-root? shape)))) -(defn is-instance-head - [shape] - (t/is (some? (:shape-ref shape))) - (t/is (some? (:component-id shape)))) - (defn is-instance-child [shape] (t/is (some? (:shape-ref shape))) @@ -38,6 +33,12 @@ (t/is (nil? (:component-file shape))) (t/is (nil? (:component-root? shape)))) +(defn is-instance-inner + [shape] + (if (some? (:component-id shape)) + (is-instance-subroot shape) + (is-instance-child shape))) + (defn is-noninstance [shape] (t/is (nil? (:shape-ref shape))) @@ -53,29 +54,33 @@ (:id file)))) (defn resolve-instance + "Get the shape with the given id and all its children, and + verify that they are a well constructed instance tree." [state root-inst-id] (let [page (thp/current-page state) root-inst (cph/get-shape page root-inst-id) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)] - ;; Validate that the instance tree is well constructed (is-instance-root (first shapes-inst)) (run! is-instance-child (rest shapes-inst)) shapes-inst)) (defn resolve-noninstance + "Get the shape with the given id and all its children, and + verify that they are not a component instance." [state root-inst-id] (let [page (thp/current-page state) root-inst (cph/get-shape page root-inst-id) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)] - ;; Validate that the tree is not an instance (run! is-noninstance shapes-inst) shapes-inst)) (defn resolve-instance-and-main + "Get the shape with the given id and all its children, and also + the main component and all its shapes." [state root-inst-id] (let [page (thp/current-page state) root-inst (cph/get-shape page root-inst-id) @@ -89,13 +94,20 @@ unique-refs (into #{} (map :shape-ref) shapes-inst) main-exists? (fn [shape] - (t/is (some #(= (:id %) (:shape-ref shape)) - shapes-main)))] + (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)) - (run! is-instance-child (rest shapes-inst)) - (run! is-noninstance shapes-main) + (run! is-instance-inner (rest shapes-inst)) (t/is (= (count shapes-inst) (count shapes-main) (count unique-refs))) @@ -104,6 +116,7 @@ [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)