From 75e95333a803c93afc7c62f24eaa124ed14f08d5 Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 24 Oct 2023 21:39:53 +0100 Subject: [PATCH] Unit and Snapshot tests for error and loading states. --- .../NotificationSettingsStateProvider.kt | 8 ++- ...DefaultNotificationSettingStateProvider.kt | 11 ++-- ...faultNotificationSettingsPresenterTests.kt | 22 ++++++++ .../NotificationSettingsPresenterTests.kt | 30 +++++++++++ .../RoomNotificationSettingsStateProvider.kt | 52 +++++++++---------- .../RoomNotificationSettingsPresenterTests.kt | 49 +++++++++++++---- .../FakeNotificationSettingsService.kt | 28 ++++++++++ 7 files changed, 159 insertions(+), 41 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index cfff59e905..9336857585 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -24,10 +24,14 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider get() = sequenceOf( aNotificationSettingsState(), + aNotificationSettingsState(changeNotificationSettingAction = Async.Loading(Unit)), + aNotificationSettingsState(changeNotificationSettingAction = Async.Failure(Throwable("error"))), ) } -fun aNotificationSettingsState() = NotificationSettingsState( +fun aNotificationSettingsState( + changeNotificationSettingAction: Async = Async.Uninitialized, +) = NotificationSettingsState( matrixSettings = NotificationSettingsState.MatrixSettings.Valid( atRoomNotificationsEnabled = true, callNotificationsEnabled = true, @@ -38,6 +42,6 @@ fun aNotificationSettingsState() = NotificationSettingsState( systemNotificationsEnabled = false, appNotificationsEnabled = true, ), - changeNotificationSettingAction = Async.Uninitialized, + changeNotificationSettingAction = changeNotificationSettingAction, eventSink = {} ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 2ff02c008c..71fbfb1e1b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -27,15 +27,20 @@ open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider override val values: Sequence get() = sequenceOf( anEditDefaultNotificationSettingsState(), - anEditDefaultNotificationSettingsState(isOneToOne = true) + anEditDefaultNotificationSettingsState(isOneToOne = true), + anEditDefaultNotificationSettingsState(changeNotificationSettingAction = Async.Loading(Unit)), + anEditDefaultNotificationSettingsState(changeNotificationSettingAction = Async.Failure(Throwable("error"))), ) } -fun anEditDefaultNotificationSettingsState(isOneToOne: Boolean = false) = EditDefaultNotificationSettingState( +private fun anEditDefaultNotificationSettingsState( + isOneToOne: Boolean = false, + changeNotificationSettingAction: Async = Async.Uninitialized +) = EditDefaultNotificationSettingState( isOneToOne = isOneToOne, mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, roomsWithUserDefinedMode = listOf(aRoomSummary()), - changeNotificationSettingAction = Async.Uninitialized, + changeNotificationSettingAction = changeNotificationSettingAction, eventSink = {} ) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt index 2269df46e1..f4cc7c80e9 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt @@ -25,6 +25,7 @@ import io.element.android.features.preferences.impl.notifications.edit.EditDefau import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -90,6 +91,27 @@ class EditDefaultNotificationSettingsPresenterTests { } } + @Test + fun `present - edit default notification setting failed`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) + notificationSettingsService.givenSetDefaultNotificationModeError(A_THROWABLE) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) + val errorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isFailure() + }.last() + Truth.assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue() + errorState.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) + val clearErrorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isUninitialized() + }.last() + Truth.assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue() + } + } + private fun createEditDefaultNotificationSettingPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), roomListService: FakeRoomListService = FakeRoomListService(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt index cc2c0a7035..c3d6f51256 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt @@ -22,6 +22,7 @@ import app.cash.turbine.test import com.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory import com.google.common.truth.Truth import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.tests.testutils.consumeItemsUntilPredicate @@ -187,6 +188,35 @@ class NotificationSettingsPresenterTests { } } + @Test + fun `present - clear notification settings change error`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + val presenter = createNotificationSettingsPresenter(notificationSettingsService) + notificationSettingsService.givenSetAtRoomError(A_THROWABLE) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val loadedState = consumeItemsUntilPredicate { + (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == false + }.last() + val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid + Truth.assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse() + + loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true)) + val errorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isFailure() + }.last() + Truth.assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue() + errorState.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) + + val clearErrorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isUninitialized() + }.last() + Truth.assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue() + cancelAndIgnoreRemainingEvents() + } + } + private fun createNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ) : NotificationSettingsPresenter { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index cfc82585f0..2df0edede7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -24,31 +24,31 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettings internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - RoomNotificationSettingsState( - showUserDefinedSettingStyle = false, - roomName = "Room 1", - Async.Success(RoomNotificationSettings( - mode = RoomNotificationMode.MUTE, - isDefault = true)), - pendingRoomNotificationMode = null, - pendingSetDefault = null, - defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, - setNotificationSettingAction = Async.Uninitialized, - restoreDefaultAction = Async.Uninitialized, - eventSink = { }, - ), - RoomNotificationSettingsState( - showUserDefinedSettingStyle = false, - roomName = "Room 1", - Async.Success(RoomNotificationSettings( - mode = RoomNotificationMode.MUTE, - isDefault = false)), - pendingRoomNotificationMode = null, - pendingSetDefault = null, - defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, - setNotificationSettingAction = Async.Uninitialized, - restoreDefaultAction = Async.Uninitialized, - eventSink = { }, - ), + aRoomNotificationSettingsState(), + aRoomNotificationSettingsState(isDefault = false), + aRoomNotificationSettingsState(setNotificationSettingAction = Async.Loading(Unit)), + aRoomNotificationSettingsState(setNotificationSettingAction = Async.Failure(Throwable("error"))), + aRoomNotificationSettingsState(restoreDefaultAction = Async.Loading(Unit)), + aRoomNotificationSettingsState(restoreDefaultAction = Async.Failure(Throwable("error"))), ) + + private fun aRoomNotificationSettingsState( + isDefault: Boolean = true, + setNotificationSettingAction: Async = Async.Uninitialized, + restoreDefaultAction: Async = Async.Uninitialized, + ): RoomNotificationSettingsState { + return RoomNotificationSettingsState( + showUserDefinedSettingStyle = false, + roomName = "Room 1", + Async.Success(RoomNotificationSettings( + mode = RoomNotificationMode.MUTE, + isDefault = isDefault)), + pendingRoomNotificationMode = null, + pendingSetDefault = null, + defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, + setNotificationSettingAction = setNotificationSettingAction, + restoreDefaultAction = restoreDefaultAction, + eventSink = { }, + ) + } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt index 31d7973109..c4134462f7 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt @@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest import org.junit.Test -import kotlin.time.Duration.Companion.milliseconds class RoomNotificationSettingsPresenterTests { @Test @@ -76,7 +75,6 @@ class RoomNotificationSettingsPresenterTests { } } - @Test fun `present - notification settings set custom failed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() @@ -87,13 +85,20 @@ class RoomNotificationSettingsPresenterTests { }.test { val initialState = awaitItem() initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false)) - val states = consumeItemsUntilPredicate { - it.roomNotificationSettings.dataOrNull()?.isDefault == false - } - states.forEach { - Truth.assertThat(it.roomNotificationSettings.dataOrNull()?.isDefault).isTrue() - Truth.assertThat(it.pendingSetDefault).isNull() - } + val failedState = consumeItemsUntilPredicate { + it.setNotificationSettingAction.isFailure() + }.last() + + Truth.assertThat(failedState.roomNotificationSettings.dataOrNull()?.isDefault).isTrue() + Truth.assertThat(failedState.pendingSetDefault).isNull() + Truth.assertThat(failedState.setNotificationSettingAction.isFailure()).isTrue() + + failedState.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) + + val errorClearedState = consumeItemsUntilPredicate { + it.setNotificationSettingAction.isUninitialized() + }.last() + Truth.assertThat(errorClearedState.setNotificationSettingAction.isUninitialized()).isTrue() } } @@ -122,7 +127,7 @@ class RoomNotificationSettingsPresenterTests { val initialState = awaitItem() initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) - val defaultState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) { + val defaultState = consumeItemsUntilPredicate { it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() Truth.assertThat(defaultState.roomNotificationSettings.dataOrNull()?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) @@ -130,6 +135,30 @@ class RoomNotificationSettingsPresenterTests { } } + @Test + fun `present - notification settings restore default failed`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + notificationSettingsService.givenRestoreDefaultNotificationModeError(A_THROWABLE) + val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) + initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) + val failedState = consumeItemsUntilPredicate { + it.restoreDefaultAction.isFailure() + }.last() + Truth.assertThat(failedState.restoreDefaultAction.isFailure()).isTrue() + failedState.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) + + val errorClearedState = consumeItemsUntilPredicate { + it.restoreDefaultAction.isUninitialized() + }.last() + Truth.assertThat(errorClearedState.restoreDefaultAction.isUninitialized()).isTrue() + cancelAndIgnoreRemainingEvents() + } + } private fun createRoomNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ): RoomNotificationSettingsPresenter{ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt index 7d7fb1a36a..039b855e7e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt @@ -43,6 +43,9 @@ class FakeNotificationSettingsService( private var callNotificationsEnabled = false private var atRoomNotificationsEnabled = false private var setNotificationModeError: Throwable? = null + private var restoreDefaultNotificationModeError: Throwable? = null + private var setDefaultNotificationModeError: Throwable? = null + private var setAtRoomError: Throwable? = null override val notificationSettingsChangeFlow: SharedFlow get() = _notificationSettingsStateFlow @@ -72,6 +75,10 @@ class FakeNotificationSettingsService( } override suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result { + val error = setDefaultNotificationModeError + if (error != null) { + return Result.failure(error) + } if (isOneToOne) { if (isEncrypted) { defaultEncryptedOneToOneRoomNotificationMode = mode @@ -102,6 +109,10 @@ class FakeNotificationSettingsService( } override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result { + val error = restoreDefaultNotificationModeError + if (error != null) { + return Result.failure(error) + } roomNotificationModeIsDefault = true roomNotificationMode = defaultEncryptedGroupRoomNotificationMode _notificationSettingsStateFlow.emit(Unit) @@ -121,6 +132,10 @@ class FakeNotificationSettingsService( } override suspend fun setRoomMentionEnabled(enabled: Boolean): Result { + val error = setAtRoomError + if (error != null) { + return Result.failure(error) + } atRoomNotificationsEnabled = enabled return Result.success(Unit) } @@ -141,4 +156,17 @@ class FakeNotificationSettingsService( fun givenSetNotificationModeError(throwable: Throwable?) { setNotificationModeError = throwable } + + fun givenRestoreDefaultNotificationModeError(throwable: Throwable?) { + restoreDefaultNotificationModeError = throwable + } + + fun givenSetAtRoomError(throwable: Throwable?) { + setAtRoomError = throwable + } + + fun givenSetDefaultNotificationModeError(throwable: Throwable?) { + setDefaultNotificationModeError = throwable + } + }