Keep Home responsive and resolve authorized request recipients
Some checks failed
review/code-claude Block: token sent to image hosts via Coil auth client; failed audiences read hides Jellyfin libraries
Some checks failed
review/code-claude Block: token sent to image hosts via Coil auth client; failed audiences read hides Jellyfin libraries
This commit is contained in:
parent
78322f588a
commit
09f1d72f30
13 changed files with 120 additions and 29 deletions
|
|
@ -9,6 +9,9 @@ import androidx.compose.ui.test.assertIsDisplayed
|
|||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.test.performKeyInput
|
||||
import androidx.compose.ui.test.pressKey
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
|
|
@ -21,7 +24,6 @@ import java.io.File
|
|||
import java.util.UUID
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
|
|
@ -50,7 +52,8 @@ class ConductarrHomeSurfaceTest {
|
|||
session.select("${server.id}:${user.id}", SHARED_TOKEN, fake.url)
|
||||
runBlocking { session.authenticate() }
|
||||
val home = mockk<HomeReadFacade>()
|
||||
coEvery { home.read(user.id) } returns HomeLibrary()
|
||||
coEvery { home.read(user.id) } returns HomeLibrary(resume = listOf(
|
||||
HomeTitle("00000000-0000-0000-0000-000000000010", "Jellyfin held item", "movie", held = true)))
|
||||
val cache = mockk<HomeLibraryCache>(relaxed = true)
|
||||
every { cache.read(any()) } returns null
|
||||
val model = WolfskinHomeViewModel(rule.activity, home, cache, NavigationManager(), WolfskinSeeAllLists(), session)
|
||||
|
|
@ -59,15 +62,18 @@ class ConductarrHomeSurfaceTest {
|
|||
rule.setContent { WolfskinTheme {
|
||||
LaunchedEffect(model) { model.loadLibrary(server, user, "shared") }
|
||||
val snapshot = model.snapshot
|
||||
LaunchedEffect(snapshot.rows) {
|
||||
val index = snapshot.rows.indexOfFirst { it.catalogue && it.cards.any { card -> card.title == "Moon" } }
|
||||
if (index >= 0) { shell.view.row = index; shell.view.home.focusedKey = snapshot.rows[index].id; shell.navFocused = false }
|
||||
}
|
||||
CompositionLocalProvider(LocalScreenRatings provides ConductarrRatings(session.identity.value!!.client)) {
|
||||
CompositionLocalProvider(LocalScreenRatings provides androidx.compose.runtime.remember(session.identity.value?.client) { ConductarrRatings(session.identity.value!!.client) }) {
|
||||
ShellSurface(shell) { HomeScreen(shell, snapshot, it) { action -> model.act(action, shell, server, user) } }
|
||||
}
|
||||
} }
|
||||
rule.waitUntil(10_000) { model.snapshot.rows.any { row -> row.cards.any { it.title == "Moon" } } }
|
||||
rule.waitUntil(timeoutMillis = 30_000) {
|
||||
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
model.snapshot.rows.any { row -> row.cards.any { it.title == "Moon" } }
|
||||
}
|
||||
rule.runOnIdle { shell.navFocused = false; shell.view.row = 0; shell.view.home.focusedKey = "hero" }
|
||||
rule.onRoot().performKeyInput { pressKey(Key.DirectionDown) }
|
||||
rule.mainClock.advanceTimeBy(500)
|
||||
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
rule.onAllNodesWithText("Moon")[0].assertIsDisplayed()
|
||||
assertTrue(model.snapshot.rows.filter { it.catalogue }.flatMap { it.cards }.none { it.held })
|
||||
val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory"))).apply { mkdirs() }
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class ConductarrSessionTest {
|
|||
fake.status = 200
|
||||
fake.ratingsStatus = 401
|
||||
val ratings = ConductarrRatings(reopened.identity.value!!.client).read(42, "movie", null, null)
|
||||
assertNull(ratings)
|
||||
assertTrue(ratings!!.isEmpty())
|
||||
assertFalse(reopened.identity.value!!.signedIn)
|
||||
assertNull(reopened.identity.value!!.operator)
|
||||
assertEquals(ClientError.NeedsSignIn, reopened.identity.value!!.error)
|
||||
|
|
|
|||
|
|
@ -45,11 +45,14 @@ class ConductarrSurfaceTest {
|
|||
val identity by session.identity.collectAsState()
|
||||
val state = rememberDiscoverState(shell, shell::enterDestination, { shell.enterDestination("discover") }, identity = identity, fixtures = false)
|
||||
current = state
|
||||
CompositionLocalProvider(LocalDiscoverArt provides { art }, LocalScreenRatings provides identity?.takeIf { it.signedIn }?.let { ConductarrRatings(it.client) }) {
|
||||
CompositionLocalProvider(LocalDiscoverArt provides { art }, LocalScreenRatings provides identity?.takeIf { it.signedIn }?.let { selected -> androidx.compose.runtime.remember(selected.client) { ConductarrRatings(selected.client) } }) {
|
||||
WolfskinTheme { ShellSurface(shell) { DiscoverScreen(state, it) } }
|
||||
}
|
||||
}
|
||||
rule.waitUntil(10_000) { current.snapshot.status == DiscoverStatus.Ready }
|
||||
rule.waitUntil(30_000) {
|
||||
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
current.snapshot.status == DiscoverStatus.Ready
|
||||
}
|
||||
rule.onNodeWithText("Moon").assertIsDisplayed()
|
||||
rule.mainClock.autoAdvance = false
|
||||
rule.runOnIdle {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class ConductarrTitleSurfaceTest {
|
|||
val viewModel = WolfskinTitleViewModel(rule.activity, facade, mockk(), mockk<NavigationManager>(relaxed = true), session, mockk<ServerRepository>(relaxed = true))
|
||||
try {
|
||||
val route = titleRoute("movie:42")
|
||||
val shell = ShellState(listOf(ShellPerson("shared", "Shared", "S", true), ShellPerson("mindel", "Mindel", "M", true)), "shared").apply {
|
||||
val shell = ShellState(listOf(ShellPerson("shared", "Shared", "S", true)), "shared").apply {
|
||||
pickerVisible = false; pickerLaunch = false; enterDestination(route)
|
||||
}
|
||||
val art = mutableMapOf<String, ImageBitmap>()
|
||||
|
|
@ -67,7 +67,10 @@ class ConductarrTitleSurfaceTest {
|
|||
rule.setContent { WolfskinTheme { CompositionLocalProvider(LocalTitleBitmaps provides art) {
|
||||
TitleDestination(route, shell, UserPreferences(AppPreferences.getDefaultInstance(), null), viewModel = viewModel, fixtures = false)
|
||||
} } }
|
||||
rule.waitUntil(10_000) { viewModel.title?.data?.connection == TitleConnection.SignedIn }
|
||||
rule.waitUntil(30_000) {
|
||||
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
viewModel.title?.data?.connection == TitleConnection.SignedIn && session.identity.value!!.provider.requesterProfiles.value.any { it.key == "mindel" }
|
||||
}
|
||||
rule.onNodeWithText("Moon").assertIsDisplayed()
|
||||
assertFalse(viewModel.title!!.options.any { it.id == "play" })
|
||||
val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory"))).apply { mkdirs() }
|
||||
|
|
@ -75,7 +78,10 @@ class ConductarrTitleSurfaceTest {
|
|||
rule.onRoot().performKeyInput { pressKey(Key.DirectionRight); pressKey(Key.DirectionCenter) }
|
||||
rule.onNodeWithText("Request for Mindel").assertIsDisplayed()
|
||||
rule.onRoot().performKeyInput { pressKey(Key.DirectionDown); pressKey(Key.DirectionCenter) }
|
||||
rule.waitUntil(10_000) { viewModel.title?.data?.requestActor == "Shared" }
|
||||
rule.waitUntil(30_000) {
|
||||
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
viewModel.title?.data?.requestActor == "Shared"
|
||||
}
|
||||
assertEquals("Mindel", viewModel.title!!.data.requestFor)
|
||||
rule.onAllNodesWithText("for Mindel · by Shared")[0].assertIsDisplayed()
|
||||
val post = fake.calls.single { it.method == "POST" && it.url.encodedPath == "/api/v1/requests" }
|
||||
|
|
@ -86,7 +92,10 @@ class ConductarrTitleSurfaceTest {
|
|||
rule.onRoot().performKeyInput { pressKey(Key.DirectionCenter) }
|
||||
fake.ratingsStatus = 401
|
||||
runBlocking { ConductarrRatings(session.identity.value!!.client).read(45, "movie", null, null) }
|
||||
rule.waitUntil(10_000) { viewModel.title?.data?.connection == TitleConnection.SignedOut && viewModel.title?.data?.status == TitleReadStatus.Unavailable }
|
||||
rule.waitUntil(30_000) {
|
||||
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
viewModel.title?.data?.connection == TitleConnection.SignedOut && viewModel.title?.data?.status == TitleReadStatus.Unavailable
|
||||
}
|
||||
assertTrue(viewModel.title!!.data.item.ratings.isEmpty())
|
||||
assertFalse(viewModel.title!!.options.any { it.id in listOf("request", "other", "add") })
|
||||
} finally { viewModel.viewModelScope.cancel() }
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ internal class FakeConductarr : Closeable {
|
|||
if (key == "season" && path.endsWith("/2")) body = JsonObject(body + ("episodes" to JsonArray(body.getValue("episodes").jsonArray.map {
|
||||
JsonObject(it.jsonObject + ("vote_average" to JsonPrimitive(seasonTwoScore)))
|
||||
})))
|
||||
return response(if (localArt) localizeImages(body).toString() else body.toString())
|
||||
val responseBody = if (localArt) localizeImages(body).toString() else body.toString()
|
||||
return response(responseBody)
|
||||
}
|
||||
}
|
||||
server.start()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue