From 514f4d05415fad02543005ad98d34838652925b6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 09:07:32 +0200 Subject: [PATCH 1/5] Add missing test on NoopAnalyticsService --- services/analytics/noop/build.gradle.kts | 2 + .../noop/NoopAnalyticsServiceTest.kt | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsServiceTest.kt diff --git a/services/analytics/noop/build.gradle.kts b/services/analytics/noop/build.gradle.kts index cd6d16d029..81f969ac2a 100644 --- a/services/analytics/noop/build.gradle.kts +++ b/services/analytics/noop/build.gradle.kts @@ -1,4 +1,5 @@ import extension.setupDependencyInjection +import extension.testCommonDependencies /* * Copyright 2023, 2024 New Vector Ltd. @@ -20,4 +21,5 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.di) api(projects.services.analytics.api) + testCommonDependencies(libs) } diff --git a/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsServiceTest.kt b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsServiceTest.kt new file mode 100644 index 0000000000..d5bc10073a --- /dev/null +++ b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsServiceTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright 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.services.analytics.noop + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.CallStarted +import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.SuperProperties +import im.vector.app.features.analytics.plan.UserProperties +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NoopAnalyticsServiceTest { + @Test + fun `getAvailableAnalyticsProviders returns emptySet`() { + val sut = NoopAnalyticsService() + assertThat(sut.getAvailableAnalyticsProviders()).isEmpty() + } + + @Test + fun `didAskUserConsentFlow emits only true`() = runTest { + val sut = NoopAnalyticsService() + sut.didAskUserConsentFlow.test { + assertThat(awaitItem()).isTrue() + awaitComplete() + } + } + + @Test + fun `analyticsIdFlow emits only empty string`() = runTest { + val sut = NoopAnalyticsService() + sut.analyticsIdFlow.test { + assertThat(awaitItem()).isEmpty() + sut.setAnalyticsId("anId") + awaitComplete() + } + } + + @Test + fun `userConsentFlow emits only false`() = runTest { + val sut = NoopAnalyticsService() + sut.userConsentFlow.test { + assertThat(awaitItem()).isFalse() + awaitComplete() + } + } + + @Test + fun `test no op methods`() = runTest { + val sut = NoopAnalyticsService() + sut.setUserConsent(false) + sut.setUserConsent(true) + sut.setDidAskUserConsent() + sut.setAnalyticsId("anId") + sut.capture(CallStarted(true, 1, true)) + sut.screen(MobileScreen(screenName = MobileScreen.ScreenName.RoomMembers)) + sut.updateUserProperties(UserProperties()) + sut.trackError(Exception("an_error")) + sut.updateSuperProperties(SuperProperties()) + } +} From 4fd860687f0d081811b620dbffb4c51ec519d2b6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 09:12:38 +0200 Subject: [PATCH 2/5] Add missing test on NoopScreenTracker --- .../analytics/noop/NoopScreenTrackerTest.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopScreenTrackerTest.kt diff --git a/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopScreenTrackerTest.kt b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopScreenTrackerTest.kt new file mode 100644 index 0000000000..43b80f3e4b --- /dev/null +++ b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopScreenTrackerTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright 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.services.analytics.noop + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.MobileScreen +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NoopScreenTrackerTest { + @Test + fun `TrackScreen is no op`() = runTest { + val sut = NoopScreenTracker() + moleculeFlow(RecompositionMode.Immediate) { + sut.TrackScreen(MobileScreen.ScreenName.RoomMembers) + }.test { + assertThat(awaitItem()).isEqualTo(Unit) + } + } +} From b1033c4bf60497626db3cfb2ddc81d3a196117ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 09:41:46 +0200 Subject: [PATCH 3/5] Add missing test on DefaultScreenTracker --- services/analytics/impl/build.gradle.kts | 2 + .../analytics/impl/DefaultScreenTracker.kt | 2 +- .../impl/DefaultScreenTrackerTest.kt | 61 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt diff --git a/services/analytics/impl/build.gradle.kts b/services/analytics/impl/build.gradle.kts index 83eebead5d..d50d4c6228 100644 --- a/services/analytics/impl/build.gradle.kts +++ b/services/analytics/impl/build.gradle.kts @@ -33,5 +33,7 @@ dependencies { testCommonDependencies(libs) testImplementation(projects.libraries.sessionStorage.test) + testImplementation(projects.services.analytics.test) testImplementation(projects.services.analyticsproviders.test) + testImplementation(projects.services.toolbox.test) } diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt index 46870ccf74..4bb09a65da 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt @@ -24,7 +24,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock @ContributesBinding(AppScope::class) class DefaultScreenTracker( private val analyticsService: AnalyticsService, - private val systemClock: SystemClock + private val systemClock: SystemClock, ) : ScreenTracker { @Composable override fun TrackScreen( diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt new file mode 100644 index 0000000000..f5afffef28 --- /dev/null +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright 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.services.analytics.impl + +import androidx.lifecycle.Lifecycle +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.MobileScreen +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.services.toolbox.api.systemclock.SystemClock +import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import io.element.android.tests.testutils.FakeLifecycleOwner +import io.element.android.tests.testutils.withFakeLifecycleOwner +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultScreenTrackerTest { + @Test + fun `TrackScreen is working as expected`() = runTest { + val analyticsService = FakeAnalyticsService() + val systemClock = FakeSystemClock(150) + val lifecycleOwner = FakeLifecycleOwner() + val sut = createDefaultScreenTracker( + analyticsService = analyticsService, + systemClock = systemClock, + ) + moleculeFlow(RecompositionMode.Immediate) { + withFakeLifecycleOwner(lifecycleOwner) { + sut.TrackScreen(MobileScreen.ScreenName.RoomMembers) + } + }.test { + // Screen resumes + lifecycleOwner.givenState(Lifecycle.State.RESUMED) + assertThat(awaitItem()).isEqualTo(Unit) + systemClock.epochMillisResult = 450 + lifecycleOwner.givenState((Lifecycle.State.DESTROYED)) + } + assertThat(analyticsService.screenEvents).containsExactly( + MobileScreen( + screenName = MobileScreen.ScreenName.RoomMembers, + durationMs = 300, + ) + ) + } +} + +private fun createDefaultScreenTracker( + analyticsService: AnalyticsService = FakeAnalyticsService(), + systemClock: SystemClock = FakeSystemClock(), +) = DefaultScreenTracker( + analyticsService = analyticsService, + systemClock = systemClock, +) From 712da7a94ddf1dcba1b50e16214cf1217b7c5c45 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 09:44:30 +0200 Subject: [PATCH 4/5] Cleanup and remove warning, this code is not reachable by production code. --- .../android/tests/testutils/WithFakeLifecycleOwner.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt index 16d4cfb2fd..20bc431033 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt @@ -12,8 +12,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.InternalComposeApi import androidx.compose.runtime.Stable import androidx.compose.runtime.currentComposer -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -26,8 +24,6 @@ import io.element.android.libraries.architecture.Presenter /** * Composable that provides a fake [LifecycleOwner] to the composition. - * - * **WARNING: DO NOT USE OUTSIDE TESTS.** */ @OptIn(InternalComposeApi::class) @Stable @@ -44,19 +40,16 @@ fun withFakeLifecycleOwner( /** * Test a [Presenter] with a fake [LifecycleOwner]. - * - * **WARNING: DO NOT USE OUTSIDE TESTS.** */ suspend fun Presenter.testWithLifecycleOwner( lifecycleOwner: FakeLifecycleOwner = FakeLifecycleOwner(), block: suspend TurbineTestContext.() -> Unit ) { moleculeFlow(RecompositionMode.Immediate) { - val ret = withFakeLifecycleOwner(lifecycleOwner) { + withFakeLifecycleOwner(lifecycleOwner) { present() } - ret - }.test(validate = block) + }.test(validate = block) } @SuppressLint("VisibleForTests") From 948c68b1ad61adb02a35cdb942b947a2d925b345 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Oct 2025 09:47:04 +0200 Subject: [PATCH 5/5] Cleanup --- .../android/services/analytics/impl/DefaultScreenTrackerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt index f5afffef28..78fa25f161 100644 --- a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt @@ -41,7 +41,7 @@ class DefaultScreenTrackerTest { lifecycleOwner.givenState(Lifecycle.State.RESUMED) assertThat(awaitItem()).isEqualTo(Unit) systemClock.epochMillisResult = 450 - lifecycleOwner.givenState((Lifecycle.State.DESTROYED)) + lifecycleOwner.givenState(Lifecycle.State.DESTROYED) } assertThat(analyticsService.screenEvents).containsExactly( MobileScreen(