1
0
Fork 0
mirror of https://github.com/heyarne/airsonic-ui.git synced 2026-05-06 10:23:39 +02:00

Add a real playlist (#20)

* Start implementing playlist

Done so far:
* Creation
* Changing playback mode
* Changing repeat mode

* Add skipping for linear playlists with all repeat modes

* Complete implementation for playlist/next-song

* Implement all the playlist skipping functionality

* Add functions to enqueue songs

* Remove start-idx parameter when constructing playlists

* Use namespaced keywords only when modifying external data

E.g. songs in the queue
This commit is contained in:
Arne Schlüter 2018-08-20 16:34:52 +02:00 committed by GitHub
commit 1888c3023c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 456 additions and 46 deletions

3
.joker
View file

@ -1 +1,2 @@
{:known-macros [cljs.test/deftest]} {:known-macros [cljs.test/deftest]
:known-namespaces [cljs.core]}

View file

@ -11,8 +11,9 @@
;; debugging ;; debugging
[day8.re-frame/re-frame-10x "0.3.3-react16"] [day8.re-frame/re-frame-10x "0.3.3-react16"]
[day8.re-frame/tracing "0.5.1"] [day8.re-frame/tracing "0.5.1"]
[philoskim/debux "0.4.11"]
;; for CIDER ;; for CIDER
[cider/cider-nrepl "0.18.0-SNAPSHOT"]] [cider/cider-nrepl "0.18.0"]]
:nrepl {:port 9000} :nrepl {:port 9000}

View file

@ -1,4 +1,7 @@
(ns airsonic-ui.audio (ns airsonic-ui.audio.core
"This namespace contains some JS interop code to interact with an audio player
and receive information about the current playback status so we can use it in
our re-frame app."
(:require [re-frame.core :as re-frame])) (:require [re-frame.core :as re-frame]))
;; TODO: Manage buffering ;; TODO: Manage buffering

View file

@ -0,0 +1,140 @@
(ns airsonic-ui.audio.playlist
"Implements playlist queues that support different kinds of repetition and
song ordering."
(:refer-clojure :exclude [peek])
(:require [airsonic-ui.utils.helpers :refer [find-where]]
[debux.cs.core :refer-macros [dbg]]))
(defrecord Playlist [queue playback-mode repeat-mode]
cljs.core/ICounted
(-count [this]
(count (:queue this))))
(defmulti ->playlist
"Creates a new playlist that behaves according to the given playback- and
repeat-mode parameters."
(fn [queue & {:keys [playback-mode #_repeat-mode]}]
playback-mode))
(defn- mark-first-song [queue]
(let [[first-idx _] (find-where #(= 0 (:playlist/order %)) queue)]
(assoc-in queue [first-idx :playlist/currently-playing??] true)))
(defmethod ->playlist :linear
[queue & {:keys [playback-mode repeat-mode]}]
(let [queue (-> (mapv (fn [order song] (assoc song :playlist/order order)) (range) queue)
(mark-first-song))]
(->Playlist queue playback-mode repeat-mode)))
(defn- -shuffle-songs [queue]
(->> (shuffle (range (count queue)))
(mapv (fn [song order] (assoc song :playlist/order order)) queue)))
(defmethod ->playlist :shuffled
[queue & {:keys [playback-mode repeat-mode]}]
(let [queue (conj (mapv #(update % :playlist/order inc) (-shuffle-songs (rest queue)))
(assoc (first queue) :playlist/order 0 :playlist/currently-playing?? true))]
(->Playlist queue playback-mode repeat-mode)))
(defn set-current-song
"Marks a song in the queue as currently playing, given its ID"
[playlist next-idx]
(let [[current-idx _] (find-where :playlist/currently-playing?? (:queue playlist))]
(-> (if current-idx
(update-in playlist [:queue current-idx] dissoc :playlist/currently-playing??)
playlist)
(assoc-in [:queue next-idx :playlist/currently-playing??] true))))
(defn set-playback-mode
"Changes the playback mode of a playlist and re-shuffles it if necessary"
[playlist playback-mode]
(if (= playback-mode :shuffled)
;; for shuffled playlists we reorder the songs make sure that the currently
;; playing song has order 0
(let [playlist (->playlist (:queue playlist) :playback-mode playback-mode :repeat-mode (:repeat-mode playlist))
[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
[swap-idx _] (find-where #(= 0 (:playlist/order %)) (:queue playlist))]
(-> (assoc-in playlist [:queue current-idx :playlist/order] 0)
(assoc-in [:queue swap-idx :playlist/order] (:playlist/order current-song))))
;; for linear songs we just make sure that the current does not change
(let [[current-idx _] (find-where :playlist/currently-playing?? (:queue playlist))]
(-> (->playlist (:queue playlist) :playback-mode playback-mode :repeat-mode (:repeat-mode playlist))
(set-current-song current-idx)))))
(defn set-repeat-mode
"Allows to change the way the next and previous song of a playlist is selected"
[playlist repeat-mode]
(assoc playlist :repeat-mode repeat-mode))
(defn peek
"Returns the song in a playlist that is currently playing"
[playlist]
(->> (:queue playlist)
(filter :playlist/currently-playing??)
(first)))
(defmulti next-song "Advances the currently playing song" :repeat-mode)
(defmethod next-song :repeat-none
[playlist]
;; this is pretty easy; get the next song and stop playing at the at
(let [[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
[next-idx _] (find-where #(= (:playlist/order %) (inc (:playlist/order current-song))) (:queue playlist))]
(update playlist :queue
(fn [queue]
(cond-> queue
current-idx (update current-idx dissoc :playlist/currently-playing??)
next-idx (assoc-in [next-idx :playlist/currently-playing??] true))))))
(defmethod next-song :repeat-single [playlist] playlist)
(defmethod next-song :repeat-all
[playlist]
(let [[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
[next-idx _] (find-where #(= (:playlist/order %) (inc (:playlist/order current-song))) (:queue playlist))]
(-> (update-in playlist [:queue current-idx] dissoc :playlist/currently-playing??)
(update :queue
(fn [queue]
;; we need special treatment here if we're playing the last song and
;; have a shuffled playlist because we need to re-shuffle
(if next-idx
(assoc-in queue [next-idx :playlist/currently-playing??] true)
(case (:playback-mode playlist)
:linear (assoc-in queue [0 :playlist/currently-playing??] true)
:shuffled (let [queue' (-shuffle-songs queue)
[next-idx _] (find-where #(= (:playlist/order %) 0) queue')]
(assoc-in queue' [next-idx :playlist/currently-playing??] true)))))))))
(defmulti previous-song "Goes back along the playback queue" :repeat-mode)
(defmethod previous-song :repeat-single [playlist] playlist)
(defmethod previous-song :repeat-none [playlist]
(let [[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
[next-idx _] (find-where #(= (:playlist/order %) (dec (:playlist/order current-song))) (:queue playlist))]
(set-current-song playlist (or next-idx current-idx))))
(defmethod previous-song :repeat-all [playlist]
(let [[_ current-song] (find-where :playlist/currently-playing?? (:queue playlist))
[next-idx _] (find-where #(= (:playlist/order %)
(rem (dec (:playlist/order current-song)) (count playlist)))
(:queue playlist))]
(if next-idx
(set-current-song playlist next-idx)
(if (= :shuffled (:playback-mode playlist))
(let [highest-order (dec (count playlist))
playlist (update playlist :queue -shuffle-songs)
[last-idx _] (find-where #(= (:playlist/order %) highest-order) (:queue playlist))]
(set-current-song playlist last-idx))
(set-current-song playlist (mod (dec (:playlist/order current-song)) (count playlist)))))))
(defn enqueue-last [playlist song]
(let [highest-order (last (sort (map :playlist/order (:queue playlist))))]
(update playlist :queue conj (assoc song :playlist/order (inc highest-order)))))
(defn enqueue-next [playlist song]
(let [[_ current-song] (find-where :playlist/currently-playing?? (:queue playlist))]
(update playlist :queue
(fn [queue]
(-> (mapv #(if (> (:playlist/order %) (:playlist/order current-song)) (update % :playlist/order inc) %) queue)
(conj (assoc song :playlist/order (inc (:playlist/order current-song)))))))))

View file

@ -5,7 +5,7 @@
[day8.re-frame.http-fx] [day8.re-frame.http-fx]
[akiroz.re-frame.storage :as storage] [akiroz.re-frame.storage :as storage]
;; our app ;; our app
[airsonic-ui.audio] ; <- just registers effects here [airsonic-ui.audio.core] ; <- just registers effects here
[airsonic-ui.events :as events] [airsonic-ui.events :as events]
[airsonic-ui.views :as views] [airsonic-ui.views :as views]
[airsonic-ui.config :as config])) [airsonic-ui.config :as config]))

View file

@ -3,8 +3,7 @@
[ajax.core :as ajax] [ajax.core :as ajax]
[airsonic-ui.routes :as routes] [airsonic-ui.routes :as routes]
[airsonic-ui.db :as db] [airsonic-ui.db :as db]
[airsonic-ui.utils.api :as api] [airsonic-ui.utils.api :as api]))
[day8.re-frame.tracing :refer-macros [fn-traced defn-traced]])) ; <- useful to debug handlers
(re-frame/reg-fx (re-frame/reg-fx
;; a simple effect to keep println statements out of our event handlers ;; a simple effect to keep println statements out of our event handlers

View file

@ -0,0 +1,10 @@
(ns airsonic-ui.utils.helpers
"Assorted helper functions")
(defn find-where
"Returns the the first item in `coll` with its index for which `(p song)`
is truthy"
[p coll]
(->> (map-indexed vector coll)
(reduce (fn [_ [idx song]]
(when (p song) (reduced [idx song]))) nil)))

View file

@ -0,0 +1,23 @@
(ns airsonic-ui.audio.core-test
(:require [airsonic-ui.audio.core :as audio]
[airsonic-ui.audio.playlist-test :as p]
[airsonic-ui.fixtures :as fixtures]
[cljs.test :refer [deftest testing is]]))
(enable-console-print!)
(deftest current-song-subscription
(letfn [(current-song [db]
(-> (audio/summary db [:audio/summary])
(audio/current-song [:audio/current-song])))]
(testing "Should provide information about the song"
(= fixtures/song (current-song p/fixture)))))
(deftest playback-status-subscription
(letfn [(is-playing? [playback-status]
(audio/is-playing? playback-status [:audio/is-playing?]))]
(testing "Should be shown as not playing when the song is paused or has ended"
(is (not (is-playing? {:paused? true, :ended? false})))
(is (not (is-playing? {:paused? false, :ended? true}))))
(testing "Should be shown as playing when the song is not paused or finished"
(is (is-playing? {:paused? false, :ended? false})))))

View file

@ -0,0 +1,259 @@
(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.fixtures :as fixtures]
[airsonic-ui.test-helpers :as helpers]
[debux.cs.core :refer-macros [dbg]]))
(enable-console-print!)
(defn- song []
(hash-map :id (rand-int 9999)
:coverArt (rand-int 9999)
:year (+ 1900 (rand-int 118))
:artist (helpers/rand-str)
:artistId (rand-int 100000)
:title (helpers/rand-str)
:album (helpers/rand-str)))
(defn- song-queue
"Generates a seq of n different songs"
[n]
(let [r-int (atom 0)]
(with-redefs [rand-int #(mod (swap! r-int inc) %1)]
(repeatedly n song))))
(def fixture
{:audio {:current-song fixtures/song
:playlist (song-queue 20)
:playback-status fixtures/playback-status}})
(defn- same-song? [a b] (= (:id a) (:id b)))
(deftest playlist-creation
(testing "Playlist creation"
(testing "should give us the correct current song"
(let [queue (song-queue 10)]
(doseq [playback-mode [:linear :shuffled]
repeat-mode [:repeat-none :repeat-single :repeat-all]]
(is (same-song? (first queue)
(-> (playlist/->playlist queue :playback-mode playback-mode :repeat-mode repeat-mode)
(playlist/peek)))
(str playback-mode ", " repeat-mode)))))
(testing "should give us a playlist with the correct number of tracks"
(let [queue (song-queue 100)]
(doseq [playback-mode [:linear :shuffled]
repeat-mode [:repeat-none :repeat-single :repeat-all]]
(is (= (count queue)
(count (playlist/->playlist queue :playback-mode playback-mode :repeat-mode repeat-mode)))
(str playback-mode ", " repeat-mode)))))))
(deftest changing-playback-mode
(testing "Changing playback mode"
(testing "from linear to shuffled"
(let [queue (song-queue 10)
linear (playlist/->playlist queue :playback-mode :linear :repeat-mode :repeat-none)
shuffled (playlist/set-playback-mode linear :shuffled)]
(testing "should re-order the tracks"
(is (not= (map :playlist/order (:queue shuffled)) (map :playlist/order (:queue linear)))))
(testing "should not change the currently playing track"
(is (same-song? (playlist/peek linear) (playlist/peek shuffled))))
(testing "should not change the repeat mode"
(is (= (:repeat-mode shuffled) (:repeat-mode linear))))))
(testing "from shuffled to linear"
(let [queue (song-queue 10)
shuffled (playlist/->playlist queue :playback-mode :shuffled :repeat-mode :repeat-none)
linear (playlist/set-playback-mode shuffled :linear)]
(testing "should set the correct order for tracks"
(is (every? #(apply same-song? %) (interleave queue (:queue linear))))
(is (< (:playlist/order (first (:queue linear))) (:playlist/order (last (:queue linear))))))
(testing "should not change the currently playing track"
(is (same-song? (playlist/peek linear) (playlist/peek shuffled))))
(testing "should not change the repeat mode"
(is (= (:repeat-mode shuffled) (:repeat-mode linear))))))))
(deftest changing-repeat-mode
(testing "Changing the repeat mode"
(testing "should not change the playback mode"
(doseq [playback-mode '(:linear :shuffled)
repeat-mode '(:repeat-none :repeat-single :repeat-all)
next-repeat-mode '(:repeat-none :repeat-single :repeat-all)]
(let [playlist (-> (playlist/->playlist (song-queue 1) :playback-mode playback-mode :repeat-mode repeat-mode)
(playlist/set-repeat-mode next-repeat-mode))]
(is (= playback-mode (:playback-mode playlist)))
(is (= next-repeat-mode (:repeat-mode playlist))
(str "from " repeat-mode " to " next-repeat-mode)))))))
(deftest linear-next-song
(testing "Should follow the same order as the queue used for creation"
(doseq [repeat-mode [:repeat-none :repeat-all]]
(let [queue (song-queue 5)
playlist (playlist/->playlist queue :playback-mode :linear :repeat-mode repeat-mode)]
(is (same-song? (nth queue 1) (-> (playlist/next-song playlist)
(playlist/peek)))
(str repeat-mode ", skipped once"))
(is (same-song? (nth queue 2) (-> (playlist/next-song playlist)
(playlist/next-song)
(playlist/peek)))
(str repeat-mode ", skipped twice")))))
(testing "Should go back to the first song when repeat-mode is all and we played the last song")
(testing "Should always give the same track when repeat-mode is single"
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :linear :repeat-mode :repeat-single)
played-back (map playlist/peek (iterate playlist/next-song playlist))]
(is (same-song? (first queue) (nth played-back 0)))
(is (same-song? (first queue) (nth played-back 1)))
(is (same-song? (first queue) (nth played-back 2)))
(is (same-song? (first queue) (nth played-back 3)) "wrapping around")))
(testing "Should stop playing at the end of the queue when repeat-mode is none"
(is (nil? (-> (song-queue 1)
(playlist/->playlist :playback-mode :linear :repeat-mode :repeat-none)
(playlist/next-song)
(playlist/peek))))))
(deftest shuffled-next-song
(testing "Should play every track once when called for the entire queue"
(doseq [repeat-mode '(:repeat-none :repeat-all)]
(let [length 10
playlist (playlist/->playlist (song-queue length) :playback-mode :shuffled :repeat-mode repeat-mode)
played-tracks (->> (iterate playlist/next-song playlist)
(map playlist/peek)
(take length))]
(is (= (count played-tracks) (count (set played-tracks)))
(str repeat-mode)))))
(testing "Should re-shuffle the playlist when wrapping around and repeat-mode is all"
(let [playlist (playlist/->playlist (song-queue 100) :playback-mode :shuffled :repeat-mode :repeat-all)
[last-idx _] (find-where #(= (:playlist/order %) 99) (:queue playlist))]
(is (not= (map :playlist/order (:queue playlist))
(map :playlist/order (:queue (-> (playlist/set-current-song playlist last-idx)
(playlist/next-song))))))))
(testing "Should always give the same track when repeat-mode is single"
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :shuffled :repeat-mode :repeat-single)
played-back (map playlist/peek (iterate playlist/next-song playlist))]
(is (same-song? (first queue) (nth played-back 0)))
(is (same-song? (first queue) (nth played-back 1)))
(is (same-song? (first queue) (nth played-back 2)))
(is (same-song? (first queue) (nth played-back 3)) "wrapping around")))
(testing "Should stop playing at the end of the queue when repeat-mode is none"
(is (nil? (-> (song-queue 1)
(playlist/->playlist :playback-mode :linear :repeat-mode :repeat-none)
(playlist/next-song)
(playlist/peek))))))
(deftest linear-previous-song
(testing "Should always give the same track when repeat-mode is single"
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :linear :repeat-mode :repeat-single)
played-back (map playlist/peek (iterate playlist/next-song playlist))]
(is (same-song? (first queue) (nth played-back 0)))
(is (same-song? (first queue) (nth played-back 1)))
(is (same-song? (first queue) (nth played-back 2)))
(is (same-song? (first queue) (nth played-back 3)) "wrapping around")))
(testing "Should keep the linear order when repeat-mode is not single"
(doseq [repeat-mode '(:repeat-none :repeat-all)]
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :linear :repeat-mode repeat-mode)]
(is (same-song? (nth queue 1) (-> (playlist/next-song playlist)
(playlist/next-song)
(playlist/previous-song)
(playlist/peek)))))))
(testing "Should repeatedly give the first song when repeat-mode is none"
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :linear :repeat-mode :repeat-none)]
(is (same-song? (first queue) (-> (playlist/previous-song playlist)
(playlist/peek))))))
(testing "Should wrap around to last song when repeat-mode is all"
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :linear :repeat-mode :repeat-all)]
(is (same-song? (last queue) (-> (playlist/previous-song playlist)
(playlist/peek)))))))
(deftest shuffled-previous-song
(with-redefs [shuffle reverse]
(testing "Should always give the same track when repeat-mode is single"
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :shuffled :repeat-mode :repeat-single)
played-back (map playlist/peek (iterate playlist/next-song playlist))]
(is (same-song? (first queue) (nth played-back 0)))
(is (same-song? (first queue) (nth played-back 1)))
(is (same-song? (first queue) (nth played-back 2)))
(is (same-song? (first queue) (nth played-back 3)) "wrapping around")))
(testing "Should keep the playing order when repeat-mode is not single"
(doseq [repeat-mode '(:repeat-none :repeat-all)]
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :shuffled :repeat-mode repeat-mode)]
(is (same-song? (playlist/peek playlist)
(-> playlist
(playlist/next-song)
(playlist/previous-song)
(playlist/peek)))
(str "for repeat mode " repeat-mode))
(is (same-song? (-> (playlist/next-song playlist)
(playlist/peek))
(-> (playlist/next-song playlist)
(playlist/next-song)
(playlist/previous-song)
(playlist/peek)))
(str "for repeat mode " repeat-mode)))))
(testing "Should re-shuffle when repeat-mode is all and we go back to before the first track"
(let [playlist (with-redefs [shuffle identity]
(playlist/->playlist (song-queue 10) :playback-mode :shuffled :repeat-mode :repeat-all))
playlist' (with-redefs [shuffle reverse]
(playlist/previous-song playlist))]
(is (not= (map :playlist/order (:queue playlist)) (map :playlist/order (:queue playlist'))))))))
(deftest set-current-song
(testing "Should correctly set the new song"
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :shuffled :repeat-mode :repeat-single)
current-track (first queue)
next-track (-> (playlist/set-current-song playlist 1)
(playlist/peek))]
(is (not (nil? next-track)))
(is (not (same-song? current-track next-track))))))
(deftest enqueue-last
(testing "Should make sure the song is played last"
(doseq [playback-mode '(:linear :shuffled)
repeat-mode '(:repeat-none :repeat-all)]
(let [length 5, queue (song-queue length)
playlist (with-redefs [shuffle identity]
(playlist/->playlist queue :playback-mode playback-mode :repeat-mode repeat-mode))
played-back (->> (iterate playlist/next-song playlist)
(take (dec length))
(map #(:id (playlist/peek %)))
(set))
to-enqueue (song)
playlist' (playlist/enqueue-last playlist to-enqueue)]
(is (nil? (played-back (-> (->> (iterate playlist/next-song playlist')
(map playlist/peek))
(nth length)
(:id))))
(str "for " playback-mode ", " repeat-mode)))))
(testing "Should not change the order of the songs already in queue"
(doseq [playback-mode '(:linear :shuffled)
repeat-mode '(:repeat-none :repeat-all)]
(let [length 5, queue (song-queue length)
playlist (playlist/->playlist queue :playback-mode playback-mode :repeat-mode repeat-mode)
played-back-songs (fn played-back-songs [playlist]
(->> (iterate playlist/next-song playlist)
(take length)
(map playlist/peek)
(map :playlist/order)))
played-back (played-back-songs playlist)
played-back' (played-back-songs (playlist/enqueue-last playlist (song)))]
(is (= played-back played-back')
(str "for " playback-mode ", " repeat-mode))))))
(deftest enqueue-next
(testing "Should play the song after the currently playing song"
(doseq [playback-mode '(:linear :shuffled)
repeat-mode '(:repeat-none :repeat-all)]
(let [length 5, queue (song-queue length)
playlist (playlist/->playlist queue :playback-mode playback-mode :repeat-mode repeat-mode)
next-song (song)]
(is (same-song? next-song (-> (playlist/enqueue-next playlist next-song)
(playlist/next-song)
(playlist/peek))))))))

View file

@ -1,40 +0,0 @@
(ns airsonic-ui.audio-test
(:require [airsonic-ui.audio :as audio]
[airsonic-ui.fixtures :as fixtures]
[airsonic-ui.test-helpers :as helpers]
[cljs.test :refer [deftest testing is]]))
(enable-console-print!)
(defn- simulate-playlist [n ]
(repeatedly n #(hash-map :id (rand-int 9999)
:coverArt (rand-int 9999)
:year (+ 1900 (rand-int 118))
:artist (helpers/rand-str)
:aristId (rand-int 100000)
:title (helpers/rand-str)
:album (helpers/rand-str))))
(def fixture
{:audio {:current-song fixtures/song
:playlist (simulate-playlist 20)
:playback-status fixtures/playback-status}})
(deftest current-song
(letfn [(current-song [db]
(-> (audio/summary db [:audio/summary])
(audio/current-song [:audio/current-song])))]
(testing "Should provide information about the song"
(= fixtures/song (current-song fixture)))))
(deftest playback-status
(letfn [(is-playing? [playback-status]
(audio/is-playing? playback-status [:audio/is-playing?]))]
(testing "Should be shown as not playing when the song is paused or has ended"
(is (not (is-playing? {:paused? true, :ended? false})))
(is (not (is-playing? {:paused? false, :ended? true}))))
(testing "Should be shown as playing when the song is not paused or finished"
(is (is-playing? {:paused? false, :ended? false})))))
#_(deftest current-playlist
(testing "Should show the complete playlist"))

View file

@ -0,0 +1,14 @@
(ns airsonic-ui.utils.helpers-test
(:require [cljs.test :refer [deftest testing is]]
[airsonic-ui.utils.helpers :as helpers]))
(deftest find-where
(testing "Finds the correct item and index"
(is (= [0 1] (helpers/find-where (partial = 1) (range 1 10))))
(is (= [2 {:foo true, :bar false}] (helpers/find-where :foo '({}
{:foo false
:bar true}
{:foo true
:bar false})))))
(testing "Returns nil when nothing is found"
(is (nil? (helpers/find-where (partial = 2) (range 2))))))