Large refactoring

- State handler intermediates between view, server
This commit is contained in:
Nicholas Kariniemi 2013-08-18 14:36:16 +03:00
parent 707fb9f22d
commit 1f9108b96a
9 changed files with 163 additions and 105 deletions

View file

@ -1,5 +1,6 @@
(ns grub.integration-test (ns grub.integration-test
(:require [clj-webdriver.taxi :as taxi] (:require [grub.db :as db]
[clj-webdriver.taxi :as taxi]
[clj-webdriver.core :as webdriver] [clj-webdriver.core :as webdriver]
[clojure.test :as test])) [clojure.test :as test]))
@ -23,20 +24,25 @@
(add-grub driver1 grub)) (add-grub driver1 grub))
(doseq [grub grubs] (doseq [grub grubs]
(test/is (taxi/find-element driver2 {:text grub}) (test/is (taxi/find-element driver2 {:text grub})
"Added grubs should appear in other browser")))) "Added grubs should appear in other browser")))
(db/clear-grubs))
(defn test-grubs-are-stored-on-server [url driver] (defn test-grubs-are-stored-on-server [url driver]
(taxi/to driver url) (taxi/to driver url)
(let [grubs (repeatedly 4 get-rand-grub)] (let [grubs (repeatedly 4 get-rand-grub)]
(doseq [grub grubs] (doseq [grub grubs]
(add-grub driver grub)) (add-grub driver grub))
(Thread/sleep 200)
(taxi/refresh driver) (taxi/refresh driver)
(Thread/sleep 200)
(doseq [grub grubs] (doseq [grub grubs]
(test/is (taxi/find-element driver {:text grub}) (test/is (taxi/find-element driver {:text grub})
"Previously added grubs should be loaded on refresh")))) "Previously added grubs should be loaded on refresh")))
(db/clear-grubs))
(defn run [port] (defn run [port]
(db/connect-and-handle-events "grub-integration-test")
(let [site-url (str "http://localhost:" port)] (let [site-url (str "http://localhost:" port)]
(println "Starting integration test") (println "Starting integration test")
(let [driver1 (get-driver site-url) (let [driver1 (get-driver site-url)
@ -44,4 +50,5 @@
(test-adding-grubs site-url driver1 driver2) (test-adding-grubs site-url driver1 driver2)
(test-grubs-are-stored-on-server site-url driver1) (test-grubs-are-stored-on-server site-url driver1)
(taxi/quit driver1) (taxi/quit driver1)
(taxi/quit driver2)))) (taxi/quit driver2)))
(db/clear-grubs))

View file

@ -17,18 +17,21 @@
(describe (describe
"grub.db" "grub.db"
(before-all (db/connect-and-handle-events test-db)) (before-all (db/connect-and-handle-events test-db))
(before (mc/drop db/grub-collection)) (before (db/clear-grubs))
(describe "Create grub" (describe "Add"
(it "should create a grub when a create event comes" (it "should add a grub when an add event comes"
(let [test-grub "testgrub" (let [test-grub "testgrub"
test-id 12345] test-id 12345]
(>!! @db/incoming-events {:event :create :_id test-id :grub test-grub}) (>!! @db/incoming-events {:event :add
:_id test-id
:grub test-grub
:completed false})
(short-delay) (short-delay)
(should= (should=
{:_id test-id :grub test-grub :completed false} {:_id test-id :grub test-grub :completed false}
(mc/find-one-as-map db/grub-collection {:_id test-id}))))) (mc/find-one-as-map db/grub-collection {:_id test-id})))))
(describe "Complete grub" (describe "Complete"
(it "should complete a grub when a complete event comes" (it "should complete a grub when a complete event comes"
(let [test-grub {:_id 123456 :completed false}] (let [test-grub {:_id 123456 :completed false}]
(mc/insert db/grub-collection test-grub) (mc/insert db/grub-collection test-grub)
@ -38,7 +41,7 @@
{:_id (:_id test-grub) :completed true} {:_id (:_id test-grub) :completed true}
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)}))))) (mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
(describe "Uncomplete grub" (describe "Uncomplete"
(it "should uncomplete a grub when an uncomplete event comes" (it "should uncomplete a grub when an uncomplete event comes"
(let [test-grub {:_id 123456 :completed true}] (let [test-grub {:_id 123456 :completed true}]
(mc/insert db/grub-collection test-grub) (mc/insert db/grub-collection test-grub)
@ -48,7 +51,7 @@
{:_id (:_id test-grub) :completed false} {:_id (:_id test-grub) :completed false}
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)}))))) (mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
(describe "Delete grub" (describe "Delete"
(it "should delete a grub when a delete event comes" (it "should delete a grub when a delete event comes"
(let [test-grub {:_id 123456 :completed true}] (let [test-grub {:_id 123456 :completed true}]
(mc/insert db/grub-collection test-grub) (mc/insert db/grub-collection test-grub)

View file

@ -7,56 +7,61 @@
[grub.macros :refer [log logs]])) [grub.macros :refer [log logs]]))
(describe (describe
"grub state" "State"
(before (reset! state/grubs []))
(describe "Add grub" (describe
(it "should add a grub to the state when an add event comes" "event handling:"
(let [test-grub {:_id 12345 :grub "testgrub" :completed true} (before (reset! state/grubs []))
add-event (assoc test-grub :event :add)]
(state/handle-event add-event)
(should-contain test-grub @state/grubs))))
(describe "Create grub" (describe "Add"
(it "should add a new grub to the state when a create event comes" (it "should add a grub to the state when an add event comes"
(let [test-grub {:grub "testgrub"} (let [test-grub {:_id 12345 :grub "testgrub" :completed true}
expected-grub (assoc test-grub :completed false) add-event (assoc test-grub :event :add)]
create-event (assoc test-grub :event :create)] (state/handle-event add-event)
(state/handle-event create-event) (should-contain test-grub @state/grubs))))
(let [created-grub (first @state/grubs)]
(should= (:grub created-grub) (:grub test-grub))))) (describe "Complete"
(it "should generate an _id for the new grub" (it "should complete a grub in the state when a complete event comes"
(let [test-grub {:grub "testgrub"} (let [test-grub {:_id 234243 :grub "testgrub" :completed false}
create-event (assoc test-grub :event :create)] expected-grub (assoc test-grub :completed true)
(state/handle-event create-event) complete-event (-> (select-keys [:_id])
(let [added-grub (first (filter #(= (:grub %) (:grub test-grub)) (assoc :event :complete))]
@state/grubs))] (reset! state/grubs [test-grub])
(should-not-be-nil (:_id added-grub)))))) (state/handle-event complete-event)
(should-contain expected-grub @state/grubs))))
(describe "Uncomplete"
(it "should uncomplete a grub in the state when an uncomplete event comes"
(let [test-grub {:_id 234243 :grub "testgrub" :completed true}
expected-grub (assoc test-grub :completed false)
complete-event (-> (select-keys [:_id])
(assoc :event :uncomplete))]
(reset! state/grubs [test-grub])
(state/handle-event complete-event)
(should-contain expected-grub @state/grubs))))
(describe "Delete"
(it "should delete a grub from the state when a delete event comes"
(let [test-grub {:_id 234243 :grub "testgrub" :completed true}
delete-event {:_id (:_id test-grub) :event :delete}]
(reset! state/grubs [test-grub])
(state/handle-event delete-event)
(should= [] @state/grubs)))))
(describe "Complete grub" (describe
(it "should complete a grub in the state when a complete event comes" "view event handling"
(let [test-grub {:_id 234243 :grub "testgrub" :completed false} (describe "Create"
expected-grub (assoc test-grub :completed true) (it "should add a new grub to the state when a create event comes"
complete-event (-> (select-keys [:_id]) (let [test-grub {:grub "testgrub"}
(assoc :event :complete))] create-event (assoc test-grub :event :create)]
(reset! state/grubs [test-grub]) (state/handle-view-event create-event)
(state/handle-event complete-event) (js/setTimeout (fn [] (let [created-grub (first @state/grubs)]
(should-contain expected-grub @state/grubs)))) (should= (:grub test-grub) (:grub created-grub)))))))
(it "should generate an _id for the new grub"
(describe "Uncomplete grub" (let [test-grub {:grub "testgrub"}
(it "should uncomplete a grub in the state when an uncomplete event comes" create-event (assoc test-grub :event :create)]
(let [test-grub {:_id 234243 :grub "testgrub" :completed true} (state/handle-event create-event)
expected-grub (assoc test-grub :completed false) (js/setTimeout (fn []
complete-event (-> (select-keys [:_id]) (let [added-grub (first (filter #(= (:grub %) (:grub test-grub))
(assoc :event :uncomplete))] @state/grubs))]
(reset! state/grubs [test-grub]) (should-not-be-nil (:_id added-grub))))))))))
(state/handle-event complete-event)
(should-contain expected-grub @state/grubs))))
(describe "Delete grub"
(it "should delete a grub from the state when a delete event comes"
(let [test-grub {:_id 234243 :grub "testgrub" :completed true}
delete-event {:_id (:_id test-grub) :event :delete}]
(reset! state/grubs [test-grub])
(state/handle-event delete-event)
(should= [] @state/grubs)))))

View file

@ -35,10 +35,19 @@
(reload/wrap-reload (handler/site #'routes) {:dirs ["src/clj"]}) (reload/wrap-reload (handler/site #'routes) {:dirs ["src/clj"]})
(handler/site routes)))) (handler/site routes))))
(def default-port 3000)
(def integration-test-port 3456)
(defn start-server [port]
(println (str "Starting server on localhost:" 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)
(stop-server)))
(defn -main [& args] (defn -main [& args]
(let [port 3000] (if (some #(= % "integration") args)
(println (str "Starting server on localhost:" port)) (run-integration-test)
(defonce stop-server (httpkit/run-server app {:port port})) (start-server default-port)))
(when (some #(= % "integration") args)
(integration-test/run port)
(stop-server))))

View file

@ -11,10 +11,9 @@
(defmulti handle-event :event :default :unknown-event) (defmulti handle-event :event :default :unknown-event)
(defmethod handle-event :create [event] (defmethod handle-event :add [event]
(let [grub (-> event (let [grub (-> event
(select-keys [:_id :grub]) (select-keys [:_id :grub :completed]))]
(assoc :completed false))]
(mc/insert grub-collection grub))) (mc/insert grub-collection grub)))
(defmethod handle-event :complete [event] (defmethod handle-event :complete [event]
@ -49,6 +48,9 @@
(>! out grub-event)))) (>! out grub-event))))
out)) out))
(defn clear-grubs []
(mc/drop grub-collection))
(def default-db "grub") (def default-db "grub")
(defn connect-and-handle-events (defn connect-and-handle-events

View file

@ -9,15 +9,12 @@
[cljs.core.async.macros :refer [go]])) [cljs.core.async.macros :refer [go]]))
(defn handle-grub-events [] (defn handle-grub-events []
(let [local-events (view/get-local-events) (a/copy-chan state/incoming-view-events view/outgoing-events)
[local-events' local-events''] (a/fan-out local-events 2) (a/copy-chan state/incoming-events ws/outgoing-events)
remote-events (ws/get-remote-events) (a/copy-chan ws/incoming-events state/outgoing-events))
events (a/fan-in [local-events' remote-events])]
(a/do-chan! ws/send-to-server local-events'')
(a/copy-chan state/incoming-events events)))
(defn init [] (defn init []
(view/render-body) (view/init)
(ws/connect-to-server) (ws/connect-to-server)
(handle-grub-events)) (handle-grub-events))

View file

@ -4,6 +4,8 @@
[cljs.core.async.macros :refer [go]])) [cljs.core.async.macros :refer [go]]))
(def incoming-events (chan)) (def incoming-events (chan))
(def incoming-view-events (chan))
(def outgoing-events (chan))
(def grubs (atom [])) (def grubs (atom []))
@ -22,13 +24,6 @@
(let [grub (select-keys event [:_id :grub :completed])] (let [grub (select-keys event [:_id :grub :completed])]
(swap! grubs (fn [current] (conj current grub))))) (swap! grubs (fn [current] (conj current grub)))))
(defmethod handle-event :create [event]
(let [grub (-> event
(select-keys [:_id :grub])
(assoc :_id (str "grub-" (.now js/Date)))
(assoc :completed false))]
(swap! grubs (fn [current] (conj current grub)))))
(defmethod handle-event :complete [event] (defmethod handle-event :complete [event]
(swap! grubs (swap! grubs
(fn [current] (fn [current]
@ -51,7 +46,33 @@
(defmethod handle-event :unknown-event [event] (defmethod handle-event :unknown-event [event]
(logs "Cannot handle unknown event:" event)) (logs "Cannot handle unknown event:" event))
(defn pass-on-view-event [event]
(go (>! incoming-events event))
(go (>! outgoing-events event)))
(defmulti handle-view-event :event :default :unknown-event)
(defmethod handle-view-event :create [event]
(let [create-event (-> event
(assoc :event :add)
(assoc :_id (str "grub-" (.now js/Date)))
(assoc :completed false))]
(pass-on-view-event create-event)))
(defmethod handle-view-event :unknown-event [event]
(pass-on-view-event event))
(defn handle-incoming-events [] (defn handle-incoming-events []
(go-loop (handle-event (<! incoming-events)))) (go-loop (handle-event (<! incoming-events))))
(defn handle-incoming-view-events []
(go-loop (handle-view-event (<! incoming-view-events))))
(handle-incoming-events) (defn init []
(handle-incoming-events)
(handle-incoming-view-events))
(init)

View file

@ -8,6 +8,8 @@
[dommy.macros :refer [deftemplate sel1 node]] [dommy.macros :refer [deftemplate sel1 node]]
[cljs.core.async.macros :refer [go]])) [cljs.core.async.macros :refer [go]]))
(def outgoing-events (chan))
(def add-grub-text (def add-grub-text
(node [:input.form-control {:id "add-grub-input" :type "text" :placeholder "2 grubs"}])) (node [:input.form-control {:id "add-grub-input" :type "text" :placeholder "2 grubs"}]))
@ -92,10 +94,6 @@
grub-events (map-chan (fn [id] {:event :delete :_id id}) ids)] grub-events (map-chan (fn [id] {:event :delete :_id id}) ids)]
grub-events))) grub-events)))
(defn get-local-events []
(fan-in [(get-added-events)
(get-completed-events)
(get-deleted-events)]))
(defn render-grub-list [grubs] (defn render-grub-list [grubs]
(let [grub-list (sel1 :#grubList) (let [grub-list (sel1 :#grubList)
@ -104,7 +102,18 @@
(doseq [grub sorted-grubs] (doseq [grub sorted-grubs]
(dommy/append! grub-list (grub-template grub))))) (dommy/append! grub-list (grub-template grub)))))
(add-watch state/grubs (defn push-outgoing-events []
:grub-add-watch (fan-in outgoing-events [(get-added-events)
(fn [key ref old new] (get-completed-events)
(render-grub-list new))) (get-deleted-events)]))
(defn watch-for-state-changes []
(add-watch state/grubs
:grub-add-watch
(fn [key ref old new]
(render-grub-list new))))
(defn init []
(render-body)
(watch-for-state-changes)
(push-outgoing-events))

View file

@ -6,18 +6,23 @@
(:require-macros [grub.macros :refer [log logs go-loop]] (:require-macros [grub.macros :refer [log logs go-loop]]
[cljs.core.async.macros :refer [go]])) [cljs.core.async.macros :refer [go]]))
(def incoming-events (chan))
(def outgoing-events (chan))
(def websocket* (atom nil)) (def websocket* (atom nil))
(defn handle-incoming-events []
(go-loop
(let [event (<! incoming-events)]
(.send @websocket* event))))
(defn handle-outgoing-events []
(aset @websocket* "onmessage" (fn [event]
(let [grub-event (cljs.reader/read-string (.-data event))]
(logs "Received:" grub-event)
(go (>! outgoing-events grub-event))))))
(defn connect-to-server [] (defn connect-to-server []
(reset! websocket* (js/WebSocket. "ws://localhost:3000/ws"))) (reset! websocket* (js/WebSocket. "ws://localhost:3000/ws"))
(handle-incoming-events)
(defn get-remote-events [] (handle-outgoing-events))
(let [out (chan)]
(aset @websocket* "onmessage" (fn [event]
(let [grub-event (cljs.reader/read-string (.-data event))]
(logs "Received:" grub-event)
(go (>! out grub-event)))))
out))
(defn send-to-server [event]
(.send @websocket* event))