Release v0.2.38
Publish Memby release / release (push) Canceled after 0s

This commit is contained in:
ponzischeme89
2026-08-09 16:04:40 +12:00
parent 0dffd59440
commit 52e167ea59
32 changed files with 442 additions and 472 deletions
@@ -2,12 +2,63 @@ package com.ponzischeme89.memby.data
import com.ponzischeme89.memby.data.model.BaseItem
import com.ponzischeme89.memby.data.model.EmbyPerson
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Test
class CastMetadataTest {
@Test
fun `episode without cast falls back to its series cast`() = runTest {
val seriesCast = listOf(actor("series-lead"))
val items = mapOf(
"episode" to BaseItem(id = "episode", type = "Episode", seriesId = "series"),
"series" to BaseItem(id = "series", type = "Series", people = seriesCast),
)
val requested = mutableListOf<String>()
val result = resolveCast("episode") { id ->
requested += id
requireNotNull(items[id])
}
assertEquals(seriesCast, result)
assertEquals(listOf("episode", "series"), requested)
}
@Test
fun `episode cast takes precedence over series cast`() = runTest {
val guestCast = listOf(actor("guest"))
val requested = mutableListOf<String>()
val result = resolveCast("episode") { id ->
requested += id
BaseItem(
id = id,
type = "Episode",
seriesId = "series",
people = guestCast,
)
}
assertEquals(guestCast, result)
assertEquals(listOf("episode"), requested)
}
@Test
fun `non-episode without cast does not use a series fallback`() = runTest {
val requested = mutableListOf<String>()
val result = resolveCast("movie") { id ->
requested += id
BaseItem(id = id, type = "Movie", seriesId = "series")
}
assertEquals(emptyList<EmbyPerson>(), result)
assertEquals(listOf("movie"), requested)
}
@Test
fun `cast contains actors and preserves Emby order`() {
val item = BaseItem(
@@ -45,4 +96,6 @@ class CastMetadataTest {
assertEquals("person-1", item.cast.single().id)
assertEquals("portrait-tag", item.cast.single().primaryImageTag)
}
private fun actor(id: String) = EmbyPerson(id = id, name = id, type = "Actor")
}
@@ -118,11 +118,12 @@ class GatewayPayloadTest {
@Test
fun `decodes explicit client server protocol mismatch`() {
val status = json.decodeFromString<GatewayServiceStatus>(
"""{"maintenance":false,"compatible":false,"compatibilityMessage":"App protocol 2, server protocol 1.","clientVersion":"0.9.1","clientProtocol":"2","serverProtocol":1}""",
"""{"maintenance":false,"compatible":false,"compatibilityMessage":"App protocol 2, server protocol 1.","clientVersion":"0.9.1","clientProtocol":"2","gatewayVersion":"0.1.29","serverProtocol":1}""",
)
assertEquals(false, status.compatible)
assertEquals("App protocol 2, server protocol 1.", status.compatibilityMessage)
assertEquals("0.1.29", status.gatewayVersion)
assertEquals(1, status.serverProtocol)
}
@@ -0,0 +1,19 @@
package com.ponzischeme89.memby.ui
import org.junit.Assert.assertEquals
import org.junit.Test
class ProfileInitialsTest {
@Test
fun `uses camel case capitals for compact usernames`() {
assertEquals("AV", profileInitials("AlwynV"))
assertEquals("MC", profileInitials("MattC"))
}
@Test
fun `uses word initials and safe short fallbacks`() {
assertEquals("MC", profileInitials("Matt Cohen"))
assertEquals("AL", profileInitials("alwyn"))
assertEquals("?", profileInitials(" "))
}
}
@@ -58,4 +58,12 @@ class GenreBrowseTest {
assertTrue(item.withFavourite(true).isFavorite)
assertFalse(item.withFavourite(true).withFavourite(false).isFavorite)
}
@Test
fun `watched overrides update a paged poster immediately`() {
val item = BaseItem(id = "film", name = "Film", type = "Movie")
assertFalse(item.userData?.played == true)
assertTrue(item.withPlayed(true).userData?.played == true)
assertFalse(item.withPlayed(true).withPlayed(false).userData?.played == true)
}
}
@@ -1,64 +0,0 @@
package com.ponzischeme89.memby.ui.whatsnew
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import com.github.takahirom.roborazzi.captureRoboImage
import com.ponzischeme89.memby.ui.PreviewSurface
import com.ponzischeme89.memby.ui.settings.ReleaseNote
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
/**
* The panel a television shows once, after it has updated itself overnight. Nobody sees it
* twice, so being able to look at it without reinstalling on hardware is the point.
*/
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [34], qualifiers = "w960dp-h540dp-television-xhdpi")
class WhatsNewScreenshotTest {
@get:Rule
val compose = createComposeRule()
@Test
fun `a typical release`() {
compose.setContent {
PreviewSurface {
WhatsNewOverlay(
release = ReleaseNote(
version = "0.2.24",
date = "2026-08-05",
changes = listOf(
"Fixed: The centre button on the remote now pauses straight away.",
"Changed: Subtitles now open in a small menu above the button.",
"Added: Continuing and ended tags on My Shows.",
),
),
onDismiss = {},
)
}
}
compose.onRoot().captureRoboImage("build/screenshots/whats-new/whats-new.png")
}
@Test
fun `a long release is capped`() {
compose.setContent {
PreviewSurface {
WhatsNewOverlay(
release = ReleaseNote(
version = "0.2.24",
date = "2026-08-05",
changes = List(9) { "Fixed: Change number ${it + 1} in a long release." },
),
onDismiss = {},
)
}
}
compose.onRoot().captureRoboImage("build/screenshots/whats-new/whats-new-long.png")
}
}
@@ -1,26 +1,19 @@
package com.ponzischeme89.memby.ui.whatsnew
import com.ponzischeme89.memby.ui.settings.ReleaseNote
import org.junit.Assert.assertEquals
import org.junit.Test
class WhatsNewTest {
private val history = listOf(
ReleaseNote("0.2.24", "2026-08-05", listOf("Fixed: A thing.", "Added: Another thing.")),
ReleaseNote("0.2.23", "2026-08-04", listOf("Added: An older thing.")),
)
private fun decide(
installed: String = "0.2.24",
seen: String? = "0.2.23",
signedIn: Boolean = true,
releases: List<ReleaseNote> = history,
) = whatsNewDecision(installed, seen, signedIn, releases)
) = whatsNewDecision(installed, seen, signedIn)
@Test
fun `an updated television is shown the notes for the build it is running`() {
assertEquals(WhatsNewDecision.Show(history[0]), decide())
fun `an updated television is notified of the build it is running`() {
assertEquals(WhatsNewDecision.Notify("0.2.24"), decide())
}
@Test
@@ -38,23 +31,17 @@ class WhatsNewTest {
@Test
fun `an install that predates the record is announced once it is signed in`() {
assertEquals(WhatsNewDecision.Show(history[0]), decide(seen = null, signedIn = true))
assertEquals(WhatsNewDecision.Notify("0.2.24"), decide(seen = null, signedIn = true))
}
@Test
fun `a signed-out television with a record waits rather than burning the notes`() {
fun `a signed-out television with a record waits rather than consuming the notice`() {
assertEquals(WhatsNewDecision.Nothing, decide(seen = "0.2.23", signedIn = false))
}
@Test
fun `a build the changelog does not describe is recorded silently`() {
assertEquals(WhatsNewDecision.MarkSeen("0.9.0"), decide(installed = "0.9.0"))
}
@Test
fun `a release with no bullets is not shown as an empty panel`() {
val empty = listOf(ReleaseNote("0.2.24", "2026-08-05", emptyList()))
assertEquals(WhatsNewDecision.MarkSeen("0.2.24"), decide(releases = empty))
fun `an updated build does not depend on a changelog entry`() {
assertEquals(WhatsNewDecision.Notify("0.9.0"), decide(installed = "0.9.0"))
}
@Test
@@ -62,13 +49,4 @@ class WhatsNewTest {
assertEquals(WhatsNewDecision.Nothing, decide(installed = " "))
}
@Test
fun `the bullet count is cut to what the screen can hold`() {
// A 720p television, which is the small case this exists for.
assertEquals(4, maxChangesFor(540))
// Room for more does not mean an unbounded list.
assertEquals(6, maxChangesFor(1200))
// Never zero: a panel with a heading and nothing under it says nothing at all.
assertEquals(1, maxChangesFor(200))
}
}