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

Refactor playlist to use a sorted-map

This commit is contained in:
heyarne 2019-03-05 00:06:04 +01:00
commit 644939c618
9 changed files with 206 additions and 229 deletions

View file

@ -1,6 +1,7 @@
module.exports = function (config) {
const configuration = {
browsers: ['ChromeHeadless'],
autoWatchBatchDelay: 1000,
// The directory where the output file lives
basePath: 'public/test',
// The file itself

6
package-lock.json generated
View file

@ -1512,7 +1512,7 @@
},
"engine.io-client": {
"version": "3.2.1",
"resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
"integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
"dev": true,
"requires": {
@ -4072,7 +4072,7 @@
},
"media-typer": {
"version": "0.3.0",
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
@ -6557,7 +6557,7 @@
},
"socket.io-parser": {
"version": "3.2.0",
"resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
"integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
"dev": true,
"requires": {

View file

@ -6,8 +6,6 @@
[airsonic-ui.audio.playlist :as playlist]
[goog.functions :refer [throttle]]))
;; TODO: Manage buffering
(defonce audio (atom nil))
(defn normalize-time-ranges [time-ranges]
@ -28,7 +26,6 @@
; explanation of these events: https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Cross-browser_audio_basics
(defn attach-listeners! [el]
(let [emit-audio-update (throttle #(rf/dispatch [:audio/update (->status el)]) 16)]
(doseq [event ["loadstart" "progress" "play" "timeupdate" "pause" "volumechange"]]
@ -116,7 +113,9 @@
"Gives us information about the currently played song as presented by
the airsonic api"
[playlist _]
(playlist/peek playlist))
(when-not (empty? playlist)
(playlist/current-song playlist)))
(rf/reg-sub
:audio/current-song

View file

@ -1,139 +1,130 @@
(ns airsonic-ui.audio.playlist
"Implements playlist queues that support different kinds of repetition and
song ordering."
(:refer-clojure :exclude [peek])
(:require [airsonic-ui.helpers :refer [find-where]]))
song ordering.")
(defrecord Playlist [items playback-mode repeat-mode]
;; Turns out we can nicely implement this by thinly wrapping a sequence of items
;; We re-use the core ClojureScript protocols internally but provide a nice and
;; explicit API to consume
(defprotocol IPlaylist
(current-song [this])
(next-song [this])
(previous-song [this])
(set-current-song [this song-idx]
"Advances the queue to the song given by song-idx")
(set-playback-mode [this playback-mode]
"Changes the playback mode of a playlist and re-shuffles it if necessary")
(set-repeat-mode [this repeat-mode]
"Allows you to change how the next and previous song are selected")
(enqueue-last [this song])
(enqueue-next [this song]))
;; helpers to manage creating playlists
(defn- mark-original-order
"This function is used if we switch from linear to shuffled; it allows us to
restore the order of the queue when it was created."
[items]
(->> (sort-by (comp meta :playlist/linear-order) items)
(map-indexed (fn [idx item]
(vary-meta item assoc :playlist/linear-order idx)))))
(defn- linear-queue
[items]
(->> (mark-original-order items)
(map-indexed vector)
(into (sorted-map))))
(defn- shuffled-queue
[items]
(let [shuffled-indices (shuffle (range (count items)))]
(->> (mark-original-order items)
(map vector shuffled-indices)
(into (sorted-map)))))
;; the exported interface:
(defrecord Playlist [items current-idx playback-mode repeat-mode]
cljs.core/ICounted
(-count [this]
(count (:items this))))
(count items))
IPlaylist
(current-song [_]
(get items current-idx))
(next-song [this]
(update this :current-idx
(fn [current-idx]
(cond
(= repeat-mode :repeat-single) current-idx
(or (= repeat-mode :repeat-all)
(< current-idx (dec (count this))))
(mod (inc current-idx) (count this))))))
(previous-song [this]
(update this :current-idx
(fn [current-idx]
(cond
(= repeat-mode :repeat-single) current-idx
(or (= repeat-mode :repeat-all)
(> current-idx 0))
(mod (dec current-idx) (count this))
:else nil))))
(set-current-song [playlist song-idx]
(assoc playlist :current-idx song-idx))
(set-playback-mode [playlist playback-mode]
(let [current-song (current-song playlist)
queue-fn (case playback-mode
:shuffled shuffled-queue
:linear linear-queue)
next-playlist (update playlist :items (comp queue-fn vals))
next-idx (first (keep (fn [[idx song]]
(when (= song current-song)
idx))
(:items next-playlist)))]
;; we have to find out the index of the currently playing song after the
;; playlist was created because it might change when shuffling / unshuffling
(set-current-song next-playlist next-idx)))
(set-repeat-mode [playlist repeat-mode]
(assoc playlist :repeat-mode repeat-mode))
(enqueue-last [this song]
(let [order (inc (key (last items)))]
;; Arguably this is a bit weird; but if you want to play something last in
;; a shuffled playlist, you want to play it last I guess.
(assoc-in this [:items order]
(vary-meta song assoc :playlist/linear-order order))))
(enqueue-next [this song]
;; we slice the songs up until the currently playing one and increase the
;; order for all the songs after
(let [songs (vec (vals items))
reordered (-> (subvec songs 0 (inc current-idx))
(conj (vary-meta song assoc :playlist/linear-order (inc current-idx)))
(concat (subvec songs (inc current-idx))))]
(assoc this :items (->> (map-indexed vector reordered)
(into (sorted-map)))))))
;; constructor wrapper
(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)))
(fn [_ & {:keys [playback-mode]}] playback-mode))
(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)))
[items & {:keys [playback-mode repeat-mode]}]
(->Playlist (linear-queue items) 0 playback-mode repeat-mode))
(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? (:items playlist))]
(-> (if current-idx
(update-in playlist [:items current-idx] dissoc :playlist/currently-playing?)
playlist)
(assoc-in [:items 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 (:items playlist) :playback-mode playback-mode :repeat-mode (:repeat-mode playlist))
[current-idx current-song] (find-where :playlist/currently-playing? (:items playlist))
[swap-idx _] (find-where #(= 0 (:playlist/order %)) (:items playlist))]
(-> (assoc-in playlist [:items current-idx :playlist/order] 0)
(assoc-in [:items 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? (:items playlist))]
(-> (->playlist (:items 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]
(->> (:items 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? (:items playlist))
[next-idx _] (find-where #(= (:playlist/order %) (inc (:playlist/order current-song))) (:items playlist))]
(update playlist :items
(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? (:items playlist))
[next-idx _] (find-where #(= (:playlist/order %) (inc (:playlist/order current-song))) (:items playlist))]
(-> (update-in playlist [:items current-idx] dissoc :playlist/currently-playing?)
(update :items
(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? (:items playlist))
[next-idx _] (find-where #(= (:playlist/order %) (dec (:playlist/order current-song))) (:items playlist))]
(set-current-song playlist (or next-idx current-idx))))
(defmethod previous-song :repeat-all [playlist]
(let [[_ current-song] (find-where :playlist/currently-playing? (:items playlist))
[next-idx _] (find-where #(= (:playlist/order %)
(rem (dec (:playlist/order current-song)) (count playlist)))
(:items playlist))]
(if next-idx
(set-current-song playlist next-idx)
(if (= :shuffled (:playback-mode playlist))
(let [highest-order (dec (count playlist))
playlist (update playlist :items -shuffle-songs)
[last-idx _] (find-where #(= (:playlist/order %) highest-order) (:items 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 (:items playlist))))]
(update playlist :items conj (assoc song :playlist/order (inc highest-order)))))
(defn enqueue-next [playlist song]
(let [[_ current-song] (find-where :playlist/currently-playing? (:items playlist))]
(update playlist :items
(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)))))))))
[items & {:keys [playback-mode repeat-mode]}]
(->Playlist (shuffled-queue items) 0 playback-mode repeat-mode))

View file

@ -9,7 +9,7 @@
(fn [{:keys [db]} [_ songs start-idx]]
(let [playlist (-> (playlist/->playlist songs :playback-mode :linear :repeat-mode :repeat-all)
(playlist/set-current-song start-idx))]
{:audio/play (api/stream-url (:credentials db) (playlist/peek playlist))
{:audio/play (api/stream-url (:credentials db) (playlist/current-song playlist))
:db (assoc-in db [:audio :current-queue] playlist)})))
(rf/reg-event-db
@ -26,7 +26,7 @@
:audio-player/next-song
(fn [{:keys [db]} _]
(let [db (update-in db [:audio :current-queue] playlist/next-song)
next (playlist/peek (get-in db [:audio :current-queue]))]
next (playlist/current-song (get-in db [:audio :current-queue]))]
{:db db
:audio/play (api/stream-url (:credentials db) next)})))
@ -34,7 +34,7 @@
:audio-player/previous-song
(fn [{:keys [db]} _]
(let [db (update-in db [:audio :current-queue] playlist/previous-song)
prev (playlist/peek (get-in db [:audio :current-queue]))]
prev (playlist/current-song (get-in db [:audio :current-queue]))]
{:db db
:audio/play (api/stream-url (:credentials db) prev)})))
@ -65,7 +65,7 @@
(rf/reg-event-fx
:audio-player/seek
(fn [{:keys [db]} [_ percentage]]
(let [duration (:duration (playlist/peek (get-in db [:audio :current-queue])))]
(let [duration (:duration (playlist/current-song (get-in db [:audio :current-queue])))]
{:audio/seek [percentage duration]})))
(rf/reg-event-fx

View file

@ -4,14 +4,6 @@
[clojure.string :as str])
(:import [goog.string format]))
(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)))
(defn muted-dispatch
"Dispatches a re-frame event while canceling default DOM behavior; to be
called for example in `:on-click`."

View file

@ -1,17 +1,20 @@
(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]
#_[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]
;; NOTE: Should the subscription be moved to the playlist.cljs?
#_(testing "Should provide information about the 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 p/fixture)))))
(= fixtures/song (current-song p/fixture))))
(testing "Should work fine when no song is playing"
(is (nil? (audio/current-song nil [:audio/current-song])))))
(deftest playback-status-subscription
(letfn [(is-playing? [playback-status]

View file

@ -1,7 +1,6 @@
(ns airsonic-ui.audio.playlist-test
(:require [cljs.test :refer [deftest testing is]]
[airsonic-ui.audio.playlist :as playlist]
[airsonic-ui.helpers :refer [find-where]]
[airsonic-ui.fixtures :as fixtures]
[airsonic-ui.test-helpers :as helpers]
[debux.cs.core :refer-macros [dbg]]))
@ -33,14 +32,22 @@
(deftest playlist-creation
(testing "Playlist creation"
(testing "should give us the correct current song"
(testing "should give us the correct current song for linear playback-mode"
(let [queue (song-queue 10)]
(doseq [playback-mode [:linear :shuffled]
repeat-mode [:repeat-none :repeat-single :repeat-all]]
(doseq [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)))))
(-> (playlist/->playlist queue :playback-mode :linear :repeat-mode repeat-mode)
(playlist/current-song)))
(str "repeat-mode: " repeat-mode)))))
(testing "any current song for shuffled playback mode"
(let [queue (song-queue 10)]
(doseq [repeat-mode [:repeat-none :repeat-single :repeat-all]]
(is (some? ((set queue)
(-> (playlist/->playlist queue :playback-mode :linear :repeat-mode repeat-mode)
(playlist/current-song))))
(str "repeat-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]
@ -56,9 +63,9 @@
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 (:items shuffled)) (map :playlist/order (:items linear)))))
(is (not= (:items shuffled) (:items linear))))
(testing "should not change the currently playing track"
(is (same-song? (playlist/peek linear) (playlist/peek shuffled))))
(is (same-song? (playlist/current-song linear) (playlist/current-song shuffled))))
(testing "should not change the repeat mode"
(is (= (:repeat-mode shuffled) (:repeat-mode linear))))))
(testing "from shuffled to linear"
@ -66,10 +73,11 @@
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 (:items linear))))
(is (< (:playlist/order (first (:items linear))) (:playlist/order (last (:items linear))))))
(is (every? #(apply same-song? %) (interleave queue (vals (:items linear)))))
(is (< (:playlist/linear-order (meta (first (vals (:items linear)))))
(:playlist/linear-order (meta (last (vals (:items linear))))))))
(testing "should not change the currently playing track"
(is (same-song? (playlist/peek linear) (playlist/peek shuffled))))
(is (same-song? (playlist/current-song linear) (playlist/current-song shuffled))))
(testing "should not change the repeat mode"
(is (= (:repeat-mode shuffled) (:repeat-mode linear))))))))
@ -91,17 +99,18 @@
(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)))
(playlist/current-song)))
(str repeat-mode ", skipped once"))
(is (same-song? (nth queue 2) (-> (playlist/next-song playlist)
(playlist/next-song)
(playlist/peek)))
(playlist/current-song)))
(str repeat-mode ", skipped twice")))))
;; TODO: Write this test
(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))]
played-back (map playlist/current-song (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)))
@ -110,7 +119,7 @@
(is (nil? (-> (song-queue 1)
(playlist/->playlist :playback-mode :linear :repeat-mode :repeat-none)
(playlist/next-song)
(playlist/peek))))))
(playlist/current-song))))))
(deftest shuffled-next-song
(testing "Should play every track once when called for the entire queue"
@ -118,35 +127,34 @@
(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)
(map playlist/current-song)
(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"
(testing "Should keep the song order 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) (:items playlist))]
(is (not= (map :playlist/order (:items playlist))
(map :playlist/order (:items (-> (playlist/set-current-song playlist last-idx)
(playlist/next-song))))))))
next-playlist (-> (playlist/set-current-song playlist 99)
(playlist/next-song))]
(= (playlist/current-song playlist)
(playlist/current-song next-playlist))))
(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")))
(let [playlist (playlist/->playlist (song-queue 10) :playback-mode :shuffled :repeat-mode :repeat-single)
played-back (map playlist/current-song (iterate playlist/next-song playlist))]
(dotimes [i 3]
(is (same-song? (nth played-back i) (nth played-back (inc i)))))))
(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))))))
(playlist/current-song))))))
(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))]
played-back (map playlist/current-song (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)))
@ -158,61 +166,61 @@
(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"
(playlist/current-song)))))))
;; TODO: Should it?
#_(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))))))
(playlist/current-song))))))
(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)))))))
(playlist/current-song)))))))
(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")))
played-back (map playlist/current-song (iterate playlist/next-song playlist))]
(dotimes [i 3]
(is (same-song? (nth played-back i) (nth played-back (inc i)))))))
(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)
(is (same-song? (playlist/current-song playlist)
(-> playlist
(playlist/next-song)
(playlist/previous-song)
(playlist/peek)))
(playlist/current-song)))
(str "for repeat mode " repeat-mode))
(is (same-song? (-> (playlist/next-song playlist)
(playlist/peek))
(playlist/current-song))
(-> (playlist/next-song playlist)
(playlist/next-song)
(playlist/previous-song)
(playlist/peek)))
(playlist/current-song)))
(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 (:items playlist)) (map :playlist/order (:items playlist'))))))))
(testing "Should keep the song order when repeat-mode is all and we go back to before the first track"
(let [playlist (playlist/->playlist (song-queue 10) :playback-mode :shuffled :repeat-mode :repeat-all)
next-playlist (-> (playlist/previous-song playlist)
(playlist/set-current-song 0))]
(is (= (playlist/current-song playlist)
(playlist/current-song next-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))))))
(doseq [repeat-mode [:repeat-all :repeat-none]]
(let [queue (song-queue 3)
playlist (playlist/->playlist queue :playback-mode :shuffled :repeat-mode repeat-mode)
next-track (-> (playlist/set-current-song playlist 1)
(playlist/current-song))]
(is (not (nil? next-track)))
(is (not (same-song? (playlist/current-song playlist)
next-track)))))))
(deftest enqueue-last
(testing "Should make sure the song is played last"
@ -223,12 +231,12 @@
(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 %)))
(map #(:id (playlist/current-song %)))
(set))
to-enqueue (song)
playlist' (playlist/enqueue-last playlist to-enqueue)]
(is (nil? (played-back (-> (->> (iterate playlist/next-song playlist')
(map playlist/peek))
(map playlist/current-song))
(nth length)
(:id))))
(str "for " playback-mode ", " repeat-mode)))))
@ -240,7 +248,7 @@
played-back-songs (fn played-back-songs [playlist]
(->> (iterate playlist/next-song playlist)
(take length)
(map playlist/peek)
(map playlist/current-song)
(map :playlist/order)))
played-back (played-back-songs playlist)
played-back' (played-back-songs (playlist/enqueue-last playlist (song)))]
@ -256,4 +264,4 @@
next-song (song)]
(is (same-song? next-song (-> (playlist/enqueue-next playlist next-song)
(playlist/next-song)
(playlist/peek))))))))
(playlist/current-song))))))))

View file

@ -2,17 +2,6 @@
(:require [cljs.test :refer [deftest testing is]]
[airsonic-ui.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))))))
(deftest add-classes
(testing "Should add classes to a simple hiccup keyword"
(is (= :div.foo (helpers/add-classes :div :foo)))
@ -43,9 +32,3 @@
(is (= "59:00" (helpers/format-duration (* 59 60) :brief? true)))
(is (= "01:00" (helpers/format-duration 60 :brief? true)))
(is (= "00:05" (helpers/format-duration 5 :brief? true)))))
(deftest vector-move
(testing "Should correctly move items in a vector"
(is (= [1] (helpers/vector-move [1] 0 0)))
(is (= [2 1] (helpers/vector-move [1 2] 0 1) (helpers/vector-move [1 2] 1 0)))
(is (= [1 4 2 3 5] (helpers/vector-move [1 2 3 4 5] 3 1)))))