From c4d7d42141c364e0cbb8aaafa7855a080a47c472 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Sep 2025 20:12:26 +0200 Subject: [PATCH] Add notification troubleshoot test about blocked users. --- .../preferences/impl/PreferencesFlowNode.kt | 4 + libraries/permissions/impl/build.gradle.kts | 1 + ...ficationTroubleshootCheckPermissionTest.kt | 6 +- ...tionTroubleshootCheckPermissionTestTest.kt | 3 +- libraries/push/impl/build.gradle.kts | 1 + .../impl/troubleshoot/IgnoredUsersTest.kt | 69 ++++++++++++++ .../impl/troubleshoot/PushLoopbackTest.kt | 6 +- .../impl/src/main/res/values/localazy.xml | 8 ++ .../impl/troubleshoot/IgnoredUsersTestTest.kt | 93 +++++++++++++++++++ .../impl/troubleshoot/PushLoopbackTestTest.kt | 3 +- .../pushproviders/firebase/build.gradle.kts | 1 + .../troubleshoot/FirebaseTokenTest.kt | 6 +- .../troubleshoot/FirebaseTokenTestTest.kt | 3 +- .../unifiedpush/build.gradle.kts | 1 + .../troubleshoot/UnifiedPushTest.kt | 6 +- .../troubleshoot/UnifiedPushTestTest.kt | 3 +- .../api/NotificationTroubleShootEntryPoint.kt | 1 + .../test/NotificationTroubleshootNavigator.kt | 12 +++ .../api/test/NotificationTroubleshootTest.kt | 5 +- .../test/NotificationTroubleshootTestState.kt | 6 +- .../impl/TroubleshootNotificationsNode.kt | 16 +++- .../TroubleshootNotificationsPresenter.kt | 15 ++- .../TroubleshootNotificationsStateProvider.kt | 19 +++- .../impl/TroubleshootNotificationsView.kt | 18 ++-- .../impl/TroubleshootTestSuite.kt | 11 ++- ...tNotificationTroubleShootEntryPointTest.kt | 3 +- .../impl/FakeNotificationTroubleshootTest.kt | 6 +- .../TroubleshootNotificationsPresenterTest.kt | 6 ++ libraries/troubleshoot/test/build.gradle.kts | 18 ++++ .../FakeNotificationTroubleshootNavigator.kt | 17 ++++ tools/localazy/config.json | 1 + 31 files changed, 338 insertions(+), 30 deletions(-) create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt create mode 100644 libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt create mode 100644 libraries/troubleshoot/test/build.gradle.kts create mode 100644 libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 57643e3931..49e297af08 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -208,6 +208,10 @@ class PreferencesFlowNode( navigateUp() } } + + override fun openIgnoredUsers() { + backstack.push(NavTarget.BlockedUsers) + } }) .build() } diff --git a/libraries/permissions/impl/build.gradle.kts b/libraries/permissions/impl/build.gradle.kts index f7811c649d..a4fafda599 100644 --- a/libraries/permissions/impl/build.gradle.kts +++ b/libraries/permissions/impl/build.gradle.kts @@ -43,5 +43,6 @@ dependencies { testCommonDependencies(libs) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.permissions.test) + testImplementation(projects.libraries.troubleshoot.test) testImplementation(projects.services.toolbox.test) } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt index 19cedab019..eeadb5598c 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt @@ -15,6 +15,7 @@ import dev.zacsweers.metro.Inject import io.element.android.libraries.permissions.api.PermissionStateProvider import io.element.android.libraries.permissions.impl.R import io.element.android.libraries.permissions.impl.action.PermissionActions +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState @@ -54,7 +55,10 @@ class NotificationTroubleshootCheckPermissionTest( override suspend fun reset() = delegate.reset() - override suspend fun quickFix(coroutineScope: CoroutineScope) { + override suspend fun quickFix( + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { // Do not bother about asking the permission inline, just lead the user to the settings permissionActions.openSettings() } diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt index a530b8dbee..f6696265bc 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt @@ -13,6 +13,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.permissions.impl.action.FakePermissionActions import io.element.android.libraries.permissions.test.FakePermissionStateProvider import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch @@ -84,7 +85,7 @@ class NotificationTroubleshootCheckPermissionTestTest { assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) // Quick fix launch { - sut.quickFix(this) + sut.quickFix(this, FakeNotificationTroubleshootNavigator()) // Run the test again (IRL it will be done thanks to the resuming of the application) sut.run(this) } diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index 3e186d31da..fa0f65cb8f 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -72,6 +72,7 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushproviders.test) testImplementation(projects.libraries.pushstore.test) + testImplementation(projects.libraries.troubleshoot.test) testImplementation(projects.features.call.test) testImplementation(projects.features.lockscreen.test) testImplementation(projects.services.appnavstate.test) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt new file mode 100644 index 0000000000..93e855c94d --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt @@ -0,0 +1,69 @@ +/* + * 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.libraries.push.impl.troubleshoot + +import dev.zacsweers.metro.ContributesIntoSet +import dev.zacsweers.metro.Inject +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.push.impl.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.api.strings.StringProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow + +@ContributesIntoSet(SessionScope::class) +@Inject +class IgnoredUsersTest( + private val matrixClient: MatrixClient, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order = 80 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_description), + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val ignorerUsers = matrixClient.ignoredUsersFlow.value + if (ignorerUsers.isEmpty()) { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_result_none), + status = NotificationTroubleshootTestState.Status.Success, + ) + } else { + delegate.updateState( + description = stringProvider.getQuantityString( + R.plurals.troubleshoot_notifications_test_blocked_users_result_some, + ignorerUsers.size, + ignorerUsers.size + ), + status = NotificationTroubleshootTestState.Status.Failure( + hasQuickFix = true, + isCritical = false, + quickFixButtonString = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_quick_fix), + ), + ) + } + } + + override suspend fun quickFix( + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { + navigator.openIgnoredUsers() + } + + override suspend fun reset() = delegate.reset() +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt index fede1a7099..3845b68b18 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt @@ -13,6 +13,7 @@ import dev.zacsweers.metro.Inject import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.api.gateway.PushGatewayFailure import io.element.android.libraries.push.impl.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState @@ -99,7 +100,10 @@ class PushLoopbackTest( ) } - override suspend fun quickFix(coroutineScope: CoroutineScope) { + override suspend fun quickFix( + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { delegate.start() pushService.getCurrentPushProvider()?.rotateToken() run(coroutineScope) diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index 069183a3a2..8e9da34b73 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -54,6 +54,14 @@ "Background synchronization" "Google Services" "No valid Google Play Services found. Notifications may not work properly." + "Checking blocked users" + "View blocked users" + "No users are blocked." + + "You blocked %1$d user. You will not receive notifications for this user." + "You blocked %1$d users. You will not receive notifications for these users." + + "Blocked users" "Get the name of the current provider." "No push providers selected." "Current push provider: %1$s." diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt new file mode 100644 index 0000000000..ed1cf93fce --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt @@ -0,0 +1,93 @@ +/* + * 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.libraries.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.A_USER_ID_2 +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class IgnoredUsersTestTest { + @Test + fun `test IgnoredUsersTest order`() = runTest { + val sut = IgnoredUsersTest( + matrixClient = FakeMatrixClient(), + stringProvider = FakeStringProvider(), + ) + assertThat(sut.order).isEqualTo(80) + } + + @Test + fun `test IgnoredUsersTest quick fix`() = runTest { + val sut = IgnoredUsersTest( + matrixClient = FakeMatrixClient(), + stringProvider = FakeStringProvider(), + ) + val openIgnoredUsersResult = lambdaRecorder {} + val navigator = object : NotificationTroubleshootNavigator { + override fun openIgnoredUsers() = openIgnoredUsersResult() + } + sut.quickFix( + coroutineScope = backgroundScope, + navigator = navigator, + ) + openIgnoredUsersResult.assertions().isCalledOnce() + } + + @Test + fun `test IgnoredUsersTest with no blocked users`() = runTest { + val sut = IgnoredUsersTest( + matrixClient = FakeMatrixClient( + ignoredUsersFlow = MutableStateFlow(persistentListOf()) + ), + stringProvider = FakeStringProvider(), + ) + backgroundScope.launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + @Test + fun `test IgnoredUsersTest with blocked users`() = runTest { + val sut = IgnoredUsersTest( + matrixClient = FakeMatrixClient( + ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2)) + ), + stringProvider = FakeStringProvider(), + ) + backgroundScope.launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + val lastStatus = lastItem.status as NotificationTroubleshootTestState.Status.Failure + assertThat(lastStatus.hasQuickFix).isTrue() + assertThat(lastStatus.isCritical).isFalse() + assertThat(lastStatus.quickFixButtonString).isNotNull() + assertThat(lastItem.description).contains("2") + } + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt index e4fa26a28b..182ffd89a8 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt @@ -15,6 +15,7 @@ import io.element.android.libraries.push.api.gateway.PushGatewayFailure import io.element.android.libraries.push.test.FakePushService import io.element.android.libraries.pushproviders.test.FakePushProvider import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -97,7 +98,7 @@ class PushLoopbackTestTest { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) val lastItem = awaitItem() assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) - sut.quickFix(this) + sut.quickFix(this, FakeNotificationTroubleshootNavigator()) assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) rotateTokenLambda.assertions().isCalledOnce() diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index acb12ae150..05e60920e5 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -76,5 +76,6 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.libraries.sessionStorage.test) + testImplementation(projects.libraries.troubleshoot.test) testImplementation(projects.services.toolbox.test) } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt index 2d6ec699da..ae70114971 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.FirebaseStore import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter import io.element.android.libraries.pushproviders.firebase.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState @@ -71,7 +72,10 @@ class FirebaseTokenTest( override suspend fun reset() = delegate.reset() - override suspend fun quickFix(coroutineScope: CoroutineScope) { + override suspend fun quickFix( + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { delegate.start() firebaseTroubleshooter.troubleshoot() run(coroutineScope) diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt index b66f094d76..b7a39feb9f 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.libraries.troubleshoot.api.test.TestFilterData +import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest @@ -62,7 +63,7 @@ class FirebaseTokenTestTest { val lastItem = awaitItem() assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) // Quick fix - sut.quickFix(this) + sut.quickFix(this, FakeNotificationTroubleshootNavigator()) assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) } diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index c2ec6b3d1c..e416ccf8bf 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -51,6 +51,7 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushproviders.test) testImplementation(projects.libraries.pushstore.test) + testImplementation(projects.libraries.troubleshoot.test) testImplementation(projects.services.toolbox.test) testImplementation(projects.services.appnavstate.test) } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt index 40f8115fd6..6791a901db 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt @@ -13,6 +13,7 @@ import dev.zacsweers.metro.Inject import io.element.android.libraries.pushproviders.unifiedpush.R import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState @@ -64,7 +65,10 @@ class UnifiedPushTest( override suspend fun reset() = delegate.reset() - override suspend fun quickFix(coroutineScope: CoroutineScope) { + override suspend fun quickFix( + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { openDistributorWebPageAction.execute() } } diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt index 7b9059a324..53d85c7fea 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt @@ -13,6 +13,7 @@ import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.libraries.troubleshoot.api.test.TestFilterData +import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest @@ -67,7 +68,7 @@ class UnifiedPushTestTest { assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) // Quick fix launch { - sut.quickFix(this) + sut.quickFix(this, FakeNotificationTroubleshootNavigator()) sut.run(this) } assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt index bc3b019ba3..bf0c6bb883 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt @@ -22,5 +22,6 @@ interface NotificationTroubleShootEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onDone() + fun openIgnoredUsers() } } diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt new file mode 100644 index 0000000000..0cce358072 --- /dev/null +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt @@ -0,0 +1,12 @@ +/* + * 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.libraries.troubleshoot.api.test + +interface NotificationTroubleshootNavigator { + fun openIgnoredUsers() +} diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt index d20370729e..f729cd3300 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt @@ -16,7 +16,10 @@ interface NotificationTroubleshootTest { fun isRelevant(data: TestFilterData): Boolean = true suspend fun run(coroutineScope: CoroutineScope) suspend fun reset() - suspend fun quickFix(coroutineScope: CoroutineScope) { + suspend fun quickFix( + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { error("Quick fix not implemented, you need to override this method in your test") } } diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt index d845332df3..ffb6d9a26a 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt @@ -17,6 +17,10 @@ data class NotificationTroubleshootTestState( data object InProgress : Status data object WaitingForUser : Status data object Success : Status - data class Failure(val hasQuickFix: Boolean) : Status + data class Failure( + val hasQuickFix: Boolean, + val isCritical: Boolean = true, + val quickFixButtonString: String? = null, + ) : Status } } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt index 1d113aee64..fcd9306171 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt @@ -19,6 +19,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.services.analytics.api.ScreenTracker @ContributesNode(SessionScope::class) @@ -26,15 +27,26 @@ import io.element.android.services.analytics.api.ScreenTracker class TroubleshootNotificationsNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val presenter: TroubleshootNotificationsPresenter, private val screenTracker: ScreenTracker, -) : Node(buildContext, plugins = plugins) { + factory: TroubleshootNotificationsPresenter.Factory, +) : Node(buildContext, plugins = plugins), + NotificationTroubleshootNavigator { + private val presenter = factory.create( + navigator = this, + ) + private fun onDone() { plugins().forEach { it.onDone() } } + override fun openIgnoredUsers() { + plugins().forEach { + it.openIgnoredUsers() + } + } + @Composable override fun View(modifier: Modifier) { screenTracker.TrackScreen(MobileScreen.ScreenName.NotificationTroubleshoot) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt index 753949f71b..b1c3c2d9b9 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt @@ -12,14 +12,23 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import kotlinx.coroutines.launch @Inject class TroubleshootNotificationsPresenter( + @Assisted private val navigator: NotificationTroubleshootNavigator, private val troubleshootTestSuite: TroubleshootTestSuite, ) : Presenter { + @AssistedFactory + fun interface Factory { + fun create(navigator: NotificationTroubleshootNavigator): TroubleshootNotificationsPresenter + } + @Composable override fun present(): TroubleshootNotificationsState { val coroutineScope = rememberCoroutineScope() @@ -34,7 +43,11 @@ class TroubleshootNotificationsPresenter( troubleshootTestSuite.runTestSuite(this) } is TroubleshootNotificationsEvents.QuickFix -> coroutineScope.launch { - troubleshootTestSuite.quickFix(event.testIndex, this) + troubleshootTestSuite.quickFix( + testIndex = event.testIndex, + coroutineScope = this, + navigator = navigator, + ) } TroubleshootNotificationsEvents.RetryFailedTests -> coroutineScope.launch { troubleshootTestSuite.retryFailedTest(this) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt index 4ea9355a8c..8c2708ed9d 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt @@ -31,8 +31,12 @@ open class TroubleshootNotificationsStateProvider : PreviewParameterProvider Unit, ) { - if ((testState.status as? Status.Idle)?.visible == false) return + val status = testState.status + if ((status as? Status.Idle)?.visible == false) return ListItem( headlineContent = { Text(text = testState.name) }, supportingContent = { Text(text = testState.description) }, - trailingContent = when (testState.status) { + trailingContent = when (status) { is Status.Idle -> null Status.InProgress -> ListItemContent.Custom { CircularProgressIndicator( @@ -98,20 +99,19 @@ private fun ColumnScope.TroubleshootTestView( Icon( contentDescription = null, modifier = Modifier.size(24.dp), - imageVector = CompoundIcons.ErrorSolid(), - tint = ElementTheme.colors.textCriticalPrimary + imageVector = if (status.isCritical) CompoundIcons.ErrorSolid() else CompoundIcons.Warning(), + tint = ElementTheme.colors.iconCriticalPrimary, ) } } ) - if ((testState.status as? Status.Failure)?.hasQuickFix == true) { + if (status is Status.Failure && status.hasQuickFix) { ListItem( - headlineContent = { - }, + headlineContent = { }, trailingContent = ListItemContent.Custom { Button( - text = stringResource(id = R.string.troubleshoot_notifications_screen_quick_fix_action), - onClick = onQuickFixClick + text = status.quickFixButtonString ?: stringResource(id = R.string.troubleshoot_notifications_screen_quick_fix_action), + onClick = onQuickFixClick, ) } ) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt index d0fd005307..c26510fc92 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt @@ -11,6 +11,7 @@ import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.NotificationTroubleshoot import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.libraries.troubleshoot.api.test.TestFilterData @@ -90,8 +91,12 @@ class TroubleshootTestSuite( ) } - suspend fun quickFix(testIndex: Int, coroutineScope: CoroutineScope) { - tests[testIndex].quickFix(coroutineScope) + suspend fun quickFix( + testIndex: Int, + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { + tests[testIndex].quickFix(coroutineScope, navigator) } } @@ -104,7 +109,7 @@ fun List.computeMainState(): AsyncAction { if (any { it.status is NotificationTroubleshootTestState.Status.WaitingForUser }) { AsyncAction.ConfirmingNoParams - } else if (any { it.status is NotificationTroubleshootTestState.Status.Failure }) { + } else if (any { it.status.let { status -> status is NotificationTroubleshootTestState.Status.Failure && status.isCritical } }) { AsyncAction.Failure(Exception("Some tests failed")) } else { AsyncAction.Success(Unit) diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt index d9bf607531..e0d817a4ca 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt @@ -28,12 +28,13 @@ class DefaultNotificationTroubleShootEntryPointTest { TroubleshootNotificationsNode( buildContext = buildContext, plugins = plugins, - presenter = createTroubleshootNotificationsPresenter(), + factory = { createTroubleshootNotificationsPresenter() }, screenTracker = FakeScreenTracker(), ) } val callback = object : NotificationTroubleShootEntryPoint.Callback { override fun onDone() = lambdaError() + override fun openIgnoredUsers() = lambdaError() } val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) .callback(callback) diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt index bfd4025a2a..7fa2e93d85 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.troubleshoot.impl +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import kotlinx.coroutines.CoroutineScope @@ -50,7 +51,10 @@ class FakeNotificationTroubleshootTest( } } - override suspend fun quickFix(coroutineScope: CoroutineScope) { + override suspend fun quickFix( + coroutineScope: CoroutineScope, + navigator: NotificationTroubleshootNavigator, + ) { updateState(NotificationTroubleshootTestState.Status.InProgress) quickFixAction()?.let { _state.emit(it) diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt index 211fa595b5..a3a32bca46 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt @@ -13,9 +13,11 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.lambda.lambdaError import kotlinx.coroutines.test.runTest import org.junit.Test @@ -111,9 +113,13 @@ private fun createTroubleshootTestSuite( } internal fun createTroubleshootNotificationsPresenter( + navigator: NotificationTroubleshootNavigator = object : NotificationTroubleshootNavigator { + override fun openIgnoredUsers() = lambdaError() + }, troubleshootTestSuite: TroubleshootTestSuite = createTroubleshootTestSuite(), ): TroubleshootNotificationsPresenter { return TroubleshootNotificationsPresenter( + navigator = navigator, troubleshootTestSuite = troubleshootTestSuite, ) } diff --git a/libraries/troubleshoot/test/build.gradle.kts b/libraries/troubleshoot/test/build.gradle.kts new file mode 100644 index 0000000000..664c4e4d1d --- /dev/null +++ b/libraries/troubleshoot/test/build.gradle.kts @@ -0,0 +1,18 @@ +/* + * 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. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.troubleshoot.test" +} + +dependencies { + implementation(projects.libraries.troubleshoot.api) + implementation(projects.tests.testutils) +} diff --git a/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt new file mode 100644 index 0000000000..63445e5a3e --- /dev/null +++ b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt @@ -0,0 +1,17 @@ +/* + * 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.libraries.troubleshoot.test + +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeNotificationTroubleshootNavigator( + private val openIgnoredUsersResult: () -> Unit = { lambdaError() }, +) : NotificationTroubleshootNavigator { + override fun openIgnoredUsers() = openIgnoredUsersResult() +} diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 0aac7e51af..4264b35229 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -123,6 +123,7 @@ "includeRegex" : [ "push_.*", "notification_.*", + "troubleshoot_notifications\\.test_blocked_users\\..*", "troubleshoot_notifications_test_current_push_provider.*", "troubleshoot_notifications_test_detect_push_provider.*", "troubleshoot_notifications_test_display_notification_.*",