Skip to content

Commit add2cd4

Browse files
committed
Make test capture error
1 parent 2de2f2f commit add2cd4

File tree

6 files changed

+81
-122
lines changed

6 files changed

+81
-122
lines changed

libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/MapboxTripStarter.kt

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.mapbox.navigation.core.trip
22

33
import android.annotation.SuppressLint
4+
import androidx.annotation.VisibleForTesting
45
import com.mapbox.android.core.permissions.PermissionsManager
56
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
67
import com.mapbox.navigation.core.MapboxNavigation
@@ -35,6 +36,10 @@ class MapboxTripStarter internal constructor() : MapboxNavigationObserver {
3536
private var replayRouteTripSession: ReplayRouteSession? = null
3637
private var mapboxNavigation: MapboxNavigation? = null
3738

39+
@VisibleForTesting
40+
internal var coroutineScopeProvider: () -> CoroutineScope = {
41+
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
42+
}
3843
private lateinit var coroutineScope: CoroutineScope
3944

4045
/**
@@ -43,7 +48,7 @@ class MapboxTripStarter internal constructor() : MapboxNavigationObserver {
4348
*/
4449
override fun onAttached(mapboxNavigation: MapboxNavigation) {
4550
this.mapboxNavigation = mapboxNavigation
46-
coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
51+
coroutineScope = coroutineScopeProvider()
4752

4853
// Initialize the options to be aware of the location permissions
4954
val context = mapboxNavigation.navigationOptions.applicationContext
@@ -77,10 +82,25 @@ class MapboxTripStarter internal constructor() : MapboxNavigationObserver {
7782
* Set new options.
7883
*/
7984
fun setOptions(options: MapboxTripStarterOptions) = apply {
80-
checkOptions(options)
8185
this.optionsFlow.value = options
8286
}
8387

88+
private fun onStarterOptionsChanged(
89+
mapboxNavigation: MapboxNavigation,
90+
options: MapboxTripStarterOptions
91+
) {
92+
checkOptions(options)
93+
if (options.tripType == MAPBOX_TRIP_STARTER_REPLAY_ROUTE) {
94+
onReplayTripEnabled(mapboxNavigation)
95+
} else if (options.tripType == MAPBOX_TRIP_STARTER_FOLLOW_DEVICE &&
96+
options.isLocationPermissionGranted
97+
) {
98+
onTripSessionEnabled(mapboxNavigation)
99+
} else {
100+
onTripDisabled(mapboxNavigation)
101+
}
102+
}
103+
84104
/**
85105
* Throws an error if location permissions are set to true but the location permissions are
86106
* not actually granted.
@@ -100,23 +120,8 @@ class MapboxTripStarter internal constructor() : MapboxNavigationObserver {
100120
}
101121
}
102122

103-
private fun onStarterOptionsChanged(
104-
mapboxNavigation: MapboxNavigation,
105-
options: MapboxTripStarterOptions
106-
) {
107-
if (options.tripType == MAPBOX_TRIP_STARTER_REPLAY_ROUTE) {
108-
onReplayTripEnabled(mapboxNavigation)
109-
} else if (options.tripType == MAPBOX_TRIP_STARTER_FOLLOW_DEVICE &&
110-
options.isLocationPermissionGranted
111-
) {
112-
onTripSessionEnabled(mapboxNavigation)
113-
} else {
114-
onTripDisabled(mapboxNavigation)
115-
}
116-
}
117-
118123
private fun onReplayTripEnabled(mapboxNavigation: MapboxNavigation) {
119-
if (mapboxNavigation.getTripSessionState() != TripSessionState.STARTED &&
124+
if (mapboxNavigation.getTripSessionState() != TripSessionState.STARTED ||
120125
!mapboxNavigation.isReplayEnabled()
121126
) {
122127
replayRouteTripSession?.onDetached(mapboxNavigation)
@@ -130,9 +135,7 @@ class MapboxTripStarter internal constructor() : MapboxNavigationObserver {
130135
private fun onTripSessionEnabled(mapboxNavigation: MapboxNavigation) {
131136
replayRouteTripSession?.onDetached(mapboxNavigation)
132137
replayRouteTripSession = null
133-
if (mapboxNavigation.getTripSessionState() != TripSessionState.STARTED) {
134-
mapboxNavigation.startTripSession()
135-
}
138+
mapboxNavigation.startTripSession()
136139
}
137140

138141
private fun onTripDisabled(mapboxNavigation: MapboxNavigation) {

libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/MapboxTripStarterTest.kt

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@ import com.mapbox.android.core.permissions.PermissionsManager
44
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
55
import com.mapbox.navigation.core.MapboxNavigation
66
import com.mapbox.navigation.core.trip.session.TripSessionState
7+
import com.mapbox.navigation.testing.MainCoroutineRule
78
import io.mockk.every
89
import io.mockk.mockk
910
import io.mockk.mockkStatic
1011
import io.mockk.verify
12+
import kotlinx.coroutines.ExperimentalCoroutinesApi
13+
import kotlinx.coroutines.test.TestCoroutineScope
1114
import org.junit.Assert.assertFalse
15+
import org.junit.Assert.assertThrows
1216
import org.junit.Assert.assertTrue
1317
import org.junit.Before
18+
import org.junit.Rule
1419
import org.junit.Test
1520

16-
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
21+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class, ExperimentalCoroutinesApi::class)
1722
class MapboxTripStarterTest {
23+
@get:Rule
24+
var coroutineRule = MainCoroutineRule()
1825

19-
private val sut = MapboxTripStarter()
26+
private val testCoroutineScope: TestCoroutineScope = coroutineRule.coroutineScope
27+
private val sut = MapboxTripStarter().apply {
28+
// TODO this solution is temporary https://mapbox.atlassian.net/browse/NAVAND-1061
29+
coroutineScopeProvider = { testCoroutineScope }
30+
}
2031

2132
@Before
2233
fun setup() {
@@ -35,12 +46,6 @@ class MapboxTripStarterTest {
3546
assertTrue(sut.getOptions().isLocationPermissionGranted)
3647
}
3748

38-
@Test(expected = IllegalStateException::class)
39-
fun `setOptions will crash if used before onAttached`() {
40-
val options = MapboxTripStarterOptions.Builder().isLocationPermissionGranted(true).build()
41-
sut.setOptions(options)
42-
}
43-
4449
@Test
4550
fun `onAttached will startTripSession when location permissions are granted`() {
4651
every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true
@@ -73,24 +78,28 @@ class MapboxTripStarterTest {
7378
verify(exactly = 1) { mapboxNavigation.startTripSession() }
7479
}
7580

76-
@Test(expected = IllegalStateException::class)
81+
@Test
7782
fun `cannot accept location permissions when they are not granted`() {
7883
every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false
7984

8085
val mapboxNavigation = mockMapboxNavigation()
8186
sut.onAttached(mapboxNavigation)
82-
83-
// This throws an error because areLocationPermissionsGranted is false
8487
sut.update { it.isLocationPermissionGranted(true) }
88+
89+
// Clean up coroutines early so that the error is caught by the tests.
90+
assertThrows(Exception::class.java) {
91+
coroutineRule.createdScopes.forEach { it.cleanupTestCoroutines() }
92+
}
93+
coroutineRule.createdScopes.clear()
8594
}
8695

8796
@Test
8897
fun `update can be used to enable replay route without location permissions`() {
8998
every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false
9099

91100
val mapboxNavigation = mockMapboxNavigation()
92-
sut.onAttached(mapboxNavigation)
93101
sut.update { it.tripType(MapboxTripStarterExtra.MAPBOX_TRIP_STARTER_REPLAY_ROUTE) }
102+
sut.onAttached(mapboxNavigation)
94103

95104
verify(exactly = 1) { mapboxNavigation.startReplayTripSession() }
96105
}
@@ -108,6 +117,31 @@ class MapboxTripStarterTest {
108117
verify(exactly = 1) { mapboxNavigation.startReplayTripSession() }
109118
}
110119

120+
@Test
121+
fun `update will not stop a trip session that has been started`() {
122+
val mapboxNavigation = mockMapboxNavigation()
123+
every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STARTED
124+
every { mapboxNavigation.isReplayEnabled() } returns false
125+
126+
sut.onAttached(mapboxNavigation)
127+
sut.update { it.tripType(MapboxTripStarterExtra.MAPBOX_TRIP_STARTER_REPLAY_ROUTE) }
128+
129+
verify(exactly = 0) { mapboxNavigation.stopTripSession() }
130+
verify(exactly = 1) { mapboxNavigation.startReplayTripSession() }
131+
}
132+
133+
@Test
134+
fun `update before onAttached will not startTripSession`() {
135+
val mapboxNavigation = mockMapboxNavigation()
136+
137+
sut.update { it.tripType(MapboxTripStarterExtra.MAPBOX_TRIP_STARTER_REPLAY_ROUTE) }
138+
sut.onAttached(mapboxNavigation)
139+
140+
verify(exactly = 0) { mapboxNavigation.stopTripSession() }
141+
verify(exactly = 0) { mapboxNavigation.startTripSession() }
142+
verify(exactly = 1) { mapboxNavigation.startReplayTripSession() }
143+
}
144+
111145
private fun mockMapboxNavigation(): MapboxNavigation {
112146
val mapboxNavigation = mockk<MapboxNavigation>(relaxed = true)
113147
every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STOPPED

libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/SharedApp.kt

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ object SharedApp {
2525
val state get() = store.state.value
2626
val routeOptionsProvider: RouteOptionsProvider = RouteOptionsProvider()
2727

28-
private val ignoreTripSessionUpdates = AtomicBoolean(false)
29-
3028
private val navigationObservers: Array<MapboxNavigationObserver> = arrayOf(
3129
RouteStateController(store),
3230
CameraStateController(store),
@@ -35,7 +33,7 @@ object SharedApp {
3533
DestinationStateController(store),
3634
RoutePreviewStateController(store, routeOptionsProvider),
3735
AudioGuidanceStateController(store),
38-
TripSessionStarterStateController(),
36+
TripSessionStarterStateController(store),
3937
)
4038

4139
@JvmOverloads
@@ -45,21 +43,12 @@ object SharedApp {
4543
if (isSetup) return
4644
isSetup = true
4745

48-
MapboxNavigationApp.registerObserver(StateResetController(store, ignoreTripSessionUpdates))
46+
MapboxNavigationApp.registerObserver(StateResetController(store))
4947
MapboxNavigationApp.registerObserver(
5048
RouteAlternativeComponent {
5149
routeAlternativeContract ?: RouteAlternativeComponentImpl(store)
5250
}
5351
)
5452
MapboxNavigationApp.lifecycleOwner.attachCreated(*navigationObservers)
5553
}
56-
57-
fun tripSessionTransaction(updateSession: () -> Unit) {
58-
// Any changes to MapboxNavigation TripSession should be done within `tripSessionTransaction { }` block.
59-
// This ensures that non of the registered TripSessionStateObserver accidentally execute cleanup logic
60-
// when TripSession state changes.
61-
ignoreTripSessionUpdates.set(true)
62-
updateSession()
63-
ignoreTripSessionUpdates.set(false)
64-
}
6554
}

libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/StateResetController.kt

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,10 @@ import com.mapbox.navigation.ui.base.lifecycle.UIComponent
1010
import java.util.concurrent.atomic.AtomicBoolean
1111

1212
internal class StateResetController(
13-
private val store: Store,
14-
private val ignoreTripSessionUpdates: AtomicBoolean
13+
private val store: Store
1514
) : UIComponent() {
16-
17-
private var prevState: TripSessionState? = null
18-
19-
private val tripSessionStateObserver = TripSessionStateObserver { newState ->
20-
// we only reset Store state when TripSessionState switches from STARTED to STOPPED.
21-
if (!ignoreTripSessionUpdates.get() &&
22-
prevState == TripSessionState.STARTED &&
23-
newState == TripSessionState.STOPPED
24-
) {
25-
store.dispatch(endNavigation())
26-
}
27-
prevState = newState
28-
}
29-
30-
override fun onAttached(mapboxNavigation: MapboxNavigation) {
31-
super.onAttached(mapboxNavigation)
32-
prevState = mapboxNavigation.getTripSessionState()
33-
mapboxNavigation.registerTripSessionStateObserver(tripSessionStateObserver)
34-
}
35-
3615
override fun onDetached(mapboxNavigation: MapboxNavigation) {
3716
super.onDetached(mapboxNavigation)
38-
mapboxNavigation.unregisterTripSessionStateObserver(tripSessionStateObserver)
3917

4018
// we reset Store state every time MapboxNavigation gets destroyed
4119
store.reset()

libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateController.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@ import com.mapbox.navigation.core.trip.MapboxTripStarterExtra
77
import com.mapbox.navigation.core.trip.update
88
import com.mapbox.navigation.ui.app.internal.Action
99
import com.mapbox.navigation.ui.app.internal.State
10+
import com.mapbox.navigation.ui.app.internal.Store
1011
import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterAction
1112

1213
/**
1314
* The class is responsible to start and stop the `TripSession` for NavigationView.
1415
*/
1516
@SuppressLint("MissingPermission")
1617
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
17-
class TripSessionStarterStateController : StateController() {
18+
class TripSessionStarterStateController(store: Store) : StateController() {
19+
20+
init {
21+
store.register(this)
22+
}
1823

1924
private val tripStarter = MapboxTripStarter.getRegisteredInstance()
2025

libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/StateResetControllerTest.kt

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -27,68 +27,18 @@ internal class StateResetControllerTest {
2727
private lateinit var mapboxNavigation: MapboxNavigation
2828
private lateinit var store: TestStore
2929
private lateinit var sut: StateResetController
30-
private lateinit var ignoreTripSessionUpdates: AtomicBoolean
3130

3231
@Before
3332
fun setUp() {
34-
mapboxNavigation = mockk(relaxed = true) {
35-
every { getTripSessionState() } returns TripSessionState.STOPPED
36-
}
33+
mapboxNavigation = mockk(relaxed = true)
3734
store = spyk(TestStore())
3835
store.setState(
3936
State(
4037
destination = Destination(Point.fromLngLat(1.0, 2.0)),
4138
navigation = NavigationState.ActiveNavigation
4239
)
4340
)
44-
ignoreTripSessionUpdates = AtomicBoolean(false)
45-
sut = StateResetController(store, ignoreTripSessionUpdates)
46-
}
47-
48-
@Test
49-
@Suppress("MaxLineLength")
50-
fun `onAttached, should dispatch actions that end navigation only when TripSessionState transitions from STARTED to STOPPED`() {
51-
val observer = slot<TripSessionStateObserver>()
52-
every {
53-
mapboxNavigation.registerTripSessionStateObserver(capture(observer))
54-
} returns Unit
55-
sut.onAttached(mapboxNavigation)
56-
57-
// STOPPED -> STOPPED
58-
observer.captured.onSessionStateChanged(TripSessionState.STOPPED)
59-
verify(exactly = 0) { store.dispatch(any()) }
60-
61-
// STOPPED -> STARTED
62-
observer.captured.onSessionStateChanged(TripSessionState.STARTED)
63-
verify(exactly = 0) { store.dispatch(any()) }
64-
65-
// STARTED -> STOPPED
66-
observer.captured.onSessionStateChanged(TripSessionState.STOPPED)
67-
verify(exactly = 1) {
68-
store.dispatch(RoutesAction.SetRoutes(emptyList()))
69-
store.dispatch(RoutePreviewAction.Ready(emptyList()))
70-
store.dispatch(DestinationAction.SetDestination(null))
71-
store.dispatch(NavigationStateAction.Update(NavigationState.FreeDrive))
72-
}
73-
}
74-
75-
@Test
76-
@Suppress("MaxLineLength")
77-
fun `onAttached, should NOT dispatch end navigation action when ignoreTripSessionUpdates is true`() {
78-
ignoreTripSessionUpdates.set(true)
79-
val observer = slot<TripSessionStateObserver>()
80-
every { mapboxNavigation.registerTripSessionStateObserver(capture(observer)) } returns Unit
81-
every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STARTED
82-
sut.onAttached(mapboxNavigation)
83-
84-
// STARTED -> STOPPED
85-
observer.captured.onSessionStateChanged(TripSessionState.STOPPED)
86-
verify(exactly = 0) {
87-
store.dispatch(RoutesAction.SetRoutes(emptyList()))
88-
store.dispatch(RoutePreviewAction.Ready(emptyList()))
89-
store.dispatch(DestinationAction.SetDestination(null))
90-
store.dispatch(NavigationStateAction.Update(NavigationState.FreeDrive))
91-
}
41+
sut = StateResetController(store)
9242
}
9343

9444
@Test

0 commit comments

Comments
 (0)