From 991c7ff7f0455475d0c177f703572bba032d7b76 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 18 Apr 2023 11:58:39 +0200 Subject: [PATCH] [FeatureFlag] add more tests --- features/preferences/impl/build.gradle.kts | 1 + .../impl/root/PreferencesRootPresenter.kt | 7 +- .../DeveloperSettingsPresenterTest.kt | 80 +++++++++++++++++++ .../impl/root/PreferencesRootPresenterTest.kt | 6 +- .../featureflag/api/FeatureFlagService.kt | 1 - .../featureflag/impl/di/FeatureFlagModule.kt | 19 ++--- libraries/featureflag/test/build.gradle.kts | 27 +++++++ .../test/FakeFeatureFlagService.kt | 36 +++++++++ settings.gradle.kts | 2 + 9 files changed, 161 insertions(+), 18 deletions(-) create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt create mode 100644 libraries/featureflag/test/build.gradle.kts create mode 100644 libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index 013672990b..e4b76916e6 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.featureflag.test) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) testImplementation(projects.features.logout.impl) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index b7b92c88c7..b1f1762dfc 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -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.libraries.architecture.Async 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 class PreferencesRootPresenter @Inject constructor( private val logoutPresenter: LogoutPreferencePresenter, private val rageshakePresenter: RageshakePreferencesPresenter, - private val buildMeta: BuildMeta, + private val buildType: BuildType, ) : Presenter { @Composable override fun present(): PreferencesRootState { val logoutState = logoutPresenter.present() val rageshakeState = rageshakePresenter.present() + val showDeveloperSettings = buildType != BuildType.RELEASE return PreferencesRootState( logoutState = logoutState, rageshakeState = rageshakeState, myUser = Async.Uninitialized, - showDeveloperSettings = buildMeta.isDebug + showDeveloperSettings = showDeveloperSettings ) } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt new file mode 100644 index 0000000000..2e4bd005ca --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -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() + } + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 83d1163e9e..38e97148ca 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -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.FakeRageshakeDataStore import io.element.android.libraries.architecture.Async +import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.matrix.test.FakeMatrixClient import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -38,7 +39,9 @@ class PreferencesRootPresenterTest { val logoutPresenter = DefaultLogoutPreferencePresenter(FakeMatrixClient()) val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()) val presenter = PreferencesRootPresenter( - logoutPresenter, rageshakePresenter + logoutPresenter, + rageshakePresenter, + BuildType.DEBUG ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -50,6 +53,7 @@ class PreferencesRootPresenterTest { assertThat(initialState.rageshakeState.isSupported).isTrue() assertThat(initialState.rageshakeState.sensitivity).isEqualTo(1.0f) assertThat(initialState.myUser).isEqualTo(Async.Uninitialized) + assertThat(initialState.showDeveloperSettings).isEqualTo(true) } } } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt index a94724e155..59e224a1ae 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.featureflag.api interface FeatureFlagService { - /** * @param feature the feature to check for * diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt index 2cafdc03f8..07ee53ceee 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt @@ -20,36 +20,29 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides 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.featureflag.impl.BuildtimeFeatureFlagProvider import io.element.android.libraries.featureflag.impl.FeatureFlagProvider import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider -import io.element.android.libraries.featureflag.impl.RuntimeFeatureFlagProvider @Module @ContributesTo(AppScope::class) object FeatureFlagModule { - @Provides - fun providesRuntimeFeatureFlagProvider(preferencesFeatureFlagProvider: PreferencesFeatureFlagProvider): RuntimeFeatureFlagProvider { - return preferencesFeatureFlagProvider - } - @JvmStatic @Provides @ElementsIntoSet fun providesFeatureFlagProvider( - buildMeta: BuildMeta, + buildType: BuildType, + runtimeFeatureFlagProvider: PreferencesFeatureFlagProvider, buildtimeFeatureFlagProvider: BuildtimeFeatureFlagProvider, - runtimeFeatureFlagProvider: RuntimeFeatureFlagProvider, ): Set { val providers = HashSet() - //TODO change this condition? - if (buildMeta.isDebug) { - providers.add(runtimeFeatureFlagProvider) - } else { + if (buildType == BuildType.RELEASE) { providers.add(buildtimeFeatureFlagProvider) + } else { + providers.add(runtimeFeatureFlagProvider) } return providers } diff --git a/libraries/featureflag/test/build.gradle.kts b/libraries/featureflag/test/build.gradle.kts new file mode 100644 index 0000000000..952b9323f6 --- /dev/null +++ b/libraries/featureflag/test/build.gradle.kts @@ -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) + } +} diff --git a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt new file mode 100644 index 0000000000..548ffa7cc4 --- /dev/null +++ b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt @@ -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 = 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 + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 43b227476f..4f51b9d6c4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,7 @@ import java.net.URI +include(":libraries:featureflag:test") + include(":libraries:featureflag:ui") /*