Browse Source

Timeline : continue to fix more tests...

pull/2759/head
ganfra 5 months ago
parent
commit
bffa2d717f
  1. 5
      appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt
  2. 2
      features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt
  3. 43
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
  4. 74
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
  5. 11
      features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/PollFixtures.kt
  6. 71
      features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt
  7. 36
      features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt
  8. 3
      samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
  9. 16
      tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt

5
appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt

@ -27,6 +27,7 @@ import com.bumble.appyx.testing.junit4.util.MainDispatcherRule @@ -27,6 +27,7 @@ import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper
import com.google.common.truth.Truth.assertThat
import io.element.android.appnav.di.RoomComponentFactory
import io.element.android.appnav.room.RoomNavigationTarget
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
@ -124,7 +125,7 @@ class JoinRoomLoadedFlowNodeTest { @@ -124,7 +125,7 @@ class JoinRoomLoadedFlowNodeTest {
// GIVEN
val room = FakeMatrixRoom()
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room)
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
val roomFlowNode = createJoinedRoomLoadedFlowNode(
plugins = listOf(inputs),
messagesEntryPoint = fakeMessagesEntryPoint,
@ -146,7 +147,7 @@ class JoinRoomLoadedFlowNodeTest { @@ -146,7 +147,7 @@ class JoinRoomLoadedFlowNodeTest {
val room = FakeMatrixRoom()
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room)
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
val roomFlowNode = createJoinedRoomLoadedFlowNode(
plugins = listOf(inputs),
messagesEntryPoint = fakeMessagesEntryPoint,

2
features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt

@ -18,9 +18,11 @@ package io.element.android.features.location.impl.common.actions @@ -18,9 +18,11 @@ package io.element.android.features.location.impl.common.actions
import com.google.common.truth.Truth.assertThat
import io.element.android.features.location.api.Location
import org.junit.Ignore
import org.junit.Test
import java.net.URLEncoder
@Ignore
internal class AndroidLocationActionsTest {
// We use an Android-native encoder in the actual app, switch to an equivalent JVM one for the tests
private fun urlEncoder(input: String) = URLEncoder.encode(input, "US-ASCII")

43
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt

@ -65,6 +65,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags @@ -65,6 +65,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore
import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
@ -83,6 +84,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser @@ -83,6 +84,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediapickers.test.FakePickerProvider
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
import io.element.android.libraries.mediaupload.api.MediaSender
@ -97,6 +99,9 @@ import io.element.android.services.analytics.test.FakeAnalyticsService @@ -97,6 +99,9 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.consumeItemsUntilTimeout
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers
import io.mockk.mockk
import kotlinx.collections.immutable.persistentListOf
@ -169,7 +174,13 @@ class MessagesPresenterTest { @@ -169,7 +174,13 @@ class MessagesPresenterTest {
@Test
fun `present - handle toggling a reaction`() = runTest {
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
val room = FakeMatrixRoom()
val toggleReactionSuccess = lambdaRecorder { _: String, _: EventId -> Result.success(Unit) }
val toggleReactionFailure = lambdaRecorder { _: String, _: EventId -> Result.failure<Unit>(IllegalStateException("Failed to send reaction")) }
val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
}
val room = FakeMatrixRoom(liveTimeline = timeline)
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -177,29 +188,42 @@ class MessagesPresenterTest { @@ -177,29 +188,42 @@ class MessagesPresenterTest {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
assertThat(room.myReactions.count()).isEqualTo(1)
// No crashes when sending a reaction failed
room.givenToggleReactionResult(Result.failure(IllegalStateException("Failed to send reaction")))
timeline.apply { toggleReactionLambda = toggleReactionFailure }
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
assertThat(room.myReactions.count()).isEqualTo(1)
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assert(toggleReactionSuccess)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID))
assert(toggleReactionFailure)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID))
}
}
@Test
fun `present - handle toggling a reaction twice`() = runTest {
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
val room = FakeMatrixRoom()
val toggleReactionSuccess = lambdaRecorder { _: String, _: EventId -> Result.success(Unit) }
val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
}
val room = FakeMatrixRoom(liveTimeline = timeline)
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
assertThat(room.myReactions.count()).isEqualTo(1)
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
assertThat(room.myReactions.count()).isEqualTo(0)
assert(toggleReactionSuccess)
.isCalledExactly(2)
.withSequence(
listOf(value("👍"), value(AN_EVENT_ID)),
listOf(value("👍"), value(AN_EVENT_ID)),
)
}
}
@ -274,7 +298,7 @@ class MessagesPresenterTest { @@ -274,7 +298,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(3)
skipItems(2)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
@ -430,7 +454,6 @@ class MessagesPresenterTest { @@ -430,7 +454,6 @@ class MessagesPresenterTest {
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Redact, aMessageEvent()))
assertThat(matrixRoom.redactEventEventIdParam).isEqualTo(AN_EVENT_ID)
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
skipItems(1) // back paginating
}
}

74
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt

@ -66,6 +66,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder @@ -66,6 +66,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediapickers.test.FakePickerProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
@ -82,6 +83,10 @@ import io.element.android.libraries.textcomposer.model.Suggestion @@ -82,6 +83,10 @@ import io.element.android.libraries.textcomposer.model.Suggestion
import io.element.android.libraries.textcomposer.model.SuggestionType
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.waitForPredicate
import io.mockk.mockk
import kotlinx.collections.immutable.persistentListOf
@ -260,7 +265,13 @@ class MessageComposerPresenterTest { @@ -260,7 +265,13 @@ class MessageComposerPresenterTest {
@Test
fun `present - edit sent message`() = runTest {
val fakeMatrixRoom = FakeMatrixRoom()
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.editMessageLambda = editMessageLambda
}
val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
val presenter = createPresenter(
this,
fakeMatrixRoom,
@ -284,7 +295,13 @@ class MessageComposerPresenterTest { @@ -284,7 +295,13 @@ class MessageComposerPresenterTest {
skipItems(1)
val messageSentState = awaitItem()
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE)
advanceUntilIdle()
assert(editMessageLambda)
.isCalledOnce()
.with(any(), any(), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
assertThat(analyticsService.capturedEvents).containsExactly(
Composer(
inThread = false,
@ -298,7 +315,13 @@ class MessageComposerPresenterTest { @@ -298,7 +315,13 @@ class MessageComposerPresenterTest {
@Test
fun `present - edit not sent message`() = runTest {
val fakeMatrixRoom = FakeMatrixRoom()
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.editMessageLambda = editMessageLambda
}
val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
val presenter = createPresenter(
this,
fakeMatrixRoom,
@ -322,7 +345,13 @@ class MessageComposerPresenterTest { @@ -322,7 +345,13 @@ class MessageComposerPresenterTest {
skipItems(1)
val messageSentState = awaitItem()
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE)
advanceUntilIdle()
assert(editMessageLambda)
.isCalledOnce()
.with(any(), any(), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
assertThat(analyticsService.capturedEvents).containsExactly(
Composer(
inThread = false,
@ -336,7 +365,13 @@ class MessageComposerPresenterTest { @@ -336,7 +365,13 @@ class MessageComposerPresenterTest {
@Test
fun `present - reply message`() = runTest {
val fakeMatrixRoom = FakeMatrixRoom()
val replyMessageLambda = lambdaRecorder {_: EventId, _: String, _: String?, _:List<Mention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
}
val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
val presenter = createPresenter(
this,
fakeMatrixRoom,
@ -356,7 +391,13 @@ class MessageComposerPresenterTest { @@ -356,7 +391,13 @@ class MessageComposerPresenterTest {
state.eventSink.invoke(MessageComposerEvents.SendMessage(A_REPLY.toMessage()))
val messageSentState = awaitItem()
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
assertThat(fakeMatrixRoom.replyMessageParameter).isEqualTo(A_REPLY to A_REPLY)
advanceUntilIdle()
assert(replyMessageLambda)
.isCalledOnce()
.with(any(),value(A_REPLY),value(A_REPLY),any())
assertThat(analyticsService.capturedEvents).containsExactly(
Composer(
inThread = false,
@ -832,7 +873,17 @@ class MessageComposerPresenterTest { @@ -832,7 +873,17 @@ class MessageComposerPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - send messages with intentional mentions`() = runTest {
val room = FakeMatrixRoom()
val replyMessageLambda = lambdaRecorder {_: EventId, _: String, _: String?, _:List<Mention> ->
Result.success(Unit)
}
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
}
val room = FakeMatrixRoom(liveTimeline = timeline)
val presenter = createPresenter(room = room, coroutineScope = this)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -867,7 +918,9 @@ class MessageComposerPresenterTest { @@ -867,7 +918,9 @@ class MessageComposerPresenterTest {
initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage()))
advanceUntilIdle()
assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_2)))
assert(replyMessageLambda)
.isCalledOnce()
.with(any(), any(), any(), value(listOf(Mention.User(A_USER_ID_2))))
// Check intentional mentions on edit message
skipItems(1)
@ -883,7 +936,10 @@ class MessageComposerPresenterTest { @@ -883,7 +936,10 @@ class MessageComposerPresenterTest {
initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage()))
advanceUntilIdle()
assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_3)))
assert(editMessageLambda)
.isCalledOnce()
.with(any(), any(), any(), any(), value(listOf(Mention.User(A_USER_ID_3))))
skipItems(1)
}

11
features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/PollFixtures.kt

@ -20,16 +20,17 @@ import io.element.android.libraries.matrix.api.core.EventId @@ -20,16 +20,17 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.poll.PollAnswer
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aPollContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
fun aPollTimeline(
fun aPollTimelineItems(
polls: Map<EventId, PollContent> = emptyMap(),
): FakeTimeline {
return FakeTimeline(
initialTimelineItems = polls.map { entry ->
): Flow<List<MatrixTimelineItem>> {
return flowOf(
polls.map { entry ->
MatrixTimelineItem.Event(
entry.key.value,
anEventTimelineItem(

71
features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt

@ -25,33 +25,42 @@ import im.vector.app.features.analytics.plan.Composer @@ -25,33 +25,42 @@ import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.analytics.plan.PollCreation
import io.element.android.features.messages.test.FakeMessageComposerContext
import io.element.android.features.poll.api.create.CreatePollMode
import io.element.android.features.poll.impl.aPollTimeline
import io.element.android.features.poll.impl.aPollTimelineItems
import io.element.android.features.poll.impl.anOngoingPollContent
import io.element.android.features.poll.impl.data.PollRepository
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.LiveTimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.SavePollInvocation
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class CreatePollPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class) class CreatePollPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
private val pollEventId = AN_EVENT_ID
private var navUpInvocationsCount = 0
private val existingPoll = anOngoingPollContent()
private val timeline = FakeTimeline(
timelineItems = aPollTimelineItems(mapOf(pollEventId to existingPoll))
)
private val fakeMatrixRoom = FakeMatrixRoom(
liveTimeline = aPollTimeline(
mapOf(pollEventId to existingPoll)
)
liveTimeline = timeline
)
private val fakeAnalyticsService = FakeAnalyticsService()
private val fakeMessageComposerContext = FakeMessageComposerContext()
@ -80,7 +89,7 @@ class CreatePollPresenterTest { @@ -80,7 +89,7 @@ class CreatePollPresenterTest {
@Test
fun `in edit mode, if poll doesn't exist, error is tracked and screen is closed`() = runTest {
val room = FakeMatrixRoom(
liveTimeline = aPollTimeline()
liveTimeline = FakeTimeline()
)
val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(AN_EVENT_ID), room = room)
moleculeFlow(RecompositionMode.Immediate) {
@ -180,6 +189,12 @@ class CreatePollPresenterTest { @@ -180,6 +189,12 @@ class CreatePollPresenterTest {
@Test
fun `edit poll sends a poll edit event`() = runTest {
val editPollLambda = lambdaRecorder { _: EventId, _: String, _: List<String>, _: Int, _: PollKind ->
Result.success(Unit)
}
timeline.apply {
this.editPollLambda = editPollLambda
}
val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId))
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -201,16 +216,18 @@ class CreatePollPresenterTest { @@ -201,16 +216,18 @@ class CreatePollPresenterTest {
).apply {
eventSink(CreatePollEvents.Save)
}
delay(1) // Wait for the coroutine to finish
assertThat(fakeMatrixRoom.editPollInvocations.size).isEqualTo(1)
assertThat(fakeMatrixRoom.editPollInvocations.last()).isEqualTo(
SavePollInvocation(
question = "Changed question",
answers = listOf("Changed answer 1", "Changed answer 2", "Maybe"),
maxSelections = 1,
pollKind = PollKind.Disclosed
advanceUntilIdle() // Wait for the coroutine to finish
assert(editPollLambda)
.isCalledOnce()
.with(
value(pollEventId),
value("Changed question"),
value(listOf("Changed answer 1", "Changed answer 2", "Maybe")),
value(1),
value(PollKind.Disclosed)
)
)
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2)
assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo(
Composer(
@ -233,6 +250,12 @@ class CreatePollPresenterTest { @@ -233,6 +250,12 @@ class CreatePollPresenterTest {
@Test
fun `when edit poll fails, error is tracked`() = runTest {
val error = Exception("cause")
val editPollLambda = lambdaRecorder { _: EventId, _: String, _: List<String>, _: Int, _: PollKind ->
Result.failure<Unit>(error)
}
timeline.apply {
this.editPollLambda = editPollLambda
}
fakeMatrixRoom.givenEditPollResult(Result.failure(error))
val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId))
moleculeFlow(RecompositionMode.Immediate) {
@ -241,8 +264,8 @@ class CreatePollPresenterTest { @@ -241,8 +264,8 @@ class CreatePollPresenterTest {
awaitDefaultItem()
awaitPollLoaded().eventSink(CreatePollEvents.SetAnswer(0, "A"))
awaitPollLoaded(newAnswer1 = "A").eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
assertThat(fakeMatrixRoom.editPollInvocations).hasSize(1)
advanceUntilIdle() // Wait for the coroutine to finish
assert(editPollLambda).isCalledOnce()
assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
assertThat(fakeAnalyticsService.trackedErrors).containsExactly(
@ -497,22 +520,22 @@ class CreatePollPresenterTest { @@ -497,22 +520,22 @@ class CreatePollPresenterTest {
newAnswer1: String? = null,
newAnswer2: String? = null,
) =
awaitItem().apply {
assertThat(canSave).isTrue()
assertThat(canAddAnswer).isTrue()
assertThat(question).isEqualTo(newQuestion ?: existingPoll.question)
assertThat(answers).isEqualTo(existingPoll.expectedAnswersState().toMutableList().apply {
awaitItem().also { state ->
assertThat(state.canSave).isTrue()
assertThat(state.canAddAnswer).isTrue()
assertThat(state.question).isEqualTo(newQuestion ?: existingPoll.question)
assertThat(state.answers).isEqualTo(existingPoll.expectedAnswersState().toMutableList().apply {
newAnswer1?.let { this[0] = Answer(it, true) }
newAnswer2?.let { this[1] = Answer(it, true) }
})
assertThat(pollKind).isEqualTo(existingPoll.kind)
assertThat(state.pollKind).isEqualTo(existingPoll.kind)
}
private fun createCreatePollPresenter(
mode: CreatePollMode = CreatePollMode.NewPoll,
room: MatrixRoom = fakeMatrixRoom,
): CreatePollPresenter = CreatePollPresenter(
repository = PollRepository(room),
repository = PollRepository(room, LiveTimelineProvider(room)),
analyticsService = fakeAnalyticsService,
messageComposerContext = fakeMessageComposerContext,
navigateUp = { navUpInvocationsCount++ },

36
features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt

@ -22,7 +22,7 @@ import app.cash.turbine.test @@ -22,7 +22,7 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.poll.api.actions.EndPollAction
import io.element.android.features.poll.api.actions.SendPollResponseAction
import io.element.android.features.poll.impl.aPollTimeline
import io.element.android.features.poll.impl.aPollTimelineItems
import io.element.android.features.poll.impl.anEndedPollContent
import io.element.android.features.poll.impl.anOngoingPollContent
import io.element.android.features.poll.impl.history.model.PollHistoryFilter
@ -32,14 +32,20 @@ import io.element.android.features.poll.test.actions.FakeEndPollAction @@ -32,14 +32,20 @@ import io.element.android.features.poll.test.actions.FakeEndPollAction
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@ -50,11 +56,14 @@ class PollHistoryPresenterTest { @@ -50,11 +56,14 @@ class PollHistoryPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
private val timeline = aPollTimeline(
polls = mapOf(
private val backwardPaginationStatus = MutableStateFlow(Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = true))
private val timeline = FakeTimeline(
timelineItems = aPollTimelineItems(
mapOf(
AN_EVENT_ID to anOngoingPollContent(),
AN_EVENT_ID_2 to anEndedPollContent()
)
)),
backwardPaginationStatus = backwardPaginationStatus
)
private val room = FakeMatrixRoom(
liveTimeline = timeline
@ -66,7 +75,6 @@ class PollHistoryPresenterTest { @@ -66,7 +75,6 @@ class PollHistoryPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().also { state ->
assertThat(state.activeFilter).isEqualTo(PollHistoryFilter.ONGOING)
assertThat(state.pollHistoryItems.size).isEqualTo(0)
@ -127,26 +135,30 @@ class PollHistoryPresenterTest { @@ -127,26 +135,30 @@ class PollHistoryPresenterTest {
@Test
fun `present - load more scenario`() = runTest {
val paginateLambda = lambdaRecorder{ _: Timeline.PaginationDirection ->
Result.success(false)
}
timeline.apply {
this.paginateLambda = paginateLambda
}
val presenter = createPollHistoryPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(2)
awaitItem().also { state ->
assertThat(state.pollHistoryItems.size).isEqualTo(2)
}
timeline.updatePaginationState {
copy(isBackPaginating = false)
}
skipItems(1)
val loadedState = awaitItem()
assertThat(loadedState.isLoading).isFalse()
loadedState.eventSink(PollHistoryEvents.LoadMore)
backwardPaginationStatus.getAndUpdate { it.copy(isPaginating = true) }
awaitItem().also { state ->
assertThat(state.isLoading).isTrue()
}
backwardPaginationStatus.getAndUpdate { it.copy(isPaginating = false) }
awaitItem().also { state ->
assertThat(state.isLoading).isFalse()
}
// Called once by the initial load and once by the load more event
assert(paginateLambda).isCalledExactly(2)
}
}

3
samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt

@ -49,6 +49,7 @@ import io.element.android.libraries.indicator.impl.DefaultIndicatorService @@ -49,6 +49,7 @@ import io.element.android.libraries.indicator.impl.DefaultIndicatorService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStore
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
import io.element.android.services.analytics.noop.NoopAnalyticsService
@ -146,7 +147,7 @@ class RoomListScreen( @@ -146,7 +147,7 @@ class RoomListScreen(
Singleton.appScope.launch {
withContext(coroutineDispatchers.io) {
matrixClient.getRoom(roomId)!!.use { room ->
room.timeline.paginateBackwards(20, 50)
room.liveTimeline.paginate(Timeline.PaginationDirection.BACKWARDS)
}
}
}

16
tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt

@ -71,6 +71,13 @@ inline fun <reified T1, reified T2, reified T3, reified T4, reified R> lambdaRec @@ -71,6 +71,13 @@ inline fun <reified T1, reified T2, reified T3, reified T4, reified R> lambdaRec
return LambdaFourParamsRecorder(ensureNeverCalled, block)
}
inline fun <reified T1, reified T2, reified T3, reified T4, reified T5, reified R> lambdaRecorder(
ensureNeverCalled: Boolean = false,
noinline block: (T1, T2, T3, T4, T5) -> R
): LambdaFiveParamsRecorder<T1, T2, T3, T4, T5, R> {
return LambdaFiveParamsRecorder(ensureNeverCalled, block)
}
class LambdaNoParamRecorder<out R>(ensureNeverCalled: Boolean, val block: () -> R) : LambdaRecorder(ensureNeverCalled), () -> R {
override fun invoke(): R {
onInvoke()
@ -109,3 +116,12 @@ class LambdaFourParamsRecorder<in T1, in T2, in T3, in T4, out R>(ensureNeverCal @@ -109,3 +116,12 @@ class LambdaFourParamsRecorder<in T1, in T2, in T3, in T4, out R>(ensureNeverCal
return block(p1, p2, p3, p4)
}
}
class LambdaFiveParamsRecorder<in T1, in T2, in T3, in T4, in T5, out R>(ensureNeverCalled: Boolean, val block: (T1, T2, T3, T4, T5) -> R) : LambdaRecorder(
ensureNeverCalled
), (T1, T2, T3, T4, T5) -> R {
override fun invoke(p1: T1, p2: T2, p3: T3, p4: T4, p5: T5): R {
onInvoke(p1, p2, p3, p4, p5)
return block(p1, p2, p3, p4, p5)
}
}

Loading…
Cancel
Save