How to properly (unit) test Om/React components? - unit-testing

I have developed Om/React components, but I feel really uncomfortable not being able to drive my development with unit tests. I have tried to setup my clojurescript project to run unit tests on those components, and so far reached the point where I am able to write unit tests and instantiate my components. What I am missing is the ability to ensure my components properly react to some events, e.g. onChange so that I can simulate user inputs.
Here is my test code:
(defn simulate-click-event
"From https://github.com/levand/domina/blob/master/test/cljs/domina/test.cljs"
[el]
(let [document (.-document js/window)]
(cond
(.-click el) (.click el)
(.-createEvent document) (let [e (.createEvent document "MouseEvents")]
(.initMouseEvent e "click" true true
js/window 0 0 0 0 0
false false false false 0 nil)
(.dispatchEvent el e))
:default (throw "Unable to simulate click event"))))
(defn simulate-change-event
"From https://github.com/levand/domina/blob/master/test/cljs/domina/test.cljs"
[el]
(let [document (.-document js/window)]
(cond
(.-onChange el) (do (print "firing on change on " el) (.onChange el))
(.-createEvent document) (let [e (.createEvent document "HTMLEvents")]
(print "firing " e " on change on " (.-id el))
(.initEvent e "change" true true)
(.dispatchEvent el e))
:default (throw "Unable to simulate change event"))))
(def sink
"contains a channel that receives messages along with notification type"
(chan))
;; see http://yobriefca.se/blog/2014/06/04/publish-and-subscribe-with-core-dot-asyncs-pub-and-sub/
(def source
(pub sink #(:topic %)))
(defn change-field!
[id value]
(let [el (sel1 (keyword (str "#" id)))]
(dommy/set-value! el value)
(simulate-change-event el)
))
(deftest ^:async password-confirmation
(testing "do not submit if passwords are not equal"
(let [subscription (chan)]
(sub source :user-registration subscription)
(om/root
(partial u/registration-view source sink)
nil
{:target (sel1 :#view)})
(go
(let [m (<! subscription)]
(is (= :error (:state m)))
(done)
))
(change-field! "userRequestedEmail" "foo#bar.com")
(change-field! "userRequestedPassword" "secret")
(change-field! "confirmPassword" "nosecret")
(simulate-click-event (sel1 :#submitRegistration))
)))
This test runs but fails because the change-field! function does not actually change the state of the component. Here is (part of) the code of the component (forgive duplication...):
(defn registration-view
"Registration form for users.
Submitting form triggers a request to server"
[source sink _ owner]
(reify
om/IInitState
(init-state [_]
{:userRequestedEmail ""
:userRequestedPassword ""
:confirmPassword ""}
)
om/IRenderState
(render-state
[this state]
(dom/fieldset
nil
(dom/legend nil "User Registration")
(dom/div #js { :className "pure-control-group" }
(dom/label #js { :for "userRequestedEmail" } "EMail")
(dom/input #js { :id "userRequestedEmail" :type "text" :placeholder "Enter an e-mail"
:value (:userRequestedEmail state)
:onChange #(om/set-state! owner :userRequestedEmail (.. % -target -value))}))
(dom/div #js { :className "pure-control-group" }
(dom/label #js { :for "userRequestedPassword" } "Password")
(dom/input #js { :id "userRequestedPassword" :type "password" :placeholder "Enter password"
:value (:userRequestedPassword state)
:onChange #(om/set-state! owner :userRequestedPassword (.. % -target -value))}))
(dom/div #js { :className "pure-control-group" }
(dom/label #js { :for "confirmPassword" } "")
(dom/input #js { :id "confirmPassword" :type "password" :placeholder "Confirm password"
:value (:confirmPassword state)
:onChange #(om/set-state! owner :confirmPassword (.. % -target -value))}))
(dom/button #js {:type "submit"
:id "submitRegistration"
:className "pure-button pure-button-primary"
:onClick #(submit-registration state sink)}
"Register")))))
What I can see by putting traces in the tests is that the state of the component is not updated when I trigger the change event, although it is correctly triggered. I suspect this has to do with the way Om/React works, wrapping DOM components, but not sure how to deal with this.

You can mock events in your components using ReactTestUtils from the react libraries.
I'm using mocha and doing something like this to test change events:
var comp = ReactTestUtils.renderIntoDocument(<Component />);
var changingElement = ReactTestUtils.findRenderedDOMComponentWithClass(comp, 'el-class');
it ('calls myChangeMethod on change', function() {
ReactTestUtils.Simulate.change(changingElement);
assert(comp.myChangeEventMethod.called, true);
}

Related

How do I generate user friendly validation messages with plumatic/schema?

I would like to be able to generate user-friendly or specify custom error messages for validation errors in these schemas:
(def Uuid (s/constrained String #(re-matches #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" (name %))))
(def FirstName s/Str)
(def LastName s/Str)
(s/defschema Person {(s/required-key :id) Uuid,
(s/required-key :first-name) FirstName,
(s/required-key :last-name) LastName})
Valid schema:
{
:uuid "e143499c-1257-41e4-b951-c9e586994ff9"
:first-name "john"
:last-name "smith"
}
Invalid schema:
{
:uuid ""
:first-name nil
:last-name nil
}
Invalid schema - Errors:
{
"id" : "(not (app.person/fn--4881 \"\"))",
"first-name" : "(not (instance? java.lang.String nil))"
"last-name" : "(not (instance? java.lang.String nil))"
}
I would like to be able to generate something a bit more readable to non-programmers, for example:
{
"id" : "invalid uuid",
"first-name" : "must be a string"
"last-name" : "must be a string"
}
Funnily exactly this was released as a library a few days ago.
See:
https://github.com/siilisolutions/humanize
First you also need to tag your Uuid schema so you can match it later on:
;; Note the last param I added:
(def Uuid (sc/constrained
String
#(re-matches #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
(name %))
'UUID))
(require '[schema.core :as sc]
'[humanize.schema :as hs])
(#'hs/explain (sc/check Person {:id "foo"
:first-name "foo"
:last-name 3})
(fn [x]
(clojure.core.match/match
x
['not ['UUID xx]]
(str xx " is not a valid UUID")
:else x)))
Results in:
=> {:id "foo is not a valid UUID", :last-name "'3' is not a string but it should be."}
Note, it needed a little trick since hs/explain is private unfortunately.

Why is my or-spec only valid for one of the given specs?

Consider the following spec for a text or a link layer port number:
(require '[clojure.spec.alpha :as spec])
(spec/def ::text (spec/and string? not-empty))
(spec/valid? ::text "a") ; => true
(spec/valid? ::text "") ; => false
(spec/def ::port (spec/and pos-int? (partial > 65535)))
(spec/valid? ::port 4) ; => true
(spec/valid? ::port 0) ; => false
(spec/def ::text-or-port (spec/or ::text ::port))
(spec/valid? ::text-or-port 5) ; => true
(spec/valid? ::text-or-port "hi") ; => false
For some reason it only accepts port-numbers and not text, why would that be?
The key to understanding this problem can be found in in the documentation and using spec/conform.
(spec/conform ::text-or-port 5)
; => [:user/text 5]
The problem is that clojure.spec.alpha/or has an API which is dissimmilar to clojure.core/or which given two arguments returns the first truthy one:
(#(or (string? %) (integer? %)) 5) ; => true
(#(or (string? %) (integer? %)) "") ; => true
(#(or (string? %) (integer? %)) :a) ; => false
Rather it takes pairs of labels and specs/predicates. And since even namespaced keywords are accepted as labels the ::text-or-port spec given in the OP matched only that which passed the requirements for ::port and gave it the label ::text. Below is a correct spec for that which we want to match:
(spec/def ::text-or-port (spec/or :text ::text
:port ::port))
(spec/valid? ::text-or-port "hi") ; => true
(spec/valid? ::text-or-port 10) ; => true

Moving partition-by's splits "back by one"

I'm parsing some Hiccup in CLJS, with the goal of taking :h2 and :h3 elements and converting them to a tree of nested :ul and :li.
My starting point is a flat vector like:
[[:h2 {} "Foo"] [:h2 {} "Bar"] [:h3 {} "Child1"] [:h2 {} "Baz"]]
If I just map over these and replace (first el) with [:li], I have a flat list. But I'd like to get something like:
[[:li "Foo"] [:li "Bar"] [:ul [:li "Child1"]] [:li "Baz"]]
If I call (partition-by #(= :h2 (first %)) my-vec), I get something almost useful:
(([:h2 {} "Foo"] [:h2 {} "Bar"]) ([:h3 {} "Child1"]) ([:h2 {} "Baz"]))
The partition happens when the predicate #(= :h2 (first %)) changes, (which is what the documentation says it does).
How can I get the behavior I'm looking for?
Here is one way to do it:
(def data [
[:h2 {} "Foo"]
[:h2 {} "Bar"]
[:h3 {} "Child1"]
[:h2 {} "Baz"] ] )
(defn formatter [elem]
(condp = (first elem)
:h2 [:li (last elem)]
:h3 [:ul [:li (last elem)]]
))
(newline) (println :data data)
(newline) (println :result (mapv formatter data))
with result
:data [[:h2 {} Foo] [:h2 {} Bar] [:h3 {} Child1] [:h2 {} Baz]]
:result [[:li Foo] [:li Bar] [:ul [:li Child1]] [:li Baz]]
Update:
Rewrite like so to get all the :h3 items in one :ul
(def data [
[:h2 {} "Foo"]
[:h3 {} "Child1"]
[:h2 {} "Bar"]
[:h3 {} "Child2"]
[:h3 {} "Child3"]
[:h2 {} "Baz"] ] )
(defn h2? [elem]
(= :h2 (first elem)))
(defn ->li [elem]
[:li (last elem)])
(defn fmt [data]
(let [h2 (filter h2? data)
h3 (filter #(not (h2? %)) data)
result (conj (mapv ->li h2)
(apply vector :ul (mapv ->li h3))) ]
result ))
(newline) (println :data data)
(newline) (println :result (fmt data))
with result
:data [[:h2 {} Foo] [:h3 {} Child1] [:h2 {} Bar] [:h3 {} Child2] [:h3 {} Child3] [:h2 {} Baz]]
:result [[:li Foo] [:li Bar] [:li Baz] [:ul [:li Child1] [:li Child2] [:li Child3]]]
Here's an answer that does the job, but is horribly inelegant, since it essentially mutates the last element in the reduce call when necessary:
(defn listify-element [element]
"Replaces element type with :li."
(vec (concat [:li (last element))]))
(defn listify-headings [headings-list]
"Takes subitems (in :h2 :h3) and creates sub :uls out of the :h3 lists."
(vec
(concat
[:ul]
(map-indexed
(fn [ind headings]
(if (= 0 (mod ind 2))
(map listify-element headings)
(vec (concat [:ul] (map listify-element headings)))))
(partition-by #(= :h2 (first %)) headings-list)))))
(defn nest-listified-headings [vector-list]
"Nests sub-:uls inside their preceding :lis."
(vec (concat [:ul]
(reduce
(fn [acc el] (if (= (first el) :ul)
(conj (pop (vec acc)) (conj (last acc) el))
(concat acc el)))
vector-list))))
Produces:
(nest-listified-headings
(listify-headings [[:h2 "Foo"] [:h2 "Bar"] [:h3 "Baz"] [:h3 "Bat"]])
[:ul [:li "Foo"]
[:li "Bar"
[:ul
[:li "Baz"]
[:li "Bat"]]]]

Clojure - How i can count vector of entry maps

How i can count mobile and web access discarding a nil values from a list of maps? the output should be anything like this " Statistic mobile = 1 web = 2", but all is imutable on other languagens a simple i++ resolve but how is in clojure. thanks.
def data [{:name "app1" :type "mobile" }
{:name "site1" :type "web" }
{:name "site1" :type "web" }
{:name "boot" :type nil }]
(frequencies (map :type data))
gives
{"mobile" 1, "web" 2, nil 1}
user=> (for [[k v] (group-by :type data) :when k] [k (count v)])
(["mobile" 1] ["web" 2])

Clojure: a function that removes a given node from a collection

I'd like to have a function that removes any node (sub-collection) from a collection containing that node.
(def coll {:a ["b" {:c "d" :e ["f" {:g "h"}]}]})
(def node {:g "h"})
What would be a good remove-node function?
(remove-node coll node)
;=> {:a ["b" {:c "d" :e ["f"]}]})
Thanks!
EDIT :
What I want to do is delete an enlive-node
(def enlive-node
[{:type :dtd, :data ["html" nil nil]}
{:tag :html,
:attrs nil,
:content ["\n"
{:tag :head,
:attrs nil,
:content ["\n \n "
{:tag :title,
:attrs nil,
:content ["Stack Overflow"]}
"\n "
{:tag :link,
:attrs {:href "//cdn.sstatic.net/stackoverflow/img/favicon.ico",
:rel "shortcut icon"},
:content nil}]}]}])
The node to remove is always a string or an entire hash-map.
(remove-node enlive-node {:tag :title,
:attrs nil,
:content ["Stack Overflow"]})
For the example you have, you can use clojure.walk/postwalk to walk the hashmap and remove the node.
(require '[clojure.walk :as walk])
(defn remove-node [coll target]
(walk/postwalk
(fn [item]
(if (vector? item)
(filterv #(not= target %) item)
item))
coll))
(remove-node coll node)
EDIT:
From you updated question, it looks like you are operating on an Enlive node collection. An additional solution, in your case, would be to generate an Enlive node selector and transform the collection using the net.cgrand.enlive-html/at* function.
(require '[net.cgrand.enlive-html :as e])
(defn gen-transform [target]
[[(cond
(string? target) e/text-node
(map? target) (:tag target)
:else e/any-node)]
#(when (not= target %) %)])
(defn remove-node [coll & nodes]
(e/at* coll (map gen-transform nodes)))
(remove-node enlive-node
{:tag :title, :attrs nil, :content ["Stack Overflow"]}
"\n")