diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 3416079a..1bf4fc9b 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -178,6 +178,12 @@ (-response-coercer [_ schema] (-coercer schema :response transformers :encode opts)) (-query-string-coercer [_ schema] - (-coercer schema :string transformers :encode opts)))))) + ;; TODO: Create encoding function that only does encode, no decoding and validation? + (-coercer (mu/open-schema schema) + :string + ;; Tune transformer to not strip extra keys + {:string {:default (-transformer string-transformer-provider (assoc opts :strip-extra-keys false))}} + :encode + opts)))))) (def coercion (create default-options)) diff --git a/test/cljc/reitit/coercion_test.cljc b/test/cljc/reitit/coercion_test.cljc index baedd1d3..88f763cb 100644 --- a/test/cljc/reitit/coercion_test.cljc +++ b/test/cljc/reitit/coercion_test.cljc @@ -160,7 +160,13 @@ (is (= "/olipa/kerran?x=a&x=b" (-> router (r/match-by-name! ::route {:a "olipa", :b "kerran"}) - (r/match->path {:x [:a :b]})))))) + (r/match->path {:x [:a :b]})))) + + (is (= "/olipa/kerran?x=a&x=b&extra=extra-param" + (-> router + (r/match-by-name! ::route {:a "olipa", :b "kerran"}) + (r/match->path {:x [:a :b] + :extra "extra-param"})))))) (testing "custom encode/string for a collection" (let [router (r/router ["/:a/:b" @@ -183,6 +189,13 @@ (r/match-by-name! ::route {:a "olipa", :b "kerran"}) (r/match->path {:x [:a :b]})))) + (testing "extra query-string parameters aren't removed by coercion" + (is (= "/olipa/kerran?x=a%2Cb&extra=extra-param" + (-> router + (r/match-by-name! ::route {:a "olipa", :b "kerran"}) + (r/match->path {:x [:a :b] + :extra "extra-param"}))))) + (is (= {:query {:x [:a :b]}} (-> (r/match-by-path router "/olipa/kerran") (assoc :query-params {:x "a,b"}) diff --git a/test/cljs/reitit/frontend/easy_test.cljs b/test/cljs/reitit/frontend/easy_test.cljs index e36d9eee..63e4ecb5 100644 --- a/test/cljs/reitit/frontend/easy_test.cljs +++ b/test/cljs/reitit/frontend/easy_test.cljs @@ -1,16 +1,30 @@ (ns reitit.frontend.easy-test - (:require [clojure.test :refer [deftest testing is are async]] + (:require [clojure.string :as str] + [clojure.test :refer [are async deftest is testing]] + [goog.events :as gevents] + [reitit.coercion.malli :as rcm] [reitit.core :as r] [reitit.frontend.easy :as rfe] - [reitit.frontend.history :as rfh] - [goog.events :as gevents])) + [reitit.frontend.history :as rfh])) (def browser (exists? js/window)) (def router (r/router ["/" ["" ::frontpage] ["foo" ::foo] - ["bar/:id" ::bar]])) + ["bar/:id" + {:name ::bar + :coercion rcm/coercion + :parameters {:query [:map + [:q {:optional true} + [:keyword + {:decode/string (fn [s] + (if (string? s) + (keyword (if (str/starts-with? s "__") + (subs s 2) + s)) + s)) + :encode/string (fn [k] (str "__" (name k)))}]]]}}]])) ;; TODO: Only tests fragment history, also test HTML5? @@ -38,10 +52,11 @@ ;; 0. / 3 (do (is (= "/" url) "go back") - (rfe/navigate ::bar {:path-params {:id 1}})) + (rfe/navigate ::bar {:path-params {:id 1} + :query-params {:q "x"}})) ;; 0. / ;; 1. /bar/1 - 4 (do (is (= "/bar/1" url) + 4 (do (is (= "/bar/1?q=__x" url) "push-state 2") (rfe/replace-state ::bar {:id 2})) ;; 0. / diff --git a/test/cljs/reitit/frontend/history_test.cljs b/test/cljs/reitit/frontend/history_test.cljs index 4d3951be..5bbcfbee 100644 --- a/test/cljs/reitit/frontend/history_test.cljs +++ b/test/cljs/reitit/frontend/history_test.cljs @@ -3,14 +3,28 @@ [reitit.core :as r] [reitit.frontend.history :as rfh] [reitit.frontend.test-utils :refer [capture-console]] - [goog.events :as gevents])) + [goog.events :as gevents] + [reitit.coercion.malli :as rcm] + [clojure.string :as str])) (def browser (exists? js/window)) (def router (r/router ["/" ["" ::frontpage] ["foo" ::foo] - ["bar/:id" ::bar]])) + ["bar/:id" + {:name ::bar + :coercion rcm/coercion + :parameters {:query [:map + [:q {:optional true} + [:keyword + {:decode/string (fn [s] + (if (string? s) + (keyword (if (str/starts-with? s "__") + (subs s 2) + s)) + s)) + :encode/string (fn [k] (str "__" (name k)))}]]]}}]])) (deftest fragment-history-test (when browser @@ -24,9 +38,12 @@ (rfh/href history ::foo))) (is (= "#/bar/5" (rfh/href history ::bar {:id 5}))) - (is (= "#/bar/5?q=x" + (testing "query string coercion doesn't strip extra keys" + (is (= "#/bar/5?extra=a" + (rfh/href history ::bar {:id 5} {:extra "a"})))) + (is (= "#/bar/5?q=__x" (rfh/href history ::bar {:id 5} {:q "x"}))) - (is (= "#/bar/5?q=x#foo" + (is (= "#/bar/5?q=__x#foo" (rfh/href history ::bar {:id 5} {:q "x"} "foo"))) (let [{:keys [value messages]} (capture-console (fn [] @@ -58,11 +75,11 @@ (.back js/window.history)) 4 (do (is (= "/" url) "go back") - (rfh/push-state history ::bar {:id 1})) - 5 (do (is (= "/bar/1" url) + (rfh/push-state history ::bar {:id 1} {:extra "a"})) + 5 (do (is (= "/bar/1?extra=a" url) "push-state 2") - (rfh/replace-state history ::bar {:id 2})) - 6 (do (is (= "/bar/2" url) + (rfh/replace-state history ::bar {:id 2} {:q "x"})) + 6 (do (is (= "/bar/2?q=__x" url) "replace-state") (.back js/window.history)) 7 (do (is (= "/" url) @@ -84,7 +101,7 @@ (rfh/href history ::foo))) (is (= "/bar/5" (rfh/href history ::bar {:id 5}))) - (is (= "/bar/5?q=x" + (is (= "/bar/5?q=__x" (rfh/href history ::bar {:id 5} {:q "x"}))) (let [{:keys [value messages]} (capture-console (fn [] @@ -119,8 +136,8 @@ (rfh/push-state history ::bar {:id 1})) 5 (do (is (= "/bar/1" url) "push-state 2") - (rfh/replace-state history ::bar {:id 2})) - 6 (do (is (= "/bar/2" url) + (rfh/replace-state history ::bar {:id 2} {:q "x"})) + 6 (do (is (= "/bar/2?q=__x" url) "replace-state") (.back js/window.history)) 7 (do (is (= "/" url)