mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
keep the up-next block reachable when the playing context track is removed while shuffled
Review found that removing the playing context track while shuffled with a
non-empty up-next block stranded the block behind the cursor: the
repository moves `upNextAnchor` to the preceding track, but the cursor's
re-derived slot position can land after the block, so the queued tracks
were skipped. Re-anchor the block to the track the cursor lands on, same
as `playAt`/`toggleShuffle`.
- regression test loops the sequence over fresh random seeds and asserts
the block stays reachable; fails without the fix ("stranded behind the
cursor").
- `removingTheCurrentTrackWhileShuffled...` now asserts the canonical slot
the current track occupies instead of recomputing the production position
formula (which only checked the implementation against itself).
- `QueueRepository.removeAt` returns Unit; every caller discarded the
snapshot and re-read it, so it no longer builds one.
This commit is contained in:
@@ -17,7 +17,6 @@ import com.subtracks.data.prefs.StreamQuality
|
||||
import com.subtracks.data.prefs.UserPreferences
|
||||
import com.subtracks.data.prefs.fakeUserPreferences
|
||||
import com.subtracks.data.repo.QueueRepository
|
||||
import com.subtracks.data.repo.Shuffle
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
@@ -1595,17 +1594,55 @@ class PlaybackControllerTest {
|
||||
|
||||
val after = runBlocking { queues.snapshot() }
|
||||
val newContextSize = (before.contextSize - 1).coerceAtLeast(0)
|
||||
val slot = removedFlat.coerceIn(0, (newContextSize - 1).coerceAtLeast(0))
|
||||
val expected = after.combined(Shuffle.toSequence(before.shuffleSeed!!, newContextSize, slot))
|
||||
val slot = removedFlat.coerceIn(0, newContextSize - 1)
|
||||
assertEquals(5L, after.size)
|
||||
assertEquals(expected, controller.state.value.position)
|
||||
assertEquals(
|
||||
runBlocking { queues.itemAt(after, expected)?.song?.id },
|
||||
// The song now occupying the removed track's canonical slot is the one playing.
|
||||
slot,
|
||||
after.flatContext(after.contextPlay(controller.state.value.position!!)),
|
||||
)
|
||||
assertEquals(
|
||||
runBlocking { queues.itemAt(after, controller.state.value.position!!)?.song?.id },
|
||||
controller.state.value.item
|
||||
?.id,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removingThePlayingTrackWhileShuffledLeavesTheBlockReachable() {
|
||||
seedAlbum(6, sourceId = 1)
|
||||
seedSong("x1", "al2")
|
||||
|
||||
repeat(30) {
|
||||
if (controller.state.value.shuffle) {
|
||||
controller.toggleShuffle()
|
||||
await { !controller.state.value.shuffle }
|
||||
}
|
||||
runBlocking { controller.clearUpNext() }
|
||||
controller.playAlbum(1, "al1", 2)
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "s3" && !controller.state.value.shuffle
|
||||
}
|
||||
controller.addToQueue(1, QueueKind.Song, "x1")
|
||||
await { runBlocking { controller.upcomingItem()?.id } == "x1" }
|
||||
|
||||
controller.toggleShuffle()
|
||||
await { controller.state.value.shuffle }
|
||||
await { runBlocking { controller.upcomingItem()?.id } == "x1" }
|
||||
val current = controller.state.value.position!!
|
||||
|
||||
runBlocking { controller.removeAt(current) }
|
||||
|
||||
val snapshot = runBlocking { queues.snapshot() }
|
||||
val position = controller.state.value.position!!
|
||||
assertTrue(
|
||||
"queued track stranded behind the cursor at $position (block at ${snapshot.anchorPlay})",
|
||||
position <= snapshot.anchorPlay || snapshot.isUpNext(position),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun movingContextTracksWhileShuffledIsIgnored() {
|
||||
seedAlbum(6, sourceId = 1)
|
||||
|
||||
Reference in New Issue
Block a user