mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
Revert the downloads block
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
package com.subtracks.data.download
|
||||
|
||||
class FakeDownloadEngine : DownloadEngine {
|
||||
private val downloads = mutableMapOf<Long, EngineDownload>()
|
||||
private var nextId = 1L
|
||||
|
||||
val requests = mutableListOf<Pair<Long, EngineRequest>>()
|
||||
val cancelled = mutableListOf<Long>()
|
||||
|
||||
override fun enqueue(request: EngineRequest): Long {
|
||||
val id = nextId++
|
||||
requests += id to request
|
||||
downloads[id] = EngineDownload(EngineStatus.Pending)
|
||||
return id
|
||||
}
|
||||
|
||||
override fun download(id: Long): EngineDownload? = downloads[id]
|
||||
|
||||
override fun cancel(id: Long) {
|
||||
cancelled += id
|
||||
}
|
||||
|
||||
fun running(
|
||||
id: Long,
|
||||
bytes: Long,
|
||||
total: Long,
|
||||
) {
|
||||
downloads[id] = EngineDownload(EngineStatus.Running, bytes, total)
|
||||
}
|
||||
|
||||
fun complete(
|
||||
id: Long,
|
||||
total: Long,
|
||||
) {
|
||||
downloads[id] = EngineDownload(EngineStatus.Completed, total, total)
|
||||
}
|
||||
|
||||
fun fail(id: Long) {
|
||||
downloads[id] = EngineDownload(EngineStatus.Failed, error = "Download failed")
|
||||
}
|
||||
|
||||
fun forget(id: Long) {
|
||||
downloads.remove(id)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.subtracks.data.download
|
||||
|
||||
import android.app.DownloadManager
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class SystemDownloadEngineTest {
|
||||
@Test
|
||||
fun successfulRunningAndFailedStatusesAreMapped() {
|
||||
assertEquals(EngineStatus.Completed, engineStatus(DownloadManager.STATUS_SUCCESSFUL))
|
||||
assertEquals(EngineStatus.Running, engineStatus(DownloadManager.STATUS_RUNNING))
|
||||
assertEquals(EngineStatus.Failed, engineStatus(DownloadManager.STATUS_FAILED))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aWaitingDownloadIsPending() {
|
||||
assertEquals(EngineStatus.Pending, engineStatus(DownloadManager.STATUS_PENDING))
|
||||
assertEquals(EngineStatus.Pending, engineStatus(DownloadManager.STATUS_PAUSED))
|
||||
assertEquals(EngineStatus.Pending, engineStatus(Int.MIN_VALUE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun failureReasonsReadAsSentences() {
|
||||
assertEquals("Not enough space to download", failureMessage(DownloadManager.ERROR_INSUFFICIENT_SPACE))
|
||||
assertEquals("Storage is unavailable", failureMessage(DownloadManager.ERROR_DEVICE_NOT_FOUND))
|
||||
assertEquals("Download failed", failureMessage(DownloadManager.ERROR_UNKNOWN))
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
package com.subtracks.data.repo
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.room3.Room
|
||||
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.download.FakeDownloadEngine
|
||||
import com.subtracks.data.model.DownloadStatus
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.model.SongDownload
|
||||
import com.subtracks.data.model.Source
|
||||
import com.subtracks.data.model.SubsonicSource
|
||||
import com.subtracks.data.prefs.fakeUserPreferences
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.OkHttpClient
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DownloadRepositoryTest {
|
||||
private lateinit var db: SubtracksDatabase
|
||||
private lateinit var sources: SourceRepository
|
||||
private lateinit var engine: FakeDownloadEngine
|
||||
private lateinit var repository: DownloadRepository
|
||||
private lateinit var dir: File
|
||||
private val messages = CopyOnWriteArrayList<String>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db =
|
||||
Room
|
||||
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
|
||||
.setDriver(BundledSQLiteDriver())
|
||||
.build()
|
||||
sources = SourceRepository(db, OkHttpClient(), fakeUserPreferences())
|
||||
engine = FakeDownloadEngine()
|
||||
dir = File(context.cacheDir, "downloads-${System.nanoTime()}")
|
||||
repository = DownloadRepository(db, sources, engine, dir, showMessage = { messages += it })
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
repository.close()
|
||||
sources.close()
|
||||
db.close()
|
||||
dir.deleteRecursively()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startingADownloadAsksTheEngineForTheOriginalAndRecordsIt() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
|
||||
val request = engine.requests.single().second
|
||||
assertTrue("expected the download endpoint, was ${request.uri}", request.uri.contains("/rest/download"))
|
||||
assertEquals("downloads/1/s1", request.path)
|
||||
assertEquals("Song 1", request.title)
|
||||
assertEquals(DownloadStatus.Queued, row(1, "s1")?.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun progressAndCompletionAreMirroredAndTheFileBecomesPlayable() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
repository.start()
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
val id = engine.requests.single().first
|
||||
|
||||
engine.running(id, bytes = 40, total = 100)
|
||||
await { row(1, "s1")?.status == DownloadStatus.Running }
|
||||
assertEquals(40L, row(1, "s1")?.bytes)
|
||||
|
||||
engine.complete(id, total = 100)
|
||||
writeFile(1, "s1", "audio")
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
await { repository.localUri("s1") != null }
|
||||
assertEquals(DownloadStatus.Completed, row(1, "s1")?.status)
|
||||
assertEquals(Uri.fromFile(file(1, "s1")).toString(), repository.localUri("s1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aPartialFileIsNotTreatedAsPlayableWhileTheDownloadRuns() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
repository.start()
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
val id = engine.requests.single().first
|
||||
engine.running(id, bytes = 40, total = 100)
|
||||
writeFile(1, "s1", "half")
|
||||
await { row(1, "s1")?.status == DownloadStatus.Running }
|
||||
|
||||
assertNull(repository.localUri("s1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aCompletedRowWhoseFileIsGoneIsFailedRatherThanLeftCompleted() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Completed, engineId = null))
|
||||
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
assertEquals(DownloadStatus.Failed, row(1, "s1")?.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aCompletedEngineStateWithoutAFileIsFailed() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
val id = engine.requests.single().first
|
||||
engine.complete(id, total = 100)
|
||||
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
assertEquals(DownloadStatus.Failed, row(1, "s1")?.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aQueuedRowWhoseEngineEntryIsGoneIsStartedAgain() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Queued, engineId = 7))
|
||||
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
val request = engine.requests.single()
|
||||
assertNotEquals(7L, request.first)
|
||||
assertEquals("downloads/1/s1", request.second.path)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removingADownloadCancelsItAndDeletesTheFile() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
writeFile(1, "s1", "audio")
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
val id = engine.requests.single().first
|
||||
|
||||
runBlocking { repository.remove(1, "s1") }
|
||||
|
||||
assertTrue(engine.cancelled.contains(id))
|
||||
assertFalse(file(1, "s1").exists())
|
||||
assertNull(row(1, "s1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aFailedDownloadCanBeStartedAgain() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Failed, engineId = 3, error = "boom"))
|
||||
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
|
||||
assertEquals(1, engine.requests.size)
|
||||
assertEquals(DownloadStatus.Queued, row(1, "s1")?.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aFailedDownloadIsNotRestartedByReconcile() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Failed, engineId = null, error = "boom"))
|
||||
|
||||
runBlocking { repository.reconcile() }
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
assertTrue(engine.requests.isEmpty())
|
||||
assertEquals(DownloadStatus.Failed, row(1, "s1")?.status)
|
||||
assertEquals("boom", row(1, "s1")?.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anEngineFailureIsMirroredWithItsMessageAndLeavesNoPartialFile() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
val id = engine.requests.single().first
|
||||
writeFile(1, "s1", "half")
|
||||
|
||||
engine.fail(id)
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
assertEquals(DownloadStatus.Failed, row(1, "s1")?.status)
|
||||
assertEquals("Download failed", row(1, "s1")?.error)
|
||||
assertFalse(file(1, "s1").exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRunningDownloadWhoseEngineEntryVanishesIsFailedRatherThanRestarted() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
repository.start()
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
val id = engine.requests.single().first
|
||||
engine.running(id, bytes = 40, total = 100)
|
||||
await { row(1, "s1")?.status == DownloadStatus.Running }
|
||||
|
||||
engine.forget(id)
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
assertEquals(DownloadStatus.Failed, row(1, "s1")?.status)
|
||||
assertEquals(1, engine.requests.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aSongRemovedFromTheServerTakesItsDownloadRowWithIt() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
|
||||
runBlocking { db.libraryDao().deleteSongs(1, listOf("s1")) }
|
||||
|
||||
assertNull(row(1, "s1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun downloadingFromASourceThatIsNotActiveIsRefused() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
db.sourcesDao().upsertSource(Source(2, "other", "http://other/", isActive = false, createdAt = 0))
|
||||
db.sourcesDao().upsertSubsonicSource(SubsonicSource(2, "user", "pass"))
|
||||
|
||||
runBlocking { repository.download(2, "s1") }
|
||||
|
||||
assertTrue(engine.requests.isEmpty())
|
||||
assertEquals(listOf("Can't download from a source that isn't active"), messages.toList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removingASourceCancelsItsDownloadsAndDeletesItsFiles() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
writeFile(1, "s1", "audio")
|
||||
runBlocking { repository.download(1, "s1") }
|
||||
val id = engine.requests.single().first
|
||||
|
||||
runBlocking { repository.removeSource(1) }
|
||||
|
||||
assertTrue(engine.cancelled.contains(id))
|
||||
assertFalse(file(1, "s1").exists())
|
||||
assertTrue(allRows().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reconcileDeletesFilesThatBelongToNoDownload() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
writeFile(1, "orphan", "audio")
|
||||
|
||||
runBlocking { repository.reconcile() }
|
||||
|
||||
assertFalse(file(1, "orphan").exists())
|
||||
}
|
||||
|
||||
private fun seedLibrary() {
|
||||
runBlocking {
|
||||
db.sourcesDao().upsertSource(Source(1, "source", "http://localhost:4533/", isActive = true, createdAt = 0))
|
||||
db.sourcesDao().upsertSubsonicSource(SubsonicSource(1, "user", "pass"))
|
||||
db.libraryDao().upsertSongs(
|
||||
listOf(
|
||||
Song(
|
||||
sourceId = 1,
|
||||
id = "s1",
|
||||
albumId = "al1",
|
||||
artistId = "ar1",
|
||||
title = "Song 1",
|
||||
album = "Album",
|
||||
artist = "Artist",
|
||||
duration = 100,
|
||||
track = 1,
|
||||
disc = 1,
|
||||
starred = null,
|
||||
genre = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
await { sources.downloadUri(1, "s1") != null }
|
||||
}
|
||||
|
||||
private fun file(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
): File = File(File(dir, sourceId.toString()), songId)
|
||||
|
||||
private fun row(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
): SongDownload? = runBlocking { db.downloadDao().find(sourceId, songId) }
|
||||
|
||||
private fun allRows(): List<SongDownload> = runBlocking { db.downloadDao().all() }
|
||||
|
||||
private fun writeFile(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
contents: String,
|
||||
) {
|
||||
val file = file(sourceId, songId)
|
||||
file.parentFile?.mkdirs()
|
||||
file.writeText(contents)
|
||||
}
|
||||
|
||||
private fun await(predicate: () -> Boolean) {
|
||||
val deadline = System.nanoTime() + 5_000_000_000L
|
||||
while (!predicate() && System.nanoTime() < deadline) Thread.sleep(10)
|
||||
assertTrue("Timed out waiting for the expected download state", predicate())
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.subtracks.playback
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.media3.datasource.DataSpec
|
||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.subtracks.data.source.streamLengthSuffix
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MediaDataSourceTest {
|
||||
private val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
|
||||
private fun dataSource() = mediaDataSourceFactory(context, OkHttpDataSource.Factory(OkHttpClient())).createDataSource()
|
||||
|
||||
@Test
|
||||
fun aDownloadedFileIsReadThroughTheStreamingChain() {
|
||||
val file = File(context.cacheDir, "downloaded-${System.nanoTime()}").apply { writeBytes(byteArrayOf(1, 2, 3, 4)) }
|
||||
val dataSource = dataSource()
|
||||
|
||||
val length = dataSource.open(DataSpec(Uri.fromFile(file)))
|
||||
val buffer = ByteArray(4)
|
||||
val read = dataSource.read(buffer, 0, buffer.size)
|
||||
dataSource.close()
|
||||
|
||||
assertEquals(4L, length)
|
||||
assertEquals(4, read)
|
||||
assertEquals(listOf<Byte>(1, 2, 3, 4), buffer.toList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aStreamWithoutAContentLengthGetsItsDeclaredLength() {
|
||||
val server = MockWebServer()
|
||||
server.enqueue(MockResponse().setChunkedBody("0123456789", 1))
|
||||
server.start()
|
||||
val url = "http://${server.hostName}:${server.port}/rest/stream" + streamLengthSuffix(1_000_000)
|
||||
val dataSource = dataSource()
|
||||
|
||||
val length = dataSource.open(DataSpec(Uri.parse(url)))
|
||||
dataSource.close()
|
||||
server.shutdown()
|
||||
|
||||
assertEquals(1_000_000L, length)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.subtracks.playback
|
||||
|
||||
import androidx.media3.common.C
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MediaItemTest {
|
||||
private val song =
|
||||
QueueItem(
|
||||
id = "s1",
|
||||
title = "Song",
|
||||
artist = "Artist",
|
||||
album = "Album",
|
||||
coverArtId = "al1",
|
||||
durationMs = 100_000L,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun aDownloadedFileIsPreferredOverTheStream() {
|
||||
val item = queueMediaItem(song, local = "file:///downloads/1/s1", stream = "http://server/stream?s=s1", transcode = false)
|
||||
|
||||
assertEquals("file:///downloads/1/s1", item.localConfiguration?.uri.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theStreamIsUsedWhenThereIsNoDownload() {
|
||||
val item = queueMediaItem(song, local = null, stream = "http://server/stream?s=s1", transcode = false)
|
||||
|
||||
assertEquals("http://server/stream?s=s1", item.localConfiguration?.uri.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aDownloadedFileIsNotClippedEvenForATranscodingQuality() {
|
||||
val item = queueMediaItem(song, local = "file:///downloads/1/s1", stream = "http://server/stream?s=s1", transcode = true)
|
||||
|
||||
assertEquals(C.TIME_END_OF_SOURCE, item.clippingConfiguration?.endPositionMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aTranscodedStreamIsClippedToItsDeclaredDuration() {
|
||||
val item = queueMediaItem(song, local = null, stream = "http://server/stream?s=s1", transcode = true)
|
||||
|
||||
assertEquals(100_000L, item.clippingConfiguration?.endPositionMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theItemKeepsItsIdTitleAndCoverArt() {
|
||||
val item = queueMediaItem(song, local = null, stream = "http://server/stream?s=s1", transcode = false)
|
||||
|
||||
assertEquals("s1", item.mediaId)
|
||||
assertEquals("Song", item.mediaMetadata.title.toString())
|
||||
assertEquals(CoverArtArtwork.uri("al1"), item.mediaMetadata.artworkUri)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ 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.download.FakeDownloadEngine
|
||||
import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.QueueEntry
|
||||
import com.subtracks.data.model.QueueKind
|
||||
@@ -17,7 +16,6 @@ import com.subtracks.data.net.NetworkMode
|
||||
import com.subtracks.data.prefs.StreamQuality
|
||||
import com.subtracks.data.prefs.UserPreferences
|
||||
import com.subtracks.data.prefs.fakeUserPreferences
|
||||
import com.subtracks.data.repo.DownloadRepository
|
||||
import com.subtracks.data.repo.QueueRepository
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -35,7 +33,6 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@@ -48,7 +45,6 @@ class PlaybackControllerTest {
|
||||
private lateinit var networkMode: MutableStateFlow<NetworkMode>
|
||||
private lateinit var sources: SourceRepository
|
||||
private lateinit var queues: QueueRepository
|
||||
private lateinit var downloads: DownloadRepository
|
||||
private lateinit var handle: FakePlayerHandle
|
||||
private lateinit var controller: PlaybackController
|
||||
private val messages = CopyOnWriteArrayList<String>()
|
||||
@@ -65,24 +61,15 @@ class PlaybackControllerTest {
|
||||
networkMode = MutableStateFlow(NetworkMode.Wifi)
|
||||
sources = SourceRepository(db, OkHttpClient(), prefs, networkMode = networkMode)
|
||||
queues = QueueRepository(db)
|
||||
downloads = DownloadRepository(db, sources, FakeDownloadEngine(), File(context.cacheDir, "downloads"))
|
||||
handle = FakePlayerHandle()
|
||||
controller =
|
||||
PlaybackController(
|
||||
sources,
|
||||
queues,
|
||||
FakePlayerConnection(handle),
|
||||
downloads,
|
||||
showMessage = { messages += it },
|
||||
dispatcher = dispatcher,
|
||||
)
|
||||
PlaybackController(sources, queues, FakePlayerConnection(handle), showMessage = { messages += it }, dispatcher = dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
controller.close()
|
||||
sources.close()
|
||||
downloads.close()
|
||||
db.close()
|
||||
dispatcher.close()
|
||||
}
|
||||
@@ -563,7 +550,7 @@ class PlaybackControllerTest {
|
||||
runBlocking { queues.setPosition(12_000L) }
|
||||
|
||||
val restoredHandle = FakePlayerHandle()
|
||||
val restored = PlaybackController(sources, queues, FakePlayerConnection(restoredHandle), downloads, dispatcher = dispatcher)
|
||||
val restored = PlaybackController(sources, queues, FakePlayerConnection(restoredHandle), dispatcher = dispatcher)
|
||||
restored.connect()
|
||||
await {
|
||||
restored.state.value.item
|
||||
@@ -671,7 +658,7 @@ class PlaybackControllerTest {
|
||||
block: Runnable,
|
||||
) = block.run()
|
||||
}
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), downloads, dispatcher = inline)
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), dispatcher = inline)
|
||||
|
||||
eager.playAlbum(1, "al1", 0)
|
||||
await {
|
||||
@@ -718,7 +705,7 @@ class PlaybackControllerTest {
|
||||
block: Runnable,
|
||||
) = block.run()
|
||||
}
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), downloads, dispatcher = inline)
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), dispatcher = inline)
|
||||
|
||||
eager.playAlbum(1, "al1", 0)
|
||||
await {
|
||||
@@ -761,7 +748,7 @@ class PlaybackControllerTest {
|
||||
block: Runnable,
|
||||
) = block.run()
|
||||
}
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), downloads, dispatcher = inline)
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), dispatcher = inline)
|
||||
|
||||
eager.playAlbum(1, "al1", 0)
|
||||
await {
|
||||
@@ -804,7 +791,7 @@ class PlaybackControllerTest {
|
||||
block: Runnable,
|
||||
) = block.run()
|
||||
}
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), downloads, dispatcher = inline)
|
||||
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), dispatcher = inline)
|
||||
|
||||
eager.playAlbum(1, "al1", 0)
|
||||
await {
|
||||
@@ -1843,7 +1830,7 @@ class PlaybackControllerTest {
|
||||
runBlocking { queues.setPosition(12_000L) }
|
||||
|
||||
val restoredHandle = FakePlayerHandle()
|
||||
val restored = PlaybackController(sources, queues, FakePlayerConnection(restoredHandle), downloads, dispatcher = dispatcher)
|
||||
val restored = PlaybackController(sources, queues, FakePlayerConnection(restoredHandle), dispatcher = dispatcher)
|
||||
restored.connect()
|
||||
await {
|
||||
restored.state.value.item
|
||||
@@ -1879,21 +1866,6 @@ class PlaybackControllerTest {
|
||||
assertEquals(0L, runBlocking { queues.cursorPositionMs() })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun refreshingMediaItemsRebuildsTheWindowAtTheSamePosition() {
|
||||
seedAlbum(3, sourceId = 1)
|
||||
controller.playAlbum(1, "al1", 0)
|
||||
await { handle.operations.contains("play") }
|
||||
handle.positionMs = 42_000L
|
||||
val windows = handle.operations.count { it.startsWith("setWindow") }
|
||||
|
||||
controller.refreshMediaItems()
|
||||
|
||||
await { handle.operations.count { it.startsWith("setWindow") } > windows }
|
||||
assertEquals("s1", handle.currentItem?.id)
|
||||
assertEquals(42_000L, handle.positionMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun positionTickerAdvancesPositionWithoutEmittingPlaybackState() =
|
||||
runBlocking {
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.subtracks.ui.library
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.subtracks.data.model.DownloadStatus
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.model.SongDownload
|
||||
import com.subtracks.ui.theme.SubtracksTheme
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
class SongRowDownloadTest {
|
||||
@get:Rule
|
||||
val composeRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
private val song =
|
||||
Song(
|
||||
sourceId = 1,
|
||||
id = "s1",
|
||||
albumId = "al1",
|
||||
artistId = "ar1",
|
||||
title = "Song",
|
||||
album = "Album",
|
||||
artist = "Artist",
|
||||
duration = 100,
|
||||
track = 1,
|
||||
disc = 1,
|
||||
starred = null,
|
||||
genre = null,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun aCompletedDownloadShowsTheDownloadedIndicator() {
|
||||
render(download(DownloadStatus.Completed))
|
||||
|
||||
composeRule.onNodeWithContentDescription("Downloaded").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRunningDownloadShowsProgressRatherThanTheDownloadedIndicator() {
|
||||
render(download(DownloadStatus.Running))
|
||||
|
||||
composeRule.onNodeWithContentDescription("Downloaded").assertDoesNotExist()
|
||||
composeRule.onNodeWithContentDescription("Downloading").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aSongWithoutADownloadShowsNoIndicator() {
|
||||
render(null)
|
||||
|
||||
composeRule.onNodeWithText("Song").assertIsDisplayed()
|
||||
composeRule.onNodeWithContentDescription("Downloaded").assertDoesNotExist()
|
||||
}
|
||||
|
||||
private fun download(status: DownloadStatus) =
|
||||
SongDownload(sourceId = 1, songId = "s1", status = status, engineId = 7, bytes = 40, total = 100)
|
||||
|
||||
private fun render(download: SongDownload?) {
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
SongRow(song = song, download = download)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,11 @@ 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.download.FakeDownloadEngine
|
||||
import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.data.model.Source
|
||||
import com.subtracks.data.prefs.fakeUserPreferences
|
||||
import com.subtracks.data.repo.DownloadRepository
|
||||
import com.subtracks.data.repo.QueueRepository
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import com.subtracks.playback.FakePlayerConnection
|
||||
@@ -31,7 +29,6 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@@ -55,8 +52,7 @@ class QueueViewModelTest {
|
||||
.build()
|
||||
val sources = SourceRepository(db, OkHttpClient(), fakeUserPreferences())
|
||||
queues = QueueRepository(db)
|
||||
val downloads = DownloadRepository(db, sources, FakeDownloadEngine(), File(context.cacheDir, "downloads"))
|
||||
controller = PlaybackController(sources, queues, FakePlayerConnection(FakePlayerHandle()), downloads, dispatcher = dispatcher)
|
||||
controller = PlaybackController(sources, queues, FakePlayerConnection(FakePlayerHandle()), dispatcher = dispatcher)
|
||||
viewModel = QueueViewModel(queues, controller)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user