Browse Source

Be able to test `PermissionsPresenterTest`. Create interface to abstract Accompanist implementation

test/jme/compound-poc
Benoit Marty 2 years ago committed by Benoit Marty
parent
commit
000ed480ee
  1. 1
      libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt
  2. 2
      libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt
  3. 43
      libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt
  4. 10
      libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt
  5. 145
      libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt
  6. 62
      libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt
  7. 2
      libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/InMemoryPermissionsStore.kt

1
libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt

@ -19,5 +19,4 @@ package io.element.android.libraries.permissions.api @@ -19,5 +19,4 @@ package io.element.android.libraries.permissions.api
sealed interface PermissionsEvents {
object OpenSystemDialog : PermissionsEvents
object CloseDialog : PermissionsEvents
object OpenSystemSettings : PermissionsEvents
}

2
libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt

@ -42,7 +42,7 @@ fun PermissionsView( @@ -42,7 +42,7 @@ fun PermissionsView(
content = "In order to let the application display notification, please grant the permission to the system settings",
submitText = "Open settings",
onSubmitClicked = {
state.eventSink.invoke(PermissionsEvents.OpenSystemSettings)
state.eventSink.invoke(PermissionsEvents.CloseDialog)
openSystemSettings()
},
onDismiss = { state.eventSink.invoke(PermissionsEvents.CloseDialog) },

43
libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(ExperimentalPermissionsApi::class)
package io.element.android.libraries.permissions.impl
import androidx.compose.runtime.Composable
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.rememberPermissionState
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
interface PermissionStateProvider {
@Composable
fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState
}
@ContributesBinding(AppScope::class)
class AccompanistPermissionStateProvider @Inject constructor() : PermissionStateProvider {
@Composable
override fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState {
return rememberPermissionState(
permission = permission,
onPermissionResult = onPermissionResult
)
}
}

10
libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt

@ -26,8 +26,8 @@ import androidx.compose.runtime.rememberCoroutineScope @@ -26,8 +26,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
@ -41,6 +41,7 @@ import javax.inject.Inject @@ -41,6 +41,7 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultPermissionsPresenter @Inject constructor(
private val permissionsStore: PermissionsStore,
private val permissionStateProvider: PermissionStateProvider,
) : PermissionsPresenter {
private lateinit var permission: String
@ -85,7 +86,7 @@ class DefaultPermissionsPresenter @Inject constructor( @@ -85,7 +86,7 @@ class DefaultPermissionsPresenter @Inject constructor(
}
}
permissionState = rememberPermissionState(
permissionState = permissionStateProvider.provide(
permission = permission,
onPermissionResult = ::onPermissionResult
)
@ -97,7 +98,7 @@ class DefaultPermissionsPresenter @Inject constructor( @@ -97,7 +98,7 @@ class DefaultPermissionsPresenter @Inject constructor(
}
}
val showDialog = rememberSaveable { mutableStateOf(true) }
val showDialog = rememberSaveable { mutableStateOf(permissionState.status !is PermissionStatus.Granted) }
fun handleEvents(event: PermissionsEvents) {
Timber.tag("PERMISSION").w("New event: $event")
@ -109,9 +110,6 @@ class DefaultPermissionsPresenter @Inject constructor( @@ -109,9 +110,6 @@ class DefaultPermissionsPresenter @Inject constructor(
permissionState.launchPermissionRequest()
showDialog.value = false
}
PermissionsEvents.OpenSystemSettings -> {
showDialog.value = false
}
}
}

145
libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt

@ -14,14 +14,17 @@ @@ -14,14 +14,17 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalPermissionsApi::class)
package io.element.android.libraries.permissions.impl
import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.permissions.api.PermissionsEvents
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -31,8 +34,135 @@ const val A_PERMISSION = "A_PERMISSION" @@ -31,8 +34,135 @@ const val A_PERMISSION = "A_PERMISSION"
class DefaultPermissionsPresenterTest {
@Test
fun `present - initial state`() = runTest {
val permissionsStore = InMemoryPermissionsStore()
val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Granted)
val permissionStateProvider = FakePermissionStateProvider(permissionState)
val presenter = DefaultPermissionsPresenter(
InMemoryPermissionsStore()
permissionsStore,
permissionStateProvider
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.setParameter(A_PERMISSION)
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.permission).isEqualTo(A_PERMISSION)
assertThat(initialState.permissionGranted).isTrue()
assertThat(initialState.shouldShowRationale).isFalse()
assertThat(initialState.permissionAlreadyAsked).isFalse()
assertThat(initialState.permissionAlreadyDenied).isFalse()
assertThat(initialState.showDialog).isFalse()
}
}
@Test
fun `present - user closes dialog`() = runTest {
val permissionsStore = InMemoryPermissionsStore()
val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false))
val permissionStateProvider = FakePermissionStateProvider(permissionState)
val presenter = DefaultPermissionsPresenter(
permissionsStore,
permissionStateProvider
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.setParameter(A_PERMISSION)
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.showDialog).isTrue()
initialState.eventSink.invoke(PermissionsEvents.CloseDialog)
assertThat(awaitItem().showDialog).isFalse()
}
}
@Test
fun `present - user does not grant permission`() = runTest {
val permissionsStore = InMemoryPermissionsStore()
val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false))
val permissionStateProvider = FakePermissionStateProvider(permissionState)
val presenter = DefaultPermissionsPresenter(
permissionsStore,
permissionStateProvider
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.setParameter(A_PERMISSION)
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.showDialog).isTrue()
initialState.eventSink.invoke(PermissionsEvents.OpenSystemDialog)
assertThat(permissionState.launchPermissionRequestCalled).isTrue()
assertThat(awaitItem().showDialog).isFalse()
// User does not grant permission
permissionStateProvider.userGiveAnswer(answer = false, firstTime = true)
skipItems(1)
val state = awaitItem()
assertThat(state.permissionGranted).isFalse()
assertThat(state.showDialog).isFalse()
assertThat(state.permissionAlreadyDenied).isFalse()
assertThat(state.permissionAlreadyAsked).isTrue()
}
}
@Test
fun `present - user does not grant permission second time`() = runTest {
val permissionsStore = InMemoryPermissionsStore()
val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = true))
val permissionStateProvider = FakePermissionStateProvider(permissionState)
val presenter = DefaultPermissionsPresenter(
permissionsStore,
permissionStateProvider
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.setParameter(A_PERMISSION)
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.showDialog).isTrue()
initialState.eventSink.invoke(PermissionsEvents.OpenSystemDialog)
assertThat(permissionState.launchPermissionRequestCalled).isTrue()
assertThat(awaitItem().showDialog).isFalse()
// User does not grant permission
permissionStateProvider.userGiveAnswer(answer = false, firstTime = false)
skipItems(2)
val state = awaitItem()
assertThat(state.permissionGranted).isFalse()
assertThat(state.showDialog).isFalse()
assertThat(state.permissionAlreadyDenied).isTrue()
assertThat(state.permissionAlreadyAsked).isTrue()
}
}
@Test
fun `present - user does not grant permission third time`() = runTest {
val permissionsStore = InMemoryPermissionsStore(permissionDenied = true, permissionAsked = true)
val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false))
val permissionStateProvider = FakePermissionStateProvider(permissionState)
val presenter = DefaultPermissionsPresenter(
permissionsStore,
permissionStateProvider
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.setParameter(A_PERMISSION)
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.showDialog).isTrue()
assertThat(initialState.permissionGranted).isFalse()
assertThat(initialState.permissionAlreadyDenied).isTrue()
assertThat(initialState.permissionAlreadyAsked).isTrue()
}
}
@Test
fun `present - user grants permission`() = runTest {
val permissionsStore = InMemoryPermissionsStore()
val permissionState = FakePermissionState(A_PERMISSION, PermissionStatus.Denied(shouldShowRationale = false))
val permissionStateProvider = FakePermissionStateProvider(permissionState)
val presenter = DefaultPermissionsPresenter(
permissionsStore,
permissionStateProvider
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.setParameter(A_PERMISSION)
@ -40,6 +170,17 @@ class DefaultPermissionsPresenterTest { @@ -40,6 +170,17 @@ class DefaultPermissionsPresenterTest {
}.test {
val initialState = awaitItem()
assertThat(initialState.showDialog).isTrue()
initialState.eventSink.invoke(PermissionsEvents.OpenSystemDialog)
assertThat(permissionState.launchPermissionRequestCalled).isTrue()
assertThat(awaitItem().showDialog).isFalse()
// User grants permission
permissionStateProvider.userGiveAnswer(answer = true, firstTime = true)
skipItems(1)
val state = awaitItem()
assertThat(state.permissionGranted).isTrue()
assertThat(state.showDialog).isFalse()
assertThat(state.permissionAlreadyDenied).isFalse()
assertThat(state.permissionAlreadyAsked).isTrue()
}
}
}

62
libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(ExperimentalPermissionsApi::class)
package io.element.android.libraries.permissions.impl
import androidx.compose.runtime.*
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.PermissionStatus
class FakePermissionStateProvider constructor(
private val permissionState: FakePermissionState
) : PermissionStateProvider {
private lateinit var onPermissionResult: (Boolean) -> Unit
@OptIn(ExperimentalPermissionsApi::class)
@Composable
override fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState {
this.onPermissionResult = onPermissionResult
return permissionState
}
fun userGiveAnswer(answer: Boolean, firstTime: Boolean) {
onPermissionResult.invoke(answer)
permissionState.givenPermissionStatus(answer, firstTime)
}
}
@Stable
class FakePermissionState(
override val permission: String,
initialStatus: PermissionStatus,
) : PermissionState {
override var status: PermissionStatus by mutableStateOf(initialStatus)
var launchPermissionRequestCalled = false
private set
override fun launchPermissionRequest() {
launchPermissionRequestCalled = true
}
fun givenPermissionStatus(hasPermission: Boolean, shouldShowRationale: Boolean) {
status = if (hasPermission) PermissionStatus.Granted else PermissionStatus.Denied(shouldShowRationale)
}
}

2
libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/InMemoryPermissionsStore.kt

@ -33,7 +33,7 @@ class InMemoryPermissionsStore( @@ -33,7 +33,7 @@ class InMemoryPermissionsStore(
override fun isPermissionDenied(permission: String): Flow<Boolean> = permissionDeniedFlow
override suspend fun setPermissionAsked(permission: String, value: Boolean) {
permissionAskedFlow.value
permissionAskedFlow.value = value
}
override fun isPermissionAsked(permission: String): Flow<Boolean> = permissionAskedFlow

Loading…
Cancel
Save