feature (room upgrade) : add tests
This commit is contained in:
@@ -88,6 +88,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||
),
|
||||
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.Verified),
|
||||
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.VerificationViolation),
|
||||
aMessagesState(successorRoom = SuccessorRoom(RoomId("!id:domain"), null)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ package io.element.android.features.messages.impl
|
||||
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
@@ -20,6 +21,7 @@ class FakeMessagesNavigator(
|
||||
private val onReportContentClickLambda: (eventId: EventId, senderId: UserId) -> Unit = { _, _ -> lambdaError() },
|
||||
private val onEditPollClickLambda: (eventId: EventId) -> Unit = { _ -> lambdaError() },
|
||||
private val onPreviewAttachmentLambda: (attachments: ImmutableList<Attachment>) -> Unit = { _ -> lambdaError() },
|
||||
private val onNavigateToRoomLambda: (roomId: RoomId) -> Unit = { _ -> lambdaError() }
|
||||
) : MessagesNavigator {
|
||||
override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
||||
onShowEventDebugInfoClickLambda(eventId, debugInfo)
|
||||
@@ -40,4 +42,8 @@ class FakeMessagesNavigator(
|
||||
override fun onPreviewAttachment(attachments: ImmutableList<Attachment>) {
|
||||
onPreviewAttachmentLambda(attachments)
|
||||
}
|
||||
|
||||
override fun onNavigateToRoom(roomId: RoomId) {
|
||||
onNavigateToRoomLambda(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
@@ -57,6 +58,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
@@ -1130,6 +1132,57 @@ class MessagesPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - room with successor room includes successor info in state`() = runTest {
|
||||
val successorRoomId = RoomId("!successor:server.org")
|
||||
val successorReason = "This room has been moved to a new location"
|
||||
val room = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
initialRoomInfo = aRoomInfo(
|
||||
successorRoom = SuccessorRoom(
|
||||
roomId = successorRoomId,
|
||||
reason = successorReason
|
||||
)
|
||||
)
|
||||
),
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(joinedRoom = room)
|
||||
presenter.testWithLifecycleOwner {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.successorRoom).isNotNull()
|
||||
assertThat(initialState.successorRoom?.roomId).isEqualTo(successorRoomId)
|
||||
assertThat(initialState.successorRoom?.reason).isEqualTo(successorReason)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - room without successor room has null successor info in state`() = runTest {
|
||||
val room = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
initialRoomInfo = aRoomInfo(successorRoom = null)
|
||||
),
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(joinedRoom = room)
|
||||
presenter.testWithLifecycleOwner {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.successorRoom).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is encrypted and a DM, the DM user's identity state is fetched onResume`() = runTest {
|
||||
val room = FakeJoinedRoom(
|
||||
|
||||
@@ -52,7 +52,9 @@ import io.element.android.features.messages.impl.timeline.components.receipt.aRe
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
@@ -65,6 +67,7 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParamsAndResult
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.assertNoNodeWithText
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
@@ -554,6 +557,36 @@ class MessagesViewTest {
|
||||
rule.onNodeWithText("This is a pinned message").performClick()
|
||||
eventsRecorder.assertSingle(TimelineEvents.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on successor room button emits expected event`() {
|
||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
||||
val successorRoomId = RoomId("!successor:server.org")
|
||||
val state = aMessagesState(
|
||||
successorRoom = SuccessorRoom(
|
||||
roomId = successorRoomId,
|
||||
reason = "This room has been upgraded"
|
||||
),
|
||||
timelineState = aTimelineState(eventSink = eventsRecorder)
|
||||
)
|
||||
rule.setMessagesView(state = state)
|
||||
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
||||
// The bottomsheet subcompose seems to make the node to appear twice
|
||||
rule.onAllNodesWithText(text).onFirst().performClick()
|
||||
eventsRecorder.assertSingle(TimelineEvents.NavigateToRoom(successorRoomId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no banner shown when there is no successor room`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
successorRoom = null,
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
rule.setMessagesView(state = state)
|
||||
rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_message)
|
||||
rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessagesView(
|
||||
|
||||
@@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
@@ -64,6 +65,7 @@ import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
@@ -80,7 +82,7 @@ import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
class TimelinePresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
@@ -705,6 +707,73 @@ class TimelinePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - timeline room info includes predecessor room when room has predecessor`() = runTest {
|
||||
val predecessorRoomId = RoomId("!predecessor:server.org")
|
||||
val predecessorEventId = EventId("\$predecessorEvent:server.org")
|
||||
val predecessorRoom = PredecessorRoom(
|
||||
roomId = predecessorRoomId,
|
||||
lastEventId = predecessorEventId
|
||||
)
|
||||
|
||||
val room = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
predecessorRoomResult = { predecessorRoom }
|
||||
),
|
||||
)
|
||||
|
||||
val presenter = createTimelinePresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.timelineRoomInfo.predecessorRoom).isNotNull()
|
||||
assertThat(initialState.timelineRoomInfo.predecessorRoom?.roomId).isEqualTo(predecessorRoomId)
|
||||
assertThat(initialState.timelineRoomInfo.predecessorRoom?.lastEventId).isEqualTo(predecessorEventId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - timeline room info no predecessor`() = runTest {
|
||||
val room = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
predecessorRoomResult = { null }
|
||||
),
|
||||
)
|
||||
val presenter = createTimelinePresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.timelineRoomInfo.predecessorRoom).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - timeline event navigate to room`() = runTest {
|
||||
val room = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
),
|
||||
)
|
||||
val onNavigateToRoomLambda = lambdaRecorder<RoomId, Unit> {}
|
||||
val navigator = FakeMessagesNavigator(
|
||||
onNavigateToRoomLambda = onNavigateToRoomLambda
|
||||
)
|
||||
val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator)
|
||||
presenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(TimelineEvents.NavigateToRoom(A_ROOM_ID))
|
||||
assert(onNavigateToRoomLambda)
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
||||
return awaitItem()
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
@@ -66,6 +67,7 @@ class FakeBaseRoom(
|
||||
private val getRoomVisibilityResult: () -> Result<RoomVisibility> = { lambdaError() },
|
||||
private val forgetResult: () -> Result<Unit> = { lambdaError() },
|
||||
private val reportRoomResult: (String?) -> Result<Unit> = { lambdaError() },
|
||||
private val predecessorRoomResult: () -> PredecessorRoom? = { null },
|
||||
) : BaseRoom {
|
||||
private val _roomInfoFlow: MutableStateFlow<RoomInfo> = MutableStateFlow(initialRoomInfo)
|
||||
override val roomInfoFlow: StateFlow<RoomInfo> = _roomInfoFlow
|
||||
@@ -215,6 +217,8 @@ class FakeBaseRoom(
|
||||
}
|
||||
|
||||
override suspend fun reportRoom(reason: String?) = reportRoomResult(reason)
|
||||
|
||||
override fun predecessorRoom(): PredecessorRoom? = predecessorRoomResult()
|
||||
}
|
||||
|
||||
fun defaultRoomPowerLevels() = RoomPowerLevels(
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.ui.test.hasTestTag
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.onFirst
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import org.junit.rules.TestRule
|
||||
@@ -54,3 +55,8 @@ fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.pressBackKey() {
|
||||
fun SemanticsNodeInteractionsProvider.pressTag(tag: String) {
|
||||
onNode(hasTestTag(tag)).performClick()
|
||||
}
|
||||
|
||||
fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.assertNoNodeWithText(@StringRes res: Int) {
|
||||
val text = activity.getString(res)
|
||||
onNodeWithText(text).assertDoesNotExist()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user