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
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,20 @@ public Cursor buildQuery(String fromTable) {
query.append(hiddenWhere);
}

// Names in order for the phone's language, whatever their case or accents: Android's
// SQLite collates LOCALIZED with ICU. Plain SQLite would order them by their bytes, putting
// "abba" after "ZZ Top", and "脡mile" after both. LOCALIZED finds names differing only in
// case equal, so their bytes then put them in a fixed order: "Apple" before "apple".
if (isTitleLevel()) {
query.append(" ORDER BY ");
query.append(" album, disc, track ");
query.append(" album COLLATE LOCALIZED, album, disc, track ");
query.append(mSort);
} else {
String field = mSelectedFields[mLevel];
query.append(" GROUP BY ");
query.append(mSelectedFields[mLevel]);
query.append(field);
query.append(" ORDER BY ");
query.append(mSelectedFields[mLevel]);
query.append(" ");
query.append(mSort);
query.append(field + " COLLATE LOCALIZED " + mSort + ", " + field + " " + mSort);
}

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package de.qspool.clementineremote.backend.database

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import androidx.test.core.app.ApplicationProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.io.File

/** The library lists names in order for the phone's language, whatever their case or accents. */
@RunWith(RobolectricTestRunner::class)
class DynamicSongQueryTest {

private val context: Context = ApplicationProvider.getApplicationContext()

private val database = File(context.cacheDir, "sorting-test.db")

/** ASC or DESC. Read by the query's constructor, so not a constructor argument of its own. */
private var order = "ASC"

private inner class Query : DynamicSongQuery(context) {
override fun getSelectedFields() = arrayOf("artist", "album", "title")
override fun getSorting(): String = order
override fun getTable() = "songs"
override fun getReadableDatabase(): SQLiteDatabase =
SQLiteDatabase.openDatabase(database.path, null, SQLiteDatabase.OPEN_READONLY)
}

@Before
fun setUp() {
database.delete()
SQLiteDatabase.openOrCreateDatabase(database, null).use { db ->
db.execSQL("CREATE TABLE songs (artist TEXT, album TEXT, title TEXT, filename TEXT, disc INTEGER, track INTEGER)")
for ((artist, album) in listOf(
"ZZ Top" to "Eliminator", "abba" to "arrival", "脡rik Satie" to "Gymnop茅dies", "Blondie" to "Parallel Lines",
)) {
db.execSQL("INSERT INTO songs VALUES (?, ?, 'Song', 'file:///song', 1, 1)", arrayOf(artist, album))
}
db.execSQL("INSERT INTO songs VALUES ('Various', 'zebra', 'Song', 'file:///song', 1, 1)")
// Names differing only in case, the lower-case one first, so their order doesn't come
// from the rows'.
db.execSQL("INSERT INTO songs VALUES ('Various', 'apple', 'Song', 'file:///song', 1, 1)")
db.execSQL("INSERT INTO songs VALUES ('Various', '脡t茅', 'Song', 'file:///song', 1, 3)")
db.execSQL("INSERT INTO songs VALUES ('Various', 'Apple', 'Song', 'file:///song', 1, 1)")
// An album's songs, in disc and track order whatever their names.
db.execSQL("INSERT INTO songs VALUES ('Various', '脡t茅', 'b second', 'file:///song', 1, 2)")
db.execSQL("INSERT INTO songs VALUES ('Various', '脡t茅', 'A first', 'file:///song', 1, 1)")
}
}

@After
fun tearDown() {
database.delete()
}

/** The names at the query's current level, in the order it lists them. */
private fun names(query: DynamicSongQuery): List<String> {
query.openDatabase()
try {
return query.selectData().map { it.listTitle }
} finally {
query.closeDatabase()
}
}

@Test
fun sortsIgnoringCaseAndAccents() {
assertEquals(listOf("abba", "Blondie", "脡rik Satie", "Various", "ZZ Top"), names(Query()))
}

@Test
fun sortsAnArtistsAlbumsIgnoringCaseAndAccents() {
val query = Query().apply {
level = 1
selection = arrayOf("Various")
}
assertEquals(listOf("Apple", "apple", "脡t茅", "zebra"), names(query))
}

@Test
fun keepsAnAlbumsSongsInTrackOrder() {
val query = Query().apply {
level = 2
selection = arrayOf("Various", "脡t茅")
}
assertEquals(listOf("A first", "b second", "Song"), names(query))
}

@Test
fun sortsBackwardsIgnoringCase() {
order = "DESC"
assertEquals(listOf("ZZ Top", "Various", "脡rik Satie", "Blondie", "abba"), names(Query()))
val albums = Query().apply {
level = 1
selection = arrayOf("Various")
}
assertEquals(listOf("zebra", "脡t茅", "apple", "Apple"), names(albums))
}
}
Loading