[FeatureFlag] add more tests
This commit is contained in:
parent
e5d70ff48e
commit
82c9ee9982
9 changed files with 161 additions and 18 deletions
|
|
@ -57,6 +57,7 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
testImplementation(projects.features.rageshake.impl)
|
testImplementation(projects.features.rageshake.impl)
|
||||||
testImplementation(projects.features.logout.impl)
|
testImplementation(projects.features.logout.impl)
|
||||||
|
|
|
||||||
|
|
@ -21,24 +21,25 @@ import io.element.android.features.logout.api.LogoutPreferencePresenter
|
||||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
|
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
|
||||||
import io.element.android.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildType
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PreferencesRootPresenter @Inject constructor(
|
class PreferencesRootPresenter @Inject constructor(
|
||||||
private val logoutPresenter: LogoutPreferencePresenter,
|
private val logoutPresenter: LogoutPreferencePresenter,
|
||||||
private val rageshakePresenter: RageshakePreferencesPresenter,
|
private val rageshakePresenter: RageshakePreferencesPresenter,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildType: BuildType,
|
||||||
) : Presenter<PreferencesRootState> {
|
) : Presenter<PreferencesRootState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): PreferencesRootState {
|
override fun present(): PreferencesRootState {
|
||||||
val logoutState = logoutPresenter.present()
|
val logoutState = logoutPresenter.present()
|
||||||
val rageshakeState = rageshakePresenter.present()
|
val rageshakeState = rageshakePresenter.present()
|
||||||
|
val showDeveloperSettings = buildType != BuildType.RELEASE
|
||||||
return PreferencesRootState(
|
return PreferencesRootState(
|
||||||
logoutState = logoutState,
|
logoutState = logoutState,
|
||||||
rageshakeState = rageshakeState,
|
rageshakeState = rageshakeState,
|
||||||
myUser = Async.Uninitialized,
|
myUser = Async.Uninitialized,
|
||||||
showDeveloperSettings = buildMeta.isDebug
|
showDeveloperSettings = showDeveloperSettings
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* 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(ExperimentalCoroutinesApi::class)
|
||||||
|
|
||||||
|
package io.element.android.features.preferences.impl.developer
|
||||||
|
|
||||||
|
import app.cash.molecule.RecompositionClock
|
||||||
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DeveloperSettingsPresenterTest {
|
||||||
|
@Test
|
||||||
|
fun `present - ensures initial state is correct`() = runTest {
|
||||||
|
val presenter = DeveloperSettingsPresenter(
|
||||||
|
FakeFeatureFlagService()
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.features).isEmpty()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - ensures feature list is loaded`() = runTest {
|
||||||
|
val presenter = DeveloperSettingsPresenter(
|
||||||
|
FakeFeatureFlagService()
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
skipItems(1)
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.features).hasSize(FeatureFlags.values().size)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - ensures state is updated when enabled feature event is triggered`() = runTest {
|
||||||
|
val presenter = DeveloperSettingsPresenter(
|
||||||
|
FakeFeatureFlagService()
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
skipItems(1)
|
||||||
|
val stateBeforeEvent = awaitItem()
|
||||||
|
val featureBeforeEvent = stateBeforeEvent.features.first()
|
||||||
|
stateBeforeEvent.eventSink(DeveloperSettingsEvents.UpdateEnabledFeature(featureBeforeEvent, !featureBeforeEvent.isEnabled))
|
||||||
|
val stateAfterEvent = awaitItem()
|
||||||
|
val featureAfterEvent = stateAfterEvent.features.first()
|
||||||
|
assertThat(featureBeforeEvent.key).isEqualTo(featureAfterEvent.key)
|
||||||
|
assertThat(featureBeforeEvent.isEnabled).isNotEqualTo(featureAfterEvent.isEnabled)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePr
|
||||||
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
|
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
|
||||||
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
|
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
|
||||||
import io.element.android.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.core.meta.BuildType
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -38,7 +39,9 @@ class PreferencesRootPresenterTest {
|
||||||
val logoutPresenter = DefaultLogoutPreferencePresenter(FakeMatrixClient())
|
val logoutPresenter = DefaultLogoutPreferencePresenter(FakeMatrixClient())
|
||||||
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
||||||
val presenter = PreferencesRootPresenter(
|
val presenter = PreferencesRootPresenter(
|
||||||
logoutPresenter, rageshakePresenter
|
logoutPresenter,
|
||||||
|
rageshakePresenter,
|
||||||
|
BuildType.DEBUG
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -50,6 +53,7 @@ class PreferencesRootPresenterTest {
|
||||||
assertThat(initialState.rageshakeState.isSupported).isTrue()
|
assertThat(initialState.rageshakeState.isSupported).isTrue()
|
||||||
assertThat(initialState.rageshakeState.sensitivity).isEqualTo(1.0f)
|
assertThat(initialState.rageshakeState.sensitivity).isEqualTo(1.0f)
|
||||||
assertThat(initialState.myUser).isEqualTo(Async.Uninitialized)
|
assertThat(initialState.myUser).isEqualTo(Async.Uninitialized)
|
||||||
|
assertThat(initialState.showDeveloperSettings).isEqualTo(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package io.element.android.libraries.featureflag.api
|
package io.element.android.libraries.featureflag.api
|
||||||
|
|
||||||
interface FeatureFlagService {
|
interface FeatureFlagService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param feature the feature to check for
|
* @param feature the feature to check for
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -20,36 +20,29 @@ import com.squareup.anvil.annotations.ContributesTo
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.multibindings.ElementsIntoSet
|
import dagger.multibindings.ElementsIntoSet
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildType
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.featureflag.impl.BuildtimeFeatureFlagProvider
|
import io.element.android.libraries.featureflag.impl.BuildtimeFeatureFlagProvider
|
||||||
import io.element.android.libraries.featureflag.impl.FeatureFlagProvider
|
import io.element.android.libraries.featureflag.impl.FeatureFlagProvider
|
||||||
import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider
|
import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider
|
||||||
import io.element.android.libraries.featureflag.impl.RuntimeFeatureFlagProvider
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@ContributesTo(AppScope::class)
|
@ContributesTo(AppScope::class)
|
||||||
object FeatureFlagModule {
|
object FeatureFlagModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun providesRuntimeFeatureFlagProvider(preferencesFeatureFlagProvider: PreferencesFeatureFlagProvider): RuntimeFeatureFlagProvider {
|
|
||||||
return preferencesFeatureFlagProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@ElementsIntoSet
|
@ElementsIntoSet
|
||||||
fun providesFeatureFlagProvider(
|
fun providesFeatureFlagProvider(
|
||||||
buildMeta: BuildMeta,
|
buildType: BuildType,
|
||||||
|
runtimeFeatureFlagProvider: PreferencesFeatureFlagProvider,
|
||||||
buildtimeFeatureFlagProvider: BuildtimeFeatureFlagProvider,
|
buildtimeFeatureFlagProvider: BuildtimeFeatureFlagProvider,
|
||||||
runtimeFeatureFlagProvider: RuntimeFeatureFlagProvider,
|
|
||||||
): Set<FeatureFlagProvider> {
|
): Set<FeatureFlagProvider> {
|
||||||
val providers = HashSet<FeatureFlagProvider>()
|
val providers = HashSet<FeatureFlagProvider>()
|
||||||
//TODO change this condition?
|
if (buildType == BuildType.RELEASE) {
|
||||||
if (buildMeta.isDebug) {
|
|
||||||
providers.add(runtimeFeatureFlagProvider)
|
|
||||||
} else {
|
|
||||||
providers.add(buildtimeFeatureFlagProvider)
|
providers.add(buildtimeFeatureFlagProvider)
|
||||||
|
} else {
|
||||||
|
providers.add(runtimeFeatureFlagProvider)
|
||||||
}
|
}
|
||||||
return providers
|
return providers
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
libraries/featureflag/test/build.gradle.kts
Normal file
27
libraries/featureflag/test/build.gradle.kts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.libraries.featureflag.test"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(projects.libraries.featureflag.api)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.featureflag.test
|
||||||
|
|
||||||
|
import io.element.android.libraries.featureflag.api.Feature
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
|
||||||
|
class FakeFeatureFlagService(
|
||||||
|
initialState: Map<String, Boolean> = emptyMap()
|
||||||
|
) : FeatureFlagService {
|
||||||
|
|
||||||
|
private val enabledFeatures = HashMap(initialState)
|
||||||
|
|
||||||
|
override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean {
|
||||||
|
enabledFeatures[feature.key] = enabled
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isFeatureEnabled(feature: Feature): Boolean {
|
||||||
|
return enabledFeatures[feature.key] ?: feature.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
include(":libraries:featureflag:test")
|
||||||
|
|
||||||
include(":libraries:featureflag:ui")
|
include(":libraries:featureflag:ui")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue