diff --git a/modules/reitit-frontend/src/reitit/frontend.cljs b/modules/reitit-frontend/src/reitit/frontend.cljs index ae2e5816..ba56ac12 100644 --- a/modules/reitit-frontend/src/reitit/frontend.cljs +++ b/modules/reitit-frontend/src/reitit/frontend.cljs @@ -43,7 +43,7 @@ (coercion/coerce-request (:result match) {:query-params q :path-params (:path-params match)}) {:query q - :path (:param match)})] + :path (:path-params match)})] (assoc match :parameters parameters))))) (defn match-by-name diff --git a/modules/reitit-frontend/src/reitit/frontend/controllers.cljs b/modules/reitit-frontend/src/reitit/frontend/controllers.cljs new file mode 100644 index 00000000..fe97f675 --- /dev/null +++ b/modules/reitit-frontend/src/reitit/frontend/controllers.cljs @@ -0,0 +1,40 @@ +(ns reitit.frontend.controllers) + +(defn- pad-same-length [a b] + (concat a (take (- (count b) (count a)) (repeat nil)))) + +(defn get-params + "Get controller parameters given match. If controller provides :params + function that will be called with the match. Default is nil." + [controller match] + (if-let [f (:params controller)] + (f match))) + +(defn apply-controller + "Run side-effects (:start or :stop) for controller. + The side-effect function is called with controller params." + [controller method] + (when-let [f (get controller method)] + (f (::params controller)))) + +(defn apply-controllers + "Applies changes between current controllers and + those previously enabled. Resets controllers whose + parameters have changed." + [old-controllers new-match] + (let [new-controllers (map (fn [controller] + (assoc controller ::params (get-params controller new-match))) + (:controllers (:data new-match))) + changed-controllers (->> (map (fn [old new] + ;; different controllers, or params changed + (if (not= old new) + {:old old, :new new})) + (pad-same-length old-controllers new-controllers) + (pad-same-length new-controllers old-controllers)) + (keep identity) + vec)] + (doseq [controller (map :old changed-controllers)] + (apply-controller controller :stop)) + (doseq [controller (map :new changed-controllers)] + (apply-controller controller :start)) + new-controllers)) diff --git a/test/cljs/reitit/doo_runner.cljs b/test/cljs/reitit/doo_runner.cljs index 25f522c6..ce4d6ed7 100644 --- a/test/cljs/reitit/doo_runner.cljs +++ b/test/cljs/reitit/doo_runner.cljs @@ -6,7 +6,8 @@ reitit.middleware-test reitit.ring-test #_reitit.spec-test - reitit.frontend.core-test)) + reitit.frontend.core-test + reitit.frontend.controllers-test)) (enable-console-print!) @@ -16,4 +17,5 @@ 'reitit.middleware-test 'reitit.ring-test #_'reitit.spec-test - 'reitit.frontend.core-test) + 'reitit.frontend.core-test + 'reitit.frontend.controllers-test) diff --git a/test/cljs/reitit/frontend/controllers_test.cljs b/test/cljs/reitit/frontend/controllers_test.cljs new file mode 100644 index 00000000..b4d2238c --- /dev/null +++ b/test/cljs/reitit/frontend/controllers_test.cljs @@ -0,0 +1,68 @@ +(ns reitit.frontend.controllers-test + (:require [clojure.test :refer [deftest testing is are]] + [reitit.frontend.controllers :as rfc])) + +(deftest apply-controller-test + (is (= :ok (rfc/apply-controller {:stop (fn [_] :ok)} :stop))) + (is (= :ok (rfc/apply-controller {:start (fn [_] :ok)} :start)))) + +(deftest apply-controllers-test + (let [log (atom []) + controller-state (atom []) + controller-1 {:start (fn [_] (swap! log conj :start-1)) + :stop (fn [_] (swap! log conj :stop-1))} + controller-2 {:start (fn [_] (swap! log conj :start-2)) + :stop (fn [_] (swap! log conj :stop-2))} + controller-3 {:start (fn [{:keys [foo]}] (swap! log conj [:start-3 foo])) + :stop (fn [{:keys [foo]}] (swap! log conj [:stop-3 foo])) + :params (fn [match] + {:foo (-> match :parameters :path :foo)})}] + + (testing "single controller started" + (swap! controller-state rfc/apply-controllers + {:data {:controllers [controller-1]}}) + + (is (= [:start-1] @log)) + (is (= [(assoc controller-1 ::rfc/params nil)] @controller-state)) + (reset! log [])) + + (testing "second controller started" + (swap! controller-state rfc/apply-controllers + {:data {:controllers [controller-1 controller-2]}}) + + (is (= [:start-2] @log)) + (is (= [(assoc controller-1 ::rfc/params nil) + (assoc controller-2 ::rfc/params nil)] + @controller-state)) + (reset! log [])) + + (testing "second controller replaced" + (swap! controller-state rfc/apply-controllers + {:data {:controllers [controller-1 controller-3]} + :parameters {:path {:foo 5}}}) + + (is (= [:stop-2 [:start-3 5]] @log)) + (is (= [(assoc controller-1 ::rfc/params nil) + (assoc controller-3 ::rfc/params {:foo 5})] + @controller-state)) + (reset! log [])) + + (testing "controller parameter changed" + (swap! controller-state rfc/apply-controllers + {:data {:controllers [controller-1 controller-3]} + :parameters {:path {:foo 1}}}) + + (is (= [[:stop-3 5] [:start-3 1]] @log)) + (is (= [(assoc controller-1 ::rfc/params nil) + (assoc controller-3 ::rfc/params {:foo 1})] + @controller-state)) + (reset! log [])) + + (testing "all controllers stopped" + (swap! controller-state rfc/apply-controllers + {:data {:controllers []}}) + + (is (= [:stop-1 [:stop-3 1]] @log)) + (is (= [] @controller-state)) + (reset! log [])) + )) diff --git a/test/cljs/reitit/frontend/core_test.cljs b/test/cljs/reitit/frontend/core_test.cljs index 2d522a4b..6e45ee38 100644 --- a/test/cljs/reitit/frontend/core_test.cljs +++ b/test/cljs/reitit/frontend/core_test.cljs @@ -4,7 +4,7 @@ [reitit.frontend :as rf] [reitit.coercion :as coercion] [schema.core :as s] - [reitit.coercion.schema :as schema])) + [reitit.coercion.schema :as schema-coercion])) (deftest match-by-path-test (testing "simple" @@ -12,30 +12,42 @@ ["" ::frontpage] ["foo" ::foo] ["bar" ::bar]])] - (is (= {:template "/" - :data {:name ::frontpage} - :path "/"} + (is (= (r/map->Match + {:template "/" + :data {:name ::frontpage} + :path-params {} + :path "/" + :parameters {:query {} + :path {}}}) (rf/match-by-path router "/"))) - (is (= {:template "/foo" - :data {:name ::foo} - :path "/foo"} + (is (= (r/map->Match + {:template "/foo" + :data {:name ::foo} + :path-params {} + :path "/foo" + :parameters {:query {} + :path {}}}) (rf/match-by-path router "/foo"))))) (testing "schema coercion" (let [router (r/router ["/" - [":id" - {:name ::foo - :parameters {:path {:id s/Int} - :query {(s/optional-key :mode) s/Keyword}}}]])] - #_#_ - (is (= {:template "/5" - :data {:name ::foo} - :path "/5" - :parameters {:path {:id 5}}} - (rf/match-by-path router "/5"))) - (is (= {:template "/5?mode=foo" - :data {:name ::foo} - :path "/5?mode=foo" - :parameters {:path {:id 5} - :query {:mode :foo}}} - (rf/match-by-path router "/5?mode=foo")))))) + [":id" {:name ::foo + :parameters {:path {:id s/Int} + :query {(s/optional-key :mode) s/Keyword}}}]] + {:compile coercion/compile-request-coercers + :data {:coercion schema-coercion/coercion}})] + (is (= (r/map->Match + {:template "/:id" + :path-params {:id "5"} + :path "/5" + :parameters {:query {} + :path {:id 5}}}) + (assoc (rf/match-by-path router "/5") :data nil :result nil))) + (is (= (r/map->Match + {:template "/:id" + :path-params {:id "5"} + ;; Note: query not included in path + :path "/5" + :parameters {:path {:id 5} + :query {:mode :foo}}}) + (assoc (rf/match-by-path router "/5?mode=foo") :data nil :result nil))))))