Merge branch 'om'
This commit is contained in:
commit
3f687afb4b
16 changed files with 17304 additions and 897 deletions
|
@ -1,59 +0,0 @@
|
|||
(ns grub.integration-test
|
||||
(:require [grub.db :as db]
|
||||
[grub.websocket :as ws]
|
||||
[clj-webdriver.taxi :as taxi]
|
||||
[clj-webdriver.core :as webdriver]
|
||||
[clojure.test :as test]))
|
||||
|
||||
;; Hard-code path to chromedriver
|
||||
(defn set-chromedriver-path! []
|
||||
(System/setProperty "webdriver.chrome.driver" "bin/chromedriver"))
|
||||
|
||||
(defn add-grub [driver grub-text]
|
||||
(taxi/input-text driver "#add-grub-input" grub-text)
|
||||
(taxi/click driver {:text "Add"}))
|
||||
|
||||
(defn get-driver [url]
|
||||
(webdriver/start {:browser :chrome} url))
|
||||
|
||||
(defn get-rand-grub []
|
||||
(str "testgrub" (rand-int 10000)))
|
||||
|
||||
(defn test-adding-synced-grubs [url driver1 driver2]
|
||||
(taxi/to driver1 url)
|
||||
(taxi/to driver2 url)
|
||||
(let [grubs (repeatedly 4 get-rand-grub)]
|
||||
(doseq [grub grubs]
|
||||
(add-grub driver1 grub))
|
||||
(doseq [grub grubs]
|
||||
(test/is (taxi/find-element driver2 {:text grub})
|
||||
"Added grubs should appear in other browser")))
|
||||
(db/clear-grubs))
|
||||
|
||||
(defn test-grubs-are-stored-on-server [url driver]
|
||||
(taxi/to driver url)
|
||||
(let [grubs (repeatedly 4 get-rand-grub)]
|
||||
(doseq [grub grubs]
|
||||
(add-grub driver grub))
|
||||
(Thread/sleep 200)
|
||||
(taxi/refresh driver)
|
||||
(Thread/sleep 200)
|
||||
(doseq [grub grubs]
|
||||
(test/is (taxi/find-element driver {:text grub})
|
||||
"Previously added grubs should be loaded on refresh")))
|
||||
(db/clear-grubs))
|
||||
|
||||
|
||||
(defn run [port]
|
||||
(set-chromedriver-path!)
|
||||
(let [db-chan (db/connect-and-handle-events "grub-integration-test")
|
||||
site-url (str "http://localhost:" port)]
|
||||
(println "Starting integration test")
|
||||
(ws/pass-received-events-to-clients-and-db db-chan)
|
||||
(let [driver1 (get-driver site-url)
|
||||
driver2 (get-driver site-url)]
|
||||
(test-grubs-are-stored-on-server site-url driver1)
|
||||
(test-adding-synced-grubs site-url driver1 driver2)
|
||||
(taxi/quit driver1)
|
||||
(taxi/quit driver2)))
|
||||
(db/clear-grubs))
|
|
@ -4,7 +4,7 @@
|
|||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:dependencies [[org.clojure/clojure "1.6.0"]
|
||||
[org.clojure/clojurescript "0.0-2234"]
|
||||
[org.clojure/clojurescript "0.0-2277"]
|
||||
[org.clojure/core.async "0.1.303.0-886421-alpha"]
|
||||
[http-kit "2.1.18"]
|
||||
[compojure "1.1.8"]
|
||||
|
@ -14,7 +14,10 @@
|
|||
[prismatic/dommy "0.1.2"]
|
||||
[com.novemberain/monger "2.0.0"]
|
||||
[org.clojure/tools.cli "0.3.1"]
|
||||
[clj-webdriver "0.6.1" :exclusions [org.clojure/core.cache]]]
|
||||
[clj-webdriver "0.6.1" :exclusions [org.clojure/core.cache]]
|
||||
[om "0.7.0"]
|
||||
[sablono "0.2.17"]
|
||||
[cljs-uuid "0.0.4"]]
|
||||
:profiles {:uberjar {:aot :all}}
|
||||
:min-lein-version "2.1.2"
|
||||
:plugins [[lein-cljsbuild "1.0.3"]
|
||||
|
@ -27,7 +30,7 @@
|
|||
:prod {:source-paths ["src/cljs"]
|
||||
:compiler {:output-to "public/js/grub.js"
|
||||
:optimizations :advanced}}}}
|
||||
:source-paths ["src/clj" "integration"]
|
||||
:source-paths ["src/clj" "src/test"]
|
||||
:test-paths ["spec/clj"]
|
||||
:ring {:handler grub.core/app}
|
||||
:uberjar-name "grub-standalone.jar"
|
||||
|
|
|
@ -1,161 +1,162 @@
|
|||
html, body {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.recipes-title {
|
||||
clear: right;
|
||||
clear: right;
|
||||
}
|
||||
|
||||
.recipe-list {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#recipe-grubs {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.recipe-add-grubs-btn {
|
||||
float: right;
|
||||
clear: both;
|
||||
margin: 2px;
|
||||
float: right;
|
||||
clear: both;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leftmost-column {
|
||||
margin-bottom: 45px;
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
|
||||
.grub-close {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
tr:hover .grub-close {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#grub-list {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.add-grub-input-form .input-group-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grub-item {
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.grub-item.grub-active {
|
||||
color: #ffffff;
|
||||
background-color: rgb(71, 73, 73);
|
||||
color: #ffffff;
|
||||
background-color: rgb(71, 73, 73);
|
||||
}
|
||||
|
||||
.grub-item .input-span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.grub-item .grub-static {
|
||||
display: inline;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.grub-item .grub-input {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.grub-item.edit .grub-static {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.grub-item.edit .grub-input {
|
||||
display: inline;
|
||||
width: 100%;
|
||||
display: inline;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grub-item .glyphicon {
|
||||
margin-right: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.completed {
|
||||
text-decoration: line-through;
|
||||
color: #a9a9a9;
|
||||
text-decoration: line-through;
|
||||
color: #a9a9a9;
|
||||
}
|
||||
|
||||
.completed.edit {
|
||||
text-decoration: none;
|
||||
color: default;
|
||||
text-decoration: none;
|
||||
color: default;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 10px;
|
||||
padding-top: 0px;
|
||||
padding: 10px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
background-color: #ffffff;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.recipe-panel {
|
||||
padding: 0px;
|
||||
margin-bottom: -1px;
|
||||
padding: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.recipe-panel > .recipe-header {
|
||||
background-color: #ffffff;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.recipe-header {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border-bottom: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.recipe-header-input {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
border-bottom: none;
|
||||
width: inherit;
|
||||
display: inline;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
border-bottom: none;
|
||||
display: inline;
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.recipe-header-input:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.recipe-btn {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.recipe-grubs {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.recipe-grubs-input {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
border-bottom: none;
|
||||
resize: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
border-bottom: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.recipe-grubs-input:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
border-bottom: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
|
16644
public/js/react-0.9.0.js
Normal file
16644
public/js/react-0.9.0.js
Normal file
File diff suppressed because it is too large
Load diff
21
public/js/react-0.9.0.min.js
vendored
Normal file
21
public/js/react-0.9.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,84 +0,0 @@
|
|||
(ns grub.db-spec
|
||||
(:require [speclj.core :refer :all]
|
||||
[grub.db :as db]
|
||||
[monger.collection :as mc]
|
||||
[clojure.core.async :refer [>!! <!! timeout]]))
|
||||
|
||||
(def test-db "grub-test")
|
||||
|
||||
(defn short-delay []
|
||||
(<!! (timeout 50)))
|
||||
|
||||
(defn get-test-grub []
|
||||
{:_id (str "grub" (rand-int 10000))
|
||||
:grub (str "testtext" (rand-int 1000))
|
||||
:completed ([true false] (rand-int 1))})
|
||||
|
||||
(describe
|
||||
"grub.db"
|
||||
(before-all (db/connect-and-handle-events test-db))
|
||||
(before (db/clear-grubs))
|
||||
(describe "Add"
|
||||
(it "should add a grub when an add event comes"
|
||||
(let [test-grub "testgrub"
|
||||
test-id 12345]
|
||||
(>!! @db/incoming-events {:event :add
|
||||
:_id test-id
|
||||
:grub test-grub
|
||||
:completed false})
|
||||
(short-delay)
|
||||
(should=
|
||||
{:_id test-id :grub test-grub :completed false}
|
||||
(mc/find-one-as-map db/grub-collection {:_id test-id})))))
|
||||
|
||||
(describe "Complete"
|
||||
(it "should complete a grub when a complete event comes"
|
||||
(let [test-grub {:_id 123456 :completed false}]
|
||||
(mc/insert db/grub-collection test-grub)
|
||||
(>!! @db/incoming-events {:event :complete :_id (:_id test-grub)})
|
||||
(short-delay)
|
||||
(should=
|
||||
{:_id (:_id test-grub) :completed true}
|
||||
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
|
||||
|
||||
(describe "Uncomplete"
|
||||
(it "should uncomplete a grub when an uncomplete event comes"
|
||||
(let [test-grub {:_id 123456 :completed true}]
|
||||
(mc/insert db/grub-collection test-grub)
|
||||
(>!! @db/incoming-events {:event :uncomplete :_id (:_id test-grub)})
|
||||
(short-delay)
|
||||
(should=
|
||||
{:_id (:_id test-grub) :completed false}
|
||||
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
|
||||
|
||||
(describe "Update"
|
||||
(it "should update grub info when an update event comes"
|
||||
(let [test-grub {:_id 123456 :grub "original"}]
|
||||
(mc/insert db/grub-collection test-grub)
|
||||
(>!! @db/incoming-events {:event :update
|
||||
:_id (:_id test-grub)
|
||||
:grub "updated"})
|
||||
(short-delay)
|
||||
(should=
|
||||
{:_id (:_id test-grub) :grub "updated"}
|
||||
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
|
||||
|
||||
(describe "Delete"
|
||||
(it "should delete a grub when a delete event comes"
|
||||
(let [test-grub {:_id 123456 :completed true}]
|
||||
(mc/insert db/grub-collection test-grub)
|
||||
(>!! @db/incoming-events {:event :delete :_id (:_id test-grub)})
|
||||
(short-delay)
|
||||
(should=
|
||||
nil
|
||||
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
|
||||
|
||||
(describe "Clear all"
|
||||
(it "should delete all grubs"
|
||||
(let [test-grub {:_id 123456 :completed true}]
|
||||
(mc/insert db/grub-collection test-grub)
|
||||
(>!! @db/incoming-events {:event :clear-all})
|
||||
(short-delay)
|
||||
(should
|
||||
(empty?
|
||||
(mc/find-maps db/grub-collection)))))))
|
|
@ -1,7 +1,7 @@
|
|||
(ns grub.core
|
||||
(:require [grub.websocket :as ws]
|
||||
[grub.db :as db]
|
||||
[grub.integration-test :as integration-test]
|
||||
[grub.test.integration.core :as integration-test]
|
||||
[ring.middleware.reload :as reload]
|
||||
[ring.middleware.file :as file]
|
||||
[ring.util.response :as resp]
|
||||
|
@ -25,7 +25,8 @@
|
|||
(html5
|
||||
index-page-header
|
||||
[:body
|
||||
(include-js "http://fb.me/react-0.9.0.js")
|
||||
[:div#container]
|
||||
(include-js "/js/react-0.9.0.min.js")
|
||||
(include-js "/js/jquery.js")
|
||||
(include-js "/js/bootstrap.js")
|
||||
(include-js "/js/grub.js")]))
|
||||
|
@ -34,7 +35,8 @@
|
|||
(html5
|
||||
index-page-header
|
||||
[:body
|
||||
(include-js "http://fb.me/react-0.9.0.js")
|
||||
[:div#container]
|
||||
(include-js "/js/react-0.9.0.js")
|
||||
(include-js "/js/out/goog/base.js")
|
||||
(include-js "/js/jquery.js")
|
||||
(include-js "/js/bootstrap.js")
|
||||
|
@ -58,14 +60,13 @@
|
|||
(handler/site routes))))
|
||||
|
||||
(def default-port 3000)
|
||||
(def integration-test-port 3456)
|
||||
|
||||
(defn start-server [port]
|
||||
(httpkit/run-server app {:port port}))
|
||||
|
||||
(defn run-integration-test []
|
||||
(let [stop-server (start-server integration-test-port)]
|
||||
(integration-test/run integration-test-port)
|
||||
(let [stop-server (start-server integration-test/server-port)]
|
||||
(integration-test/run)
|
||||
(stop-server)))
|
||||
|
||||
(defn start-production-server [{:keys [port mongo-url]}]
|
||||
|
|
|
@ -12,6 +12,13 @@
|
|||
(defn clear-grubs []
|
||||
(mc/drop @db grub-collection))
|
||||
|
||||
(defn clear-recipes []
|
||||
(mc/drop @db recipe-collection))
|
||||
|
||||
(defn clear-all []
|
||||
(clear-grubs)
|
||||
(clear-recipes))
|
||||
|
||||
(defmulti handle-event :event :default :unknown-event)
|
||||
|
||||
(defn insert-grub [event]
|
||||
|
@ -38,9 +45,9 @@
|
|||
{mo/$set {:completed false}}))
|
||||
|
||||
(defmethod handle-event :update-grub [event]
|
||||
(mc/update @db grub-collection
|
||||
{:_id (:id event)}
|
||||
{mo/$set {:grub (:grub event)}}))
|
||||
(let [orig (mc/find-one-as-map @db grub-collection {:_id (:id event)})
|
||||
new (dissoc event :event-type :id)]
|
||||
(mc/update-by-id @db grub-collection (:id event) (merge orig new))))
|
||||
|
||||
(defmethod handle-event :clear-all-grubs [event]
|
||||
(clear-grubs))
|
||||
|
@ -60,22 +67,18 @@
|
|||
(println "Cannot handle unknown event:" event))
|
||||
|
||||
(defn get-current-grubs []
|
||||
(let [raw-grubs (mc/find-maps @db grub-collection)
|
||||
sorted-grubs (sort-by :_id (vec raw-grubs))
|
||||
grubs (map (fn [g] (-> g
|
||||
(select-keys [:_id :grub :completed])
|
||||
(clojure.set/rename-keys {:_id :id})))
|
||||
sorted-grubs)]
|
||||
grubs))
|
||||
(->> (mc/find-maps @db grub-collection)
|
||||
(sort-by :_id)
|
||||
(map #(select-keys % [:_id :grub :completed]))
|
||||
(map #(clojure.set/rename-keys % {:_id :id}))
|
||||
(vec)))
|
||||
|
||||
(defn get-current-recipes []
|
||||
(let [raw-recipes (mc/find-maps @db recipe-collection)
|
||||
sorted-recipes (sort-by :_id (vec raw-recipes))
|
||||
recipes (map (fn [r] (-> r
|
||||
(select-keys [:_id :name :grubs])
|
||||
(clojure.set/rename-keys {:_id :id})))
|
||||
sorted-recipes)]
|
||||
recipes))
|
||||
(->> (mc/find-maps @db recipe-collection)
|
||||
(sort-by :_id)
|
||||
(map #(select-keys % [:_id :name :grubs]))
|
||||
(map #(clojure.set/rename-keys % {:_id :id}))
|
||||
(vec)))
|
||||
|
||||
(def production-db "grub")
|
||||
(def development-db "grub-dev")
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
(ns grub.core
|
||||
(:require [grub.view :as view]
|
||||
(:require [grub.state :as state]
|
||||
[grub.websocket :as ws]
|
||||
[cljs.core.async :as a :refer [<! >! chan]])
|
||||
(:require-macros [grub.macros :refer [log logs go-loop]]
|
||||
(:require-macros [grub.macros :refer [log logs]]
|
||||
[cljs.core.async.macros :refer [go]]))
|
||||
|
||||
(defn wire-channels-together []
|
||||
(let [to-remote (chan)
|
||||
to-view (chan)
|
||||
to-state (chan)
|
||||
from-remote (ws/get-remote-chan to-remote)
|
||||
from-view (view/setup-and-get-view-events to-view)]
|
||||
(a/pipe from-remote to-view)
|
||||
(a/pipe from-view to-remote)))
|
||||
from-state (state/update-state-and-render to-state)]
|
||||
(a/pipe from-remote to-state)
|
||||
(a/pipe from-state to-remote)))
|
||||
|
||||
(wire-channels-together)
|
||||
|
|
72
src/cljs/grub/state.cljs
Normal file
72
src/cljs/grub/state.cljs
Normal file
|
@ -0,0 +1,72 @@
|
|||
(ns grub.state
|
||||
(:require [grub.view.app :as view]
|
||||
[cljs.core.async :as a :refer [<! >! chan]])
|
||||
(:require-macros [grub.macros :refer [log logs]]
|
||||
[cljs.core.async.macros :refer [go go-loop]]))
|
||||
|
||||
(def app-state (atom {:grubs {}
|
||||
:recipes {}}))
|
||||
|
||||
(defmulti handle-event (fn [event state] (:event event))
|
||||
:default :unknown-event)
|
||||
|
||||
(defmethod handle-event :unknown-event [event state]
|
||||
state)
|
||||
|
||||
(defn new-grub [id grub completed]
|
||||
{:id id :grub grub :completed completed})
|
||||
|
||||
(defmethod handle-event :add-grub [event state]
|
||||
(let [grub (new-grub (:id event) (:grub event) (:completed event))]
|
||||
(assoc-in state [:grubs (:id grub)] grub)))
|
||||
|
||||
(defn map-by-key [key coll]
|
||||
(->> coll
|
||||
(map (fn [a] [(get a key) a]))
|
||||
(into {})))
|
||||
|
||||
(defmethod handle-event :add-grub-list [event state]
|
||||
(->> event
|
||||
:grubs
|
||||
(map-by-key :id)
|
||||
(merge (:grubs state))
|
||||
(assoc state :grubs)))
|
||||
|
||||
(defmethod handle-event :update-grub [event state]
|
||||
(let [new-grub-info (dissoc event :event-type)
|
||||
orig-grub (get-in state [:grubs (:id event)])]
|
||||
(assoc-in state [:grubs (:id event)] (merge orig-grub new-grub-info))))
|
||||
|
||||
(defmethod handle-event :clear-all-grubs [event state]
|
||||
(assoc state :grubs {}))
|
||||
|
||||
(defn new-recipe [id name grubs]
|
||||
{:id id :name name :grubs grubs})
|
||||
|
||||
(defmethod handle-event :add-recipe [event state]
|
||||
(let [recipe (new-recipe (:id event) (:name event) (:grubs event))]
|
||||
(assoc-in state [:recipes (:id recipe)] recipe)))
|
||||
|
||||
(defmethod handle-event :add-recipe-list [event state]
|
||||
(->> event
|
||||
:recipes
|
||||
(map-by-key :id)
|
||||
(merge (:recipes state))
|
||||
(assoc state :recipes)))
|
||||
|
||||
(defmethod handle-event :update-recipe [event state]
|
||||
(-> state
|
||||
(assoc-in [:recipes (:id event) :name] (:name event))
|
||||
(assoc-in [:recipes (:id event) :grubs] (:grubs event))))
|
||||
|
||||
(defn update-state-and-render [remote]
|
||||
(let [out (chan)
|
||||
view-events (view/render-app app-state)]
|
||||
(go-loop []
|
||||
(let [[event ch] (alts! [remote view-events])
|
||||
new-state (handle-event event @app-state)]
|
||||
(reset! app-state new-state)
|
||||
(when (= ch view-events)
|
||||
(>! out event))
|
||||
(recur)))
|
||||
out))
|
|
@ -1,24 +0,0 @@
|
|||
(ns grub.view
|
||||
(:require [grub.view.dom :as dom]
|
||||
[grub.view.grub :as grub-view]
|
||||
[grub.view.recipe :as recipe-view]
|
||||
[dommy.core :as dommy]
|
||||
[cljs.core.async :as a :refer [<! >! chan]])
|
||||
(:require-macros [grub.macros :refer [log logs and-let]]
|
||||
[dommy.macros :refer [deftemplate sel1 node]]
|
||||
[cljs.core.async.macros :refer [go go-loop]]))
|
||||
|
||||
(defn setup-and-get-view-events [remote-channel]
|
||||
(dom/render-body)
|
||||
(let [out (chan)
|
||||
remote (a/mult remote-channel)
|
||||
to-grubs (chan)
|
||||
to-recipes (chan)
|
||||
from-grubs (grub-view/handle-grubs to-grubs)
|
||||
from-recipes (a/mult (recipe-view/handle-recipes to-recipes))]
|
||||
(a/tap remote to-grubs)
|
||||
(a/tap remote to-recipes)
|
||||
(a/tap from-recipes to-grubs)
|
||||
(a/tap from-recipes out)
|
||||
(a/pipe from-grubs out)
|
||||
out))
|
48
src/cljs/grub/view/app.cljs
Normal file
48
src/cljs/grub/view/app.cljs
Normal file
|
@ -0,0 +1,48 @@
|
|||
(ns grub.view.app
|
||||
(:require [om.core :as om :include-macros true]
|
||||
[sablono.core :as html :refer-macros [html]]
|
||||
[cljs.core.async :as a :refer [<! put! chan]]
|
||||
[grub.view.dom :as dom]
|
||||
[grub.view.grub :as grub]
|
||||
[grub.view.recipe :as recipe])
|
||||
(:require-macros [grub.macros :refer [log logs]]
|
||||
[cljs.core.async.macros :refer [go go-loop]]))
|
||||
|
||||
(defn app-view [props owner]
|
||||
(reify
|
||||
om/IRender
|
||||
(render [this]
|
||||
(html
|
||||
[:div.container
|
||||
[:div.row
|
||||
[:div.col-sm-6.leftmost-column
|
||||
(om/build grub/grubs-view (:grubs props))]
|
||||
[:div.col-sm-6
|
||||
(om/build recipe/recipes-view (:recipes props))]]]))
|
||||
om/IWillMount
|
||||
(will-mount [_]
|
||||
(let [>events (om/get-shared owner :>events)]
|
||||
(dom/on-document-mousedown #(put! >events {:type :body-mousedown :event %}))))))
|
||||
|
||||
(defn render-app [state]
|
||||
(let [grub-add (chan)
|
||||
grub-update (chan)
|
||||
grub-clear-all (chan)
|
||||
recipe-add (chan)
|
||||
recipe-add-grubs (chan)
|
||||
recipe-update (chan)
|
||||
out (a/merge [grub-add grub-update grub-clear-all recipe-add recipe-add-grubs recipe-update])
|
||||
>events (chan)
|
||||
<events (a/pub >events :type)]
|
||||
(om/root app-view
|
||||
state
|
||||
{:target (.getElementById js/document "container")
|
||||
:shared {:grub-add grub-add
|
||||
:grub-update grub-update
|
||||
:grub-clear-all grub-clear-all
|
||||
:recipe-add recipe-add
|
||||
:recipe-add-grubs recipe-add-grubs
|
||||
:recipe-update recipe-update
|
||||
:>events >events
|
||||
:<events <events}})
|
||||
out))
|
|
@ -1,317 +1,14 @@
|
|||
(ns grub.view.dom
|
||||
(:require [dommy.core :as dommy]
|
||||
[cljs.core.async :as a :refer [<! >! chan]])
|
||||
(:require-macros [grub.macros :refer [log logs go-loop]]
|
||||
[dommy.macros :refer [deftemplate sel1 node]]
|
||||
[cljs.core.async.macros :refer [go]]))
|
||||
(ns grub.view.dom)
|
||||
|
||||
(defn listen
|
||||
([el type] (listen el type nil))
|
||||
([el type f] (listen el type f (chan)))
|
||||
([el type f out]
|
||||
(let [push-fn (fn [e] (when f (f e)) (go (>! out e)))
|
||||
unlisten #(do (dommy/unlisten! el type push-fn)
|
||||
(a/close! out))]
|
||||
(dommy/listen! el type push-fn)
|
||||
{:chan out :unlisten unlisten})))
|
||||
(defn enter-pressed? [event]
|
||||
(let [enter-keycode 13]
|
||||
(= (.-which event) enter-keycode)))
|
||||
|
||||
(def ENTER-KEYCODE 13)
|
||||
(defn click-on-self? [event node]
|
||||
(.contains node (.-target event)))
|
||||
|
||||
(defn listen-once
|
||||
([el type] (listen el type nil))
|
||||
([el type f] (listen el type f (chan)))
|
||||
([el type f out]
|
||||
(let [push-fn (fn [e] (when f (f e)) (go (>! out e)))
|
||||
unlisten #(do (dommy/unlisten! el type push-fn)
|
||||
(a/close! out))]
|
||||
(dommy/listen-once! el type push-fn)
|
||||
{:chan out :unlisten unlisten})))
|
||||
(defn on-document-mousedown [f]
|
||||
(.addEventListener js/document "mousedown" f))
|
||||
|
||||
(defn get-away-clicks [elem]
|
||||
(let [{c :chan unlisten :unlisten} (listen (sel1 :body) :click)
|
||||
filtered-chan (a/filter< #(not (dommy/descendant? (.-target %) elem)) c)]
|
||||
{:unlisten unlisten :chan filtered-chan}))
|
||||
|
||||
(defn get-clicks [elem]
|
||||
(listen elem :click))
|
||||
|
||||
(defn get-enters [elem]
|
||||
(let [{c :chan unlisten :unlisten} (listen elem :keyup)
|
||||
filtered-chan (a/filter< #(= (.-which %) ENTER-KEYCODE) c)]
|
||||
{:unlisten unlisten
|
||||
:chan filtered-chan}))
|
||||
|
||||
(defn get-ctrl-enters []
|
||||
(let [{c :chan unlisten :unlisten} (listen (sel1 :body) :keyup)
|
||||
filtered-chan (a/filter< #(and (= (.-which %) ENTER-KEYCODE)
|
||||
(.-ctrlKey %))
|
||||
c)]
|
||||
{:chan filtered-chan :unlisten unlisten}))
|
||||
|
||||
(defn get-body-enters []
|
||||
(get-enters (sel1 :body)))
|
||||
|
||||
(def add-grub-text
|
||||
(node [:input.form-control {:id "add-grub-input" :type "text" :placeholder "2 grubs"}]))
|
||||
|
||||
(def add-grub-btn
|
||||
(node [:button.btn.btn-primary {:id "add-grub-btn" :type "button"} "Add"]))
|
||||
|
||||
(def clear-all-btn
|
||||
(node [:button.btn.hidden.pull-right
|
||||
{:id "clear-all-btn" :type "button"}
|
||||
"Clear all"]))
|
||||
|
||||
(defn clear-grubs! []
|
||||
(dommy/set-html! (sel1 :#grub-list) ""))
|
||||
|
||||
(defn get-grub-completed-glyph [completed]
|
||||
(node (if completed
|
||||
[:span.glyphicon.glyphicon-check]
|
||||
[:span.glyphicon.glyphicon-unchecked])))
|
||||
|
||||
(defn make-grub-node [id grub completed]
|
||||
(node [:li.list-group-item.grub-item
|
||||
{:id id
|
||||
:class (when completed "completed")}
|
||||
[:span.grub-static
|
||||
(get-grub-completed-glyph completed)
|
||||
[:span.grub-text grub]]
|
||||
[:input.grub-input {:type "text" :value grub}]]))
|
||||
|
||||
(defn grubs-selector []
|
||||
[(sel1 :#grub-list) :.grub-item])
|
||||
|
||||
(defn make-recipe-node
|
||||
([id name grubs] (make-recipe-node id name grubs false))
|
||||
([id name grubs new-recipe]
|
||||
(node [:div.panel.panel-default.recipe-panel
|
||||
{:id id}
|
||||
[:div.panel-heading.recipe-header
|
||||
[:input.form-control.recipe-header-input
|
||||
{:id "recipe-name"
|
||||
:type "text"
|
||||
:placeholder "Grub pie"
|
||||
:value name}]
|
||||
(when-not new-recipe
|
||||
[:button.btn.btn-primary.btn-sm.recipe-add-grubs-btn {:type "button"} "Add Grubs"])]
|
||||
[:div.panel-body.recipe-grubs.hidden
|
||||
[:textarea.form-control.recipe-grubs-input
|
||||
{:id "recipe-grubs"
|
||||
:rows 3
|
||||
:placeholder "2 grubs"}
|
||||
grubs]
|
||||
[:button.btn.btn-primary.hidden.pull-right.recipe-btn.recipe-done-btn
|
||||
{:type "button"} "Done"]]])))
|
||||
|
||||
(def new-recipe (make-recipe-node "new-recipe" "" "" true))
|
||||
|
||||
(def new-recipe-done-btn
|
||||
(sel1 new-recipe ".recipe-done-btn"))
|
||||
|
||||
(defn recipes-selector []
|
||||
[(sel1 :#recipe-list) :.recipe-panel])
|
||||
|
||||
(defn recipe-done-btns-selector []
|
||||
[(sel1 :body) :.recipe-done-btn])
|
||||
|
||||
(defn recipe-done-btn-selector [recipe-elem]
|
||||
(sel1 recipe-elem :.recipe-done-btn))
|
||||
|
||||
(defn recipe-add-grubs-btns-selector []
|
||||
[(sel1 :body) :.recipe-add-grubs-btn])
|
||||
|
||||
(deftemplate main-template []
|
||||
[:div.container
|
||||
[:div.row
|
||||
[:div.col-sm-6.leftmost-column
|
||||
[:h3 "Grub List"]
|
||||
[:div.input-group
|
||||
add-grub-text
|
||||
[:span.input-group-btn
|
||||
add-grub-btn]]
|
||||
[:ul#grub-list.list-group]
|
||||
clear-all-btn]
|
||||
[:div.col-sm-6
|
||||
[:h3.recipes-title "Recipes"]
|
||||
new-recipe
|
||||
[:ul#recipe-list.list-group.recipe-list]]]])
|
||||
|
||||
(deftemplate grub-list-template [grubs]
|
||||
(node (for [grub grubs]
|
||||
(make-grub-node (:id grub) (:grub grub) (:completed grub)))))
|
||||
|
||||
(defn render-body []
|
||||
(dommy/prepend! (sel1 :body) (main-template)))
|
||||
|
||||
(defn render-grub-list [grubs]
|
||||
(let [grub-list (sel1 :#grub-list)]
|
||||
(aset grub-list "innerHTML" "")
|
||||
(dommy/replace-contents! grub-list (grub-list-template grubs))))
|
||||
|
||||
(defn get-add-grub-text []
|
||||
(dommy/value add-grub-text))
|
||||
|
||||
(defn clear-add-grub-text []
|
||||
(dommy/set-value! add-grub-text ""))
|
||||
|
||||
(defn get-recipe-add-grubs-clicks []
|
||||
(->> (:chan (listen (recipe-add-grubs-btns-selector) :click))
|
||||
(a/map< #(dommy/closest (.-selectedTarget %) :.recipe-panel))))
|
||||
|
||||
(defn get-edit-recipe-input-click []
|
||||
(->> (:chan (listen-once (recipes-selector) :click))
|
||||
(a/filter< #(not (dommy/has-class? (.-selectedTarget %) :btn)))
|
||||
(a/map< #(.-selectedTarget %))))
|
||||
|
||||
(defprotocol IHideable
|
||||
(-hide! [this])
|
||||
(-show! [this]))
|
||||
|
||||
(defprotocol IGrub
|
||||
(-activate! [this])
|
||||
(-deactivate! [this])
|
||||
|
||||
(-id [this])
|
||||
(-grub-text [this])
|
||||
|
||||
(-complete! [this])
|
||||
(-uncomplete! [this])
|
||||
(-completed? [this])
|
||||
|
||||
(-set-editing! [this])
|
||||
(-unset-editing! [this])
|
||||
(-editing? [this])
|
||||
(-update-grub! [this grub]))
|
||||
|
||||
(defprotocol IRecipe
|
||||
(-expand! [this])
|
||||
(-unexpand! [this])
|
||||
|
||||
(-update-recipe! [this])
|
||||
|
||||
(-get-name [this])
|
||||
(-get-grubs-str [this])
|
||||
(-get-grubs [this]))
|
||||
|
||||
(defprotocol IClearable
|
||||
(-clear! [this]))
|
||||
|
||||
(extend-type js/HTMLElement
|
||||
IHideable
|
||||
(-hide! [this]
|
||||
(dommy/add-class! this :hidden))
|
||||
(-show! [this]
|
||||
(dommy/remove-class! this :hidden)))
|
||||
|
||||
|
||||
(extend-type js/HTMLElement
|
||||
IGrub
|
||||
(-activate! [this]
|
||||
(dommy/add-class! this :grub-active))
|
||||
(-deactivate! [this]
|
||||
(dommy/remove-class! this :grub-active))
|
||||
|
||||
(-id [this]
|
||||
(.-id this))
|
||||
(-grub-text [this]
|
||||
(.-value (sel1 this :.grub-input)))
|
||||
|
||||
(-complete! [this]
|
||||
(dommy/add-class! this :completed)
|
||||
(dommy/replace! (sel1 this ".glyphicon")
|
||||
(get-grub-completed-glyph true)))
|
||||
(-uncomplete! [this]
|
||||
(dommy/remove-class! this :completed)
|
||||
(dommy/replace! (sel1 this ".glyphicon")
|
||||
(get-grub-completed-glyph false)))
|
||||
(-completed? [this]
|
||||
(dommy/has-class? this :completed))
|
||||
|
||||
(-set-editing! [this]
|
||||
(-deactivate! this)
|
||||
(dommy/add-class! this :edit)
|
||||
(.focus (sel1 this :input)))
|
||||
(-unset-editing! [this]
|
||||
(dommy/remove-class! this :edit))
|
||||
(-editing? [this]
|
||||
(dommy/has-class? this :edit)))
|
||||
|
||||
(defrecord Grub [elem id grub completed]
|
||||
dommy.template/PElement
|
||||
(-elem [this] elem)
|
||||
|
||||
IGrub
|
||||
(-set-editing! [this] (-set-editing! elem))
|
||||
(-unset-editing! [this] (-unset-editing! elem))
|
||||
(-editing? [this] (-editing? elem))
|
||||
|
||||
(-complete! [this] (-complete! elem))
|
||||
(-uncomplete! [this] (-uncomplete! elem))
|
||||
(-completed? [this] (-completed? elem))
|
||||
|
||||
(-set-editing! [this] (-set-editing! elem))
|
||||
(-unset-editing! [this] (-unset-editing! elem))
|
||||
(-editing? [this] (-editing? elem))
|
||||
|
||||
(-update-grub! [this grub]
|
||||
(dommy/set-text! (sel1 elem ".grub-text") grub)
|
||||
(dommy/set-value! (sel1 elem ".grub-input") grub)))
|
||||
|
||||
(defn make-new-grub [id grub completed]
|
||||
(let [node (make-grub-node id grub completed)
|
||||
grub (Grub. node id grub completed)
|
||||
grub-list (sel1 :#grub-list)]
|
||||
grub))
|
||||
|
||||
(defn clear-new-grub-input! []
|
||||
(dommy/set-value! (sel1 :#add-grub-input) ""))
|
||||
|
||||
(defn focus-new-grub-input! []
|
||||
(.focus (sel1 :#add-grub-input)))
|
||||
|
||||
(extend-type js/HTMLDivElement
|
||||
IRecipe
|
||||
(-expand! [this]
|
||||
(dommy/remove-class! (sel1 this ".recipe-grubs") :hidden)
|
||||
(dommy/remove-class! (sel1 this ".recipe-done-btn") :hidden))
|
||||
(-unexpand! [this]
|
||||
(dommy/add-class! (sel1 this ".recipe-grubs") :hidden)
|
||||
(dommy/add-class! (sel1 this ".recipe-done-btn") :hidden))
|
||||
|
||||
(-get-name [this]
|
||||
(dommy/value (sel1 this :#recipe-name)))
|
||||
(-get-grubs-str [this]
|
||||
(dommy/value (sel1 this :#recipe-grubs)))
|
||||
(-get-grubs [this]
|
||||
(let [split-grubs (clojure.string/split-lines (-get-grubs-str this))]
|
||||
(when split-grubs (into [] split-grubs)))))
|
||||
|
||||
|
||||
(extend-type js/HTMLDivElement
|
||||
IClearable
|
||||
(-clear! [this]
|
||||
(dommy/set-value! (sel1 this "#recipe-name") "")
|
||||
(dommy/set-value! (sel1 this "#recipe-grubs") "")))
|
||||
|
||||
(defrecord Recipe [elem id name grubs]
|
||||
dommy.template/PElement
|
||||
(-elem [this] elem)
|
||||
|
||||
IRecipe
|
||||
(-expand! [this] (-expand! elem))
|
||||
(-unexpand! [this] (-unexpand! elem))
|
||||
|
||||
(-clear! [this] (-clear! elem))
|
||||
|
||||
(-update-recipe! [this]
|
||||
(dommy/set-value! (sel1 this :#recipe-name) name)
|
||||
(dommy/set-text! (sel1 this :#recipe-grubs) grubs)))
|
||||
|
||||
(defn add-new-recipe! [id name grubs]
|
||||
(let [node (make-recipe-node id name grubs)
|
||||
recipe (Recipe. node id name grubs)
|
||||
recipe-list (sel1 :#recipe-list)]
|
||||
(dommy/append! recipe-list recipe)
|
||||
recipe))
|
||||
|
||||
(defn event-val [event]
|
||||
(.. event -target -value))
|
||||
|
|
|
@ -1,193 +1,148 @@
|
|||
(ns grub.view.grub
|
||||
(:require [grub.view.dom :as dom]
|
||||
[cljs.core.async :as a :refer [<! >! chan]])
|
||||
(:require-macros [grub.macros :refer [log logs and-let]]
|
||||
(:require [om.core :as om :include-macros true]
|
||||
[sablono.core :as html :refer-macros [html]]
|
||||
[cljs.core.async :as a :refer [<! put! chan]]
|
||||
[grub.view.dom :as dom]
|
||||
[cljs-uuid.core :as uuid])
|
||||
(:require-macros [grub.macros :refer [log logs]]
|
||||
[cljs.core.async.macros :refer [go go-loop]]))
|
||||
|
||||
(defn get-add-grub-clicks []
|
||||
(:chan (dom/get-clicks dom/add-grub-btn)))
|
||||
(defn new-grub [grub]
|
||||
{:id (str "grub-" (uuid/make-random))
|
||||
:grub grub
|
||||
:completed false})
|
||||
|
||||
(defn get-add-grub-enters []
|
||||
(:chan (dom/get-enters dom/add-grub-text)))
|
||||
(defn add-event [grub]
|
||||
(assoc (new-grub grub) :event :add-grub))
|
||||
|
||||
(defn get-create-events []
|
||||
(let [events (a/merge [(get-add-grub-clicks)
|
||||
(get-add-grub-enters)])]
|
||||
(->> events
|
||||
(a/map< #(dom/get-add-grub-text))
|
||||
(a/filter< #(not (empty? %)))
|
||||
(a/map< (fn [g]
|
||||
{:event :add-grub
|
||||
:id (str "grub-" (.now js/Date))
|
||||
:grub g
|
||||
:completed false})))))
|
||||
(defn add-list-event [grubs]
|
||||
{:event :add-grub-list
|
||||
:grubs grubs})
|
||||
|
||||
(defn parse-complete-event [elem]
|
||||
(let [id (.-id elem)
|
||||
completed (dom/-completed? elem)
|
||||
event-type (if completed :uncomplete-grub :complete-grub)]
|
||||
{:id id
|
||||
:event event-type}))
|
||||
(defn edit-event [id grub]
|
||||
{:event :update-grub
|
||||
:id id
|
||||
:grub grub})
|
||||
|
||||
(defn get-complete-events []
|
||||
(->> (:chan (dom/listen (dom/grubs-selector) :click))
|
||||
(a/map< #(.-selectedTarget %))
|
||||
(a/filter< #(not (dom/-editing? %)))
|
||||
(a/map< parse-complete-event)))
|
||||
(defn complete-event [{:keys [id completed]}]
|
||||
{:event :update-grub
|
||||
:id id
|
||||
:completed (not completed)})
|
||||
|
||||
(defn get-clear-all-events []
|
||||
(->> (:chan (dom/listen dom/clear-all-btn :click))
|
||||
(a/map< (fn [e] {:event :clear-all-grubs}))))
|
||||
(defn grub-view [{:keys [id grub completed] :as props} owner]
|
||||
(reify
|
||||
om/IInitState
|
||||
(init-state [_]
|
||||
(let [publisher (chan)]
|
||||
{:edit-state :waiting
|
||||
:>local-events publisher
|
||||
:<local-events (a/pub publisher identity)
|
||||
:grub grub}))
|
||||
|
||||
(defn get-grub-mousedown-events []
|
||||
(let [{c :chan unlisten :unlisten} (dom/listen (dom/grubs-selector) :mousedown)]
|
||||
{:unlisten unlisten
|
||||
:chan (a/map< (fn [e] {:selected-grub (.-selectedTarget e)}) c)}))
|
||||
om/IRenderState
|
||||
(render-state [_ {:keys [edit-state >local-events] :as state}]
|
||||
(html
|
||||
[:li.list-group-item.grub-item
|
||||
{:class [(when completed "completed")
|
||||
(when (= edit-state :pressed) "grub-active")
|
||||
(when (= edit-state :editing) "edit")]
|
||||
:on-mouse-down #(put! >local-events :mouse-down)
|
||||
:on-mouse-up #(put! >local-events :mouse-up)
|
||||
:on-mouse-leave #(put! >local-events :mouse-leave)
|
||||
:on-click #(when (#{:waiting :pressed} edit-state)
|
||||
(put! (om/get-shared owner :grub-update) (complete-event @props)))}
|
||||
[:span.grub-static
|
||||
(if completed
|
||||
[:span.glyphicon.glyphicon-check]
|
||||
[:span.glyphicon.glyphicon-unchecked])
|
||||
[:span.grub-text grub]]
|
||||
[:input.grub-input
|
||||
{:type "text"
|
||||
:value (:grub state)
|
||||
:on-change #(om/set-state! owner :grub (.. % -target -value))
|
||||
:on-key-up #(when (dom/enter-pressed? %) (put! >local-events :enter))}]]))
|
||||
|
||||
(defn get-grub-mouseup-events [grub-elem]
|
||||
(dom/listen grub-elem :mouseup))
|
||||
|
||||
(defn get-grub-mouseleave-events [grub-elem]
|
||||
(dom/listen grub-elem :mouseleave))
|
||||
|
||||
(defn wait-for-mousedown-on-grub []
|
||||
(let [out (chan)]
|
||||
(go (let [{mousedown :chan unlisten :unlisten} (get-grub-mousedown-events)
|
||||
event (<! mousedown)
|
||||
selected-grub (:selected-grub event)]
|
||||
(unlisten)
|
||||
(>! out selected-grub)))
|
||||
out))
|
||||
|
||||
(defn wait-for-grub-mousedown-timeout [grub]
|
||||
(let [out (chan)]
|
||||
(dom/-activate! grub)
|
||||
(go (let [{mouseup :chan
|
||||
unlisten-mouseup :unlisten} (get-grub-mouseup-events grub)
|
||||
{mouseleave :chan
|
||||
unlisten-mouseleave :unlisten } (get-grub-mouseleave-events grub)
|
||||
timeout (a/timeout 500)
|
||||
[_ c] (a/alts! [mouseup mouseleave timeout])]
|
||||
(unlisten-mouseleave)
|
||||
(unlisten-mouseup)
|
||||
(dom/-deactivate! grub)
|
||||
(>! out (= c timeout))))
|
||||
out))
|
||||
|
||||
(defn make-grub-update-event [grub-elem orig-grub-text]
|
||||
(let [grub-text (dom/-grub-text grub-elem)
|
||||
id (dom/-id grub-elem)]
|
||||
(when (not (= grub-text orig-grub-text))
|
||||
{:event :update-grub
|
||||
:grub grub-text
|
||||
:id id})))
|
||||
|
||||
(defn wait-for-update-event [grub]
|
||||
(let [out (chan)
|
||||
orig-grub (dom/-grub-text grub)]
|
||||
(go (let [{bodyclick :chan
|
||||
unlisten-bodyclick :unlisten} (dom/get-away-clicks grub)
|
||||
{enter :chan
|
||||
unlisten-enter :unlisten} (dom/get-body-enters)]
|
||||
(dom/-set-editing! grub)
|
||||
(a/alts! [bodyclick enter])
|
||||
(unlisten-bodyclick)
|
||||
(unlisten-enter)
|
||||
(dom/-unset-editing! grub)
|
||||
(if-let [update-event (make-grub-update-event grub orig-grub)]
|
||||
(>! out update-event)
|
||||
(a/close! out))))
|
||||
out))
|
||||
|
||||
(defn get-update-events []
|
||||
(let [out (chan)]
|
||||
(go-loop []
|
||||
(and-let [grub (<! (wait-for-mousedown-on-grub))
|
||||
timeout? (<! (wait-for-grub-mousedown-timeout grub))
|
||||
update-event (<! (wait-for-update-event grub))]
|
||||
(>! out update-event))
|
||||
(recur))
|
||||
out))
|
||||
|
||||
(defn get-grub-with-index [grubs id]
|
||||
(let [grub-index (->> grubs
|
||||
(map-indexed vector)
|
||||
(filter #(= (:id (second %)) id))
|
||||
(first)
|
||||
(first))
|
||||
grub (grubs grub-index)]
|
||||
[grub-index grub]))
|
||||
om/IWillMount
|
||||
(will-mount [_]
|
||||
(let [<local-events (om/get-state owner :<local-events)
|
||||
<events (om/get-shared owner :<events)
|
||||
subscriber (chan)]
|
||||
(go-loop []
|
||||
(om/set-state! owner :edit-state :waiting)
|
||||
(let [subscriber (chan)]
|
||||
(a/sub <local-events :mouse-down subscriber)
|
||||
(<! subscriber)
|
||||
(a/unsub <local-events :mouse-down subscriber)
|
||||
(a/close! subscriber))
|
||||
(om/set-state! owner :edit-state :pressed)
|
||||
(a/sub <local-events :mouse-leave subscriber)
|
||||
(a/sub <local-events :mouse-up subscriber)
|
||||
(let [timeout (a/timeout 500)
|
||||
[event c] (a/alts! [timeout subscriber])]
|
||||
(a/unsub <local-events :mouse-leave subscriber)
|
||||
(a/unsub <local-events :mouse-up subscriber)
|
||||
(if (= c timeout)
|
||||
(do (om/set-state! owner :edit-state :editing)
|
||||
(a/sub <events :body-mousedown subscriber)
|
||||
(a/sub <local-events :enter subscriber)
|
||||
(loop []
|
||||
(let [event (<! subscriber)]
|
||||
(when (and (= (:type event) :body-mousedown)
|
||||
(dom/click-on-self? (:event event) (om/get-node owner)))
|
||||
(recur))))
|
||||
(a/unsub <events :body-mousedown subscriber)
|
||||
(a/unsub <local-events :enter subscriber)
|
||||
(put! (om/get-shared owner :grub-update)
|
||||
(edit-event id (om/get-state owner :grub))))
|
||||
(om/set-state! owner :edit-state :waiting)))
|
||||
(recur))))))
|
||||
|
||||
(defn get-grub-ingredient [grub]
|
||||
(let [text (clojure.string/lower-case (:grub grub))
|
||||
match (re-find #"[a-z]{3}.*$" text)]
|
||||
match))
|
||||
(when-not (nil? (:grub grub))
|
||||
(let [text (clojure.string/lower-case (:grub grub))
|
||||
match (re-find #"[a-z]{3}.*$" text)]
|
||||
match)))
|
||||
|
||||
(defn sort-and-render-grub-list! [grubs]
|
||||
(let [sorted-grubs (sort-by (juxt :completed get-grub-ingredient) (vals grubs))]
|
||||
(dom/render-grub-list sorted-grubs)))
|
||||
(defn sort-grubs [grubs]
|
||||
(sort-by (juxt :completed get-grub-ingredient :grub) (vals grubs)))
|
||||
|
||||
(defmulti handle-event (fn [event grubs] (:event event))
|
||||
:default :unknown-event)
|
||||
(defn add-grub [add new-grub owner]
|
||||
(when (not (empty? new-grub))
|
||||
(om/set-state! owner :new-grub "")
|
||||
(put! add (add-event new-grub))))
|
||||
|
||||
(defmethod handle-event :unknown-event [event grubs]
|
||||
;(logs "Cannot handle unknown event:" event)
|
||||
grubs)
|
||||
|
||||
(defmethod handle-event :add-grub [event grubs]
|
||||
(let [grub (dom/make-new-grub (:id event) (:grub event) (:completed event))
|
||||
new-grubs (assoc grubs (:id grub) grub)]
|
||||
(dom/-show! dom/clear-all-btn)
|
||||
(sort-and-render-grub-list! new-grubs)
|
||||
(dom/clear-new-grub-input!)
|
||||
(dom/focus-new-grub-input!)
|
||||
new-grubs))
|
||||
|
||||
(defn assoc-new-grub [current new]
|
||||
(assoc current (:id new)
|
||||
(dom/make-new-grub (:id new) (:grub new) (:completed new))))
|
||||
|
||||
(defn make-add-grubs-map [grub-events]
|
||||
(reduce assoc-new-grub {} grub-events))
|
||||
|
||||
(defmethod handle-event :add-grub-list [event grubs]
|
||||
(let [add-grub-events (:grubs event)
|
||||
add-grubs (make-add-grubs-map add-grub-events)
|
||||
new-grubs (merge grubs add-grubs)]
|
||||
(dom/-show! dom/clear-all-btn)
|
||||
(sort-and-render-grub-list! new-grubs)
|
||||
new-grubs))
|
||||
|
||||
(defmethod handle-event :complete-grub [event grubs]
|
||||
(let [grub (get grubs (:id event))
|
||||
new-grubs (assoc-in grubs [(:id event) :completed] true)]
|
||||
(sort-and-render-grub-list! new-grubs)
|
||||
new-grubs))
|
||||
|
||||
(defmethod handle-event :uncomplete-grub [event grubs]
|
||||
(let [new-grubs (assoc-in grubs [(:id event) :completed] false)]
|
||||
(sort-and-render-grub-list! new-grubs)
|
||||
new-grubs))
|
||||
|
||||
(defmethod handle-event :update-grub [event grubs]
|
||||
(let [new-grubs (assoc-in grubs [(:id event) :grub] (:grub event))]
|
||||
(sort-and-render-grub-list! new-grubs)
|
||||
new-grubs))
|
||||
|
||||
(defmethod handle-event :clear-all-grubs [event grubs]
|
||||
(dom/-hide! dom/clear-all-btn)
|
||||
(dom/clear-grubs!)
|
||||
{})
|
||||
|
||||
(defn handle-grubs [remote-events]
|
||||
(let [out (chan)
|
||||
local-events [(get-create-events)
|
||||
(get-complete-events)
|
||||
(get-clear-all-events)
|
||||
(get-update-events)]]
|
||||
(go-loop [grubs {}]
|
||||
(let [[event c] (a/alts! (conj local-events remote-events))]
|
||||
(when-not (= c remote-events)
|
||||
(>! out event))
|
||||
(recur (handle-event event grubs))))
|
||||
out))
|
||||
(defn grubs-view [props owner]
|
||||
(reify
|
||||
om/IInitState
|
||||
(init-state [_]
|
||||
{:new-grub ""})
|
||||
om/IRenderState
|
||||
(render-state [this {:keys [new-grub] :as state}]
|
||||
(let [add (om/get-shared owner :grub-add)]
|
||||
(html
|
||||
[:div
|
||||
[:h3 "Grub List"]
|
||||
[:div.input-group.add-grub-input-form
|
||||
[:span.input-group-btn
|
||||
[:input.form-control#add-grub-input
|
||||
{:type "text"
|
||||
:placeholder "What do you need?"
|
||||
:value new-grub
|
||||
:on-key-up #(when (dom/enter-pressed? %)
|
||||
(add-grub add new-grub owner))
|
||||
:on-change #(om/set-state! owner :new-grub (dom/event-val %))}]]
|
||||
[:button.btn.btn-primary
|
||||
{:id "add-grub-btn"
|
||||
:type "button"
|
||||
:on-click #(add-grub add new-grub owner)}
|
||||
"Add"]]
|
||||
[:ul#grub-list.list-group
|
||||
(for [grub (sort-grubs props)]
|
||||
(om/build grub-view grub {:key :id}))]
|
||||
[:button.btn.pull-right
|
||||
{:id "clear-all-btn"
|
||||
:class (when (empty? props) "hidden")
|
||||
:type "button"
|
||||
:on-click #(put! (om/get-shared owner :grub-clear-all)
|
||||
{:event :clear-all-grubs})}
|
||||
"Clear all"]])))))
|
||||
|
|
|
@ -1,152 +1,194 @@
|
|||
(ns grub.view.recipe
|
||||
(:require [grub.view.dom :as dom]
|
||||
[cljs.core.async :as a :refer [<! >! chan]])
|
||||
(:require-macros [grub.macros :refer [log logs and-let]]
|
||||
(:require [om.core :as om :include-macros true]
|
||||
[sablono.core :as html :refer-macros [html]]
|
||||
[cljs.core.async :as a :refer [<! put! chan]]
|
||||
[cljs-uuid.core :as uuid]
|
||||
[grub.view.dom :as dom]
|
||||
[grub.view.grub :as grub-view])
|
||||
(:require-macros [grub.macros :refer [log logs]]
|
||||
[cljs.core.async.macros :refer [go go-loop]]))
|
||||
|
||||
(defn wait-for-new-recipe-input-click []
|
||||
(:chan (dom/listen-once dom/new-recipe :click)))
|
||||
(defn add-event [name grubs]
|
||||
{:event :add-recipe
|
||||
:id (str "recipe-" (uuid/make-random))
|
||||
:name name
|
||||
:grubs grubs})
|
||||
|
||||
(defn parse-new-recipe-event []
|
||||
(let [name (dom/-get-name dom/new-recipe)
|
||||
grubs (dom/-get-grubs-str dom/new-recipe)]
|
||||
(when (not (or (empty? name) (empty? grubs)))
|
||||
(let [id (str "recipe-" (.now js/Date))]
|
||||
{:event :add-recipe
|
||||
:name name
|
||||
:grubs grubs
|
||||
:id id}))))
|
||||
(defn update-event [id name grubs]
|
||||
{:event :update-recipe
|
||||
:id id
|
||||
:name name
|
||||
:grubs grubs})
|
||||
|
||||
(defn wait-for-create-event []
|
||||
(let [out (chan)
|
||||
{ctrl-enters :chan
|
||||
ctrl-enters-unlisten :unlisten} (dom/get-ctrl-enters)
|
||||
{away-clicks :chan
|
||||
away-clicks-unlisten :unlisten} (dom/get-away-clicks dom/new-recipe)
|
||||
{done-clicks :chan
|
||||
done-clicks-unlisten :unlisten} (dom/get-clicks dom/new-recipe-done-btn)]
|
||||
(go (a/alts! [ctrl-enters away-clicks done-clicks])
|
||||
(ctrl-enters-unlisten)
|
||||
(away-clicks-unlisten)
|
||||
(done-clicks-unlisten)
|
||||
(when-let [event (parse-new-recipe-event)]
|
||||
(>! out event))
|
||||
(a/close! out))
|
||||
out))
|
||||
(defn parse-grubs-from-str [grubs-str]
|
||||
(->> grubs-str
|
||||
(clojure.string/split-lines)
|
||||
(map grub-view/new-grub)
|
||||
(into [])))
|
||||
|
||||
(defn get-create-events []
|
||||
(let [out (chan)]
|
||||
(go-loop []
|
||||
(<! (wait-for-new-recipe-input-click))
|
||||
(dom/-expand! dom/new-recipe)
|
||||
(when-let [create-event (<! (wait-for-create-event))]
|
||||
(>! out create-event)
|
||||
(dom/-clear! dom/new-recipe))
|
||||
(dom/-unexpand! dom/new-recipe)
|
||||
(recur))
|
||||
out))
|
||||
(defn add-grubs [add-grubs-ch grubs-str]
|
||||
(let [grubs (parse-grubs-from-str grubs-str)
|
||||
event (grub-view/add-list-event grubs)]
|
||||
(put! add-grubs-ch event)))
|
||||
|
||||
(defn wait-for-edit-recipe-input-click []
|
||||
(dom/get-edit-recipe-input-click))
|
||||
(defn update-recipe [ch id name grubs owner]
|
||||
(when (and (not (empty? name))
|
||||
(not (empty? grubs)))
|
||||
(om/set-state! owner :editing false)
|
||||
(put! ch (update-event id name grubs))))
|
||||
|
||||
(defn parse-update-recipe-event [elem]
|
||||
(let [id (.-id elem)
|
||||
name (dom/-get-name elem)
|
||||
grubs (dom/-get-grubs-str elem)]
|
||||
(when (not (or (empty? name) (empty? grubs)))
|
||||
{:event :update-recipe
|
||||
:name name
|
||||
:grubs grubs
|
||||
:id id})))
|
||||
(defn recipe-view [{:keys [id] :as props} owner]
|
||||
(reify
|
||||
om/IInitState
|
||||
(init-state [_]
|
||||
(let [publisher (chan)]
|
||||
{:editing false
|
||||
:>local-events publisher
|
||||
:<local-events (a/pub publisher identity)
|
||||
:name (:name props)
|
||||
:grubs (:grubs props)}))
|
||||
|
||||
(defn wait-for-update-event [elem]
|
||||
(let [out (chan)
|
||||
{ctrl-enters :chan
|
||||
ctrl-enters-unlisten :unlisten} (dom/get-ctrl-enters)
|
||||
{away-clicks :chan
|
||||
away-clicks-unlisten :unlisten} (dom/get-away-clicks elem)
|
||||
{done-clicks :chan
|
||||
done-clicks-unlisten :unlisten} (dom/get-clicks (dom/recipe-done-btn-selector elem))]
|
||||
(go (a/alts! [ctrl-enters away-clicks done-clicks])
|
||||
(ctrl-enters-unlisten)
|
||||
(away-clicks-unlisten)
|
||||
(done-clicks-unlisten)
|
||||
(when-let [event (parse-update-recipe-event elem)]
|
||||
(>! out event))
|
||||
(a/close! out))
|
||||
out))
|
||||
om/IWillReceiveProps
|
||||
(will-receive-props [this next-props]
|
||||
(om/set-state! owner :name (:name next-props))
|
||||
(om/set-state! owner :grubs (:grubs next-props)))
|
||||
|
||||
(defn get-update-events []
|
||||
(let [out (chan)]
|
||||
(go-loop []
|
||||
(let [recipe-elem (<! (wait-for-edit-recipe-input-click))]
|
||||
(dom/-expand! recipe-elem)
|
||||
(when-let [update-event (<! (wait-for-update-event recipe-elem))]
|
||||
(>! out update-event))
|
||||
(dom/-unexpand! recipe-elem)
|
||||
(recur)))
|
||||
out))
|
||||
om/IRenderState
|
||||
(render-state [this {:keys [editing >local-events name grubs]}]
|
||||
(let [update (om/get-shared owner :recipe-update)
|
||||
add-grubs-ch (om/get-shared owner :recipe-add-grubs)]
|
||||
(html
|
||||
[:div.panel.panel-default.recipe-panel
|
||||
{:on-click #(put! >local-events :click)}
|
||||
[:div.panel-heading.recipe-header
|
||||
[:input.form-control.recipe-header-input
|
||||
{:type "text"
|
||||
:value name
|
||||
:on-change #(om/set-state! owner :name (dom/event-val %))}]
|
||||
[:button.btn.btn-primary.btn-sm.recipe-add-grubs-btn
|
||||
{:type "button"
|
||||
:on-click #(add-grubs add-grubs-ch grubs)}
|
||||
"Add Grubs"]]
|
||||
[:div.panel-body.recipe-grubs
|
||||
{:class (when (not editing) "hidden")}
|
||||
[:textarea.form-control.recipe-grubs-input
|
||||
{:id "recipe-grubs"
|
||||
:rows 3
|
||||
:value grubs
|
||||
:on-change #(om/set-state! owner :grubs (dom/event-val %))}]
|
||||
[:button.btn.btn-primary.pull-right.recipe-btn.recipe-done-btn
|
||||
{:type "button"
|
||||
:on-click #(update-recipe update id name grubs owner)}
|
||||
"Save"]]])))
|
||||
|
||||
om/IWillMount
|
||||
(will-mount [_]
|
||||
(let [<local-events (om/get-state owner :<local-events)
|
||||
<events (om/get-shared owner :<events)]
|
||||
(go-loop []
|
||||
(let [subscriber (chan)]
|
||||
(a/sub <local-events :click subscriber)
|
||||
(<! subscriber)
|
||||
(a/unsub <local-events :click subscriber)
|
||||
(a/close! subscriber))
|
||||
(om/set-state! owner :editing true)
|
||||
(let [subscriber (chan)]
|
||||
(a/sub <events :body-mousedown subscriber)
|
||||
(loop []
|
||||
(let [event (<! subscriber)]
|
||||
(when (and (= (:type event) :body-mousedown)
|
||||
(dom/click-on-self? (:event event) (om/get-node owner)))
|
||||
(recur))))
|
||||
(a/unsub <events :body-mousedown subscriber)
|
||||
(a/close! subscriber))
|
||||
(om/set-state! owner :editing false)
|
||||
(recur))))))
|
||||
|
||||
(defn get-add-grub-events []
|
||||
(let [out (chan)
|
||||
recipe-add-grubs-clicks (dom/get-recipe-add-grubs-clicks)]
|
||||
(go-loop []
|
||||
(let [elem (<! recipe-add-grubs-clicks)
|
||||
grub-texts (dom/-get-grubs elem)
|
||||
grubs (map-indexed (fn [index g] {:id (str "grub-" (.now js/Date) index)
|
||||
:grub g
|
||||
:completed false})
|
||||
grub-texts)
|
||||
event {:event :add-grub-list
|
||||
:grubs grubs}]
|
||||
(>! out event))
|
||||
(recur))
|
||||
out))
|
||||
|
||||
(defn add-recipe [ch name grubs owner]
|
||||
(when (and (not (empty? name))
|
||||
(not (empty? grubs)))
|
||||
(om/set-state! owner :new-recipe-name "")
|
||||
(om/set-state! owner :new-recipe-grubs "")
|
||||
(om/set-state! owner :editing false)
|
||||
(put! ch (add-event name grubs))))
|
||||
|
||||
(defmulti handle-event (fn [event recipes] (:event event))
|
||||
:default :unknown-event)
|
||||
(defn new-recipe-view [_ owner]
|
||||
(reify
|
||||
om/IInitState
|
||||
(init-state [_]
|
||||
(let [publisher (chan)]
|
||||
{:editing false
|
||||
:>local-events publisher
|
||||
:<local-events (a/pub publisher identity)
|
||||
:new-recipe-name ""
|
||||
:new-recipe-grubs ""}))
|
||||
|
||||
(defmethod handle-event :unknown-event [event recipes]
|
||||
;(logs "Cannot handle unknown event:" event)
|
||||
recipes)
|
||||
om/IRenderState
|
||||
(render-state [this {:keys [editing >local-events new-recipe-name new-recipe-grubs]}]
|
||||
(let [add (om/get-shared owner :recipe-add)]
|
||||
(html
|
||||
[:div.panel.panel-default.recipe-panel
|
||||
{:on-click #(put! >local-events :click)}
|
||||
[:div.panel-heading.recipe-header
|
||||
[:input.form-control.recipe-header-input
|
||||
{:id "new-recipe-name"
|
||||
:type "text"
|
||||
:placeholder "New recipe"
|
||||
:value new-recipe-name
|
||||
:on-change #(om/set-state! owner :new-recipe-name (dom/event-val %))}]]
|
||||
[:div.panel-body.recipe-grubs
|
||||
{:class (when (not editing) "hidden")}
|
||||
[:textarea.form-control.recipe-grubs-input
|
||||
{:id "new-recipe-grubs"
|
||||
:rows 3
|
||||
:placeholder "Recipe ingredients"
|
||||
:value new-recipe-grubs
|
||||
:on-change #(om/set-state! owner :new-recipe-grubs (dom/event-val %))}]
|
||||
[:button.btn.btn-primary.pull-right.recipe-btn.recipe-done-btn
|
||||
{:type "button"
|
||||
:on-click #(put! >local-events :done)}
|
||||
"Done"]]])))
|
||||
|
||||
om/IWillMount
|
||||
(will-mount [_]
|
||||
(let [<local-events (om/get-state owner :<local-events)
|
||||
<events (om/get-shared owner :<events)
|
||||
add (om/get-shared owner :recipe-add)
|
||||
]
|
||||
(go-loop []
|
||||
(let [subscriber (chan)]
|
||||
(a/sub <local-events :click subscriber)
|
||||
(<! subscriber)
|
||||
(a/unsub <local-events :click subscriber)
|
||||
(a/close! subscriber))
|
||||
(om/set-state! owner :editing true)
|
||||
(let [subscriber (chan)]
|
||||
(a/sub <events :body-mousedown subscriber)
|
||||
(a/sub <local-events :done subscriber)
|
||||
(loop []
|
||||
(let [event (<! subscriber)]
|
||||
(if-not (and (= (:type event) :body-mousedown)
|
||||
(dom/click-on-self? (:event event) (om/get-node owner)))
|
||||
(when (= event :done)
|
||||
(add-recipe add
|
||||
(om/get-state owner :new-recipe-name)
|
||||
(om/get-state owner :new-recipe-grubs)
|
||||
owner))
|
||||
(recur))))
|
||||
(a/unsub <events :body-mousedown subscriber)
|
||||
(a/unsub <local-events :done subscriber)
|
||||
(a/close! subscriber))
|
||||
(om/set-state! owner :editing false)
|
||||
(recur))))))
|
||||
|
||||
(defmethod handle-event :add-recipe [event recipes]
|
||||
(let [recipe (dom/add-new-recipe! (:id event)
|
||||
(:name event)
|
||||
(:grubs event))]
|
||||
(assoc recipes (:id recipe) recipe)))
|
||||
|
||||
(defn assoc-new-recipe! [current new]
|
||||
(assoc current (:id new)
|
||||
(dom/add-new-recipe! (:id new) (:name new) (:grubs new))))
|
||||
|
||||
(defn add-new-recipes! [recipe-events]
|
||||
(reduce assoc-new-recipe! {} recipe-events))
|
||||
|
||||
(defmethod handle-event :add-recipe-list [event recipes]
|
||||
(let [add-recipe-events (:recipes event)
|
||||
added-recipes (add-new-recipes! add-recipe-events)
|
||||
new-recipes (merge recipes added-recipes)]
|
||||
new-recipes))
|
||||
|
||||
(defmethod handle-event :update-recipe [event recipes]
|
||||
(let [recipe (get recipes (:id event))
|
||||
updated-recipe (-> recipe
|
||||
(assoc :name (:name event))
|
||||
(assoc :grubs (:grubs event)))]
|
||||
(dom/-update-recipe! updated-recipe)
|
||||
(assoc recipes (:id recipes) updated-recipe)))
|
||||
|
||||
(defn handle-recipes [remote-events]
|
||||
(let [out (chan)
|
||||
local-events [(get-create-events)
|
||||
(get-update-events)]
|
||||
add-grub-events (get-add-grub-events)]
|
||||
(a/pipe add-grub-events out)
|
||||
(go-loop [recipes {}]
|
||||
(let [[event c] (a/alts! (conj local-events remote-events))]
|
||||
(when-not (= c remote-events)
|
||||
(>! out event))
|
||||
(recur (handle-event event recipes))))
|
||||
out))
|
||||
(defn recipes-view [recipes owner]
|
||||
(reify
|
||||
om/IRender
|
||||
(render [this]
|
||||
(html
|
||||
[:div
|
||||
[:h3.recipes-title "Recipes"]
|
||||
(om/build new-recipe-view recipes)
|
||||
[:ul#recipe-list.list-group.recipe-list
|
||||
(for [recipe (vals recipes)]
|
||||
(om/build recipe-view recipe {:key :id}))]]))))
|
||||
|
|
87
src/test/grub/test/integration/core.clj
Normal file
87
src/test/grub/test/integration/core.clj
Normal file
|
@ -0,0 +1,87 @@
|
|||
(ns grub.test.integration.core
|
||||
(:require [grub.db :as db]
|
||||
[grub.websocket :as ws]
|
||||
[clj-webdriver.taxi :as taxi]
|
||||
[clj-webdriver.core :as webdriver]
|
||||
[clojure.test :as test]))
|
||||
|
||||
(def server-port 3456)
|
||||
(def site-url (str "http://localhost:" server-port))
|
||||
|
||||
;; Hard-coded path to chromedriver
|
||||
(defn set-chromedriver-path! []
|
||||
(System/setProperty "webdriver.chrome.driver" "bin/chromedriver"))
|
||||
|
||||
(defn get-driver [url]
|
||||
(webdriver/start {:browser :chrome} url))
|
||||
|
||||
(defn get-rand-grub []
|
||||
(str "testgrub" (rand-int 10000)))
|
||||
|
||||
(defn add-grub [driver grub-text]
|
||||
(taxi/input-text driver "#add-grub-input" grub-text)
|
||||
(taxi/click driver {:text "Add"}))
|
||||
|
||||
(defn test-grubs-saved-to-server [url driver]
|
||||
(taxi/to driver url)
|
||||
(let [grubs (repeatedly 4 get-rand-grub)]
|
||||
(doseq [grub grubs]
|
||||
(add-grub driver grub))
|
||||
(Thread/sleep 200)
|
||||
(taxi/refresh driver)
|
||||
(Thread/sleep 200)
|
||||
(doseq [grub grubs]
|
||||
(test/is (taxi/find-element driver {:text grub})
|
||||
"Previously added grubs should be loaded on refresh")))
|
||||
(db/clear-grubs))
|
||||
|
||||
(defn test-added-grubs-sync [url driver1 driver2]
|
||||
(taxi/to driver1 url)
|
||||
(taxi/to driver2 url)
|
||||
(let [grubs (repeatedly 4 get-rand-grub)]
|
||||
(doseq [grub grubs]
|
||||
(add-grub driver1 grub))
|
||||
(doseq [grub grubs]
|
||||
(test/is (taxi/find-element driver2 {:text grub})
|
||||
"Added grubs should appear in other browser"))))
|
||||
|
||||
(defn get-rand-recipe []
|
||||
{:name (str "recipe" (rand-int 10000))
|
||||
:grubs "grubs\nstuff\nmorestuff"})
|
||||
|
||||
(defn add-recipe [driver {:keys [name grubs]}]
|
||||
(taxi/click driver "#new-recipe-name")
|
||||
(taxi/input-text driver "#new-recipe-name" name)
|
||||
(taxi/input-text driver "#new-recipe-grubs" grubs)
|
||||
(taxi/click driver {:text "Done"}))
|
||||
|
||||
(defn test-added-recipes-sync [url driver1 driver2]
|
||||
(taxi/to driver1 url)
|
||||
(taxi/to driver2 url)
|
||||
(let [recipes (repeatedly 4 get-rand-recipe )]
|
||||
(doseq [recipe recipes]
|
||||
(add-recipe driver1 recipe))
|
||||
(doseq [{:keys [name]} recipes]
|
||||
(test/is (taxi/find-element driver2 {:value name})
|
||||
"Added recipes should appear in other browser"))))
|
||||
|
||||
(defn run-tests [site-url driver1 driver2]
|
||||
(test-grubs-saved-to-server site-url driver1)
|
||||
(test-added-grubs-sync site-url driver1 driver2)
|
||||
(test-added-recipes-sync site-url driver1 driver2))
|
||||
|
||||
(defn start-db-and-websocket-server! []
|
||||
(let [db-chan (db/connect-and-handle-events "grub-integration-test")]
|
||||
(db/clear-all)
|
||||
(ws/pass-received-events-to-clients-and-db db-chan)))
|
||||
|
||||
(defn run []
|
||||
(println "Starting integration test")
|
||||
(set-chromedriver-path!)
|
||||
(start-db-and-websocket-server!)
|
||||
(let [driver1 (get-driver site-url)
|
||||
driver2 (get-driver site-url)]
|
||||
(run-tests site-url driver1 driver2)
|
||||
(taxi/quit driver1)
|
||||
(taxi/quit driver2))
|
||||
(db/clear-all))
|
Loading…
Reference in a new issue