diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index 277c9678e..b43429eb8 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -146,6 +146,10 @@ shapes (remove #(= (:type %) :frame) (vals objects))] (some contains-shape-fn shapes))) +(defn get-top-frame + [objects] + (get objects uuid/zero)) + (defn get-parent "Retrieve the id of the parent for the shape-id (if exists)" [shape-id objects] diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index c446c5baa..ce3ab54f1 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -12,6 +12,7 @@ [app.common.exceptions :as ex] [app.common.geom.point :as gpt] [app.util.object :as obj] + [app.util.globals :as globals] [cuerdas.core :as str] [goog.dom :as dom])) @@ -130,9 +131,9 @@ (defn create-element ([tag] - (.createElement js/document tag)) + (.createElement globals/document tag)) ([ns tag] - (.createElementNS js/document ns tag))) + (.createElementNS globals/document ns tag))) (defn set-html! [el html] @@ -201,11 +202,11 @@ (defn fullscreen? [] (cond - (obj/in? js/document "webkitFullscreenElement") - (boolean (.-webkitFullscreenElement js/document)) + (obj/in? globals/document "webkitFullscreenElement") + (boolean (.-webkitFullscreenElement globals/document)) - (obj/in? js/document "fullscreenElement") - (boolean (.-fullscreenElement js/document)) + (obj/in? globals/document "fullscreenElement") + (boolean (.-fullscreenElement globals/document)) :else (ex/raise :type :not-supported @@ -242,17 +243,17 @@ (-> event get-target (.releasePointerCapture (.-pointerId event)))) (defn get-root [] - (query js/document "#app")) + (query globals/document "#app")) (defn ^boolean class? [node class-name] (let [class-list (.-classList ^js node)] (.contains ^js class-list class-name))) (defn get-user-agent [] - (.-userAgent js/navigator)) + (.-userAgent globals/navigator)) (defn active? [node] - (= (.-activeElement js/document) node)) + (= (.-activeElement globals/document) node)) (defn get-data [^js node ^string attr] (.getAttribute node (str "data-" attr))) diff --git a/frontend/src/app/util/globals.js b/frontend/src/app/util/globals.js index 4c66efefe..de4539840 100644 --- a/frontend/src/app/util/globals.js +++ b/frontend/src/app/util/globals.js @@ -29,6 +29,14 @@ goog.scope(function() { } })(); + app.util.globals.document = (function() { + if (typeof goog.global.document !== "undefined") { + return goog.global.document; + } else { + return {}; + } + })(); + app.util.globals.location = (function() { if (typeof goog.global.location !== "undefined") { return goog.global.location; @@ -36,5 +44,13 @@ goog.scope(function() { return {}; } })(); + + app.util.globals.navigator = (function() { + if (typeof goog.global.navigator !== "undefined") { + return goog.global.navigator; + } else { + return {}; + } + })(); }); diff --git a/frontend/tests/app/test_helpers.cljs b/frontend/tests/app/test_helpers.cljs new file mode 100644 index 000000000..b704c2a3d --- /dev/null +++ b/frontend/tests/app/test_helpers.cljs @@ -0,0 +1,89 @@ +(ns app.test-helpers + (: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])) + + +;; ---- Helpers to manage global events + +(defn do-update + "Execute an update event and returns the new state." + [event state] + (ptk/update event state)) + +(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 + "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)." + [event state] + (->> (do-watch event state) + (rx/map (fn [new-events] + (reduce + (fn [new-state new-event] + (do-update new-event new-state)) + state + new-events))))) + + +;; ---- Helpers to manage pages and objects + +(def current-file-id (uuid/next)) + +(def initial-state + {:current-file-id current-file-id + :current-page-id nil + :workspace-local dw/workspace-local-default + :workspace-data {:id current-file-id + :components {} + :pages [] + :pages-index {}} + :workspace-libraries {}}) + +(defn current-page + [state] + (let [page-id (:current-page-id state)] + (get-in state [:workspace-data :pages-index page-id]))) + +(defn sample-page + ([state] (sample-page state {})) + ([state {:keys [id name] :as props + :or {id (uuid/next) + name "page1"}}] + (-> state + (assoc :current-page-id id) + (update :workspace-data + cp/process-changes + [{:type :add-page + :id id + :name name}])))) + +(defn sample-shape + ([state type] (sample-shape state type {})) + ([state type props] + (let [page (current-page state) + frame (cph/get-top-frame (:objects page)) + shape (-> (cp/make-minimal-shape type) + (gsh/setup {:x 0 :y 0 :width 1 :height 1}) + (merge props))] + (update state :workspace-data + cp/process-changes + [{:type :add-obj + :id (:id shape) + :page-id (:id page) + :frame-id (:id frame) + :obj shape}])))) + diff --git a/frontend/tests/app/test_library_sync.cljs b/frontend/tests/app/test_library_sync.cljs index ec9ba9aeb..a624e900f 100644 --- a/frontend/tests/app/test_library_sync.cljs +++ b/frontend/tests/app/test_library_sync.cljs @@ -2,60 +2,97 @@ (:require [cljs.test :as t :include-macros true] [cljs.pprint :refer [pprint]] [beicon.core :as rx] - [potok.core :as ptk] - [app.main.data.workspace.libraries :as dwl])) + [app.test-helpers :as th] + [app.common.data :as d] + [app.common.uuid :as uuid] + [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])) -;; ---- Helpers +(t/deftest test-create-page + (t/testing "create page" + (let [state (-> th/initial-state + (th/sample-page)) + page (th/current-page state)] + (t/is (= (:name page) "page1"))))) -(defn do-update - [state event cb] - (let [new-state (ptk/update event state)] - (cb new-state))) - -(defn do-watch - [state event cb] - (->> (ptk/watch event state nil) - (rx/reduce conj []) - (rx/subs cb))) - -(defn do-watch-update - [state event & cbs] - (do-watch state event - (fn [events] - (t/is (= (count events) (count cbs))) - (reduce - (fn [new-state [event cb]] - (do-update new-state event cb)) - state - (map list events cbs))))) - -;; ---- Tests +(t/deftest test-create-shape + (t/testing "create shape" + (let [id (uuid/next) + state (-> th/initial-state + (th/sample-page) + (th/sample-shape :rect {:id id + :name "Rect 1"})) + page (th/current-page state) + shape (cph/get-shape page id)] + (t/is (= (:name shape) "Rect 1"))))) (t/deftest synctest (t/testing "synctest" - (let [state {:workspace-local {:color-for-rename "something"}}] - (do-update - state - dwl/clear-color-for-rename - (fn [new-state] - (t/is (= (get-in new-state [:workspace-local :color-for-rename]) - nil))))))) + (let [state {:workspace-local {:color-for-rename "something"}} + new-state (->> state + (th/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 "#ffffff"}] - (do-watch-update - state - (dwl/add-recent-color color) - (fn [new-state] - (t/is (= (get-in new-state [:workspace-file - :data - :recent-colors]) - [color])) - (t/is (= (get-in new-state [:workspace-data - :recent-colors]) - [color])) - (done))))))) + (->> state + (th/do-watch-update + (dwl/add-recent-color color)) + (rx/map + (fn [new-state] + (t/is (= (get-in new-state [:workspace-file + :data + :recent-colors]) + [color])) + (t/is (= (get-in new-state [:workspace-data + :recent-colors]) + [color])))) + (rx/subs done)))))) + +(t/deftest test-add-component + (t/testing "Add a component" + (t/async done + (let [id1 (uuid/next) + state (-> th/initial-state + (th/sample-page) + (th/sample-shape :rect + {:id id1 + :name "Rect 1"}))] + (->> state + (th/do-update (dw/select-shape id1)) + (th/do-watch-update dwl/add-component) + (rx/map + (fn [new-state] + (let [page (th/current-page new-state) + shape (cph/get-shape page id1) + group (cph/get-shape page (:parent-id shape)) + + component (cph/get-component + (:component-id group) + (:current-file-id new-state) + (dwlh/get-local-file new-state) + nil) + + c-shape (cph/get-shape + component + (:shape-ref shape)) + + c-group (cph/get-shape + component + (:shape-ref group))] + + (t/is (= (:name shape) "Rect 1")) + (t/is (= (:name group) "Component-1")) + (t/is (= (:name component) "Component-1")) + (t/is (= (:name c-shape) "Rect 1")) + (t/is (= (:name c-group) "Component-1"))))) + + (rx/subs done))))))