mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
search: address review findings
Prefix result keys per type so ids shared across songs, albums, artists and playlists cannot collide and crash the list; add a loading state so a query in flight no longer reads as no results; gate on code points to match SQLite's length(); bind the three-character floor to one constant; label the search field for TalkBack; and drop the vacuous trigram-plan test. Cancel the ViewModel scope in its test teardown so it cannot leak into later Robolectric tests.
This commit is contained in:
@@ -762,20 +762,6 @@ class LibraryDaoTest {
|
||||
assertEquals(2, dao.searchAlbums(sourceId, "oad", 2).first().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun songSearchRunsThroughTheTrigramIndex() =
|
||||
runTest {
|
||||
val plan =
|
||||
plan(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"WHERE songs.sourceId = 1 AND ${SONG_SEARCH_FILTER.replace(":search", "'abc'")} " +
|
||||
"ORDER BY songs.title COLLATE NOCASE, songs.id LIMIT 20 OFFSET 0",
|
||||
)
|
||||
|
||||
assertTrue(plan, plan.contains("song_search"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crossTypeSearchIgnoresQueriesShorterThanThreeCharacters() =
|
||||
runTest {
|
||||
|
||||
@@ -83,6 +83,30 @@ class SearchScreenTest {
|
||||
composeRule.onNodeWithText("No results", substring = true).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun idsSharedAcrossTypesDoNotCollide() {
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
SearchScreen(
|
||||
query = "road",
|
||||
onQueryChange = {},
|
||||
results =
|
||||
SearchResults(
|
||||
albums = listOf(album("1", "Road Album")),
|
||||
artists = listOf(artist("1", "Road Artist")),
|
||||
playlists = listOf(playlist("1", "Road Trip")),
|
||||
songs = listOf(AlbumSongItem(song("1", "Road Song"), coverArt = null)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.onNodeWithText("Road Song").assertIsDisplayed()
|
||||
composeRule.onNodeWithText("Road Album").assertIsDisplayed()
|
||||
composeRule.onNodeWithText("Road Artist").assertIsDisplayed()
|
||||
composeRule.onNodeWithText("Road Trip").assertIsDisplayed()
|
||||
}
|
||||
|
||||
private fun album(
|
||||
id: String,
|
||||
name: String,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.subtracks.ui.search
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.room3.Room
|
||||
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
@@ -27,8 +28,10 @@ import com.subtracks.playback.PlaybackController
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
@@ -37,6 +40,7 @@ import kotlinx.coroutines.withTimeout
|
||||
import okhttp3.OkHttpClient
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -94,6 +98,7 @@ class SearchViewModelTest {
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() }
|
||||
cancelAndJoinBlocking(controllerScope, repoScope)
|
||||
db.close()
|
||||
Dispatchers.resetMain()
|
||||
@@ -129,6 +134,13 @@ class SearchViewModelTest {
|
||||
assertTrue(blank.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchReadyCountsCodePointsRatherThanUtf16Units() {
|
||||
assertTrue(searchReady("road"))
|
||||
assertFalse(searchReady("ro"))
|
||||
assertFalse(searchReady("a\uD83D\uDE00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun playingASearchResultPlaysItsAlbumInsteadOfASearchQueue() {
|
||||
val sourceId =
|
||||
|
||||
Reference in New Issue
Block a user