Skip to content

Commit eb8b016

Browse files
tomaszrybakiewiczabhishek1508
authored andcommitted
Unit-tests for CameraLayoutObserver
1 parent f3c86d7 commit eb8b016

File tree

4 files changed

+226
-18
lines changed

4 files changed

+226
-18
lines changed

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/camera/CameraLayoutObserver.kt

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.mapbox.navigation.dropin.camera
33
import android.content.res.Configuration
44
import android.view.View
55
import com.mapbox.maps.EdgeInsets
6-
import com.mapbox.maps.MapView
76
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
87
import com.mapbox.navigation.core.MapboxNavigation
98
import com.mapbox.navigation.dropin.R
@@ -16,7 +15,7 @@ import com.mapbox.navigation.ui.base.lifecycle.UIComponent
1615
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
1716
internal class CameraLayoutObserver(
1817
private val store: Store,
19-
private val mapView: MapView,
18+
private val mapView: View,
2019
private val binding: MapboxNavigationViewLayoutBinding,
2120
) : UIComponent() {
2221

@@ -30,16 +29,9 @@ internal class CameraLayoutObserver(
3029
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_landscape_h).toDouble()
3130

3231
private val layoutListener = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
33-
val edgeInsets = when (binding.root.resources.configuration.orientation) {
34-
Configuration.ORIENTATION_LANDSCAPE -> {
35-
getLandscapePadding()
36-
}
37-
Configuration.ORIENTATION_PORTRAIT -> {
38-
getPortraitPadding()
39-
}
40-
else -> {
41-
getPortraitPadding()
42-
}
32+
val edgeInsets = when (deviceOrientation()) {
33+
Configuration.ORIENTATION_LANDSCAPE -> getLandscapePadding()
34+
else -> getPortraitPadding()
4335
}
4436
store.dispatch(CameraAction.UpdatePadding(edgeInsets))
4537
}
@@ -61,11 +53,11 @@ internal class CameraLayoutObserver(
6153
is NavigationState.DestinationPreview,
6254
is NavigationState.FreeDrive,
6355
is NavigationState.RoutePreview -> {
64-
EdgeInsets(vPadding, hPadding, vPadding.plus(bottom), hPadding)
56+
EdgeInsets(vPadding, hPadding, vPadding + bottom, hPadding)
6557
}
6658
is NavigationState.ActiveNavigation,
6759
is NavigationState.Arrival -> {
68-
EdgeInsets(vPadding.plus(top), hPadding, vPadding.plus(bottom), hPadding)
60+
EdgeInsets(vPadding + top, hPadding, vPadding + bottom, hPadding)
6961
}
7062
}
7163
}
@@ -79,7 +71,7 @@ internal class CameraLayoutObserver(
7971
EdgeInsets(
8072
vPaddingLandscape,
8173
hPaddingLandscape,
82-
vPaddingLandscape.plus(bottom),
74+
vPaddingLandscape + bottom,
8375
hPaddingLandscape
8476
)
8577
}
@@ -89,11 +81,13 @@ internal class CameraLayoutObserver(
8981
is NavigationState.Arrival -> {
9082
EdgeInsets(
9183
vPaddingLandscape,
92-
hPaddingLandscape.plus(left),
93-
vPaddingLandscape.plus(bottom),
94-
hPaddingLandscape.plus(right)
84+
hPaddingLandscape + left,
85+
vPaddingLandscape + bottom,
86+
hPaddingLandscape + right
9587
)
9688
}
9789
}
9890
}
91+
92+
private fun deviceOrientation() = binding.root.resources.configuration.orientation
9993
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
@file:Suppress("PrivatePropertyName", "MaxLineLength")
2+
3+
package com.mapbox.navigation.dropin.component.camera
4+
5+
import android.app.Service
6+
import android.content.Context
7+
import android.view.LayoutInflater
8+
import android.view.View
9+
import android.view.View.OnLayoutChangeListener
10+
import android.widget.FrameLayout
11+
import androidx.test.core.app.ApplicationProvider
12+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
13+
import com.mapbox.navigation.dropin.R
14+
import com.mapbox.navigation.dropin.databinding.MapboxNavigationViewLayoutBinding
15+
import com.mapbox.navigation.dropin.util.TestStore
16+
import com.mapbox.navigation.testing.MainCoroutineRule
17+
import com.mapbox.navigation.ui.app.internal.camera.CameraAction
18+
import com.mapbox.navigation.ui.app.internal.navigation.NavigationState
19+
import io.mockk.mockk
20+
import kotlinx.coroutines.ExperimentalCoroutinesApi
21+
import kotlinx.coroutines.coroutineScope
22+
import kotlinx.coroutines.launch
23+
import kotlinx.coroutines.suspendCancellableCoroutine
24+
import org.junit.Assert.assertEquals
25+
import org.junit.Assert.assertNotEquals
26+
import org.junit.Before
27+
import org.junit.Rule
28+
import org.junit.Test
29+
import org.junit.runner.RunWith
30+
import org.robolectric.RobolectricTestRunner
31+
import org.robolectric.annotation.Config
32+
import kotlin.coroutines.resume
33+
34+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class, ExperimentalCoroutinesApi::class)
35+
@RunWith(RobolectricTestRunner::class)
36+
class CameraLayoutObserverTest {
37+
38+
@get:Rule
39+
val coroutineRule = MainCoroutineRule()
40+
41+
private lateinit var store: TestStore
42+
private lateinit var binding: MapboxNavigationViewLayoutBinding
43+
private lateinit var mapView: View
44+
private lateinit var sut: CameraLayoutObserver
45+
46+
@Before
47+
fun setUp() {
48+
val context: Context = ApplicationProvider.getApplicationContext()
49+
binding = MapboxNavigationViewLayoutBinding.inflate(
50+
context.getSystemService(Service.LAYOUT_INFLATER_SERVICE) as LayoutInflater,
51+
FrameLayout(context)
52+
).apply {
53+
// slightly modifying default layout to simulate layout changes
54+
guidanceLayout.bottom = 100
55+
roadNameLayout.top = 100
56+
actionListLayout.left = 100
57+
infoPanelLayout.right = 100
58+
}
59+
mapView = View(context)
60+
store = TestStore()
61+
sut = CameraLayoutObserver(store, mapView, binding)
62+
}
63+
64+
@Test
65+
@Config(qualifiers = "port")
66+
fun `portrait - should update bottom padding for DestinationPreview, FreeDrive and RoutePreview state`() =
67+
coroutineRule.runBlockingTest {
68+
sut.onAttached(mockk())
69+
70+
triggerLayoutChangesAndVerifyDispatchedActions(
71+
givenNavigationStates = listOf(
72+
NavigationState.DestinationPreview,
73+
NavigationState.FreeDrive,
74+
NavigationState.RoutePreview,
75+
)
76+
) { state, action ->
77+
assertEquals("$state|top", action.padding.top, PADDING_V_PORT, 0.001)
78+
assertNotEquals("$state|bottom", action.padding.bottom, PADDING_V_PORT, 0.001)
79+
assertEquals("$state|left", action.padding.left, PADDING_H_PORT, 0.001)
80+
assertEquals("$state|right", action.padding.right, PADDING_H_PORT, 0.001)
81+
}
82+
}
83+
84+
@Test
85+
@Config(qualifiers = "port")
86+
fun `portrait - should update top and bottom padding for ActiveNavigation and Arrival state`() =
87+
coroutineRule.runBlockingTest {
88+
sut.onAttached(mockk())
89+
90+
triggerLayoutChangesAndVerifyDispatchedActions(
91+
givenNavigationStates = listOf(
92+
NavigationState.ActiveNavigation,
93+
NavigationState.Arrival,
94+
)
95+
) { s, action ->
96+
assertNotEquals("$s|top", action.padding.top, PADDING_V_PORT, 0.001)
97+
assertNotEquals("$s|bottom", action.padding.bottom, PADDING_V_PORT, 0.001)
98+
assertEquals("$s|left", action.padding.left, PADDING_H_PORT, 0.001)
99+
assertEquals("$s|right", action.padding.right, PADDING_H_PORT, 0.001)
100+
}
101+
}
102+
103+
@Test
104+
@Config(qualifiers = "land")
105+
fun `landscape - should update bottom padding for FreeDrive state`() =
106+
coroutineRule.runBlockingTest {
107+
sut.onAttached(mockk())
108+
109+
triggerLayoutChangesAndVerifyDispatchedActions(
110+
givenNavigationStates = listOf(
111+
NavigationState.FreeDrive
112+
)
113+
) { s, action ->
114+
assertEquals("$s|top", action.padding.top, PADDING_V_LAND, 0.001)
115+
assertNotEquals("$s|bottom", action.padding.bottom, PADDING_V_LAND, 0.001)
116+
assertEquals("$s|left", action.padding.left, PADDING_H_LAND, 0.001)
117+
assertEquals("$s|right", action.padding.right, PADDING_H_LAND, 0.001)
118+
}
119+
}
120+
121+
@Test
122+
@Config(qualifiers = "land")
123+
fun `landscape - should update left, bottom and right padding for all states except FreeDrive`() =
124+
coroutineRule.runBlockingTest {
125+
sut.onAttached(mockk())
126+
127+
triggerLayoutChangesAndVerifyDispatchedActions(
128+
givenNavigationStates = listOf(
129+
NavigationState.DestinationPreview,
130+
NavigationState.RoutePreview,
131+
NavigationState.ActiveNavigation,
132+
NavigationState.Arrival,
133+
)
134+
) { s, action ->
135+
assertEquals("$s|top", action.padding.top, PADDING_V_LAND, 0.001)
136+
assertNotEquals("$s|bottom", action.padding.bottom, PADDING_V_LAND, 0.001)
137+
assertNotEquals("$s|left", action.padding.left, PADDING_H_LAND, 0.001)
138+
assertNotEquals("$s|right", action.padding.right, PADDING_H_LAND, 0.001)
139+
}
140+
}
141+
142+
private suspend fun triggerLayoutChangesAndVerifyDispatchedActions(
143+
givenNavigationStates: List<NavigationState>,
144+
assertAction: (state: NavigationState, action: CameraAction.UpdatePadding) -> Unit
145+
) {
146+
givenNavigationStates.forEachIndexed { index, navState ->
147+
store.updateState { it.copy(navigation = navState) }
148+
binding.coordinatorLayout.triggerLayoutChange()
149+
assertAction(navState, store.actions[index] as CameraAction.UpdatePadding)
150+
}
151+
}
152+
153+
private suspend fun View.triggerLayoutChange() = coroutineScope {
154+
launch {
155+
waitForLayoutChange()
156+
}
157+
layout(left + 1, 0, 0, 0)
158+
// this coroutine scope will wait for all launched coroutines to finish before returning
159+
}
160+
161+
private suspend fun View.waitForLayoutChange() = suspendCancellableCoroutine<Unit> { cont ->
162+
addOnLayoutChangeListener(object : OnLayoutChangeListener {
163+
override fun onLayoutChange(
164+
v: View?,
165+
left: Int,
166+
top: Int,
167+
right: Int,
168+
bottom: Int,
169+
oldLeft: Int,
170+
oldTop: Int,
171+
oldRight: Int,
172+
oldBottom: Int
173+
) {
174+
cont.resume(Unit)
175+
removeOnLayoutChangeListener(this)
176+
}
177+
})
178+
}
179+
180+
private val PADDING_V_PORT: Double
181+
get() = binding.root.resources
182+
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_v).toDouble()
183+
private val PADDING_H_PORT: Double
184+
get() = binding.root.resources
185+
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_h).toDouble()
186+
private val PADDING_V_LAND: Double
187+
get() = binding.root.resources
188+
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_landscape_v).toDouble()
189+
private val PADDING_H_LAND: Double
190+
get() = binding.root.resources
191+
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_landscape_h).toDouble()
192+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.mapbox.navigation.dropin.testutil
2+
3+
import com.mapbox.navigation.ui.app.internal.Action
4+
import com.mapbox.navigation.ui.app.internal.Middleware
5+
import com.mapbox.navigation.ui.app.internal.State
6+
7+
class RecordDispatchedActions(
8+
val actions: MutableList<Action> = mutableListOf()
9+
) : Middleware {
10+
11+
override fun onDispatch(state: State, action: Action): Boolean {
12+
actions.add(action)
13+
return false
14+
}
15+
}

libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/util/TestStore.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package com.mapbox.navigation.dropin.util
22

33
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
4+
import com.mapbox.navigation.dropin.testutil.RecordDispatchedActions
5+
import com.mapbox.navigation.ui.app.internal.Action
46
import com.mapbox.navigation.ui.app.internal.State
57
import com.mapbox.navigation.ui.app.internal.Store
68

79
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
810
internal class TestStore : Store() {
11+
val actions = mutableListOf<Action>()
12+
13+
init {
14+
registerMiddleware(RecordDispatchedActions(actions))
15+
}
916

1017
fun setState(state: State) {
1118
_state.value = state

0 commit comments

Comments
 (0)