mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
test(mutual): Robolectric Compose UI tests for prefill scroll; bump 4.4.7-ba7opf.12.2
MutualMatchPrefillScrollTest verifies the map grid-QSO Match prefill: time-range card lands at the top after scrollToItem, default open stays at station inputs, and the query auto-starts with grid+24h pre-filled. Adds Robolectric + compose ui-test-junit4 to the mutual test classpath.
This commit is contained in:
1 parent
466f2f6088
commit
3d5b55faa3
3 files changed
+152
-2
No files matched your search
@@ -4,4 +4,19 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.rtbishop.look4sat.feature.mutual"
|
namespace = "com.rtbishop.look4sat.feature.mutual"
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
// Robolectric needs real Android resources for Compose UI tests.
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(libs.test.junit4)
|
||||||
|
testImplementation(libs.test.coroutines)
|
||||||
|
testImplementation(libs.robolectric)
|
||||||
|
testImplementation(libs.androidx.test.core)
|
||||||
|
testImplementation(libs.compose.ui.test.junit4)
|
||||||
|
debugImplementation(libs.compose.debug.manifest)
|
||||||
}
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
package com.rtbishop.look4sat.feature.mutual
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the map grid-QSO dialog "Match" prefill: after
|
||||||
|
* [MutualViewModel.prefillMatchFromGrid] the page opens with the time-range
|
||||||
|
* card at the top and the 24h query auto-started.
|
||||||
|
*
|
||||||
|
* Note: `LazyListState(firstVisibleItemIndex = 1)` is deliberately NOT used to
|
||||||
|
* position the page. In this Compose version (BOM 2026.06.01 / ui 1.11.x) the
|
||||||
|
* constructor parameter is ignored — the list still starts at item 0 — while
|
||||||
|
* `listState.scrollToItem(1)` from a LaunchedEffect lands correctly. See
|
||||||
|
* [scrollToItemLandsAtTop] below, which guards the scrollToItem mechanism.
|
||||||
|
*
|
||||||
|
* Robolectric quirk: `assertIsNotDisplayed()` is unreliable here (a node
|
||||||
|
* scrolled fully out of the viewport can still report "displayed"), so the
|
||||||
|
* prefill test asserts the time-range card IS displayed (positive assertion)
|
||||||
|
* rather than that the station card is NOT.
|
||||||
|
*/
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||||
|
@Config(sdk = [34])
|
||||||
|
class MutualMatchPrefillScrollTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeRule = createComposeRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun sanity_textRenders() {
|
||||||
|
composeRule.setContent {
|
||||||
|
MaterialTheme {
|
||||||
|
Text("sanity-check-text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composeRule.onNodeWithText("sanity-check-text").assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scrollToItemLandsAtTop() {
|
||||||
|
// Guards the mechanism the prefill relies on: scrollToItem(1) from a
|
||||||
|
// LaunchedEffect moves item 1 to the top of the viewport.
|
||||||
|
composeRule.setContent {
|
||||||
|
MaterialTheme {
|
||||||
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
|
val state = remember { LazyListState(firstVisibleItemIndex = 0) }
|
||||||
|
LaunchedEffect(Unit) { state.scrollToItem(1) }
|
||||||
|
LazyColumn(state = state) {
|
||||||
|
item { Text("min-item-0") }
|
||||||
|
item { Text("min-item-1") }
|
||||||
|
item { Text("min-item-2") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composeRule.waitForIdle()
|
||||||
|
composeRule.onNodeWithText("min-item-1").assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun defaultOpen_showsStationInputsAtTop() {
|
||||||
|
val vm = MutualViewModel(FakeSatelliteRepo(), FakeSettingsRepo())
|
||||||
|
composeRule.setContent {
|
||||||
|
MaterialTheme {
|
||||||
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
|
MutualScreen(viewModel = vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Without a prefill the list starts at item 0 (station inputs).
|
||||||
|
composeRule.onNodeWithText("Your Station").assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun prefillFromMap_opensAtTimeRangeCard() {
|
||||||
|
val vm = MutualViewModel(FakeSatelliteRepo(), FakeSettingsRepo())
|
||||||
|
// Same call the map grid-QSO "Match" button makes.
|
||||||
|
vm.prefillMatchFromGrid("OL62")
|
||||||
|
|
||||||
|
composeRule.setContent {
|
||||||
|
MaterialTheme {
|
||||||
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
|
MutualScreen(viewModel = vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
composeRule.waitForIdle()
|
||||||
|
|
||||||
|
// The time-range card must be visible at the top of the page after the
|
||||||
|
// prefill scroll.
|
||||||
|
composeRule.onNodeWithText("Time range").assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun prefillAutoStartsQueryAndFillsState() {
|
||||||
|
val vm = MutualViewModel(FakeSatelliteRepo(), FakeSettingsRepo())
|
||||||
|
vm.prefillMatchFromGrid("OL62")
|
||||||
|
val s = vm.uiState.value
|
||||||
|
// Query auto-started: with the empty-satellite fake the query reaches
|
||||||
|
// the "no satellite data" guard (rather than never being triggered),
|
||||||
|
// proving prefillMatchFromGrid kicks off queryMutualPasses.
|
||||||
|
assertTrue(
|
||||||
|
"auto-query should reach the satellite guard: gridA=${s.stationAGrid} " +
|
||||||
|
"gridB=${s.stationBGrid} err=${s.errorMessage}",
|
||||||
|
s.errorMessage?.contains("satellite", ignoreCase = true) == true
|
||||||
|
)
|
||||||
|
// ...and the target grid + 24h range pre-filled.
|
||||||
|
org.junit.Assert.assertEquals("OL62", s.stationBGrid)
|
||||||
|
org.junit.Assert.assertEquals(24, s.hoursAhead)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionCode = "487"
|
appVersionCode = "488"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionName = "4.4.7-ba7opf.12.1"
|
appVersionName = "4.4.7-ba7opf.12.2"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
compileSdk = "37"
|
compileSdk = "37"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
@@ -33,6 +33,8 @@ other-okhttp = "5.4.0"
|
|||||||
other-osmdroid = "6.1.20"
|
other-osmdroid = "6.1.20"
|
||||||
|
|
||||||
test-junit4 = "4.13.2"
|
test-junit4 = "4.13.2"
|
||||||
|
robolectric = "4.16"
|
||||||
|
androidx-test-core = "1.7.0"
|
||||||
|
|
||||||
androidTest-junit = "1.3.0"
|
androidTest-junit = "1.3.0"
|
||||||
androidTest-espresso = "3.7.0"
|
androidTest-espresso = "3.7.0"
|
||||||
@@ -79,6 +81,9 @@ other-osmdroid = { module = "org.osmdroid:osmdroid-android", version.ref = "othe
|
|||||||
|
|
||||||
test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
|
test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
|
||||||
test-junit4 = { module = "junit:junit", version.ref = "test-junit4" }
|
test-junit4 = { module = "junit:junit", version.ref = "test-junit4" }
|
||||||
|
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
||||||
|
androidx-test-core = { module = "androidx.test:core-ktx", version.ref = "androidx-test-core" }
|
||||||
|
compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
# Real org.json for unit tests (android.jar stub throws "not mocked")
|
# Real org.json for unit tests (android.jar stub throws "not mocked")
|
||||||
test-json = { module = "org.json:json", version = "20240303" }
|
test-json = { module = "org.json:json", version = "20240303" }
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user