Move analytic watchers to :services:analytics

This commit is contained in:
Jorge Martín 2025-11-27 12:13:16 +01:00 committed by Jorge Martin Espinosa
parent 27d376806c
commit 71bfffe58f
14 changed files with 84 additions and 47 deletions

View file

@ -1,170 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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.appnav.analytics
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.ResumeAppUntilNewRoomsReceived
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.NavigationState
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class AnalyticsRoomListStateWatcherTest {
@Test
fun `Opening the app in a warm state tracks the time until the room list is synced`() = runTest {
val navigationStateService = FakeAppNavigationStateService()
val roomListService = FakeRoomListService().apply {
postState(RoomListService.State.Idle)
}
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsRoomListStateWatcher(
appNavigationStateService = navigationStateService,
roomListService = roomListService,
analyticsService = analyticsService,
)
watcher.start()
// Give some time to load the initial state
runCurrent()
// Make sure it's warm by changing its internal state
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
runCurrent()
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
runCurrent()
// The transaction should be present now
assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNotNull()
// And now the room list service running
roomListService.postState(RoomListService.State.Running)
runCurrent()
// And the transaction should now be gone
assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNull()
watcher.stop()
}
@Test
fun `Opening the app in a cold state does nothing`() = runTest {
val navigationStateService = FakeAppNavigationStateService().apply {
appNavigationState.emit(AppNavigationState(NavigationState.Root, false))
}
val roomListService = FakeRoomListService().apply {
postState(RoomListService.State.Idle)
}
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsRoomListStateWatcher(
appNavigationStateService = navigationStateService,
roomListService = roomListService,
analyticsService = analyticsService,
)
watcher.start()
// Give some time to load the initial state
runCurrent()
// The room list service running
roomListService.postState(RoomListService.State.Running)
runCurrent()
// The transaction was never present
assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNull()
watcher.stop()
}
@Test
fun `The transaction won't be finished until the room list is synchronised`() = runTest {
val navigationStateService = FakeAppNavigationStateService()
val roomListService = FakeRoomListService().apply {
postState(RoomListService.State.Idle)
}
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsRoomListStateWatcher(
appNavigationStateService = navigationStateService,
roomListService = roomListService,
analyticsService = analyticsService,
)
watcher.start()
// Give some time to load the initial state
runCurrent()
// Make sure it's warm by changing its internal state
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
runCurrent()
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
runCurrent()
// The transaction should be present now
assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNotNull()
runCurrent()
// But without the room list syncing, it never finishes
assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNotNull()
watcher.stop()
}
@Test
fun `Opening the app when the room list state was already Running does nothing`() = runTest {
val navigationStateService = FakeAppNavigationStateService()
val roomListService = FakeRoomListService().apply {
postState(RoomListService.State.Running)
}
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsRoomListStateWatcher(
appNavigationStateService = navigationStateService,
roomListService = roomListService,
analyticsService = analyticsService,
)
watcher.start()
// Give some time to load the initial state
runCurrent()
// Make sure it's warm by changing its internal state
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
runCurrent()
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
runCurrent()
// The transaction was never added
assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNull()
watcher.stop()
}
private fun TestScope.createAnalyticsRoomListStateWatcher(
appNavigationStateService: FakeAppNavigationStateService = FakeAppNavigationStateService(),
roomListService: FakeRoomListService = FakeRoomListService(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
) = AnalyticsRoomListStateWatcher(
appNavigationStateService = appNavigationStateService,
roomListService = roomListService,
analyticsService = analyticsService,
sessionCoroutineScope = backgroundScope,
dispatchers = testCoroutineDispatchers(),
)
}

View file

@ -1,107 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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.appnav.analytics
import com.google.common.truth.Truth.assertThat
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.ColdStartUntilCachedRoomList
import io.element.android.services.analytics.test.FakeAnalyticsService
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class DefaultAnalyticsColdStartWatcherTest {
@Test
fun `watch - until room list is visible`() = runTest {
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsColdStartWatcher(analyticsService)
// Start watching
watcher.start()
// The user has given analytics consent, we can start tracking the cold start
analyticsService.setUserConsent(true)
runCurrent()
// The transaction is running
assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNotNull()
// As soon as the room list is visible
watcher.onRoomListVisible()
runCurrent()
// The transaction is now finished
assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull()
}
@Test
fun `watch - user is logging in, transaction is cancelled since it's not a cold start`() = runTest {
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsColdStartWatcher(analyticsService)
// Start watching
watcher.start()
// The user has given analytics consent, we can start tracking the cold start
analyticsService.setUserConsent(true)
runCurrent()
// The transaction is running
assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNotNull()
// If the user starts a login flow
watcher.whenLoggingIn()
runCurrent()
// The transaction is gone
assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull()
}
@Test
fun `watch - user was logging in, transaction is never started since it's not a cold start`() = runTest {
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsColdStartWatcher(analyticsService)
// Start watching
watcher.start()
// If the user starts a login flow
watcher.whenLoggingIn()
// The user has given analytics consent, we can start tracking the cold start
analyticsService.setUserConsent(true)
runCurrent()
// The transaction never starts
assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull()
}
@Test
fun `watch - never gets consent so it does nothing`() = runTest {
val analyticsService = FakeAnalyticsService()
val watcher = createAnalyticsColdStartWatcher(analyticsService)
// Start watching
watcher.start()
// The user never gets the analytics consent, so we do nothing
runCurrent()
// The transaction is not running in that case
assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull()
}
private fun TestScope.createAnalyticsColdStartWatcher(
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
) = DefaultAnalyticsColdStartWatcher(
analyticsService = analyticsService,
appCoroutineScope = backgroundScope,
)
}