(ns com.rpl.specter.core-test #+cljs (:require-macros [cljs.test :refer [is deftest]] [cljs.test.check.cljs-test :refer [defspec]] [com.rpl.specter.cljs-test-helpers :refer [for-all+]] [com.rpl.specter.macros :refer [paramsfn defprotocolpath defpath extend-protocolpath declarepath providepath]]) (:use #+clj [clojure.test :only [deftest is]] #+clj [clojure.test.check.clojure-test :only [defspec]] #+clj [com.rpl.specter.test-helpers :only [for-all+]] #+clj [com.rpl.specter.macros :only [paramsfn defprotocolpath defpath extend-protocolpath declarepath providepath]] ) (:require #+clj [clojure.test.check.generators :as gen] #+clj [clojure.test.check.properties :as prop] #+cljs [cljs.test.check :as tc] #+cljs [cljs.test.check.generators :as gen] #+cljs [cljs.test.check.properties :as prop :include-macros true] [com.rpl.specter :as s] [clojure.set :as set])) ;;TODO: ;; test walk, codewalk (defn limit-size [n {gen :gen}] (gen/->Generator (fn [rnd _size] (gen rnd (if (< _size n) _size n))))) (defn gen-map-with-keys [key-gen val-gen & keys] (gen/bind (gen/map key-gen val-gen) (fn [m] (gen/bind (apply gen/hash-map (mapcat (fn [k] [k val-gen]) keys)) (fn [m2] (gen/return (merge m m2))))))) (defspec select-all-keyword-filter (for-all+ [kw gen/keyword v (gen/vector (limit-size 5 (gen-map-with-keys gen/keyword gen/int kw))) pred (gen/elements [odd? even?])] (= (s/select [s/ALL kw pred] v) (->> v (map kw) (filter pred)) ))) (defspec select-pos-extreme-pred (for-all+ [v (gen/vector gen/int) pred (gen/elements [odd? even?]) pos (gen/elements [[s/FIRST first] [s/LAST last]])] (= (s/select-one [(s/filterer pred) (first pos)] v) (->> v (filter pred) ((last pos))) ))) (defspec select-all-on-map (for-all+ [m (limit-size 5 (gen/map gen/keyword gen/int))] (= (s/select [s/ALL s/LAST] m) (for [[k v] m] v)) )) (deftest select-one-test (is (thrown? #+clj Exception #+cljs js/Error (s/select-one [s/ALL even?] [1 2 3 4]))) (is (= 1 (s/select-one [s/ALL odd?] [2 4 1 6]))) ) (deftest select-first-test (is (= 7 (s/select-first [(s/filterer odd?) s/ALL #(> % 4)] [3 4 2 3 7 5 9 8]))) (is (nil? (s/select-first [s/ALL even?] [1 3 5 9]))) ) (defspec transform-all-on-map (for-all+ [m (limit-size 5 (gen/map gen/keyword gen/int))] (= (s/transform [s/ALL s/LAST] inc m) (into {} (for [[k v] m] [k (inc v)])) ))) (defspec transform-all (for-all+ [v (gen/vector gen/int)] (let [v2 (s/transform [s/ALL] inc v)] (and (vector? v2) (= v2 (map inc v))) ))) (defspec transform-all-list (for-all+ [v (gen/list gen/int)] (let [v2 (s/transform [s/ALL] inc v)] (and (seq? v2) (= v2 (map inc v))) ))) (defspec transform-all-filter (for-all+ [v (gen/vector gen/int) pred (gen/elements [odd? even?]) action (gen/elements [inc dec])] (let [v2 (s/transform [s/ALL pred] action v)] (= v2 (map (fn [v] (if (pred v) (action v) v)) v)) ))) (defspec transform-last (for-all+ [v (gen/not-empty (gen/vector gen/int)) pred (gen/elements [inc dec])] (let [v2 (s/transform [s/LAST] pred v)] (= v2 (concat (butlast v) [(pred (last v))])) ))) (defspec transform-first (for-all+ [v (gen/not-empty (gen/vector gen/int)) pred (gen/elements [inc dec])] (let [v2 (s/transform [s/FIRST] pred v)] (= v2 (concat [(pred (first v))] (rest v) )) ))) (defspec transform-filterer-all-equivalency (prop/for-all [v (gen/vector gen/int) pred (gen/elements [even? odd?]) updater (gen/elements [inc dec])] (let [v2 (s/transform [(s/filterer pred) s/ALL] updater v) v3 (s/transform [s/ALL pred] updater v)] (= v2 v3)) )) (defspec transform-with-context (for-all+ [kw1 gen/keyword kw2 gen/keyword m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2)) pred (gen/elements [odd? even?])] (= (s/transform [(s/collect-one kw2) kw1 pred] + m) (if (pred (kw1 m)) (assoc m kw1 (+ (kw1 m) (kw2 m))) m )))) (defn differing-elements [v1 v2] (->> (map vector v1 v2) (map-indexed (fn [i [e1 e2]] (if (not= e1 e2) i))) (filter identity))) (defspec transform-last-compound (for-all+ [pred (gen/elements [odd? even?]) v (gen/such-that #(some pred %) (gen/vector gen/int))] (let [v2 (s/transform [(s/filterer pred) s/LAST] inc v) differing-elems (differing-elements v v2)] (and (= (count v2) (count v)) (= (count differing-elems) 1) (every? (complement pred) (drop (first differing-elems) v2)) )))) ;; max sizes prevent too much data from being generated and keeps test from taking forever (defspec transform-keyword (for-all+ [k1 (limit-size 3 gen/keyword) k2 (limit-size 3 gen/keyword) m1 (limit-size 5 (gen-map-with-keys gen/keyword (gen-map-with-keys gen/keyword gen/int k2) k1)) pred (gen/elements [inc dec])] (let [m2 (s/transform [k1 k2] pred m1)] (and (= (assoc-in m1 [k1 k2] nil) (assoc-in m2 [k1 k2] nil)) (= (pred (get-in m1 [k1 k2])) (get-in m2 [k1 k2]))) ))) (defspec replace-in-test (for-all+ [v (gen/vector gen/int)] (let [res (->> v (map (fn [v] (if (even? v) (inc v) v)))) user-ret (->> v (filter even?) (map (fn [v] [v v])) (apply concat)) user-ret (if (empty? user-ret) nil user-ret)] (= (s/replace-in [s/ALL even?] (fn [v] [(inc v) [v v]]) v) [res user-ret] )))) (defspec replace-in-custom-merge (for-all+ [v (gen/vector gen/int)] (let [res (->> v (map (fn [v] (if (even? v) (inc v) v)))) last-even (->> v (filter even?) last) user-ret (if last-even {:a last-even})] (= (s/replace-in [s/ALL even?] (fn [v] [(inc v) v]) v :merge-fn (fn [curr new] (assoc curr :a new))) [res user-ret] )))) (defspec srange-extremes-test (for-all+ [v (gen/vector gen/int) v2 (gen/vector gen/int)] (let [b (s/setval s/BEGINNING v2 v) e (s/setval s/END v2 v)] (and (= b (concat v2 v)) (= e (concat v v2))) ))) (defspec srange-test (for-all+ [v (gen/vector gen/int) b (gen/elements (-> v count inc range)) e (gen/elements (range b (-> v count inc))) ] (let [sv (subvec v b e) predcount (fn [pred v] (->> v (filter pred) count)) even-count (partial predcount even?) odd-count (partial predcount odd?) b (s/transform (s/srange b e) (fn [r] (filter odd? r)) v)] (and (= (odd-count v) (odd-count b)) (= (+ (even-count b) (even-count sv)) (even-count v))) ))) (deftest structure-path-directly-test (is (= 3 (s/select-one :b {:a 1 :b 3}))) (is (= 5 (s/select-one (s/comp-paths :a :b) {:a {:b 5}}))) ) (defspec view-test (for-all+ [i gen/int afn (gen/elements [inc dec])] (= (first (s/select (s/view afn) i)) (afn i) (s/transform (s/view afn) identity i) ))) (deftest selected?-test (is (= [[1 3 5] [2 :a] [7 11 4 2 :a] [10 1 :a] []] (s/setval [s/ALL (s/selected? s/ALL even?) s/END] [:a] [[1 3 5] [2] [7 11 4 2] [10 1] []] )))) (defspec identity-test (for-all+ [i gen/int afn (gen/elements [inc dec])] (and (= [i] (s/select nil i)) (= (afn i) (s/transform nil afn i))))) (deftest nil-comp-test (is (= [5] (s/select (com.rpl.specter.impl/comp-paths* nil) 5)))) (defspec putval-test (for-all+ [kw gen/keyword m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw)) c gen/int] (= (s/transform [(s/putval c) kw] + m) (s/transform [kw (s/putval c)] + m) (assoc m kw (+ c (get m kw))) ))) (defspec empty-selector-test (for-all+ [v (gen/vector gen/int)] (= [v] (s/select [] v) (s/select nil v) (s/select (s/comp-paths) v) (s/select (s/comp-paths nil) v) (s/select [nil nil nil] v) ))) (defspec empty-selector-transform-test (for-all+ [kw gen/keyword m (limit-size 10 (gen-map-with-keys gen/keyword gen/int kw))] (and (= m (s/transform nil identity m) (s/transform [] identity m) (s/transform (s/comp-paths []) identity m) (s/transform (s/comp-paths nil nil) identity m) ) (= (s/transform kw inc m) (s/transform [nil kw] inc m) (s/transform (s/comp-paths kw nil) inc m) (s/transform (s/comp-paths nil kw nil) inc m) )))) (deftest compose-empty-comp-path-test (let [m {:a 1}] (is (= [1] (s/select [:a (s/comp-paths)] m) (s/select [(s/comp-paths) :a] m) )))) (defspec mixed-selector-test (for-all+ [k1 (limit-size 3 gen/keyword) k2 (limit-size 3 gen/keyword) m (limit-size 5 (gen-map-with-keys gen/keyword (gen-map-with-keys gen/keyword gen/int k2) k1))] (= [(-> m k1 k2)] (s/select [k1 (s/comp-paths k2)] m) (s/select [(s/comp-paths k1) k2] m) (s/select [(s/comp-paths k1 k2) nil] m) (s/select [(s/comp-paths) k1 k2] m) (s/select [k1 (s/comp-paths) k2] m) ))) (deftest cond-path-test (is (= [4 2 6 8 10] (s/select [s/ALL (s/cond-path even? [(s/view inc) (s/view inc)] #(= 3 %) (s/view dec))] [1 2 3 4 5 6 7 8]))) (is (empty? (s/select (s/if-path odd? (s/view inc)) 2))) (is (= [6 2 10 6 14] (s/transform [(s/putval 2) s/ALL (s/if-path odd? [(s/view inc) (s/view inc)] (s/view dec))] * [1 2 3 4 5] ))) (is (= 2 (s/transform [(s/putval 2) (s/if-path odd? (s/view inc))] * 2))) ) (defspec cond-path-selector-test (for-all+ [k1 (limit-size 3 gen/keyword) k2 (limit-size 3 gen/keyword) k3 (limit-size 3 gen/keyword) m (limit-size 5 (gen-map-with-keys gen/keyword gen/int k1 k2 k3)) pred (gen/elements [odd? even?]) ] (let [v1 (get m k1) k (if (pred v1) k2 k3)] (and (= (s/transform (s/if-path [k1 pred] k2 k3) inc m) (s/transform k inc m)) (= (s/select (s/if-path [k1 pred] k2 k3) m) (s/select k m)) )))) (defspec multi-path-test (for-all+ [k1 (limit-size 3 gen/keyword) k2 (limit-size 3 gen/keyword) m (limit-size 5 (gen-map-with-keys gen/keyword gen/int k1 k2)) ] (= (s/transform (s/multi-path k1 k2) inc m) (->> m (s/transform k1 inc) (s/transform k2 inc))) )) (deftest empty-pos-transform (is (empty? (s/select s/FIRST []))) (is (empty? (s/select s/LAST []))) (is (= [] (s/transform s/FIRST inc []))) (is (= [] (s/transform s/LAST inc []))) ) (defspec set-filter-test (for-all+ [k1 gen/keyword k2 (gen/such-that #(not= k1 %) gen/keyword) k3 (gen/such-that (complement #{k1 k2}) gen/keyword) v (gen/vector (gen/elements [k1 k2 k3]))] (= (filter #{k1 k2} v) (s/select [s/ALL #{k1 k2}] v)) )) (deftest nil-select-one-test (is (= nil (s/select-one! s/ALL [nil]))) (is (thrown? #+clj Exception #+cljs js/Error (s/select-one! s/ALL []))) ) (defspec transformed-test (for-all+ [v (gen/vector gen/int) pred (gen/elements [even? odd?]) op (gen/elements [inc dec])] (= (s/select-one (s/transformed [s/ALL pred] op) v) (s/transform [s/ALL pred] op v)) )) (defspec basic-parameterized-composition-test (for-all+ [k1 (limit-size 3 gen/keyword) k2 (limit-size 3 gen/keyword) m1 (limit-size 5 (gen-map-with-keys gen/keyword (gen-map-with-keys gen/keyword gen/int k2) k1)) pred (gen/elements [inc dec])] (let [p (s/comp-paths s/keypath s/keypath)] (and (= (s/compiled-select (p k1 k2) m1) (s/select [k1 k2] m1)) (= (s/compiled-transform (p k1 k2) pred m1) (s/transform [k1 k2] pred m1)) )))) (defspec various-orders-comp-test (for-all+ [k1 (limit-size 3 gen/keyword) k2 (limit-size 3 gen/keyword) k3 (limit-size 3 gen/keyword) m1 (limit-size 5 (gen-map-with-keys gen/keyword (gen-map-with-keys gen/keyword (gen-map-with-keys gen/keyword gen/int k3 ) k2) k1)) pred (gen/elements [inc dec])] (let [paths [((s/comp-paths s/keypath s/keypath k3) k1 k2) (s/comp-paths k1 k2 k3) ((s/comp-paths s/keypath k2 s/keypath) k1 k3) ((s/comp-paths k1 s/keypath k3) k2) (s/comp-paths k1 (s/keypath k2) k3) ((s/comp-paths (s/keypath k1) s/keypath (s/keypath k3)) k2) ((s/comp-paths s/keypath (s/keypath k2) s/keypath) k1 k3) ] ] (and (apply = (for [p paths] (s/compiled-select p m1))) (apply = (for [p paths] (s/compiled-transform p pred m1))) )))) (defspec filterer-param-test (for-all+ [k gen/keyword k2 gen/keyword v (gen/vector (limit-size 5 (gen-map-with-keys gen/keyword gen/int k k2 ))) pred (gen/elements [odd? even?]) updater (gen/elements [inc dec])] (and (= (s/compiled-select ((s/filterer s/keypath pred) k) v) (s/compiled-select (s/filterer k pred) v)) (= (s/compiled-transform ((s/comp-paths (s/filterer s/keypath pred) s/ALL k2) k) updater v) (s/compiled-transform (s/comp-paths (s/filterer k pred) s/ALL k2) updater v)) ))) (deftest nested-param-paths (let [p (s/filterer s/keypath (s/selected? s/ALL s/keypath (s/filterer s/keypath even?) s/ALL)) p2 (p :a :b :c) p3 (s/filterer :a (s/selected? s/ALL :b (s/filterer :c even?) s/ALL)) data [{:a [{:b [{:c 4 :d 5}]}]} {:a [{:c 3}]} {:a [{:b [{:c 7}] :e [1]}]}] ] (is (= (s/select p2 data) (s/select p3 data) [[{:a [{:b [{:c 4 :d 5}]}]}]] )) )) (defspec param-multi-path-test (for-all+ [k1 gen/keyword k2 gen/keyword k3 gen/keyword m (limit-size 5 (gen-map-with-keys gen/keyword gen/int k1 k2 k3 )) pred1 (gen/elements [odd? even?]) pred2 (gen/elements [odd? even?]) updater (gen/elements [inc dec]) ] (let [paths [((s/multi-path [s/keypath pred1] [s/keypath pred2] k3) k1 k2) ((s/multi-path [k1 pred1] [s/keypath pred2] s/keypath) k2 k3) ((s/multi-path [s/keypath pred1] [s/keypath pred2] s/keypath) k1 k2 k3) (s/multi-path [k1 pred1] [k2 pred2] k3) ((s/multi-path [k1 pred1] [s/keypath pred2] k3) k2) ]] (and (apply = (for [p paths] (s/select p m) )) (apply = (for [p paths] (s/transform p updater m) )) )))) (defspec paramsfn-test (for-all+ [v (gen/vector (gen/elements (range 10))) val (gen/elements (range 10)) op (gen/elements [inc dec]) comparator (gen/elements [= > <])] (let [path (s/comp-paths s/ALL (paramsfn [p] [v] (comparator v p)))] (= (s/transform (path val) op v) (s/transform [s/ALL #(comparator % val)] op v))) )) (defspec subset-test (for-all+ [s1 (gen/vector (limit-size 5 gen/keyword)) s2 (gen/vector (limit-size 5 gen/keyword)) s3 (gen/vector (limit-size 5 gen/int)) s4 (gen/vector (limit-size 5 gen/keyword))] (let [s1 (set s1) s2 (set s1) s3 (set s1) s4 (set s1) combined (set/union s1 s2) ss (set/union s2 s3)] (and (= (s/transform (s/subset s3) identity combined) combined) (= (s/setval (s/subset s3) #{} combined) (set/difference combined s2)) (= (s/setval (s/subset s3) s4 combined) (-> combined (set/difference s2) (set/union s4))) )))) (deftest nil->val-test (is (= {:a #{:b}} (s/setval [:a s/NIL->SET (s/subset #{})] #{:b} nil))) (is (= {:a #{:b :c :d}} (s/setval [:a s/NIL->SET (s/subset #{})] #{:b} {:a #{:c :d}}))) (is (= {:a [:b]} (s/setval [:a s/NIL->VECTOR s/END] [:b] nil))) ) (defspec void-test (for-all+ [s1 (gen/vector (limit-size 5 gen/int))] (and (empty? (s/select s/VOID s1)) (empty? (s/select [s/VOID s/ALL s/ALL s/ALL s/ALL] s1)) (= s1 (s/transform s/VOID inc s1)) (= s1 (s/transform [s/ALL s/VOID s/ALL] inc s1)) (= (s/transform [s/ALL (s/cond-path even? nil odd? s/VOID)] inc s1) (s/transform [s/ALL even?] inc s1)) ))) (deftest stay-continue-tests (is (= [[1 2 [:a :b]] [3 [:a :b]] [:a :b [:a :b]]] (s/setval [(s/stay-then-continue s/ALL) s/END] [[:a :b]] [[1 2] [3]]))) (is (= [[1 2 [:a :b]] [3 [:a :b]] [:a :b]] (s/setval [(s/continue-then-stay s/ALL) s/END] [[:a :b]] [[1 2] [3]]))) (is (= [[1 2 3] 1 3] (s/select (s/stay-then-continue s/ALL odd?) [1 2 3]))) (is (= [1 3 [1 2 3]] (s/select (s/continue-then-stay s/ALL odd?) [1 2 3]))) ) (declarepath MyWalker) (providepath MyWalker (s/if-path vector? (s/if-path [s/FIRST #(= :abc %)] (s/continue-then-stay s/ALL MyWalker) [s/ALL MyWalker] ))) (deftest recursive-path-test (is (= [9 1 10 3 1] (s/select [MyWalker s/ALL number?] [:bb [:aa 34 [:abc 10 [:ccc 9 8 [:abc 9 1]]]] [:abc 1 [:abc 3]]]) )) (is (= [:bb [:aa 34 [:abc 11 [:ccc 9 8 [:abc 10 2]]]] [:abc 2 [:abc 4]]] (s/transform [MyWalker s/ALL number?] inc [:bb [:aa 34 [:abc 10 [:ccc 9 8 [:abc 9 1]]]] [:abc 1 [:abc 3]]]) )) ) (declarepath map-key-walker [akey]) (providepath map-key-walker [s/ALL (s/if-path [s/FIRST (paramsfn [akey] [curr] (= curr akey))] s/LAST [s/LAST (s/params-reset map-key-walker)])]) (deftest recursive-params-path-test (is (= #{1 2 3} (set (s/select (map-key-walker :aaa) {:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}})))) (is (= {:a {:aaa 4 :b {:c {:aaa 3} :aaa 2}}} (s/transform (map-key-walker :aaa) inc {:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}}))) (is (= {:a {:c {:b "X"}}}) (s/setval (map-key-walker :b) "X" {:a {:c {:b {:d 1}}}})) ) (deftest recursive-params-composable-path-test (let [p (s/comp-paths s/keypath map-key-walker)] (is (= [1] (s/select (p 1 :a) [{:a 3} {:a 1} {:a 2}]))) )) (deftest all-map-test (is (= {3 3} (s/transform [s/ALL s/FIRST] inc {2 3}))) (is (= {3 21 4 31} (s/transform [s/ALL s/ALL] inc {2 20 3 30}))) ) (declarepath NestedHigherOrderWalker [k]) (providepath NestedHigherOrderWalker (s/if-path vector? (s/if-path [s/FIRST (paramsfn [k] [e] (= k e))] (s/continue-then-stay s/ALL (s/params-reset NestedHigherOrderWalker)) [s/ALL (s/params-reset NestedHigherOrderWalker)] ))) (deftest nested-higher-order-walker-test (is (= [:q [:abc :I 3] [:ccc [:abc :I] [:abc :I "a" [:abc :I [:abc :I [:d]]]]]] (s/setval [(NestedHigherOrderWalker :abc) (s/srange 1 1)] [:I] [:q [:abc 3] [:ccc [:abc] [:abc "a" [:abc [:abc [:d]]]]]] )))) #+clj (deftest large-params-test (let [path (apply s/comp-paths (repeat 25 s/keypath)) m (reduce (fn [m k] {k m}) :a (reverse (range 25)))] (is (= :a (s/select-one (apply path (range 25)) m))) )) ;;TODO: there's a bug in clojurescript that won't allow ;; non function implementations of IFn to have more than 20 arguments #+clj (do (defprotocolpath AccountPath []) (defrecord Account [funds]) (defrecord User [account]) (defrecord Family [accounts]) (extend-protocolpath AccountPath User :account Family [:accounts s/ALL]) ) #+clj (deftest protocolpath-basic-test (let [data [(->User (->Account 30)) (->User (->Account 50)) (->Family [(->Account 51) (->Account 52)])]] (is (= [30 50 51 52] (s/select [s/ALL AccountPath :funds] data))) (is (= [(->User (->Account 31)) (->User (->Account 51)) (->Family [(->Account 52) (->Account 53)])] (s/transform [s/ALL AccountPath :funds] inc data))) )) #+clj (do (defprotocolpath LabeledAccountPath [label]) (defrecord LabeledUser [account]) (defrecord LabeledFamily [accounts]) (extend-protocolpath LabeledAccountPath LabeledUser [:account s/keypath] LabeledFamily [:accounts s/keypath s/ALL]) ) #+clj (deftest protocolpath-params-test (let [data [(->LabeledUser {:a (->Account 30)}) (->LabeledUser {:a (->Account 50)}) (->LabeledFamily {:a [(->Account 51) (->Account 52)]})]] (is (= [30 50 51 52] (s/select [s/ALL (LabeledAccountPath :a) :funds] data))) (is (= [(->LabeledUser {:a (->Account 31)}) (->LabeledUser {:a (->Account 51)}) (->LabeledFamily {:a [(->Account 52) (->Account 53)]})] (s/transform [s/ALL (LabeledAccountPath :a) :funds] inc data))) )) #+clj (do (defprotocolpath CustomWalker []) (extend-protocolpath CustomWalker Object nil clojure.lang.PersistentHashMap [(s/keypath :a) CustomWalker] clojure.lang.PersistentArrayMap [(s/keypath :a) CustomWalker] clojure.lang.PersistentVector [s/ALL CustomWalker] ) ) #+clj (deftest mixed-rich-regular-protocolpath (is (= [1 2 3 11 21 22 25] (s/select [CustomWalker number?] [{:a [1 2 :c [3]]} [[[[[[11]]] 21 [22 :c 25]]]]]))) (is (= [2 3 [[[4]] :b 0] {:a 4 :b 10}] (s/transform [CustomWalker number?] inc [1 2 [[[3]] :b -1] {:a 3 :b 10}]))) )