mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
address review: anchor relocation and empty-context positions
- clamp anchorPlay to the context size, so an empty context with a queued block puts the block at combined 0 (previously position 0 was unresolvable and the queue view emptied). - move/removeAt now derive the playing track's new position from its id after an edit, instead of assuming a fixed list delta; moving or removing the anchor relocates the whole block, which movedCursor ignored. - removing the currently playing track reloads at the model's next position (the anchor may have relocated) rather than removing it from the player in place. - the queue view's remove reconciles its rows from the repository instead of decrementing positions by one, so a block relocation stays in sync.
This commit is contained in:
@@ -926,6 +926,81 @@ class PlaybackControllerTest {
|
||||
assertFalse(handle.operations.any { it.startsWith("setWindow") })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removingTheLastContextTrackWithAQueuedTrackKeepsTheQueue() {
|
||||
seedAlbum(1, sourceId = 1)
|
||||
seedSong("x1", "al2")
|
||||
|
||||
controller.playAlbum(1, "al1", 0)
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "s1"
|
||||
}
|
||||
controller.addToQueue(1, QueueKind.Song, "x1")
|
||||
await { runBlocking { controller.upcomingItem()?.id } == "x1" }
|
||||
|
||||
runBlocking { controller.removeAt(0) }
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "x1"
|
||||
}
|
||||
|
||||
val snapshot = runBlocking { queues.snapshot() }
|
||||
assertEquals(1L, snapshot.size)
|
||||
assertTrue(runBlocking { queues.range(snapshot, 0, 0) }.isNotEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun movingTheAnchorKeepsThePlayingTrackPositionInSync() {
|
||||
seedAlbum(4, sourceId = 1)
|
||||
seedSong("x1", "al2")
|
||||
|
||||
controller.playAlbum(1, "al1", 1)
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "s2"
|
||||
}
|
||||
controller.addToQueue(1, QueueKind.Song, "x1")
|
||||
await { runBlocking { controller.upcomingItem()?.id } == "x1" }
|
||||
controller.next()
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "x1"
|
||||
}
|
||||
|
||||
runBlocking { controller.move(1, 0) }
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "x1"
|
||||
}
|
||||
|
||||
val snapshot = runBlocking { queues.snapshot() }
|
||||
assertEquals(runBlocking { queues.combinedIndexOf(snapshot, "x1") }, controller.state.value.position)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removingThePlayingAnchorTrackPlaysTheModelNextTrack() {
|
||||
seedAlbum(4, sourceId = 1)
|
||||
seedSong("x1", "al2")
|
||||
|
||||
controller.playAlbum(1, "al1", 1)
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "s2"
|
||||
}
|
||||
controller.addToQueue(1, QueueKind.Song, "x1")
|
||||
await { runBlocking { controller.upcomingItem()?.id } == "x1" }
|
||||
|
||||
runBlocking { controller.removeAt(1) }
|
||||
await {
|
||||
controller.state.value.item
|
||||
?.id == "s3"
|
||||
}
|
||||
|
||||
val snapshot = runBlocking { queues.snapshot() }
|
||||
assertEquals(runBlocking { queues.combinedIndexOf(snapshot, "s3") }, controller.state.value.position)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nextFollowsTheReorderedQueue() {
|
||||
seedAlbum(3, sourceId = 1)
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.subtracks.data.db.SubtracksDatabase
|
||||
import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.data.model.Source
|
||||
@@ -225,6 +226,37 @@ class QueueViewModelTest {
|
||||
assertEquals((220L..399L).toList(), viewModel.rows.map { it.position })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removingATrackUnderTheBlockReloadsRows() {
|
||||
runBlocking { seedSongs(10) }
|
||||
runBlocking {
|
||||
db.libraryDao().upsertSongs(
|
||||
listOf(Song(1, "x1", "al2", "ar1", "X", "Other Album", "Artist", 100, 1, 1, null, null)),
|
||||
)
|
||||
}
|
||||
controller.playAlbum(1, "al1", 2)
|
||||
await { controller.state.value.position == 2L }
|
||||
controller.addToQueue(1, QueueKind.Song, "x1")
|
||||
await { runBlocking { controller.upcomingItem()?.id } == "x1" }
|
||||
|
||||
viewModel.open()
|
||||
await { viewModel.ready }
|
||||
|
||||
viewModel.remove(2)
|
||||
await {
|
||||
val positions = viewModel.rows.map { it.position }
|
||||
positions.isNotEmpty() && positions == (positions.first()..positions.last()).toList()
|
||||
}
|
||||
|
||||
val snapshot = runBlocking { queues.snapshot() }
|
||||
val expected =
|
||||
runBlocking {
|
||||
(viewModel.rows.first().position..viewModel.rows.last().position)
|
||||
.mapNotNull { queues.itemAt(snapshot, it)?.song?.id }
|
||||
}
|
||||
assertEquals(expected, viewModel.rows.map { it.song.song.id })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeAndUndoKeepTheBoundedWindowConsistent() {
|
||||
runBlocking { seedSongs(400) }
|
||||
|
||||
Reference in New Issue
Block a user