Favorite : rework and add tests
This commit is contained in:
parent
a63d331f36
commit
7ca931c35f
21 changed files with 454 additions and 144 deletions
|
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.lifecycle.Lifecycle
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
|
|
@ -44,7 +45,6 @@ import io.element.android.libraries.matrix.api.room.StateEventType
|
|||
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
|
||||
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
|
||||
import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags
|
||||
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -61,6 +61,7 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
private val roomMembersDetailsPresenterFactory: RoomMemberDetailsPresenter.Factory,
|
||||
private val leaveRoomPresenter: LeaveRoomPresenter,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val setRoomIsFavorite: SetRoomIsFavoriteAction,
|
||||
) : Presenter<RoomDetailsState> {
|
||||
@Composable
|
||||
override fun present(): RoomDetailsState {
|
||||
|
|
@ -126,9 +127,8 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
is RoomDetailsEvent.SetIsFavorite -> {
|
||||
scope.launch(dispatchers.io) {
|
||||
val tags = RoomNotableTags(isFavorite = event.isFavorite)
|
||||
room.updateNotableTags(tags)
|
||||
scope.launch {
|
||||
setRoomIsFavorite(room, event.isFavorite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,18 @@ package io.element.android.features.roomdetails
|
|||
import androidx.lifecycle.Lifecycle
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.TurbineTestContext
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
|
||||
import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction
|
||||
import io.element.android.features.roomactions.test.FakeSetRoomIsFavoriteAction
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsEvent
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsState
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsType
|
||||
import io.element.android.features.roomdetails.impl.RoomTopicState
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||
|
|
@ -40,6 +44,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
|||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
|
|
@ -71,10 +76,11 @@ class RoomDetailsPresenterTests {
|
|||
}
|
||||
|
||||
private fun TestScope.createRoomDetailsPresenter(
|
||||
room: MatrixRoom,
|
||||
room: MatrixRoom = aMatrixRoom(),
|
||||
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService()
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
setRoomIsFavoriteAction: SetRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction(),
|
||||
): RoomDetailsPresenter {
|
||||
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
||||
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
|
||||
|
|
@ -86,25 +92,30 @@ class RoomDetailsPresenterTests {
|
|||
mapOf(FeatureFlags.NotificationSettings.key to true)
|
||||
)
|
||||
return RoomDetailsPresenter(
|
||||
matrixClient,
|
||||
room,
|
||||
featureFlagService,
|
||||
matrixClient.notificationSettingsService(),
|
||||
roomMemberDetailsPresenterFactory,
|
||||
leaveRoomPresenter,
|
||||
dispatchers
|
||||
client = matrixClient,
|
||||
room = room,
|
||||
featureFlagService = featureFlagService,
|
||||
notificationSettingsService = matrixClient.notificationSettingsService(),
|
||||
roomMembersDetailsPresenterFactory = roomMemberDetailsPresenterFactory,
|
||||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
dispatchers = dispatchers,
|
||||
setRoomIsFavorite = setRoomIsFavoriteAction,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun RoomDetailsPresenter.test(validate: suspend TurbineTestContext<RoomDetailsState>.() -> Unit) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
present()
|
||||
}
|
||||
}.test(validate = validate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - initial state is created from room if roomInfo is null`() = runTest {
|
||||
val room = aMatrixRoom()
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomId).isEqualTo(room.roomId.value)
|
||||
assertThat(initialState.roomName).isEqualTo(room.name)
|
||||
|
|
@ -124,11 +135,7 @@ class RoomDetailsPresenterTests {
|
|||
givenRoomInfo(roomInfo)
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val updatedState = awaitItem()
|
||||
assertThat(updatedState.roomName).isEqualTo(roomInfo.name)
|
||||
|
|
@ -143,11 +150,7 @@ class RoomDetailsPresenterTests {
|
|||
fun `present - initial state with no room name`() = runTest {
|
||||
val room = aMatrixRoom(name = null)
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomName).isEqualTo(room.displayName)
|
||||
|
||||
|
|
@ -167,11 +170,7 @@ class RoomDetailsPresenterTests {
|
|||
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
|
||||
|
||||
|
|
@ -185,11 +184,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(true))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canInvite).isFalse()
|
||||
// Then the asynchronous check completes and it becomes true
|
||||
|
|
@ -205,11 +200,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
assertThat(awaitItem().canInvite).isFalse()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
|
|
@ -222,11 +213,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.failure(Throwable("Whoops")))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
assertThat(awaitItem().canInvite).isFalse()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
|
|
@ -242,11 +229,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
// Then the asynchronous check completes and it becomes true
|
||||
|
|
@ -273,11 +256,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
// Then the asynchronous check completes, but editing is still disallowed because it's a DM
|
||||
|
|
@ -305,11 +284,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
|
||||
// There's no topic, so we hide the entire UI for DMs
|
||||
|
|
@ -328,11 +303,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
// Then the asynchronous check completes and it becomes true
|
||||
|
|
@ -351,11 +322,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false, and no further events
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
|
||||
|
|
@ -371,11 +338,7 @@ class RoomDetailsPresenterTests {
|
|||
}
|
||||
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// The initial state is "hidden" and no further state changes happen
|
||||
assertThat(awaitItem().roomTopic).isEqualTo(RoomTopicState.Hidden)
|
||||
|
||||
|
|
@ -392,11 +355,7 @@ class RoomDetailsPresenterTests {
|
|||
}
|
||||
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Ignore the initial state
|
||||
skipItems(1)
|
||||
|
||||
|
|
@ -416,11 +375,7 @@ class RoomDetailsPresenterTests {
|
|||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
dispatchers = testCoroutineDispatchers()
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
awaitItem().eventSink(RoomDetailsEvent.LeaveRoom)
|
||||
|
||||
assertThat(leaveRoomPresenter.events).contains(LeaveRoomEvent.ShowConfirmation(room.roomId))
|
||||
|
|
@ -439,11 +394,7 @@ class RoomDetailsPresenterTests {
|
|||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||
val updatedState = consumeItemsUntilPredicate {
|
||||
it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||
|
|
@ -458,11 +409,7 @@ class RoomDetailsPresenterTests {
|
|||
val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
||||
val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
awaitItem().eventSink(RoomDetailsEvent.MuteNotification)
|
||||
val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) {
|
||||
it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE
|
||||
|
|
@ -480,11 +427,7 @@ class RoomDetailsPresenterTests {
|
|||
)
|
||||
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
||||
val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification)
|
||||
val updatedState = consumeItemsUntilPredicate {
|
||||
it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES
|
||||
|
|
@ -493,6 +436,35 @@ class RoomDetailsPresenterTests {
|
|||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
|
||||
val setRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction()
|
||||
val presenter = createRoomDetailsPresenter(setRoomIsFavoriteAction = setRoomIsFavoriteAction)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(RoomDetailsEvent.SetIsFavorite(true))
|
||||
setRoomIsFavoriteAction.assertCalled(1)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - changes in notable tags updates is favorite flag`() = runTest {
|
||||
val room = aMatrixRoom()
|
||||
val presenter = createRoomDetailsPresenter(room = room)
|
||||
presenter.test {
|
||||
room.updateNotableTags(RoomNotableTags(true))
|
||||
consumeItemsUntilPredicate { it.isFavorite }.last().let { state ->
|
||||
assertThat(state.isFavorite).isTrue()
|
||||
}
|
||||
room.updateNotableTags(RoomNotableTags(false))
|
||||
consumeItemsUntilPredicate { !it.isFavorite }.last().let { state ->
|
||||
assertThat(state.isFavorite).isFalse()
|
||||
}
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun aMatrixRoom(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue