Integrate reviewed Requests and Discover surfaces
Some checks failed
review/design-claude Changes requested: 13 blocking design/behaviour findings (see PR comment)
Some checks failed
review/design-claude Changes requested: 13 blocking design/behaviour findings (see PR comment)
This commit is contained in:
commit
e243894a20
730 changed files with 4496 additions and 92 deletions
|
|
@ -0,0 +1,101 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.tv
|
||||
|
||||
import android.app.Application
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performKeyInput
|
||||
import androidx.compose.ui.test.pressKey
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.justinvisser.wolfskin.design.DiscoverFixture
|
||||
import org.justinvisser.wolfskin.design.DiscoverScreen
|
||||
import org.justinvisser.wolfskin.design.DiscoverState
|
||||
import org.justinvisser.wolfskin.design.DiscoverStatus
|
||||
import org.justinvisser.wolfskin.design.ShellPerson
|
||||
import org.justinvisser.wolfskin.design.ShellState
|
||||
import org.justinvisser.wolfskin.design.ShellSurface
|
||||
import org.justinvisser.wolfskin.design.WolfskinTheme
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
/** The real asynchronous host must return focus from its loading nav to the first loaded action. */
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi")
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
class DiscoverLoadingFocusTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test fun firstOkAfterLoadingOpensTheHeroInsteadOfSelectingTheNav() {
|
||||
val pending = CompletableDeferred<DiscoverFixture>()
|
||||
val shell = ShellState(listOf(ShellPerson("justin", "Justin", "J", true)), "justin").apply {
|
||||
pickerVisible = false
|
||||
enterDestination("discover")
|
||||
}
|
||||
lateinit var current: DiscoverState
|
||||
rule.setContent {
|
||||
val state = rememberDiscoverState(shell, shell::enterDestination, { shell.enterDestination("discover") }, loadFixture = { pending.await() })
|
||||
current = state
|
||||
WolfskinTheme { ShellSurface(shell) { DiscoverScreen(state, it) } }
|
||||
}
|
||||
rule.runOnIdle {
|
||||
assertEquals(DiscoverStatus.Loading, current.snapshot.status)
|
||||
assertTrue(shell.navFocused)
|
||||
val json = rule.activity.assets.open("wolfskin-discover/fixture.json").bufferedReader().use { it.readText() }
|
||||
pending.complete(DiscoverFixture(json) { "file:///android_asset/wolfskin-discover/$it" })
|
||||
}
|
||||
rule.waitForIdle()
|
||||
rule.runOnIdle {
|
||||
assertEquals(DiscoverStatus.Ready, current.snapshot.status)
|
||||
assertFalse(shell.navFocused)
|
||||
assertEquals(0, current.focusedRow)
|
||||
}
|
||||
rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.DirectionCenter) }
|
||||
rule.runOnIdle {
|
||||
assertNotNull(current.title)
|
||||
assertEquals("The Amazing Spider-Man", current.title!!.title)
|
||||
assertEquals("discover-title/${current.title!!.key}", shell.route)
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun finishingLoadPreservesANavTargetChosenWhileLoading() {
|
||||
val pending = CompletableDeferred<DiscoverFixture>()
|
||||
val shell = ShellState(listOf(ShellPerson("justin", "Justin", "J", true)), "justin").apply {
|
||||
pickerVisible = false
|
||||
enterDestination("discover")
|
||||
}
|
||||
lateinit var current: DiscoverState
|
||||
rule.setContent {
|
||||
val state = rememberDiscoverState(shell, shell::enterDestination, { shell.enterDestination("discover") }, loadFixture = { pending.await() })
|
||||
current = state
|
||||
WolfskinTheme { ShellSurface(shell) { DiscoverScreen(state, it) } }
|
||||
}
|
||||
rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.DirectionRight) }
|
||||
rule.runOnIdle {
|
||||
assertEquals(DiscoverStatus.Loading, current.snapshot.status)
|
||||
assertEquals(2, shell.navIndex)
|
||||
val json = rule.activity.assets.open("wolfskin-discover/fixture.json").bufferedReader().use { it.readText() }
|
||||
pending.complete(DiscoverFixture(json) { "file:///android_asset/wolfskin-discover/$it" })
|
||||
}
|
||||
rule.waitForIdle()
|
||||
rule.runOnIdle {
|
||||
assertEquals(DiscoverStatus.Ready, current.snapshot.status)
|
||||
assertTrue(shell.navFocused)
|
||||
assertEquals(2, shell.navIndex)
|
||||
}
|
||||
rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.DirectionCenter) }
|
||||
rule.runOnIdle {
|
||||
assertEquals("movies", shell.route)
|
||||
assertEquals(null, current.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.tv
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.justinvisser.wolfskin.design.ComingInRequest
|
||||
import org.justinvisser.wolfskin.design.ComingInSnapshot
|
||||
import org.justinvisser.wolfskin.design.ComingInStage
|
||||
import org.justinvisser.wolfskin.design.ComingInStatus
|
||||
import org.justinvisser.wolfskin.design.ShellPerson
|
||||
import org.justinvisser.wolfskin.design.ShellState
|
||||
|
||||
/** The fake clock checks refresh boundaries and cancellation without a server or a real wait. */
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class RequestsRefreshTest {
|
||||
@Test fun foregroundOpenUnsettledSettledAndStoppedTransitions() = runTest {
|
||||
val state = ShellState(listOf(ShellPerson("justin", "Justin", "J", false)), "justin")
|
||||
val request = ComingInRequest("one", "movie", 1, "One", "justin", "Justin", "shared", "Shared", ComingInStage.Waiting)
|
||||
state.requests.update(ComingInSnapshot(status = ComingInStatus.Ready, signedIn = true))
|
||||
var reads = 0
|
||||
val active = launch { refreshRequestsWhileStarted(state) { reads++ } }
|
||||
runCurrent()
|
||||
assertEquals(1, reads)
|
||||
advanceTimeBy(30_000); runCurrent()
|
||||
assertEquals("Closed and settled does not poll", 1, reads)
|
||||
state.openRequests()
|
||||
advanceTimeBy(29_999); runCurrent()
|
||||
assertEquals(1, reads)
|
||||
advanceTimeBy(1); runCurrent()
|
||||
assertEquals("An open empty sheet still refreshes at 30 seconds", 2, reads)
|
||||
state.closeRequests()
|
||||
state.requests.update(ComingInSnapshot(listOf(request), ComingInStatus.Ready, signedIn = true))
|
||||
advanceTimeBy(30_000); runCurrent()
|
||||
assertEquals("An unsettled request refreshes after the sheet closes", 3, reads)
|
||||
state.requests.update(ComingInSnapshot(listOf(request.copy(stage = ComingInStage.Ready)), ComingInStatus.Ready, signedIn = true))
|
||||
advanceTimeBy(30_000); runCurrent()
|
||||
assertEquals("Ready requests stop polling", 3, reads)
|
||||
active.cancel(); runCurrent()
|
||||
state.openRequests()
|
||||
advanceTimeBy(60_000); runCurrent()
|
||||
assertEquals("A stopped lifecycle cannot poll", 3, reads)
|
||||
val resumed = launch { refreshRequestsWhileStarted(state) { reads++ } }
|
||||
runCurrent()
|
||||
assertEquals("Returning to the foreground refreshes immediately", 4, reads)
|
||||
resumed.cancel()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue