From 7653af5dd19b7d86f7fd311286a272c1dc8f2957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Schl=C3=BCter?= Date: Tue, 28 Aug 2018 16:07:45 +0200 Subject: [PATCH] Merge feature/search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit 8a19df91f8daa1b791d40cc910947c94355a8d0d Author: Arne Schlüter Date: Tue Aug 28 16:06:35 2018 +0200 Implement search UI (closes #19) commit bf661dd25ec9f1d5569df88a8a87f94c1bc1b317 Author: Arne Schlüter Date: Tue Aug 28 11:09:46 2018 +0200 Re-add subscription for single endpoint and move helpers to a different location --- src/cljs/airsonic_ui/api/helpers.cljs | 18 ++++- src/cljs/airsonic_ui/api/subs.cljs | 7 ++ src/cljs/airsonic_ui/audio/playlist.cljs | 2 +- .../airsonic_ui/components/search/events.cljs | 15 +++++ .../airsonic_ui/components/search/subs.cljs | 5 ++ .../airsonic_ui/components/search/views.cljs | 67 +++++++++++++++++++ src/cljs/airsonic_ui/core.cljs | 2 + src/cljs/airsonic_ui/{utils => }/helpers.cljs | 8 ++- src/cljs/airsonic_ui/routes.cljs | 15 +++-- src/cljs/airsonic_ui/views.cljs | 12 ++-- src/cljs/airsonic_ui/views/album.cljs | 32 ++++----- src/cljs/airsonic_ui/views/audio_player.cljs | 8 +-- src/cljs/airsonic_ui/views/breadcrumbs.cljs | 10 ++- src/cljs/airsonic_ui/views/cover.cljs | 5 ++ src/cljs/airsonic_ui/views/icon.cljs | 2 +- src/cljs/airsonic_ui/views/song.cljs | 8 +-- src/sass/app.sass | 14 +++- test/cljs/airsonic_ui/api/helpers_test.cljs | 20 +++++- test/cljs/airsonic_ui/api/subs_test.cljs | 6 ++ .../cljs/airsonic_ui/audio/playlist_test.cljs | 2 +- test/cljs/airsonic_ui/fixtures.cljs | 14 ++++ .../airsonic_ui/{utils => }/helpers_test.cljs | 13 +++- 22 files changed, 236 insertions(+), 49 deletions(-) create mode 100644 src/cljs/airsonic_ui/components/search/events.cljs create mode 100644 src/cljs/airsonic_ui/components/search/subs.cljs create mode 100644 src/cljs/airsonic_ui/components/search/views.cljs rename src/cljs/airsonic_ui/{utils => }/helpers.cljs (65%) rename test/cljs/airsonic_ui/{utils => }/helpers_test.cljs (52%) diff --git a/src/cljs/airsonic_ui/api/helpers.cljs b/src/cljs/airsonic_ui/api/helpers.cljs index 8bf2bcc..7a21051 100644 --- a/src/cljs/airsonic_ui/api/helpers.cljs +++ b/src/cljs/airsonic_ui/api/helpers.cljs @@ -1,5 +1,6 @@ (ns airsonic-ui.api.helpers - (:require [clojure.string :as str])) + (:require [clojure.string :as str] + [clojure.set :as set])) (def default-params {:f "json" :c "airsonic-ui-cljs" @@ -41,11 +42,22 @@ "Retrieves the actual response body" [response] (if (is-error? response) - (let [error (:error response)] - (throw (->exception response))) + (throw (->exception response)) (unwrap-response* response))) (defn error-msg [exception-info] (let [{:keys [code message]} (ex-data exception-info)] (str "Error " code ": " message))) + +(defn content-type + "Given some piece of data returned by the api, returns a keyword that + describes what we look at" + [data] + (keyword :content-type + (condp set/subset? (set (keys data)) + #{:path} :song + #{:artistId :name :songCount :artist} :album + #{:id :name :albumCount} :artist + :unknown))) + diff --git a/src/cljs/airsonic_ui/api/subs.cljs b/src/cljs/airsonic_ui/api/subs.cljs index 01c0d5a..a7bc9c5 100644 --- a/src/cljs/airsonic_ui/api/subs.cljs +++ b/src/cljs/airsonic_ui/api/subs.cljs @@ -2,6 +2,13 @@ (:require [clojure.string :as str] [re-frame.core :refer [reg-sub]])) +(defn response-for + "Returns the cached response for a single endpoint" + [db [_ endpoint params]] + (get-in db [:api/responses [endpoint params]])) + +(reg-sub :api/response-for response-for) + (defn endpoint->kw "Given an endpoint like `getAlbumList2`, returns a cleaned keyword like `:album-list``. diff --git a/src/cljs/airsonic_ui/audio/playlist.cljs b/src/cljs/airsonic_ui/audio/playlist.cljs index 75fe44d..d36d2c8 100644 --- a/src/cljs/airsonic_ui/audio/playlist.cljs +++ b/src/cljs/airsonic_ui/audio/playlist.cljs @@ -2,7 +2,7 @@ "Implements playlist queues that support different kinds of repetition and song ordering." (:refer-clojure :exclude [peek]) - (:require [airsonic-ui.utils.helpers :refer [find-where]])) + (:require [airsonic-ui.helpers :refer [find-where]])) (defrecord Playlist [queue playback-mode repeat-mode] cljs.core/ICounted diff --git a/src/cljs/airsonic_ui/components/search/events.cljs b/src/cljs/airsonic_ui/components/search/events.cljs new file mode 100644 index 0000000..1e5fac7 --- /dev/null +++ b/src/cljs/airsonic_ui/components/search/events.cljs @@ -0,0 +1,15 @@ +(ns airsonic-ui.components.search.events + (:require [re-frame.core :refer [reg-event-fx reg-event-db]] + [airsonic-ui.routes :as routes])) + +(reg-event-db + ;; this is called on navigation and handled in routes.cljs; the reason is that + ;; when we're navigating to search?query=foo we don't have the term in our db. + :search/restore-term-from-param + (fn [db [_ term]] + (assoc-in db [:search :term] term))) + +(reg-event-fx + :search/do-search + (fn do-search [fx [_ term]] + {:dispatch [:routes/do-navigation [::routes/search {} {:query term}]]})) diff --git a/src/cljs/airsonic_ui/components/search/subs.cljs b/src/cljs/airsonic_ui/components/search/subs.cljs new file mode 100644 index 0000000..0c1293f --- /dev/null +++ b/src/cljs/airsonic_ui/components/search/subs.cljs @@ -0,0 +1,5 @@ +(ns airsonic-ui.components.search.subs + (:require [re-frame.core :refer [reg-sub subscribe]])) + +(reg-sub :search/current-term (fn current-term [db _] + (get-in db [:search :term]))) diff --git a/src/cljs/airsonic_ui/components/search/views.cljs b/src/cljs/airsonic_ui/components/search/views.cljs new file mode 100644 index 0000000..58a08a2 --- /dev/null +++ b/src/cljs/airsonic_ui/components/search/views.cljs @@ -0,0 +1,67 @@ +(ns airsonic-ui.components.search.views + (:require [clojure.pprint :refer [pprint]] + [re-frame.core :refer [dispatch subscribe]] + [goog.functions :refer [debounce]] + [airsonic-ui.routes :as routes :refer [url-for]] + [airsonic-ui.views.song :as song] + [airsonic-ui.views.cover :refer [card]])) + +(defn form [] + (let [search-term @(subscribe [:search/current-term]) + throttled-search (debounce #(dispatch [:search/do-search (.. % -target -value)]) 100)] + (fn [] + [:form {:on-submit #(.preventDefault %)} + [:div.feld>p.control + [:input.input {:on-change (fn [e] + ;; the event might be gone when we the dispatched + ;; function is fired, we need to persist it + (.persist e) + (throttled-search e)) + :default-value search-term + :placeholder "Search"}]]]))) + +(defn artist-results [{:keys [artist]}] + [:div.columns.is-multiline.is-mobile + (for [[idx artist] (map-indexed vector artist)] + (let [url #(url-for ::routes/artist-view (select-keys % [:id]))] + ^{:key idx} [:div.column.is-2 + [card artist + :url-fn url + :content [:div>a + {:href (url artist), :title (:name artist)} + (:name artist)]]]))]) + +(defn album-results [{:keys [album]}] + [:div.columns.is-multiline.is-mobile + (for [[idx album] (map-indexed vector album)] + (let [url #(url-for ::routes/album-view (select-keys % [:id])) + title (str (:name album) " (" (:artist album) ")")] + ^{:key idx} [:div.column.is-2 [card album + :url-fn url + :content [:div>a + {:href (url album), :title title} + title]]]))]) + +(defn song-results [{:keys [song]}] + [song/listing song]) + +(defn results [{:keys [search]}] + (let [term @(subscribe [:search/current-term])] + [:div + [:h2.title (str "Search results for \"" term "\"")] + (if (empty? search) + [:p "The server returned no results."] + [:div.content + (when-not (empty? (:artist search)) + [:section.section.is-small + [:h3.subtitle.is-5 "Artists"] + [artist-results search]]) + (when-not (empty? (:album search)) + [:section.section.is-small + [:h3.subtitle.is-5 "Albums"] + [album-results search]]) + (when-not (empty? (:song search)) + [:section.section.is-small + [:h3.subtitle.is-5 "Songs"] + [song-results search]])]) + [:pre (with-out-str (pprint search))]])) diff --git a/src/cljs/airsonic_ui/core.cljs b/src/cljs/airsonic_ui/core.cljs index 37b9c18..9728053 100644 --- a/src/cljs/airsonic_ui/core.cljs +++ b/src/cljs/airsonic_ui/core.cljs @@ -10,6 +10,8 @@ [airsonic-ui.audio.core] [airsonic-ui.api.events] [airsonic-ui.api.subs] + [airsonic-ui.components.search.events] + [airsonic-ui.components.search.subs] [airsonic-ui.events :as events] [airsonic-ui.views :as views] [airsonic-ui.config :as config])) diff --git a/src/cljs/airsonic_ui/utils/helpers.cljs b/src/cljs/airsonic_ui/helpers.cljs similarity index 65% rename from src/cljs/airsonic_ui/utils/helpers.cljs rename to src/cljs/airsonic_ui/helpers.cljs index 663414b..c2b2883 100644 --- a/src/cljs/airsonic_ui/utils/helpers.cljs +++ b/src/cljs/airsonic_ui/helpers.cljs @@ -1,4 +1,4 @@ -(ns airsonic-ui.utils.helpers +(ns airsonic-ui.helpers "Assorted helper functions" (:require [re-frame.core :as rf])) @@ -16,3 +16,9 @@ (fn [e] (.preventDefault e) (rf/dispatch ev))) + +(defn add-classes + "Adds one or more classes to a hiccup keyword" + [elem & classes] + (keyword (apply str (name elem) (->> (filter identity classes) + (map #(str "." (name %))))))) diff --git a/src/cljs/airsonic_ui/routes.cljs b/src/cljs/airsonic_ui/routes.cljs index a92c404..bcaeedb 100644 --- a/src/cljs/airsonic_ui/routes.cljs +++ b/src/cljs/airsonic_ui/routes.cljs @@ -5,11 +5,12 @@ (def default-route ::login) -(def router +(defonce router (r/router [["/" ::login] ["/main" ::main] ["/artist/:id" ::artist-view] - ["/album/:id" ::album-view]])) + ["/album/:id" ::album-view] + ["/search" ::search]])) ;; use this in views to construct a url (defn url-for @@ -17,7 +18,7 @@ ([k params] (str "#" (r/resolve router k params)))) ;; which routes need valid login credentials? -(def protected-routes #{::main ::artist-view ::album-view}) +(def protected-routes #{::main ::artist-view ::album-view ::search}) ;; which data should be requested for which route? can either be a vector or a function returning a vector @@ -42,6 +43,11 @@ [route-id params query] [:api/request "getAlbum" (select-keys params [:id])]) +(defmethod -route-events ::search + [route-id params query] + [[:search/restore-term-from-param (:query query)] + [:api/request "search3" query]]) + ;; shouldn't need to change anything below (defn- n-events? @@ -91,8 +97,9 @@ credentials'(get-in context [:coeffects :db :credentials])] (println "calling do-navigation with" route credentials') (reset! credentials credentials') + (println "context" context) (apply r/navigate! router route) - context)))) + (dissoc context :event))))) (re-frame/reg-event-fx :routes/do-navigation do-navigation (fn [& _] nil)) diff --git a/src/cljs/airsonic_ui/views.cljs b/src/cljs/airsonic_ui/views.cljs index 8f89a67..57f71e0 100644 --- a/src/cljs/airsonic_ui/views.cljs +++ b/src/cljs/airsonic_ui/views.cljs @@ -3,13 +3,15 @@ [airsonic-ui.routes :as routes :refer [url-for]] [airsonic-ui.events :as events] [airsonic-ui.subs :as subs] + [airsonic-ui.helpers :refer [add-classes]] [airsonic-ui.views.notifications :refer [notification-list]] [airsonic-ui.views.breadcrumbs :refer [breadcrumbs]] [airsonic-ui.views.audio-player :refer [audio-player]] [airsonic-ui.views.login :refer [login-form]] [airsonic-ui.views.album :as album] - [airsonic-ui.views.song :as song])) + [airsonic-ui.views.song :as song] + [airsonic-ui.components.search.views :as search])) ;; TODO: Find better names and places for these. @@ -31,6 +33,7 @@ (defn sidebar [user] [:aside.menu.section + [search/form] [:p.menu-label "Music"] [:ul.menu-list [:li [:a "By artist"]] @@ -56,20 +59,21 @@ [:main.columns [:div.column.is-2.sidebar [sidebar user]] - [:div.column + [:div.column.is-10 [:section.section [breadcrumbs content] (case route-id ::routes/main [most-recent content] ::routes/artist-view [artist-detail content] - ::routes/album-view [album-detail content])]]] + ::routes/album-view [album-detail content] + ::routes/search [search/results content])]]] [audio-player]])) (defn main-panel [] (let [notifications @(subscribe [::subs/notifications]) is-booting? @(subscribe [::subs/is-booting?]) [route-id params query] @(subscribe [:routes/current-route])] - [:div + [(add-classes :div route-id) [notification-list notifications] (if is-booting? [:div.app-loading>div.loader] diff --git a/src/cljs/airsonic_ui/views/album.cljs b/src/cljs/airsonic_ui/views/album.cljs index a4ddce3..645d68c 100644 --- a/src/cljs/airsonic_ui/views/album.cljs +++ b/src/cljs/airsonic_ui/views/album.cljs @@ -1,23 +1,23 @@ (ns airsonic-ui.views.album (:require [airsonic-ui.routes :as routes :refer [url-for]] - [airsonic-ui.views.cover :refer [cover]])) + [airsonic-ui.views.cover :refer [cover card]])) (defn preview [album] - (let [{:keys [artist artistId name coverArt id]} album] - [:article.card.album-preview - [:div.card-image - [:a {:href (url-for ::routes/album-view {:id id})} [cover album 256]]] - [:div.card-content - ;; link to album - [:div.title.is-5 - [:a {:href (url-for ::routes/album-view {:id id})} name]] - ;; link to artist page - [:div.subtitle.is-6 [:a {:href (url-for ::routes/artist-view {:id artistId})} artist]]]])) + (let [{:keys [artist artistId name id]} album] + [card album + :url-fn #(url-for ::routes/album-view {:id id}) + :content [:div + ;; link to album + [:div.title.is-5 + [:a {:href (url-for ::routes/album-view {:id id}) + :title name} name]] + ;; link to artist page + [:div.subtitle.is-6 [:a {:href (url-for ::routes/artist-view {:id artistId}) + :title artist} artist]]]])) (defn listing [albums] ;; always show 5 in a row - [:div - [:div.columns.is-multiline.is-mobile - (for [[idx album] (map-indexed vector albums)] - ^{:key idx} [:div.column.is-one-fifth-desktop.is-one-quarter-tablet.is-half-mobile - [preview album]])]]) + [:div.columns.is-multiline.is-mobile + (for [[idx album] (map-indexed vector albums)] + ^{:key idx} [:div.column.is-one-fifth-desktop.is-one-quarter-tablet.is-half-mobile + [preview album]])]) diff --git a/src/cljs/airsonic_ui/views/audio_player.cljs b/src/cljs/airsonic_ui/views/audio_player.cljs index 5887f4d..c8bd791 100644 --- a/src/cljs/airsonic_ui/views/audio_player.cljs +++ b/src/cljs/airsonic_ui/views/audio_player.cljs @@ -1,6 +1,6 @@ (ns airsonic-ui.views.audio-player (:require [re-frame.core :refer [subscribe]] - [airsonic-ui.utils.helpers :refer [dispatch]] + [airsonic-ui.helpers :refer [add-classes dispatch]] [airsonic-ui.events :as events] [airsonic-ui.views.cover :refer [cover]] [airsonic-ui.views.icon :refer [icon]])) @@ -25,12 +25,6 @@ [icon icon-glyph]]) buttons))]) -(defn- add-classes - "Adds one or more classes to a hiccup keyword" - [elem & classes] - (keyword (apply str (name elem) (->> (filter identity classes) - (map #(str "." (name %))))))) - (defn- toggle-shuffle [playback-mode] (dispatch [::events/set-playback-mode (if (= playback-mode :shuffled) :linear :shuffled)])) diff --git a/src/cljs/airsonic_ui/views/breadcrumbs.cljs b/src/cljs/airsonic_ui/views/breadcrumbs.cljs index efe08bc..538ba2f 100644 --- a/src/cljs/airsonic_ui/views/breadcrumbs.cljs +++ b/src/cljs/airsonic_ui/views/breadcrumbs.cljs @@ -5,12 +5,13 @@ ;; hierarchy no matter how you came to the url. They should allow easy ;; navigation upwards that hierarchy (e.g. album -> artist) -(defn content-type +(defn page-type "Helper to see what kind of view we're currently dealing with" [content] (case (set (keys content)) #{:artist :artist-info} :artist #{:album} :album + #{:search} :search :other-content)) (defn- bulma-breadcrumbs [& items] @@ -20,7 +21,7 @@ [:li {:key idx} [:a {:href href} label]]) [:li.is-active>a (last items)]]]) -(defmulti breadcrumbs content-type) +(defmulti breadcrumbs page-type) (defmethod breadcrumbs :default [content] [bulma-breadcrumbs "Start"]) @@ -35,3 +36,8 @@ [(url-for ::routes/main) "Start"] [(url-for ::routes/artist-view {:id (:artistId album)}) (:artist album)] (:name album)]) + +(defmethod breadcrumbs :search [_] + [bulma-breadcrumbs + [(url-for ::routes/main) "Start"] + "Search"]) diff --git a/src/cljs/airsonic_ui/views/cover.cljs b/src/cljs/airsonic_ui/views/cover.cljs index adbb3ad..a44c270 100644 --- a/src/cljs/airsonic_ui/views/cover.cljs +++ b/src/cljs/airsonic_ui/views/cover.cljs @@ -65,3 +65,8 @@ [:img {:src original :srcSet (str original ", " retina " 2x")}] [missing-cover item size])])) + +(defn card [item & {:keys [url-fn content size] :or {size 256}}] + [:article.card.preview-card + [:div.card-image [:a {:href (url-fn item)} [cover item size]]] + [:div.card-content content]]) diff --git a/src/cljs/airsonic_ui/views/icon.cljs b/src/cljs/airsonic_ui/views/icon.cljs index 5fd2841..b7c9ee4 100644 --- a/src/cljs/airsonic_ui/views/icon.cljs +++ b/src/cljs/airsonic_ui/views/icon.cljs @@ -1,4 +1,4 @@ (ns airsonic-ui.views.icon) -(defn icon [glyph] +(defn icon [glyph & extra] [:span.icon [:span.oi {:data-glyph (name glyph)}]]) diff --git a/src/cljs/airsonic_ui/views/song.cljs b/src/cljs/airsonic_ui/views/song.cljs index 43183f5..8f675cd 100644 --- a/src/cljs/airsonic_ui/views/song.cljs +++ b/src/cljs/airsonic_ui/views/song.cljs @@ -1,5 +1,5 @@ (ns airsonic-ui.views.song - (:require [airsonic-ui.utils.helpers :refer [dispatch]] + (:require [airsonic-ui.helpers :refer [dispatch]] [airsonic-ui.events :as events] [airsonic-ui.routes :as routes :refer [url-for]] [airsonic-ui.views.icon :refer [icon]])) @@ -7,9 +7,9 @@ (defn item [songs song idx] (let [artist-id (:artistId song)] [:div - [:a - (when artist-id {:href (url-for ::routes/artist-view {:id artist-id})}) - (:artist song)] + (if artist-id + [:a {:href (url-for ::routes/artist-view {:id artist-id})} (:artist song)] + (:artist song)) " - " [:a {:href "#" :on-click (dispatch [::events/play-songs songs idx])} diff --git a/src/sass/app.sass b/src/sass/app.sass index 0ea5c62..aefb50f 100644 --- a/src/sass/app.sass +++ b/src/sass/app.sass @@ -67,7 +67,9 @@ .missing-cover display: block -.album-preview +// preview card for album or artist listings +.preview-card + .card-content > div, .title, .subtitle overflow: hidden @@ -79,6 +81,7 @@ height: auto max-width: 256px max-height: 256px + margin: 0 // occurs in album detail view .table @@ -107,3 +110,12 @@ .loading-spinner .icon animation: 1s infinite you-spin-my-head-right-round + + +// route specific styling +.search + .content .section + padding: 1.5rem 0 + + .preview-card .card-content + padding: 0.375rem 0.75rem 0.75rem diff --git a/test/cljs/airsonic_ui/api/helpers_test.cljs b/test/cljs/airsonic_ui/api/helpers_test.cljs index e272f83..0c3486d 100644 --- a/test/cljs/airsonic_ui/api/helpers_test.cljs +++ b/test/cljs/airsonic_ui/api/helpers_test.cljs @@ -1,7 +1,7 @@ (ns airsonic-ui.api.helpers-test (:require [cljs.test :refer [deftest testing is]] [clojure.string :as str] - [airsonic-ui.fixtures :refer [responses]] + [airsonic-ui.fixtures :as fixtures :refer [responses]] [airsonic-ui.api.helpers :as api])) (defn- url @@ -20,6 +20,12 @@ (is (string? (re-find #"f=json" (fixtures :default-url)))) (is (string? (re-find #"v=1\.15\.0" (fixtures :default-url)))))) +(deftest parameter-encoding + (testing "Should escape url parameters" + (let [query "äöüß" + encoded-str (js/encodeURIComponent query)] + (is (str/includes? (api/url "http://localhost" "search3" {:query query}) encoded-str))))) + (deftest song-urls (testing "Should construct the url based on a song's id" (let [song {:id 1234}] @@ -46,7 +52,7 @@ (try (api/unwrap-response error-response) (catch ExceptionInfo e - (= (:error error-response) (ex-data e))))))) + (is (= (get-in error-response [:subsonic-response :error]) (ex-data e)))))))) (deftest error-recognition (testing "Should detect error responses" @@ -55,3 +61,13 @@ (testing "Should pass on good responses" (is (false? (api/is-error? (:ok responses)))) (is (false? (api/is-error? (:auth-success responses)))))) + +(deftest content-type + (testing "Should detect whether the data we look at represents a song" + (is (= :content-type/song (api/content-type fixtures/song)))) + (testing "Should detect whether the data we look at represents an artist" + (is (= :content-type/artist (api/content-type fixtures/artist))) + (is (= :content-type/artist (api/content-type (dissoc fixtures/artist :coverArt))))) + (testing "Should detect whether the data we look at represents an album" + (is (= :content-type/album (api/content-type fixtures/album))) + (is (= :content-type/album (api/content-type (dissoc fixtures/album :coverArt)))))) diff --git a/test/cljs/airsonic_ui/api/subs_test.cljs b/test/cljs/airsonic_ui/api/subs_test.cljs index ded1966..463567e 100644 --- a/test/cljs/airsonic_ui/api/subs_test.cljs +++ b/test/cljs/airsonic_ui/api/subs_test.cljs @@ -4,6 +4,12 @@ (enable-console-print!) +(deftest single-response + (testing "Should return the response for a specified endpoint" + (let [db {:api/responses {["search2" {:query "query term"}] :result}}] + (is (= :result (sub/response-for db [:api/response-for "search2" {:query "query term"}]))) + (is (nil? (sub/response-for db [:api/response-for "search2" {:query "another query term"}])))))) + (deftest endpoint-keywordification (testing "Should strip prefixes" (is (= :artist-info (sub/endpoint->kw "getArtistInfo"))) diff --git a/test/cljs/airsonic_ui/audio/playlist_test.cljs b/test/cljs/airsonic_ui/audio/playlist_test.cljs index 37a1546..e48ae14 100644 --- a/test/cljs/airsonic_ui/audio/playlist_test.cljs +++ b/test/cljs/airsonic_ui/audio/playlist_test.cljs @@ -1,7 +1,7 @@ (ns airsonic-ui.audio.playlist-test (:require [cljs.test :refer [deftest testing is]] [airsonic-ui.audio.playlist :as playlist] - [airsonic-ui.utils.helpers :refer [find-where]] + [airsonic-ui.helpers :refer [find-where]] [airsonic-ui.fixtures :as fixtures] [airsonic-ui.test-helpers :as helpers] [debux.cs.core :refer-macros [dbg]])) diff --git a/test/cljs/airsonic_ui/fixtures.cljs b/test/cljs/airsonic_ui/fixtures.cljs index fc7db2c..10aabf9 100644 --- a/test/cljs/airsonic_ui/fixtures.cljs +++ b/test/cljs/airsonic_ui/fixtures.cljs @@ -21,6 +21,20 @@ :error {:code 40 :message "Wrong username or password."}}}}) +(def artist + {:id "499", :name "Tomemitsu", :coverArt "ar-497", :albumCount 1}) + +(def album + {:artistId "258", + :name "Tocotronic", + :songCount 26, + :created "2017-12-31T08:18:45.000Z", + :duration 7383, + :artist "Tocotronic", + :year 2015, + :id "439", + :coverArt "al-439"}) + (def song {:artistId 42, :path "DJ Koze/DJ Koze - Reincarnations Part 2, The Remix Chapter 2009-2014/14. Apparat - Black Water (DJ Koze Remix).mp3", diff --git a/test/cljs/airsonic_ui/utils/helpers_test.cljs b/test/cljs/airsonic_ui/helpers_test.cljs similarity index 52% rename from test/cljs/airsonic_ui/utils/helpers_test.cljs rename to test/cljs/airsonic_ui/helpers_test.cljs index a53c34d..5c5825a 100644 --- a/test/cljs/airsonic_ui/utils/helpers_test.cljs +++ b/test/cljs/airsonic_ui/helpers_test.cljs @@ -1,6 +1,6 @@ -(ns airsonic-ui.utils.helpers-test +(ns airsonic-ui.helpers-test (:require [cljs.test :refer [deftest testing is]] - [airsonic-ui.utils.helpers :as helpers])) + [airsonic-ui.helpers :as helpers])) (deftest find-where (testing "Finds the correct item and index" @@ -12,3 +12,12 @@ :bar false}))))) (testing "Returns nil when nothing is found" (is (nil? (helpers/find-where (partial = 2) (range 2)))))) + +(deftest add-classes + (testing "Should add classes to a simple hiccup keyword" + (is (= :div.foo (helpers/add-classes :div :foo))) + (is (= :div.bar.bar (helpers/add-classes :div.bar :bar))) + (is (= :div.foo.bar (helpers/add-classes :div.foo :bar)))) + (testing "Should add classes to the innermost child of a nested hiccup element" + (is (= :p>input.input (helpers/add-classes :p>input :input))) + (is (= :div.field>p>input.input.has-background-red (helpers/add-classes :div.field>p>input.input :has-background-red)))))