diff --git a/src/cljs/ulysses/utils.cljs b/src/cljs/ulysses/utils.cljs index 19cd9cd..c3efa64 100644 --- a/src/cljs/ulysses/utils.cljs +++ b/src/cljs/ulysses/utils.cljs @@ -25,6 +25,20 @@ (defn lazy-seq? [x] (instance? LazySeq x)) +;; (native object? doesn't work for objects like js/window) +;; basically, this predicate asks "can i access a js property +;; on x without throwing an error?"" +;; ex. (objectifiable? #js [1 2 3]) => true +;; ex. (objectifiable? #js {"foo" "bar"}) => true +;; ex. (objectifiable? [1 2 3]) => true +;; ex. (objectifiable? #{1 2 3}) => true +;; ex. (objectifiable? :foo) => true +;; ex. (objectifiable? nil) => false +;; ex. (objectifiable? true) => false +;; ex. (objectifiable? 3) => false +(defn objectifiable? [x] + (instance? js/Object x)) + (defn ptr "print x, then return it. useful for debugging inside threading macros or map" @@ -230,7 +244,7 @@ (defn wop "when obj is a javascript object, get property" [obj prop-name] - (when (object? obj) + (when (objectifiable? obj) (aget obj (name prop-name)))) ;; ---------------------------------------------------------------------------- @@ -240,22 +254,22 @@ (defn classes-vector "same as classes, but a vector instead of a space-sep string" [& args] - (reduce - (fn [final h] - (cond - (nil? h) final - (map? h) (into final - (apply classes-vector - (->> h - (vec) - (remove (comp false? second)) - (map first)))) - (coll? h) (into final (apply classes-vector h)) - (keyword? h) (conj final (name h)) - (string? h) (conj final h) - :else (conj final (str h)))) - [] - args)) + (distinct + (reduce + (fn [final h] + (cond + (nil? h) final + (map? h) (into final + (apply classes-vector + (->> h + (vec) + (remove (comp false? second)) + (map first)))) + (coll? h) (into final (apply classes-vector h)) + ((some-fn keyword? string? symbol?) h) (conj final (name h)) + :else (conj final (str h)))) + (vector) + args))) (defn classes "make a string of classes intelligently. @@ -297,32 +311,32 @@ (defn window-size [] "get the :width and :height of the current window" - {:width (.-innerWidth js/window) - :height (.-innerHeight js/window)}) + {:width (or (wop js/window :innerWidth) 0) + :height (or (wop js/window :innerHeight) 0)}) (defn document-size "get the :width and :height of the current document" [] - (or - (when-let [d js/document] - (let [body (wop d :body) - de (wop d :documentElement) - wb (partial wop body) - wde (partial wop de)] - (when (or body de) - {:width (max - (wb "scrollWidth") (wb "offsetWidth") - (wde "clientWidth") (wde "scrollWidth") (wde "offsetWidth")) - :height (max - (wb "scrollHeight") (wb "offsetHeight") - (wde "clientHeight") (wde "scrollHeight") (wde "offsetHeight"))}))) - {:width 0 :height 0})) + (let [body (wop js/document :body) + de (wop js/document :documentElement) + wb (partial wop body) + wde (partial wop de)] + {:width (or (max + (wb "scrollWidth") (wb "offsetWidth") + (wde "clientWidth") (wde "scrollWidth") (wde "offsetWidth")) + 0) + :height (or (max + (wb "scrollHeight") (wb "offsetHeight") + (wde "clientHeight") (wde "scrollHeight") (wde "offsetHeight")) + 0)})) (defn window-scroll-position "get the :y (scrollTop) and :x (scrollLeft) of the current window" [] (let [w js/window - de (.-documentElement js/document)] + de (wop js/document :documentElement) + ww (partial wop w) + wde (partial wop de)] {:x (or (.-pageXOffset w) (.-scrollLeft de) 0) :y (or (.-pageYOffset w) (.-scrollTop de) 0)})) @@ -356,15 +370,15 @@ (get-in db [:builder-filters :faculty-years-uconn])}))) (defn metric-maxes - "given metrics builder filters (0-100) and faculties, - return the maximum values for each metric" + "given metrics builder filters and faculties, + return the maximum values for each metric across faculties" [metrics faculties] (let [fm (map :metrics faculties)] (remap-v (fn [metric _] (->> fm (map (fn [f] (get f metric))) - (filter integer?) + (filter number?) (apply max))) metrics))) diff --git a/test/cljs/ulysses/utils_test.cljs b/test/cljs/ulysses/utils_test.cljs index eccabdd..8b7fbe3 100644 --- a/test/cljs/ulysses/utils_test.cljs +++ b/test/cljs/ulysses/utils_test.cljs @@ -7,22 +7,32 @@ [] [1] [1 2])) (deftest boolean? - (are [x y] (= x (utils/boolean? y)) - true true - true false - false nil - false "" - false 0)) + (testing "yes" + (are [y] (utils/boolean? y) + true + false)) + (testing "no" + (are [y] (not (utils/boolean? y)) + nil "" 0 "true" "false"))) (deftest lazy-seq? - (are [x y] (= x (utils/lazy-seq? y)) - true (lazy-seq) - true (map integer? [1 2 3]) - false (list) - false (vector) - false (hash-map) - false nil - false "")) + (testing "yes" + (are [y] (utils/lazy-seq? y) + (lazy-seq) + (map integer? [1 2 3]))) + (testing "no" + (are [y] (not (utils/lazy-seq? y)) + (list) (vector) (hash-map) nil ""))) + +(deftest objectifiable? + (testing "yes" + (are [y] (utils/objectifiable? y) + #js {} + #js {"foo" "bar"} + ["clojure" "too"])) + (testing "no" + (are [y] (not (utils/objectifiable? y)) + nil 3 4))) ; missing: ptr @@ -218,25 +228,101 @@ ; missing: debounce-standard (deftest wop - (testing "is object" + (testing "is objectifiable" (are [x y prop] (= x (utils/wop y prop)) 1 #js {"ok" 1} "ok" 1 #js {"ok" 1} :ok nil #js {"ok" 1} "missing" - nil #js {"ok" 1} :missing)) - (testing "is not object" + nil #js {"ok" 1} :missing + nil {"ok" 1} :missing + nil :foo-bar :missing)) + (testing "is not objectifiable" (are [y prop] (nil? (utils/wop y prop)) - #js [1 2 3] :foo + 3 :foo nil :foo - {:foo 3} :foo))) - -; missing: classes-vector - -; missing: classes - -; missing: classes-attr - -; missing: btn-classes + true :foo))) + +(deftest classes-vector + ;; need to test using set since the order of maps is not guarenteed + (are [x args] (= (set x) (set (apply utils/classes-vector args))) + [] nil + [] [] + [] [nil] + ["foo"] ["foo"] + ["foo"] [:foo] + ["foo"] ['foo] + ["100"] [100] + ["foo"] [["foo"]] + ["foo"] [#{"foo"}] + ["foo"] [[:foo]] + ["foo"] [[[[:foo]]]] + ["foo"] [{:foo true}] + [] [{:foo false}] + ["foo" "bar"] [:foo :bar] + ["foo"] [{:foo true :bar false}] + ["foo" "bar"] [{:foo true [:bar] true}] + ["foo" "bar"] [{:foo true [{:bar true}] true}] + ["foo" "bar"] [{:foo true [{["bar"] true}] true}] + ["foo"] [{:foo true [{:bar false}] true}] + ["btn" "btn-sm" "btn-primary"] [:btn [:btn-sm {:btn-primary true}]] + ["btn" "btn-sm"] [:btn [:btn-sm {:btn-primary false}]])) + +(deftest classes + ;; most functionality is tested already in classes-vector + ;; also, can only test using single classes when there are maps + ;; since order of maps is not guarenteed + (are [x args] (= x (apply utils/classes args)) + "" nil + "" [] + "" [nil] + "foo" ["foo"] + "foo" [:foo] + "foo" ['foo] + "100" [100] + "foo" [["foo"]] + "foo" [#{"foo"}] + "foo" [[:foo]] + "foo" [[[[:foo]]]] + "foo" [{:foo true}] + "" [{:foo false}] + "foo bar" [:foo :bar] + "foo bar" [:foo {:bar true}] + "foo" [{:foo true :bar false}] + "foo" [{:foo true [{:bar false}] true}])) + +(deftest classes-attr + ;; most functionality is tested already in classes-vector and classes + ;; also, can only test using single classes when there are maps + ;; since order of maps is not guarenteed + (are [x args] (= {:class x} (apply utils/classes-attr args)) + "" nil + "" [] + "" [nil] + "foo" ["foo"] + "foo" [:foo] + "foo" ['foo] + "100" [100] + "foo" [["foo"]] + "foo" [#{"foo"}] + "foo" [[:foo]] + "foo" [[[[:foo]]]] + "foo" [{:foo true}] + "" [{:foo false}] + "foo bar" [:foo :bar] + "foo bar" [:foo {:bar true}] + "foo" [{:foo true :bar false}] + "foo" [{:foo true [{:bar false}] true}])) + +(deftest btn-classes + ;; most functionality is tested already in classes-vector and classes + ;; also, can only test using single classes when there are maps + ;; since order of maps is not guarenteed + (are [x args] (= x (apply utils/btn-classes args)) + "btn" nil + "btn" [] + "btn btn-sm" [:sm] + "btn btn-sm" [:sm {:primary false}] + "btn btn-sm btn-primary" [:sm {:primary true}])) ; missing: d-p @@ -256,7 +342,25 @@ ; missing: faculties-pool-params -; missing: metric-maxes +(deftest metric-maxes + (let [metrics {:publicationCount 0 + :grantCount 0 + :grantFunds 0 + :recentGrantCount 0 + :recentGrantFunds 0} + metrics-v1 (assoc metrics :grantCount 0.9) + metrics-v2 (assoc metrics :grantCount 1.2) + metrics-v3 (assoc metrics :grantFunds 43.9)] + (is (= metrics-v1 + (utils/metric-maxes metrics [{:metrics metrics-v1}]))) + (is (= metrics-v2 + (utils/metric-maxes metrics [{:metrics metrics-v2} + {:metrics metrics-v1}]))) + (is (= (assoc metrics-v2 :grantFunds (:grantFunds metrics-v3)) + (utils/metric-maxes metrics [{:metrics metrics-v2} + {:metrics metrics-v3} + {:metrics metrics-v1}]))))) + ; missing: verify-user-poll-response