Fix and add tests related to location
This commit is contained in:
@@ -57,12 +57,14 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
|
||||
)
|
||||
}
|
||||
|
||||
private fun aShareLocationState(
|
||||
fun aShareLocationState(
|
||||
currentUser: MatrixUser = MatrixUser(UserId("@user:matrix.org")),
|
||||
dialogState: ShareLocationState.Dialog,
|
||||
trackUserPosition: Boolean,
|
||||
hasLocationPermission: Boolean,
|
||||
dialogState: ShareLocationState.Dialog = ShareLocationState.Dialog.None,
|
||||
trackUserPosition: Boolean = false,
|
||||
hasLocationPermission: Boolean = false,
|
||||
canShareLiveLocation: Boolean = false,
|
||||
appName: String = APP_NAME,
|
||||
eventSink: (ShareLocationEvent) -> Unit = {},
|
||||
): ShareLocationState {
|
||||
return ShareLocationState(
|
||||
currentUser = currentUser,
|
||||
@@ -70,7 +72,7 @@ private fun aShareLocationState(
|
||||
trackUserLocation = trackUserPosition,
|
||||
hasLocationPermission = hasLocationPermission,
|
||||
canShareLiveLocation = canShareLiveLocation,
|
||||
appName = APP_NAME,
|
||||
eventSink = {}
|
||||
appName = appName,
|
||||
eventSink = eventSink
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,22 +20,25 @@ import io.element.android.features.location.impl.common.permissions.FakePermissi
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -49,9 +52,12 @@ class ShareLocationPresenterTest {
|
||||
private val fakeMessageComposerContext = FakeMessageComposerContext()
|
||||
private val fakeLocationActions = FakeLocationActions()
|
||||
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
|
||||
private val fakeFeatureFlagService = FakeFeatureFlagService()
|
||||
private val fakeMatrixClient = FakeMatrixClient(sessionId = A_USER_ID)
|
||||
|
||||
private fun createShareLocationPresenter(
|
||||
joinedRoom: JoinedRoom = FakeJoinedRoom(),
|
||||
locationActions: FakeLocationActions = fakeLocationActions,
|
||||
): ShareLocationPresenter = ShareLocationPresenter(
|
||||
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
||||
override fun create(permissions: List<String>): PermissionsPresenter = fakePermissionsPresenter
|
||||
@@ -60,13 +66,14 @@ class ShareLocationPresenterTest {
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
analyticsService = fakeAnalyticsService,
|
||||
messageComposerContext = fakeMessageComposerContext,
|
||||
locationActions = fakeLocationActions,
|
||||
locationActions = locationActions,
|
||||
buildMeta = fakeBuildMeta,
|
||||
featureFlagService = fakeFeatureFlagService,
|
||||
client = fakeMatrixClient,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `initial state with permissions granted`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fun `initial state with permissions granted and location enabled`() = runTest {
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
@@ -74,25 +81,18 @@ class ShareLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.SenderLocation)
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
|
||||
// Swipe the map to switch mode
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToPinLocationMode)
|
||||
val myLocationState = awaitItem()
|
||||
assertThat(myLocationState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(myLocationState.hasLocationPermission).isTrue()
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val state = awaitItem()
|
||||
assertThat(state.trackUserLocation).isTrue()
|
||||
assertThat(state.hasLocationPermission).isTrue()
|
||||
assertThat(state.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state with permissions partially granted`() = runTest {
|
||||
fun `initial state with permissions partially granted and location enabled`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
@@ -104,17 +104,11 @@ class ShareLocationPresenterTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.SenderLocation)
|
||||
assertThat(initialState.trackUserLocation).isTrue()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
|
||||
// Swipe the map to switch mode
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToPinLocationMode)
|
||||
val myLocationState = awaitItem()
|
||||
assertThat(myLocationState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(myLocationState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,22 +125,18 @@ class ShareLocationPresenterTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(initialState.trackUserLocation).isFalse()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
|
||||
// Click on the button to switch mode
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToMyLocationMode)
|
||||
val myLocationState = awaitItem()
|
||||
assertThat(myLocationState.dialogState).isEqualTo(ShareLocationState.Dialog.PermissionDenied)
|
||||
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(myLocationState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state with permissions denied once`() = runTest {
|
||||
fun `initial state with permissions denied with rationale`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
@@ -155,25 +145,62 @@ class ShareLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(initialState.trackUserLocation).isFalse()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
|
||||
// Click on the button to switch mode
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToMyLocationMode)
|
||||
val myLocationState = awaitItem()
|
||||
assertThat(myLocationState.dialogState).isEqualTo(ShareLocationState.Dialog.PermissionRationale)
|
||||
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(myLocationState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rationale dialog dismiss`() = runTest {
|
||||
fun `initial state with location services disabled`() = runTest {
|
||||
val locationActions = FakeLocationActions(isLocationEnabled = false)
|
||||
val shareLocationPresenter = createShareLocationPresenter(locationActions = locationActions)
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.trackUserLocation).isFalse()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `StopTrackingUserLocation event sets trackUserLocation to false`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.trackUserLocation).isTrue()
|
||||
|
||||
initialState.eventSink(ShareLocationEvent.StopTrackingUserLocation)
|
||||
val stoppedState = awaitItem()
|
||||
assertThat(stoppedState.trackUserLocation).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DismissDialog event clears dialog state`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
@@ -182,30 +209,21 @@ class ShareLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
// Skip initial state
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale)
|
||||
)
|
||||
|
||||
// Click on the button to switch mode
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToMyLocationMode)
|
||||
val myLocationState = awaitItem()
|
||||
assertThat(myLocationState.dialogState).isEqualTo(ShareLocationState.Dialog.PermissionRationale)
|
||||
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(myLocationState.hasLocationPermission).isFalse()
|
||||
|
||||
// Dismiss the dialog
|
||||
myLocationState.eventSink(ShareLocationEvent.DismissDialog)
|
||||
val dialogDismissedState = awaitItem()
|
||||
assertThat(dialogDismissedState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(dialogDismissedState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
|
||||
initialState.eventSink(ShareLocationEvent.DismissDialog)
|
||||
val dismissedState = awaitItem()
|
||||
assertThat(dismissedState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rationale dialog continue`() = runTest {
|
||||
fun `RequestPermissions event triggers permission request`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
@@ -214,27 +232,20 @@ class ShareLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
// Skip initial state
|
||||
shareLocationPresenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ShareLocationEvent.RequestPermissions)
|
||||
|
||||
// Click on the button to switch mode
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToMyLocationMode)
|
||||
val myLocationState = awaitItem()
|
||||
assertThat(myLocationState.dialogState).isEqualTo(ShareLocationState.Dialog.PermissionRationale)
|
||||
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(myLocationState.hasLocationPermission).isFalse()
|
||||
// Wait for dialog to be dismissed
|
||||
awaitItem()
|
||||
|
||||
// Continue the dialog sends permission request to the permissions presenter
|
||||
myLocationState.eventSink(ShareLocationEvent.StartTrackingUserLocation)
|
||||
assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `permission denied dialog dismiss`() = runTest {
|
||||
fun `OpenAppSettings event opens settings and clears dialog`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
@@ -243,31 +254,94 @@ class ShareLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
// Skip initial state
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ShareLocationEvent.OpenAppSettings)
|
||||
val settingsOpenedState = awaitItem()
|
||||
|
||||
// Click on the button to switch mode
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToMyLocationMode)
|
||||
val myLocationState = awaitItem()
|
||||
assertThat(myLocationState.dialogState).isEqualTo(ShareLocationState.Dialog.PermissionDenied)
|
||||
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(myLocationState.hasLocationPermission).isFalse()
|
||||
|
||||
// Dismiss the dialog
|
||||
myLocationState.eventSink(ShareLocationEvent.DismissDialog)
|
||||
val dialogDismissedState = awaitItem()
|
||||
assertThat(dialogDismissedState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(dialogDismissedState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
|
||||
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
|
||||
assertThat(settingsOpenedState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(fakeLocationActions.openSettingsInvocationsCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `share sender location`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, EventId?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
fun `OpenLocationSettings event opens location settings and clears dialog`() = runTest {
|
||||
val locationActions = FakeLocationActions(isLocationEnabled = false)
|
||||
val shareLocationPresenter = createShareLocationPresenter(locationActions = locationActions)
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled)
|
||||
)
|
||||
|
||||
initialState.eventSink(ShareLocationEvent.OpenLocationSettings)
|
||||
val settingsOpenedState = awaitItem()
|
||||
|
||||
assertThat(settingsOpenedState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(locationActions.openLocationSettingsInvocationsCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ShowLiveLocationDurationPicker shows duration dialog when constraints pass`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ShareLocationEvent.ShowLiveLocationDurationPicker)
|
||||
val durationDialogState = awaitItem()
|
||||
|
||||
assertThat(durationDialogState.dialogState).isEqualTo(ShareLocationState.Dialog.LiveLocationDuration)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ShowLiveLocationDurationPicker shows constraint dialog when permissions denied`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
// Dismiss initial dialog
|
||||
initialState.eventSink(ShareLocationEvent.DismissDialog)
|
||||
val dismissedState = awaitItem()
|
||||
|
||||
dismissedState.eventSink(ShareLocationEvent.ShowLiveLocationDurationPicker)
|
||||
val constraintDialogState = awaitItem()
|
||||
|
||||
assertThat(constraintDialogState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied)
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ShareStaticLocation sends user location`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder { _: String, _: String, _: String?, _: Int?, _: AssetType?, _: EventId? ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
@@ -283,29 +357,18 @@ class ShareLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
// Skip initial state
|
||||
shareLocationPresenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Send location
|
||||
initialState.eventSink(
|
||||
ShareLocationEvent.ShareStaticLocation(
|
||||
cameraPosition = ShareLocationEvent.ShareStaticLocation.CameraPosition(
|
||||
lat = 0.0,
|
||||
lon = 1.0,
|
||||
zoom = 2.0,
|
||||
),
|
||||
location = Location(
|
||||
lat = 3.0,
|
||||
lon = 4.0,
|
||||
accuracy = 5.0f,
|
||||
)
|
||||
location = Location(lat = 3.0, lon = 4.0, accuracy = 5.0f),
|
||||
isPinned = false,
|
||||
)
|
||||
)
|
||||
|
||||
delay(1) // Wait for the coroutine to finish
|
||||
advanceUntilIdle()
|
||||
|
||||
sendLocationResult.assertions().isCalledOnce()
|
||||
.with(
|
||||
@@ -326,12 +389,13 @@ class ShareLocationPresenterTest {
|
||||
messageType = Composer.MessageType.LocationUser,
|
||||
)
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `share pin location`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, EventId?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
fun `ShareStaticLocation sends pinned location`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder { _: String, _: String, _: String?, _: Int?, _: AssetType?, _: EventId? ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
@@ -342,39 +406,26 @@ class ShareLocationPresenterTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter(joinedRoom)
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
// Skip initial state
|
||||
shareLocationPresenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Send location
|
||||
initialState.eventSink(
|
||||
ShareLocationEvent.ShareStaticLocation(
|
||||
cameraPosition = ShareLocationEvent.ShareStaticLocation.CameraPosition(
|
||||
lat = 0.0,
|
||||
lon = 1.0,
|
||||
zoom = 2.0,
|
||||
),
|
||||
location = Location(
|
||||
lat = 3.0,
|
||||
lon = 4.0,
|
||||
accuracy = 5.0f,
|
||||
)
|
||||
location = Location(lat = 1.0, lon = 2.0, accuracy = 3.0f),
|
||||
isPinned = true,
|
||||
)
|
||||
)
|
||||
|
||||
delay(1) // Wait for the coroutine to finish
|
||||
|
||||
advanceUntilIdle()
|
||||
sendLocationResult.assertions().isCalledOnce()
|
||||
.with(
|
||||
value("Location was shared at geo:0.0,1.0"),
|
||||
value("geo:0.0,1.0"),
|
||||
value("Location was shared at geo:1.0,2.0;u=3.0"),
|
||||
value("geo:1.0,2.0;u=3.0"),
|
||||
value(null),
|
||||
value(15),
|
||||
value(AssetType.PIN),
|
||||
@@ -390,107 +441,7 @@ class ShareLocationPresenterTest {
|
||||
messageType = Composer.MessageType.LocationPin,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `composer context passes through analytics`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, EventId?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
liveTimeline = FakeTimeline().apply {
|
||||
sendLocationLambda = sendLocationResult
|
||||
},
|
||||
)
|
||||
val shareLocationPresenter = createShareLocationPresenter(joinedRoom)
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
fakeMessageComposerContext.apply {
|
||||
composerMode = MessageComposerMode.Edit(
|
||||
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
|
||||
content = ""
|
||||
)
|
||||
}
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
// Skip initial state
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Send location
|
||||
initialState.eventSink(
|
||||
ShareLocationEvent.ShareStaticLocation(
|
||||
cameraPosition = ShareLocationEvent.ShareStaticLocation.CameraPosition(
|
||||
lat = 0.0,
|
||||
lon = 1.0,
|
||||
zoom = 2.0,
|
||||
),
|
||||
location = null
|
||||
)
|
||||
)
|
||||
|
||||
delay(1) // Wait for the coroutine to finish
|
||||
|
||||
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
|
||||
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = true,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.LocationPin,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `open settings activity`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
fakeMessageComposerContext.apply {
|
||||
composerMode = MessageComposerMode.Edit(
|
||||
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
|
||||
content = ""
|
||||
)
|
||||
}
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
// Skip initial state
|
||||
val initialState = awaitItem()
|
||||
|
||||
initialState.eventSink(ShareLocationEvent.SwitchToMyLocationMode)
|
||||
val dialogShownState = awaitItem()
|
||||
|
||||
// Open settings
|
||||
dialogShownState.eventSink(ShareLocationEvent.OpenAppSettings)
|
||||
val settingsOpenedState = awaitItem()
|
||||
|
||||
assertThat(settingsOpenedState.dialogState).isEqualTo(ShareLocationState.Dialog.None)
|
||||
assertThat(fakeLocationActions.openSettingsInvocationsCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `application name is in state`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
shareLocationPresenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.appName).isEqualTo("app name")
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.share
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ShareLocationViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `test back action`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>(expectEvents = false)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setShareLocationView(
|
||||
state = aShareLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = callback,
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fab click`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||
rule.setShareLocationView(
|
||||
aShareLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
)
|
||||
rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
||||
eventsRecorder.assertSingle(ShareLocationEvent.StartTrackingUserLocation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission denied is displayed user can open the settings`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||
rule.setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(ShareLocationEvent.OpenAppSettings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission denied is displayed user can close the dialog`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||
rule.setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission rationale is displayed user can request permissions`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||
rule.setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(ShareLocationEvent.RequestPermissions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission rationale is displayed user can close the dialog`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||
rule.setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when location service disabled is displayed user can open location settings`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||
rule.setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||
hasLocationPermission = true,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(ShareLocationEvent.OpenLocationSettings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when location service disabled is displayed user can close the dialog`() {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||
rule.setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||
hasLocationPermission = true,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setShareLocationView(
|
||||
state: ShareLocationState,
|
||||
navigateUp: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
// Simulate a LocalInspectionMode for MapLibreMap
|
||||
CompositionLocalProvider(LocalInspectionMode provides true) {
|
||||
ShareLocationView(
|
||||
state = state,
|
||||
navigateUp = navigateUp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,11 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.api.ShowLocationEntryPoint
|
||||
import io.element.android.features.location.api.ShowLocationMode
|
||||
import io.element.android.features.location.impl.common.actions.FakeLocationActions
|
||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
@@ -32,21 +35,27 @@ class DefaultShowLocationEntryPointTest {
|
||||
ShowLocationNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
presenterFactory = { location: Location, description: String? ->
|
||||
ShowLocationPresenter(
|
||||
presenterFactory = object : ShowLocationPresenter.Factory {
|
||||
override fun create(mode: ShowLocationMode) = ShowLocationPresenter(
|
||||
mode = mode,
|
||||
permissionsPresenterFactory = { FakePermissionsPresenter() },
|
||||
locationActions = FakeLocationActions(),
|
||||
buildMeta = aBuildMeta(),
|
||||
location = location,
|
||||
description = description,
|
||||
dateFormatter = FakeDateFormatter(),
|
||||
)
|
||||
},
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
)
|
||||
}
|
||||
val inputs = ShowLocationEntryPoint.Inputs(
|
||||
location = Location(37.4219983, -122.084, 10f),
|
||||
description = "My location",
|
||||
mode = ShowLocationMode.Static(
|
||||
location = Location(37.4219983, -122.084, 10f),
|
||||
senderName = "Alice",
|
||||
senderId = UserId("@alice:matrix.org"),
|
||||
senderAvatarUrl = null,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
assetType = null,
|
||||
),
|
||||
)
|
||||
val result = entryPoint.createNode(
|
||||
parentNode = parentNode,
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
@@ -66,9 +67,8 @@ class ShowLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
@@ -84,9 +84,8 @@ class ShowLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
@@ -97,9 +96,8 @@ class ShowLocationPresenterTest {
|
||||
fun `emits initial state with location permission`() = runTest {
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
@@ -110,9 +108,8 @@ class ShowLocationPresenterTest {
|
||||
fun `emits initial state with partial location permission`() = runTest {
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.SomeGranted))
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
@@ -121,9 +118,8 @@ class ShowLocationPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `uses action to share location`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ShowLocationEvents.Share(location))
|
||||
|
||||
@@ -135,9 +131,8 @@ class ShowLocationPresenterTest {
|
||||
fun `centers on user location`() = runTest {
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
@@ -168,9 +163,8 @@ class ShowLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
// Skip initial state
|
||||
val initialState = awaitItem()
|
||||
|
||||
@@ -198,10 +192,8 @@ class ShowLocationPresenterTest {
|
||||
shouldShowRationale = true,
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
// Skip initial state
|
||||
val initialState = awaitItem()
|
||||
|
||||
@@ -227,9 +219,8 @@ class ShowLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
// Skip initial state
|
||||
val initialState = awaitItem()
|
||||
|
||||
@@ -258,9 +249,8 @@ class ShowLocationPresenterTest {
|
||||
)
|
||||
)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter().present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
// Skip initial state
|
||||
val initialState = awaitItem()
|
||||
|
||||
@@ -291,9 +281,8 @@ class ShowLocationPresenterTest {
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
fakeLocationActions.givenLocationEnabled(false)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter(locationActions = fakeLocationActions).present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
|
||||
@@ -311,9 +300,8 @@ class ShowLocationPresenterTest {
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
fakeLocationActions.givenLocationEnabled(false)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
createShowLocationPresenter(locationActions = fakeLocationActions).present()
|
||||
}.test {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
initialState.eventSink(ShowLocationEvents.TrackMyLocation(true))
|
||||
|
||||
Reference in New Issue
Block a user