Big changes
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
package com.ponzischeme89.memby.data
|
||||
|
||||
import com.ponzischeme89.memby.data.model.GatewayHome
|
||||
import com.ponzischeme89.memby.data.model.GatewayNextEpisode
|
||||
import com.ponzischeme89.memby.data.model.GatewayPlayback
|
||||
import com.ponzischeme89.memby.data.model.GatewaySearchHistory
|
||||
import com.ponzischeme89.memby.data.model.GatewayServiceStatus
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
@@ -23,6 +27,17 @@ class GatewayPayloadTest {
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
@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}""",
|
||||
)
|
||||
|
||||
assertEquals(false, status.compatible)
|
||||
assertEquals("App protocol 2, server protocol 1.", status.compatibilityMessage)
|
||||
assertEquals(1, status.serverProtocol)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodes a home payload with emby-shaped items`() {
|
||||
val payload = """
|
||||
@@ -79,6 +94,30 @@ class GatewayPayloadTest {
|
||||
assertEquals("Solaris", home.rows.last().items.single().name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodes explainable For You metadata`() {
|
||||
val payload = """
|
||||
{
|
||||
"rows":[{
|
||||
"id":"for-you:picks",
|
||||
"title":"Top picks that fit in 60 minutes",
|
||||
"kind":"for-you",
|
||||
"items":[{
|
||||
"Id":"9","Name":"Pilot","Type":"Episode",
|
||||
"MembyRecommendationReason":"Matches your Drama viewing · fits your 60-minute window",
|
||||
"MembyCompatibility":"Direct plays well on this TV"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val item = json.decodeFromString<com.ponzischeme89.memby.data.model.GatewayRows>(payload)
|
||||
.rows.single().items.single()
|
||||
|
||||
assertTrue(item.membyRecommendationReason!!.contains("60-minute"))
|
||||
assertEquals("Direct plays well on this TV", item.membyCompatibility)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodes the informational Sonarr schedule row`() {
|
||||
val payload = """
|
||||
@@ -135,6 +174,15 @@ class GatewayPayloadTest {
|
||||
assertTrue(home.partial)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodes persisted recent searches`() {
|
||||
val history = json.decodeFromString<GatewaySearchHistory>(
|
||||
"""{"queries":["severance","slow horses","arrival"]}""",
|
||||
)
|
||||
|
||||
assertEquals(listOf("severance", "slow horses", "arrival"), history.queries)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodes a playback response`() {
|
||||
val playback = json.decodeFromString<GatewayPlayback>(
|
||||
@@ -145,6 +193,48 @@ class GatewayPayloadTest {
|
||||
assertTrue(playback.url.startsWith("https://emby.example/Videos/9/stream"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodes the next-episode response the player counts down to`() {
|
||||
val next = json.decodeFromString<GatewayNextEpisode>(
|
||||
"""
|
||||
{
|
||||
"item": {
|
||||
"Id":"11","Name":"The Bicameral Mind","Type":"Episode",
|
||||
"SeriesName":"Westworld","SeriesId":"7",
|
||||
"IndexNumber":5,"ParentIndexNumber":2,
|
||||
"ImageTags":{"Primary":"abc"}
|
||||
},
|
||||
"title": "Westworld – The Bicameral Mind",
|
||||
"url": "https://emby.example/Videos/11/stream?static=true",
|
||||
"resumePositionMs": 0
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
assertEquals("11", next.item.id)
|
||||
assertEquals("Westworld", next.item.seriesName)
|
||||
// The banner's subtitle is built from these, so they have to survive the wire.
|
||||
assertEquals("S2 · E5", next.item.episodeCode)
|
||||
assertEquals(0L, next.resumePositionMs)
|
||||
assertTrue(next.url.startsWith("https://emby.example/Videos/11/stream"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an episode without a season still gets a usable code`() {
|
||||
val next = json.decodeFromString<GatewayNextEpisode>(
|
||||
"""{"item":{"Id":"12","Name":"Special","Type":"Episode","IndexNumber":3},"url":"https://e/x"}""",
|
||||
)
|
||||
assertEquals("E3", next.item.episodeCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an item with no episode numbering has no code`() {
|
||||
val next = json.decodeFromString<GatewayNextEpisode>(
|
||||
"""{"item":{"Id":"13","Name":"Arrival","Type":"Movie"},"url":"https://e/x"}""",
|
||||
)
|
||||
assertNull(next.item.episodeCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `device allowance response becomes a specific sign-in error`() {
|
||||
val error = parseDeviceLimit(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ponzischeme89.memby.data
|
||||
|
||||
import com.ponzischeme89.memby.data.model.DeviceProfile
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
@@ -13,4 +14,16 @@ class PlaybackReportMathTest {
|
||||
fun negativePositionsAreClamped() {
|
||||
assertEquals(0L, millisecondsToTicks(-1L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deviceProfileExternalizesTextAndEncodesBitmapSubtitles() {
|
||||
val profiles = DeviceProfile.embyAndroidTv().subtitleProfiles.associate {
|
||||
it.format to it.method
|
||||
}
|
||||
assertEquals("External", profiles["srt"])
|
||||
assertEquals("External", profiles["ass"])
|
||||
assertEquals("External", profiles["mov_text"])
|
||||
assertEquals("Encode", profiles["pgssub"])
|
||||
assertEquals("Encode", profiles["dvdsub"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,21 @@ class ProfileSettingsTest {
|
||||
|
||||
assertNull(settings.activeProfileId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `for you choices belong to each profile`() {
|
||||
val matt = profile.copy(forYouMinutes = 60, hasOpenedForYou = true)
|
||||
val family = profile.copy(
|
||||
id = "server::family",
|
||||
userId = "family",
|
||||
username = "FamilyTV",
|
||||
forYouMinutes = 30,
|
||||
hasOpenedForYou = false,
|
||||
)
|
||||
|
||||
assertEquals(60, matt.forYouMinutes)
|
||||
assertEquals(true, matt.hasOpenedForYou)
|
||||
assertEquals(30, family.forYouMinutes)
|
||||
assertEquals(false, family.hasOpenedForYou)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import org.junit.Test
|
||||
*/
|
||||
class ServerHomeRowsTest {
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
private fun row(id: String, kind: String, vararg itemIds: String) = HomeRow(
|
||||
id = id,
|
||||
title = id.replaceFirstChar(Char::uppercase),
|
||||
@@ -69,6 +71,14 @@ class ServerHomeRowsTest {
|
||||
assertEquals("Favorites", personalizedFavoritesTitle(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successful login welcome uses authenticated username`() {
|
||||
assertEquals(
|
||||
"You're now logged in as Matt. Welcome to Memby!",
|
||||
loginWelcomeMessage(" Matt "),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disabling a section hides its rows but never the recommendations`() {
|
||||
val settings = Settings(homeSections = "continue")
|
||||
@@ -93,6 +103,91 @@ class ServerHomeRowsTest {
|
||||
assertEquals(MediaRowKind.MOVIES, rows.getValue("similar:sev").kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `For You destination keeps reasons visible and exposes loading placeholder`() {
|
||||
val loading = forYouBrowseRows(ForYouUiState(loading = true))
|
||||
assertEquals(1, loading.size)
|
||||
assertTrue(loading.single().loading)
|
||||
|
||||
val rows = forYouBrowseRows(
|
||||
ForYouUiState(
|
||||
rows = listOf(
|
||||
row("for-you:picks", "for-you", "pick"),
|
||||
row("for-you:because:six-feet-under", "for-you", "because"),
|
||||
row("for-you:genre:drama", "for-you", "genre"),
|
||||
),
|
||||
availableMinutes = 60,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
listOf(
|
||||
"for-you:picks",
|
||||
"for-you:because:six-feet-under",
|
||||
"for-you:genre:drama",
|
||||
),
|
||||
rows.map { it.id },
|
||||
)
|
||||
assertTrue(rows.all { it.kind == MediaRowKind.MOVIES })
|
||||
assertTrue(rows.all { it.showSecondaryMetadata })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shows destination includes empty favorites and ranked server shelves`() {
|
||||
val rankedRows = listOf(
|
||||
row("curated:comedy-shows", "shows", "c1"),
|
||||
row("curated:horror-shows", "shows", "h1"),
|
||||
row("curated:drama-shows", "shows", "d1"),
|
||||
)
|
||||
val state = HomeUiState(
|
||||
rows = serverRows + rankedRows,
|
||||
favorites = emptyList(),
|
||||
loading = emptySet(),
|
||||
)
|
||||
|
||||
val rows = homeRowsFor(BrowseDestination.SHOWS, state, Settings())
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
"next-up",
|
||||
"continue-shows",
|
||||
"favourite-shows",
|
||||
"curated:comedy-shows",
|
||||
"curated:horror-shows",
|
||||
"curated:drama-shows",
|
||||
),
|
||||
rows.map { it.id },
|
||||
)
|
||||
assertTrue(rows.first { it.id == "favourite-shows" }.items.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `movies destination uses personalised genre and studio shelves`() {
|
||||
val rankedRows = listOf(
|
||||
row("curated:movies:studio:pixar", "movies", "p1"),
|
||||
row("curated:movies:genre:animation", "movies", "a1"),
|
||||
row("curated:drama-shows", "shows", "s1"),
|
||||
row("recommended", "recommended", "mixed"),
|
||||
)
|
||||
val state = HomeUiState(
|
||||
rows = serverRows + rankedRows,
|
||||
favorites = listOf(BaseItem(id = "f1", type = "Movie")),
|
||||
latestMovies = listOf(BaseItem(id = "latest", type = "Movie")),
|
||||
loading = emptySet(),
|
||||
)
|
||||
|
||||
val rows = homeRowsFor(BrowseDestination.MOVIES, state, Settings())
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
"curated:movies:studio:pixar",
|
||||
"curated:movies:genre:animation",
|
||||
"favourite-movies",
|
||||
),
|
||||
rows.map { it.id },
|
||||
)
|
||||
assertTrue(rows.none { it.id == "latest-movies" || it.id == "recommended" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Sonarr schedule rows use show cards and cannot be hidden by old preferences`() {
|
||||
val schedule = row("sonarr-airing-today", "schedule", "sonarr:7:42")
|
||||
@@ -160,7 +255,7 @@ class ServerHomeRowsTest {
|
||||
fun `a cache written before rows existed still decodes`() {
|
||||
val legacy = """{"continueWatching":[{"Id":"a"}],"favorites":[],"nextUp":[],"latestMovies":[]}"""
|
||||
|
||||
val restored = HomeUiState.from(Json { ignoreUnknownKeys = true }.decodeFromString<HomeCache>(legacy))
|
||||
val restored = HomeUiState.from(json.decodeFromString<HomeCache>(legacy))
|
||||
|
||||
assertTrue(restored.rows.isEmpty())
|
||||
assertEquals("a", restored.continueWatching.single().id)
|
||||
|
||||
Reference in New Issue
Block a user