Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tests improving coverage #19

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions src/alandipert/storage_atom.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,26 @@
(-commit! [this value]
(.setItem store (clj->json key) (clj->json value))))

(defn exchange! [a x]
(let [y @a]
(if (compare-and-set! a y x)
y
(recur a x))))

(defn debounce-factory
"Return a function that will always store a future call into the
same atom. If recalled before the time is elapsed, the call is
replaced without being executed." []
(let [f (atom nil)]
(fn [func ttime]
(when @f
(timer/clear @f))
(reset! f (timer/callOnce func ttime)))))
(let [timed-func (when-not (= ttime :none)
(timer/callOnce func ttime))
old-timed-func (exchange! f timed-func)]
(when old-timed-func
(timer/clear old-timed-func))
(when-not timed-func
(func))
nil))))

(def storage-delay
"Delay in ms before a change is committed to the local storage. If a
Expand Down Expand Up @@ -87,16 +97,23 @@ discarded an only the new one is committed."
(.addEventListener js/window "storage"
#(maybe-update-backend atom storage k default %))))

(defn dispatch-remove-event!
"Create and dispatch a synthetic StorageEvent. Expects key to be a string.
An empty key indicates that all storage is being cleared."
[storage key]
(let [event (.createEvent js/document "StorageEvent")]
(.initStorageEvent event "storage" false false key nil nil
(-> js/window .-location .-href)
storage)
(.dispatchEvent js/window event)
nil))
(defn dispatch-synthetic-event!
"Create and dispatch a synthetic StorageEvent. Expects `key` to be a string
and `value` to be a string or nil. An empty `key` indicates that all
storage is being cleared. A nil or empty `value` indicates that the key is
being removed."
[storage key value]
(.dispatchEvent js/window
(doto (.createEvent js/document "StorageEvent")
(.initStorageEvent "storage"
false
false
key
nil
value
(.. js/window -location -href)
storage)))
nil)

;;; mostly for tests

Expand Down Expand Up @@ -132,7 +149,7 @@ discarded an only the new one is committed."
so its atoms will be cleared as well."
[storage]
(.clear storage)
(dispatch-remove-event! storage ""))
(dispatch-synthetic-event! storage "" nil))

(defn clear-local-storage! []
(clear-html-storage! js/localStorage))
Expand All @@ -146,7 +163,7 @@ discarded an only the new one is committed."
[storage k]
(let [key (clj->json k)]
(.removeItem storage key)
(dispatch-remove-event! storage key)))
(dispatch-synthetic-event! storage key nil)))

(defn remove-local-storage! [k]
(remove-html-storage! js/localStorage k))
Expand Down
120 changes: 80 additions & 40 deletions test/alandipert/storage_atom/test.cljs
Original file line number Diff line number Diff line change
@@ -1,44 +1,84 @@
(ns alandipert.storage-atom.test
(:require [alandipert.storage-atom :refer [local-storage]]
(:require [alandipert.storage-atom
:refer [clear-local-storage! clj->json dispatch-synthetic-event!
load-local-storage local-storage remove-local-storage!
*storage-delay*]]
[alandipert.storage-atom.test.macros
:refer-macros [with-local-storage-testing-scope]]
[cljs.test :refer-macros [deftest is testing run-tests]]))

;;; localStorage tests
(deftest test-local-storage-life-cycle
(with-local-storage-testing-scope
(let [a1 (local-storage (atom {:x 1}) "foo")]
(testing "Initialization to given value"
(is (= @a1 {:x 1}))
(is (= (load-local-storage "foo") {:x 1})))
(testing "Ordinary update"
(swap! a1 assoc :x 10)
(is (= @a1 {:x 10}))
(is (= (load-local-storage "foo") {:x 10})))
(testing "Update with no actual change"
(reset! a1 {:x 10})
(is (= @a1 {:x 10}))
(is (= (load-local-storage "foo") {:x 10})))
(testing "Collection types preserved"
(swap! a1 assoc :ys [#{1 2 3}])
(is (= @a1 {:x 10 :ys [#{1 2 3}]}))
(is (= (load-local-storage "foo") {:x 10 :ys [#{1 2 3}]})))
(let [a2 (local-storage (atom nil) "foo")]
(testing "Initialization to persisted value"
(is (= @a2 {:x 10 :ys [#{1 2 3}]}))
(is (= (load-local-storage "foo") {:x 10 :ys [#{1 2 3}]})))
(testing "Update propagation"
(swap! a2 dissoc :ys)
(is (= (load-local-storage "foo") {:x 10}))
(is (= @a2 {:x 10}))
;; Simulating the resulting event synchronously here
(dispatch-synthetic-event! js/localStorage
(clj->json "foo")
(clj->json {:x 10}))
(is (= @a1 {:x 10})))
(testing "Reset to initial values upon clearing storage"
(clear-local-storage!)
(is (= @a1 {:x 1}))
;; This is exactly correct but not sure if intended
(is (= @a2 nil))
(is (= (load-local-storage "foo") nil)))))))

(def a1 (local-storage (atom {}) "k1"))
(deftest test-swap
(swap! a1 assoc :x 10)
(is (= (:x @a1) 10)))

(def cnt (atom 0))
(deftest test-watch
(add-watch a1 :x (fn [_ _ _ _] (swap! cnt inc)))
(reset! a1 {})
(swap! a1 assoc "computers" "rule")
(is (= 2 @cnt)))

(def a2 (local-storage (atom 0 :validator even?) :foo))
(deftest test-validation
(is (= @a2 0)))

;;; Can't test the 'update' event, because it's only fired
;;; when changes come from another window.

(def a3 (local-storage
(atom {:x {:y {:z 42}}} :meta {:some :metadata}) "k3"))

(deftest test-update
(is (= (get-in @a3 [:x :y :z]) 42))
(is (= (:some (meta a3)) :metadata)))


(def a4 (local-storage
(atom {:xs [1 2 3]})
"k4"))

(deftest test-collection-types-preserved
(swap! a4 update :xs conj 4)
(is (= (peek (get @a4 :xs)) 4))
(swap! a4 assoc :ys [#{1 2 3}])
(is (vector? (get @a4 :ys)))
(is (set? (first (get @a4 :ys))))
(is (= (get a4 :ys [#{1 2 3}]))))
(deftest test-local-storage-isolation
(with-local-storage-testing-scope
(let [foo (local-storage (atom {:x 1}) :foo)
bar (local-storage (atom {:y 2}) :bar)]
(testing "Initial state is good"
(is (= (load-local-storage :foo) {:x 1}))
(is (= (load-local-storage :bar) {:y 2})))
(testing "Updates don't interfere"
(swap! foo assoc :y 3)
(swap! bar assoc :x 4)
(is (= @foo {:x 1 :y 3}))
(is (= @bar {:x 4 :y 2}))
(is (= (load-local-storage :foo) {:x 1 :y 3}))
(is (= (load-local-storage :bar) {:x 4 :y 2})))
(testing "Removing single key doesn't interfere"
(remove-local-storage! :foo)
(is (= @foo {:x 1}))
(is (= @bar {:x 4 :y 2}))
(is (= (load-local-storage :foo) nil))
(is (= (load-local-storage :bar) {:x 4 :y 2}))
(swap! foo assoc :y 5)
(remove-local-storage! :bar)
(is (= @foo {:x 1 :y 5}))
(is (= @bar {:y 2}))
(is (= (load-local-storage :foo) {:x 1 :y 5}))
(is (= (load-local-storage :bar) nil))
(swap! bar assoc :x 6)
(is (= @foo {:x 1 :y 5}))
(is (= @bar {:x 6 :y 2}))
(is (= (load-local-storage :foo) {:x 1 :y 5}))
(is (= (load-local-storage :bar) {:x 6 :y 2})))
(testing "Clearing local storage removes everything"
(clear-local-storage!)
(is (= @foo {:x 1}))
(is (= @bar {:y 2}))
(is (= (load-local-storage :foo) nil))
(is (= (load-local-storage :bar) nil))))))
7 changes: 7 additions & 0 deletions test/alandipert/storage_atom/test/macros.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(ns alandipert.storage-atom.test.macros)

(defmacro with-local-storage-testing-scope
[& body]
`(binding [alandipert.storage-atom/*storage-delay* :none]
~@body
(.clear js/localStorage)))