Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions packages/common/src/store/playback/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ export const getRetries = getPlaybackRetryCount
// Shuffle / repeat / overshoot
export const getRepeat = (state: CommonState) => state.playback.repeat
export const getShuffle = (state: CommonState) => state.playback.shuffle
export const getShuffleIndex = (state: CommonState) =>
state.playback.shuffleIndex
export const getShuffleOrder = (state: CommonState) =>
state.playback.shuffleOrder
export const getOvershot = (state: CommonState) => state.playback.overshot
export const getUndershot = (state: CommonState) => state.playback.undershot

Expand Down
200 changes: 200 additions & 0 deletions packages/common/src/store/playback/slice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,204 @@ describe('playback slice', () => {
expect(cleared.queue).toHaveLength(0)
expect(cleared.index).toBe(-1)
})

describe('shuffle', () => {
it('enabling shuffle reorders queue with current track at position 0', () => {
const tracks = [track(1), track(2), track(3), track(4), track(5)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 2 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
expect(shuffled.shuffle).toBe(true)
expect(shuffled.index).toBe(0)
expect(shuffled.queue[0]).toEqual(track(3))
expect(shuffled.queue).toHaveLength(tracks.length)
expect(new Set(shuffled.queue.map((t) => t.trackId))).toEqual(
new Set(tracks.map((t) => t.trackId))
)
expect(shuffled.shuffleOriginalQueue).toEqual(tracks)
expect(shuffled.shuffleOriginalIndices[0]).toBe(2)
})

it('next/previous walk the shuffled queue in visible order', () => {
const tracks = [track(1), track(2), track(3), track(4)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 0 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
const after1 = playbackReducer(shuffled, actions.next({}))
expect(after1.index).toBe(1)
expect(after1.queue[after1.index]).toEqual(shuffled.queue[1])
const after2 = playbackReducer(after1, actions.previous())
expect(after2.index).toBe(0)
})

it('disabling shuffle restores original order with current track preserved', () => {
const tracks = [track(1), track(2), track(3), track(4), track(5)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 0 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
// Advance once in the shuffled queue.
const advanced = playbackReducer(shuffled, actions.next({}))
const playingTrack = advanced.queue[advanced.index]
const restored = playbackReducer(
advanced,
actions.setShuffle({ enable: false })
)
expect(restored.shuffle).toBe(false)
expect(restored.queue).toEqual(tracks)
expect(restored.queue[restored.index]).toEqual(playingTrack)
expect(restored.shuffleOriginalQueue).toEqual([])
expect(restored.shuffleOriginalIndices).toEqual([])
})

it('removeFromQueue while shuffled removes from both visible and original', () => {
const tracks = [track(1), track(2), track(3), track(4)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 0 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
// Remove the visible track at position 1 (some random track).
const removedTrack = shuffled.queue[1]
const afterRemove = playbackReducer(
shuffled,
actions.removeFromQueue({ index: 1 })
)
expect(afterRemove.queue).toHaveLength(3)
expect(afterRemove.queue.find((t) => t.trackId === removedTrack.trackId))
.toBeUndefined()
expect(afterRemove.shuffleOriginalQueue).toHaveLength(3)
expect(
afterRemove.shuffleOriginalQueue.find(
(t) => t.trackId === removedTrack.trackId
)
).toBeUndefined()
// Disabling shuffle should restore the remaining 3 in original order.
const restored = playbackReducer(
afterRemove,
actions.setShuffle({ enable: false })
)
const expectedRemaining = tracks.filter(
(t) => t.trackId !== removedTrack.trackId
)
expect(restored.queue).toEqual(expectedRemaining)
})

it('addToQueue while shuffled appends to both visible and original', () => {
const tracks = [track(1), track(2), track(3)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 0 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
const added = playbackReducer(
shuffled,
actions.addToQueue({ tracks: [track(4)] })
)
expect(added.queue).toHaveLength(4)
expect(added.queue[3]).toEqual(track(4))
expect(added.shuffleOriginalQueue).toHaveLength(4)
expect(added.shuffleOriginalQueue[3]).toEqual(track(4))
})

it('appendPage while shuffled mirrors into original queue', () => {
const tracks = [track(1), track(2)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 0 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
const appended = playbackReducer(
shuffled,
actions.appendPage({ tracks: [track(3), track(4)] })
)
expect(appended.queue).toHaveLength(4)
expect(appended.shuffleOriginalQueue).toHaveLength(4)
expect(appended.shuffleOriginalIndices).toHaveLength(4)
})

it('reorder while shuffled keeps shuffleOriginalQueue intact', () => {
const tracks = [track(1), track(2), track(3)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 0 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
const reordered = playbackReducer(
shuffled,
actions.reorder({ orderedIndices: [2, 0, 1] })
)
expect(reordered.queue).toEqual([
shuffled.queue[2],
shuffled.queue[0],
shuffled.queue[1]
])
// Original queue is unchanged.
expect(reordered.shuffleOriginalQueue).toEqual(tracks)
// shuffleOriginalIndices follow their tracks.
expect(reordered.shuffleOriginalIndices).toEqual([
shuffled.shuffleOriginalIndices[2],
shuffled.shuffleOriginalIndices[0],
shuffled.shuffleOriginalIndices[1]
])
})

it('playFrom while shuffled re-shuffles the new queue', () => {
const seeded = playbackReducer(
{ ...initialState, shuffle: true },
actions.playFrom({
tracks: [track(1), track(2), track(3), track(4)],
startIndex: 1
})
)
expect(seeded.shuffle).toBe(true)
expect(seeded.index).toBe(0)
// Track originally at startIndex sits at visible position 0.
expect(seeded.queue[0]).toEqual(track(2))
expect(seeded.shuffleOriginalQueue).toHaveLength(4)
})

it('clearUpcoming while shuffled keeps current track and clears state', () => {
const tracks = [track(1), track(2), track(3)]
const seeded = playbackReducer(
initialState,
actions.playFrom({ tracks, startIndex: 0 })
)
const shuffled = playbackReducer(
seeded,
actions.setShuffle({ enable: true })
)
const cleared = playbackReducer(shuffled, actions.clearUpcoming())
expect(cleared.queue).toHaveLength(1)
expect(cleared.index).toBe(0)
expect(cleared.shuffleOriginalQueue).toEqual([cleared.queue[0]])
expect(cleared.shuffleOriginalIndices).toEqual([0])
})
})
})
Loading
Loading