Merge pull request #802 from vector-im/feature/cjs/stop-editing-dms

This commit is contained in:
Chris Smith 2023-07-10 09:14:43 +01:00 committed by GitHub
commit f059970db1
2 changed files with 89 additions and 20 deletions

View file

@ -22,7 +22,7 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.api.LeaveRoomPresenter
@ -55,14 +55,14 @@ class RoomDetailsPresenter @Inject constructor(
val canEditTopic by getCanSendStateEvent(membersState, StateEventType.ROOM_TOPIC) val canEditTopic by getCanSendStateEvent(membersState, StateEventType.ROOM_TOPIC)
val dmMember by room.getDirectRoomMember(membersState) val dmMember by room.getDirectRoomMember(membersState)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember) val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType = getRoomType(dmMember) val roomType by getRoomType(dmMember)
val topicState = remember(canEditTopic, room.topic) { val topicState = remember(canEditTopic, room.topic, roomType) {
val topic = room.topic val topic = room.topic
when { when {
!topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic) !topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic)
canEditTopic -> RoomTopicState.CanAddTopic canEditTopic && roomType is RoomDetailsType.Room -> RoomTopicState.CanAddTopic
else -> RoomTopicState.Hidden else -> RoomTopicState.Hidden
} }
} }
@ -85,8 +85,8 @@ class RoomDetailsPresenter @Inject constructor(
memberCount = room.joinedMemberCount, memberCount = room.joinedMemberCount,
isEncrypted = room.isEncrypted, isEncrypted = room.isEncrypted,
canInvite = canInvite, canInvite = canInvite,
canEdit = canEditAvatar || canEditName || canEditTopic, canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room,
roomType = roomType.value, roomType = roomType,
roomMemberDetailsState = roomMemberDetailsState, roomMemberDetailsState = roomMemberDetailsState,
leaveRoomState = leaveRoomState, leaveRoomState = leaveRoomState,
eventSink = ::handleEvents, eventSink = ::handleEvents,
@ -112,20 +112,12 @@ class RoomDetailsPresenter @Inject constructor(
} }
@Composable @Composable
private fun getCanInvite(membersState: MatrixRoomMembersState): State<Boolean> { private fun getCanInvite(membersState: MatrixRoomMembersState) = produceState(false, membersState) {
val canInvite = remember(membersState) { mutableStateOf(false) } value = room.canInvite().getOrElse { false }
LaunchedEffect(membersState) {
canInvite.value = room.canInvite().getOrElse { false }
}
return canInvite
} }
@Composable @Composable
private fun getCanSendStateEvent(membersState: MatrixRoomMembersState, type: StateEventType): State<Boolean> { private fun getCanSendStateEvent(membersState: MatrixRoomMembersState, type: StateEventType) = produceState(false, membersState) {
val canSendEvent = remember(membersState) { mutableStateOf(false) } value = room.canSendStateEvent(type).getOrElse { false }
LaunchedEffect(membersState) {
canSendEvent.value = room.canSendStateEvent(type).getOrElse { false }
}
return canSendEvent
} }
} }

View file

@ -20,7 +20,10 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.fake.LeaveRoomPresenterFake import io.element.android.features.leaveroom.fake.LeaveRoomPresenterFake
import io.element.android.features.roomdetails.impl.RoomDetailsEvent
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
import io.element.android.features.roomdetails.impl.RoomDetailsType import io.element.android.features.roomdetails.impl.RoomDetailsType
import io.element.android.features.roomdetails.impl.RoomTopicState import io.element.android.features.roomdetails.impl.RoomTopicState
@ -44,13 +47,13 @@ import org.junit.Test
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
class RoomDetailsPresenterTests { class RoomDetailsPresenterTests {
private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter { private fun aRoomDetailsPresenter(room: MatrixRoom, leaveRoomPresenter: LeaveRoomPresenter = LeaveRoomPresenterFake()): RoomDetailsPresenter {
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMemberId) return RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMemberId)
} }
} }
return RoomDetailsPresenter(room, roomMemberDetailsPresenterFactory, LeaveRoomPresenterFake()) return RoomDetailsPresenter(room, roomMemberDetailsPresenterFactory, leaveRoomPresenter)
} }
@Test @Test
@ -173,6 +176,64 @@ class RoomDetailsPresenterTests {
} }
} }
@Test
fun `present - initial state when user can edit attributes in a DM`() = runTest {
val myRoomMember = aRoomMember(A_SESSION_ID)
val otherRoomMember = aRoomMember(A_USER_ID_2)
val room = aMatrixRoom(
isEncrypted = true,
isDirect = true,
).apply {
val roomMembers = listOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
// Initially false
assertThat(awaitItem().canEdit).isFalse()
// Then the asynchronous check completes, but editing is still disallowed because it's a DM
val settledState = awaitItem()
assertThat(settledState.canEdit).isFalse()
// If there is a topic, it's visible
assertThat(settledState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.topic!!))
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - initial state when in a DM with no topic`() = runTest {
val myRoomMember = aRoomMember(A_SESSION_ID)
val otherRoomMember = aRoomMember(A_USER_ID_2)
val room = aMatrixRoom(
isEncrypted = true,
isDirect = true,
topic = null,
).apply {
val roomMembers = listOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
}
val presenter = aRoomDetailsPresenter(room)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
// There's no topic, so we hide the entire UI for DMs
assertThat(awaitItem().roomTopic).isEqualTo(RoomTopicState.Hidden)
cancelAndIgnoreRemainingEvents()
}
}
@Test @Test
fun `present - initial state when user can edit all attributes`() = runTest { fun `present - initial state when user can edit all attributes`() = runTest {
val room = aMatrixRoom().apply { val room = aMatrixRoom().apply {
@ -247,6 +308,22 @@ class RoomDetailsPresenterTests {
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
} }
} }
@Test
fun `present - leave room event is passed on to leave room presenter`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val room = aMatrixRoom()
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
awaitItem().eventSink(RoomDetailsEvent.LeaveRoom)
assertThat(leaveRoomPresenter.events).contains(LeaveRoomEvent.ShowConfirmation(room.roomId))
cancelAndIgnoreRemainingEvents()
}
}
} }
fun aMatrixRoom( fun aMatrixRoom(