-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(android): leave / rejoin predictions & alerts after backgrounding
- Loading branch information
1 parent
a264d30
commit b5740cd
Showing
5 changed files
with
227 additions
and
225 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 76 additions & 101 deletions
177
...pp/src/androidTest/java/com/mbta/tid/mbta_app/android/state/SubscribeToPredictionsTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,133 +1,108 @@ | ||
package com.mbta.tid.mbta_app.android.state | ||
|
||
import androidx.activity.ComponentActivity | ||
import androidx.compose.runtime.CompositionLocalProvider | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.test.junit4.createAndroidComposeRule | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.ViewModelProvider | ||
import androidx.lifecycle.ViewModelStore | ||
import com.mbta.tid.mbta_app.android.util.TimerViewModel | ||
import androidx.lifecycle.Lifecycle | ||
import androidx.lifecycle.compose.LocalLifecycleOwner | ||
import androidx.lifecycle.testing.TestLifecycleOwner | ||
import com.mbta.tid.mbta_app.model.ObjectCollectionBuilder | ||
import com.mbta.tid.mbta_app.model.response.ApiResult | ||
import com.mbta.tid.mbta_app.model.response.PredictionsByStopJoinResponse | ||
import com.mbta.tid.mbta_app.model.response.PredictionsByStopMessageResponse | ||
import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse | ||
import com.mbta.tid.mbta_app.repositories.IPredictionsRepository | ||
import kotlin.time.Duration.Companion.seconds | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.channels.Channel | ||
import kotlinx.coroutines.launch | ||
import com.mbta.tid.mbta_app.repositories.MockPredictionsRepository | ||
import kotlinx.coroutines.test.runTest | ||
import kotlinx.datetime.Instant | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertNull | ||
import org.junit.Rule | ||
import org.junit.Test | ||
|
||
class MockPredictionsRepository(private val scope: CoroutineScope) : IPredictionsRepository { | ||
val stopIdsChannel = Channel<List<String>>() | ||
lateinit var onJoin: (ApiResult<PredictionsByStopJoinResponse>) -> Unit | ||
lateinit var onMessage: (ApiResult<PredictionsByStopMessageResponse>) -> Unit | ||
var disconnectHook: () -> Unit = { println("original disconnect hook called") } | ||
|
||
override fun connect( | ||
stopIds: List<String>, | ||
onReceive: (ApiResult<PredictionsStreamDataResponse>) -> Unit | ||
) { | ||
/* null-op */ | ||
} | ||
|
||
override fun connectV2( | ||
stopIds: List<String>, | ||
onJoin: (ApiResult<PredictionsByStopJoinResponse>) -> Unit, | ||
onMessage: (ApiResult<PredictionsByStopMessageResponse>) -> Unit | ||
) { | ||
|
||
this.onJoin = onJoin | ||
scope.launch { stopIdsChannel.send(stopIds) } | ||
} | ||
|
||
override var lastUpdated: Instant? = null | ||
|
||
override fun shouldForgetPredictions(predictionCount: Int) = false | ||
|
||
override fun disconnect() { | ||
disconnectHook() | ||
} | ||
} | ||
|
||
class SubscribeToPredictionsTest { | ||
@get:Rule val composeTestRule = createAndroidComposeRule<ComponentActivity>() | ||
|
||
@Test | ||
fun testPredictions() = runTest { | ||
fun buildSomePredictions(): PredictionsByStopJoinResponse { | ||
val objects = ObjectCollectionBuilder() | ||
objects.prediction() | ||
objects.prediction() | ||
return PredictionsByStopJoinResponse(objects) | ||
} | ||
val predictionsRepo = MockPredictionsRepository(this) | ||
val objects = ObjectCollectionBuilder() | ||
objects.prediction() | ||
objects.prediction() | ||
val predictionsOnJoin = PredictionsByStopJoinResponse(objects) | ||
|
||
var connectProps: List<String>? = null | ||
var disconnectCount = 0 | ||
|
||
val predictionsRepo = | ||
MockPredictionsRepository( | ||
{}, | ||
{ stops -> connectProps = stops }, | ||
{ disconnectCount += 1 }, | ||
null, | ||
predictionsOnJoin | ||
) | ||
|
||
var stopIds by mutableStateOf(listOf("place-a")) | ||
var unmounted by mutableStateOf(false) | ||
var stopIds = mutableStateOf(listOf("place-a")) | ||
var predictions: PredictionsStreamDataResponse? = | ||
PredictionsStreamDataResponse(ObjectCollectionBuilder()) | ||
|
||
composeTestRule.setContent { | ||
if (!unmounted) predictions = subscribeToPredictions(stopIds, predictionsRepo) | ||
var stopIds by remember { stopIds } | ||
predictions = subscribeToPredictions(stopIds, predictionsRepo) | ||
} | ||
|
||
composeTestRule.awaitIdle() | ||
assertEquals(listOf("place-a"), predictionsRepo.stopIdsChannel.receive()) | ||
assertNull(predictions) | ||
|
||
val expectedPredictions1 = buildSomePredictions() | ||
predictionsRepo.onJoin(ApiResult.Ok(expectedPredictions1)) | ||
composeTestRule.awaitIdle() | ||
assertEquals(expectedPredictions1.toPredictionsStreamDataResponse(), predictions) | ||
|
||
stopIds = listOf("place-b") | ||
composeTestRule.awaitIdle() | ||
assertEquals(listOf("place-b"), predictionsRepo.stopIdsChannel.receive()) | ||
predictionsRepo.onJoin(ApiResult.Ok(expectedPredictions1)) | ||
composeTestRule.awaitIdle() | ||
assertEquals(expectedPredictions1.toPredictionsStreamDataResponse(), predictions) | ||
|
||
val expectedPredictions2 = buildSomePredictions() | ||
predictionsRepo.onJoin(ApiResult.Ok(expectedPredictions2)) | ||
composeTestRule.awaitIdle() | ||
assertEquals(expectedPredictions2.toPredictionsStreamDataResponse(), predictions) | ||
|
||
unmounted = true | ||
composeTestRule.awaitIdle() | ||
composeTestRule.waitUntil { connectProps == listOf("place-a") } | ||
|
||
composeTestRule.waitUntil { | ||
predictions != null && | ||
predictions == predictionsOnJoin?.toPredictionsStreamDataResponse() | ||
} | ||
|
||
assertEquals(0, disconnectCount) | ||
|
||
stopIds.value = listOf("place-b") | ||
composeTestRule.waitUntil { disconnectCount == 1 } | ||
|
||
composeTestRule.waitUntil { connectProps == listOf("place-b") } | ||
} | ||
|
||
@Test | ||
fun testPredictionsOnClear() = runTest { | ||
var disconnectCalled = false | ||
val stopIds by mutableStateOf(listOf("place-a")) | ||
val mockPredictionsRepository = MockPredictionsRepository(this.backgroundScope) | ||
mockPredictionsRepository.disconnectHook = { disconnectCalled = true } | ||
|
||
val viewModelStore = ViewModelStore() | ||
val viewModelProvider = | ||
ViewModelProvider( | ||
viewModelStore, | ||
object : ViewModelProvider.Factory { | ||
override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
return PredictionsViewModel( | ||
stopIds, | ||
mockPredictionsRepository, | ||
TimerViewModel(1.seconds) | ||
) | ||
as T | ||
} | ||
} | ||
fun testDisconnectsOnPause() = runTest { | ||
val lifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED) | ||
|
||
var connectCount = 0 | ||
var disconnectCount = 0 | ||
|
||
val predictionsRepo = | ||
MockPredictionsRepository( | ||
{}, | ||
{ stopIds -> connectCount += 1 }, | ||
{ disconnectCount += 1 }, | ||
null, | ||
null | ||
) | ||
viewModelProvider.get(PredictionsViewModel::class) | ||
viewModelStore.clear() | ||
assertEquals(true, disconnectCalled) | ||
|
||
var stopIds = mutableStateOf(listOf("place-a")) | ||
var predictions: PredictionsStreamDataResponse? = | ||
PredictionsStreamDataResponse(ObjectCollectionBuilder()) | ||
|
||
composeTestRule.setContent { | ||
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { | ||
var stopIds by remember { stopIds } | ||
predictions = subscribeToPredictions(stopIds, predictionsRepo) | ||
} | ||
} | ||
|
||
composeTestRule.waitUntil { connectCount == 1 } | ||
assertEquals(0, disconnectCount) | ||
|
||
composeTestRule.runOnIdle { lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) } | ||
|
||
composeTestRule.waitUntil { disconnectCount == 1 } | ||
assertEquals(1, connectCount) | ||
|
||
composeTestRule.runOnIdle { lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } | ||
|
||
composeTestRule.waitUntil { connectCount == 2 } | ||
assertEquals(1, disconnectCount) | ||
} | ||
} |
Oops, something went wrong.