From 7d551163470b3c80610f184e74ab3792e7b22c63 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 15:05:35 +0100 Subject: [PATCH 01/12] Ensure link are clickable on simple body (#2038) --- .../TimelineItemContentMessageFactory.kt | 12 ++++++- .../TimelineItemContentMessageFactoryTest.kt | 34 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 77e514fa60..7b690505ab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -21,6 +21,7 @@ import android.text.style.URLSpan import android.text.util.Linkify import androidx.core.text.buildSpannedString import androidx.core.text.getSpans +import androidx.core.text.toSpannable import androidx.core.text.util.LinkifyCompat import io.element.android.features.location.api.Location import io.element.android.features.messages.api.timeline.HtmlConverterProvider @@ -178,7 +179,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemTextContent( body = messageType.body, htmlDocument = messageType.formatted?.toHtmlDocument(), - formattedBody = parseHtml(messageType.formatted), + formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(), isEdited = content.isEdited, ) } @@ -237,3 +238,12 @@ class TimelineItemContentMessageFactory @Inject constructor( return this } } + +private fun String.withLinks(): CharSequence? { + val spannable = toSpannable() + LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS) + if (spannable.getSpans().isEmpty()) { + return null + } + return spannable +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 5945ccbee2..40c0c7b8ca 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -17,8 +17,9 @@ package io.element.android.features.messages.impl.timeline.factories.event import android.text.SpannableString -import android.text.SpannableStringBuilder +import android.text.Spanned import android.text.style.URLSpan +import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import com.google.common.truth.Truth.assertThat import io.element.android.features.location.api.Location @@ -144,6 +145,37 @@ class TimelineItemContentMessageFactoryTest { assertThat(result).isEqualTo(expected) } + @Test + fun `test create TextMessageType with simple link`() = runTest { + val sut = createTimelineItemContentMessageFactory() + val result = sut.create( + content = createMessageContent(type = TextMessageType("https://www.example.org", null)), + senderDisplayName = "Bob", + eventId = AN_EVENT_ID, + ) as TimelineItemTextContent + val expected = TimelineItemTextContent( + body = "https://www.example.org", + htmlDocument = null, + plainText = "https://www.example.org", + isEdited = false, + formattedBody = buildSpannedString { + inSpans(URLSpan("https://www.example.org")) { + append("https://www.example.org") + } + } + ) + assertThat(result.body).isEqualTo(expected.body) + assertThat(result.htmlDocument).isEqualTo(expected.htmlDocument) + assertThat(result.plainText).isEqualTo(expected.plainText) + assertThat(result.isEdited).isEqualTo(expected.isEdited) + assertThat(result.formattedBody).isInstanceOf(Spanned::class.java) + val spanned = result.formattedBody as Spanned + assertThat(spanned.toString()).isEqualTo("https://www.example.org") + val urlSpans = spanned.getSpans(0, spanned.length, URLSpan::class.java) + assertThat(urlSpans).hasLength(1) + assertThat(urlSpans[0].url).isEqualTo("https://www.example.org") + } + @Test fun `test create TextMessageType with HTML formatted body`() = runTest { val expected = SpannableStringBuilder().apply { From 94e7b59e3767ff205d2809739685e39b12feab03 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 14:51:28 +0100 Subject: [PATCH 02/12] Format file. --- .../TimelineItemContentMessageFactoryTest.kt | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 40c0c7b8ca..291805bb41 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -192,10 +192,12 @@ class TimelineItemContentMessageFactoryTest { htmlConverterTransform = { expected } ) val result = sut.create( - content = createMessageContent(type = TextMessageType( - body = "body", - formatted = FormattedBody(MessageFormat.HTML, expected.toString()) - )), + content = createMessageContent( + type = TextMessageType( + body = "body", + formatted = FormattedBody(MessageFormat.HTML, expected.toString()) + ) + ), senderDisplayName = "Bob", eventId = AN_EVENT_ID, ) @@ -208,10 +210,12 @@ class TimelineItemContentMessageFactoryTest { htmlConverterTransform = { it } ) val result = sut.create( - content = createMessageContent(type = TextMessageType( - body = "body", - formatted = FormattedBody(MessageFormat.UNKNOWN, "formatted") - )), + content = createMessageContent( + type = TextMessageType( + body = "body", + formatted = FormattedBody(MessageFormat.UNKNOWN, "formatted") + ) + ), senderDisplayName = "Bob", eventId = AN_EVENT_ID, ) @@ -552,10 +556,12 @@ class TimelineItemContentMessageFactoryTest { fun `test create NoticeMessageType with HTML formatted body`() = runTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( - content = createMessageContent(type = NoticeMessageType( - body = "body", - formatted = FormattedBody(MessageFormat.HTML, "formatted") - )), + content = createMessageContent( + type = NoticeMessageType( + body = "body", + formatted = FormattedBody(MessageFormat.HTML, "formatted") + ) + ), senderDisplayName = "Bob", eventId = AN_EVENT_ID, ) @@ -584,10 +590,12 @@ class TimelineItemContentMessageFactoryTest { fun `test create EmoteMessageType with HTML formatted body`() = runTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( - content = createMessageContent(type = EmoteMessageType( - body = "body", - formatted = FormattedBody(MessageFormat.HTML, "formatted") - )), + content = createMessageContent( + type = EmoteMessageType( + body = "body", + formatted = FormattedBody(MessageFormat.HTML, "formatted") + ) + ), senderDisplayName = "Bob", eventId = AN_EVENT_ID, ) From 70abf6226f42e9798290bcf18f8f2650796ddab1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 15:05:47 +0100 Subject: [PATCH 03/12] Use `buildSpannedString` --- .../factories/event/TimelineItemContentMessageFactoryTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 291805bb41..0fe79b8963 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -178,7 +178,7 @@ class TimelineItemContentMessageFactoryTest { @Test fun `test create TextMessageType with HTML formatted body`() = runTest { - val expected = SpannableStringBuilder().apply { + val expected = buildSpannedString { append("link to ") inSpans(URLSpan("https://matrix.org")) { append("https://matrix.org") From 74729fe534143330c441227fa64f4add05803f10 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 15:11:44 +0100 Subject: [PATCH 04/12] Apply the same fix for emotes, notices and other message type. --- .../TimelineItemContentMessageFactory.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 7b690505ab..2e6445deda 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -69,12 +69,15 @@ class TimelineItemContentMessageFactory @Inject constructor( suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent { return when (val messageType = content.type) { - is EmoteMessageType -> TimelineItemEmoteContent( - body = "* $senderDisplayName ${messageType.body}", - htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"), - formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisplayName"), - isEdited = content.isEdited, - ) + is EmoteMessageType -> { + val emoteBody = "* $senderDisplayName ${messageType.body}" + TimelineItemEmoteContent( + body = emoteBody, + htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"), + formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisplayName") ?: emoteBody.withLinks(), + isEdited = content.isEdited, + ) + } is ImageMessageType -> { val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemImageContent( @@ -172,7 +175,7 @@ class TimelineItemContentMessageFactory @Inject constructor( is NoticeMessageType -> TimelineItemNoticeContent( body = messageType.body, htmlDocument = messageType.formatted?.toHtmlDocument(), - formattedBody = parseHtml(messageType.formatted), + formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(), isEdited = content.isEdited, ) is TextMessageType -> { @@ -186,7 +189,7 @@ class TimelineItemContentMessageFactory @Inject constructor( is OtherMessageType -> TimelineItemTextContent( body = messageType.body, htmlDocument = null, - formattedBody = null, + formattedBody = messageType.body.withLinks(), isEdited = content.isEdited, ) } From d800b225b6648e17981311169784e45cc4db57c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 15:16:23 +0100 Subject: [PATCH 05/12] Consider the returned value of `LinkifyCompat.addLinks` --- .../factories/event/TimelineItemContentMessageFactory.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 2e6445deda..7aa821d5c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -244,9 +244,6 @@ class TimelineItemContentMessageFactory @Inject constructor( private fun String.withLinks(): CharSequence? { val spannable = toSpannable() - LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS) - if (spannable.getSpans().isEmpty()) { - return null - } - return spannable + val addedLinks = LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS) + return spannable.takeIf { addedLinks } } From ff2fc7ff3ae249b3a38a59fc37c84f174358a4e4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 15:38:34 +0100 Subject: [PATCH 06/12] Also linkify emails. --- .../factories/event/TimelineItemContentMessageFactory.kt | 4 ++-- .../libraries/designsystem/components/ClickableLinkText.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 7aa821d5c4..2e43bfe593 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -230,7 +230,7 @@ class TimelineItemContentMessageFactory @Inject constructor( Pair(start, end) } // Find and set as URLSpans any links present in the text - LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS) + LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES) // Restore old spans if they don't conflict with the new ones for ((urlSpan, location) in oldURLSpans) { val (start, end) = location @@ -244,6 +244,6 @@ class TimelineItemContentMessageFactory @Inject constructor( private fun String.withLinks(): CharSequence? { val spannable = toSpannable() - val addedLinks = LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS) + val addedLinks = LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES) return spannable.takeIf { addedLinks } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt index 105757b431..701d2945ba 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt @@ -142,7 +142,7 @@ fun ClickableLinkText( fun AnnotatedString.linkify(linkStyle: SpanStyle): AnnotatedString { val original = this val spannable = SpannableString(this.text) - LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS) + LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES) val spans = spannable.getSpans(0, spannable.length, URLSpan::class.java) return buildAnnotatedString { From b5a2c85e66dbf63eb45c735df3a609667deac66f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 18:13:29 +0100 Subject: [PATCH 07/12] Let `consumeItemsUntilPredicate` fail if predicate is never true or if Complete occurs. Do not fail on error for `consumeItemsUntilTimeout` --- .../android/tests/testutils/ReceiveTurbine.kt | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt index 3e47dd63ce..0c8bd89951 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt @@ -19,7 +19,7 @@ package io.element.android.tests.testutils import app.cash.turbine.Event import app.cash.turbine.ReceiveTurbine import app.cash.turbine.withTurbineTimeout -import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.core.bool.orFalse import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -29,7 +29,7 @@ import kotlin.time.Duration.Companion.milliseconds * @return the list of consumed items. */ suspend fun ReceiveTurbine.consumeItemsUntilTimeout(timeout: Duration = 100.milliseconds): List { - return consumeItemsUntilPredicate(timeout) { false } + return consumeItemsUntilPredicate(timeout, ignoreTimeoutError = true) { false } } /** @@ -49,22 +49,29 @@ suspend fun ReceiveTurbine.awaitLastSequentialItem(): T { */ suspend fun ReceiveTurbine.consumeItemsUntilPredicate( timeout: Duration = 100.milliseconds, + ignoreTimeoutError: Boolean = false, predicate: (T) -> Boolean, ): List { val items = ArrayList() - tryOrNull { - var foundItemOrFinished = false - while (!foundItemOrFinished) { + var exitLoop = false + try { + while (!exitLoop) { when (val event = withTurbineTimeout(timeout) { awaitEvent() }) { is Event.Item -> { items.add(event.value) - if (predicate(event.value)) { - foundItemOrFinished = true - } + exitLoop = predicate(event.value) } - Event.Complete, is Event.Error -> foundItemOrFinished = true + Event.Complete -> error("Unexpected complete") + is Event.Error -> throw event.throwable } } + } catch (assertionError: AssertionError) { + // TurbineAssertionError is internal :/, so rely on the message + if (assertionError.message?.startsWith("No value produced in").orFalse() && ignoreTimeoutError) { + // Timeout, ignore + } else { + throw assertionError + } } return items } From 1b0a4093d91daeda2c9205f52b6d0d497e6a3ec8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 17:55:05 +0100 Subject: [PATCH 08/12] Fix test which was passing for wrong reason. --- .../element/android/appnav/loggedin/LoggedInPresenterTest.kt | 3 ++- .../libraries/matrix/test/roomlist/FakeRoomListService.kt | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index fcfba8f7a1..d7b951fbbc 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -60,8 +60,9 @@ class LoggedInPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.showSyncSpinner).isFalse() + roomListService.postSyncIndicator(RoomListService.SyncIndicator.Show) consumeItemsUntilPredicate { it.showSyncSpinner } - roomListService.postState(RoomListService.State.Running) + roomListService.postSyncIndicator(RoomListService.SyncIndicator.Hide) consumeItemsUntilPredicate { !it.showSyncSpinner } } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index 5c7d0983cd..073a314e9f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -52,6 +52,10 @@ class FakeRoomListService : RoomListService { roomListStateFlow.emit(state) } + suspend fun postSyncIndicator(value: RoomListService.SyncIndicator) { + syncIndicatorStateFlow.emit(value) + } + var latestSlidingSyncRange: IntRange? = null private set From 5d4eaae9338368a5fba5f5bdd4b8cc7c04e52632 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 17:58:19 +0100 Subject: [PATCH 09/12] Ensure test does not fail. --- .../factories/event/TimelineItemContentMessageFactory.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 2e43bfe593..69c453bb44 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -242,8 +242,10 @@ class TimelineItemContentMessageFactory @Inject constructor( } } +@Suppress("USELESS_ELVIS") private fun String.withLinks(): CharSequence? { - val spannable = toSpannable() + /* Note: toSpannable() can return null when running unit tests */ + val spannable = toSpannable() ?: return null val addedLinks = LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES) return spannable.takeIf { addedLinks } } From dca7edd977331befd85c82eaa68eea7ff9d5f373 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 21:08:50 +0100 Subject: [PATCH 10/12] Fix more test. --- .../impl/history/PollHistoryPresenterTest.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt index 172ec5fa28..39494e1f34 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt @@ -52,14 +52,15 @@ class PollHistoryPresenterTest { @get:Rule val warmUpRule = WarmUpRule() - private val room = FakeMatrixRoom( - matrixTimeline = aPollTimeline( - polls = mapOf( - AN_EVENT_ID to anOngoingPollContent(), - AN_EVENT_ID_2 to anEndedPollContent() - ) + private val timeline = aPollTimeline( + polls = mapOf( + AN_EVENT_ID to anOngoingPollContent(), + AN_EVENT_ID_2 to anEndedPollContent() ) ) + private val room = FakeMatrixRoom( + matrixTimeline = timeline + ) @Test fun `present - initial states`() = runTest { @@ -134,10 +135,14 @@ class PollHistoryPresenterTest { presenter.present() }.test { consumeItemsUntilPredicate { - it.pollHistoryItems.size == 2 && !it.isLoading - }.last().also { state -> - state.eventSink(PollHistoryEvents.LoadMore) + it.pollHistoryItems.size == 2 + } + timeline.updatePaginationState { + copy(isBackPaginating = false) } + val loadedState = awaitItem() + assertThat(loadedState.isLoading).isFalse() + loadedState.eventSink(PollHistoryEvents.LoadMore) consumeItemsUntilPredicate { it.isLoading } From bd03831745ca9f958bb2866c0d50dd936a9f1cd8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Dec 2023 21:14:56 +0100 Subject: [PATCH 11/12] Fix more test. --- .../impl/user/editprofile/EditUserProfilePresenterTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt index 8746670e03..3e32ed675f 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt @@ -37,6 +37,7 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -292,11 +293,10 @@ class EditUserProfilePresenterTest { val initialState = awaitItem() initialState.eventSink(EditUserProfileEvents.UpdateDisplayName(" Name ")) initialState.eventSink(EditUserProfileEvents.Save) - consumeItemsUntilPredicate { matrixClient.setDisplayNameCalled && !matrixClient.removeAvatarCalled && !matrixClient.uploadAvatarCalled } + consumeItemsUntilTimeout() assertThat(matrixClient.setDisplayNameCalled).isFalse() assertThat(matrixClient.uploadAvatarCalled).isFalse() assertThat(matrixClient.removeAvatarCalled).isFalse() - cancelAndIgnoreRemainingEvents() } } From 0129fdd370ff991af5d225168ff2b67bff5e5bae Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sun, 17 Dec 2023 11:25:28 +0100 Subject: [PATCH 12/12] Attempt to fix test on CI... --- .../features/roomlist/impl/RoomListPresenterTests.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index bf7c085ac7..4573456762 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -192,17 +192,22 @@ class RoomListPresenterTests { presenter.present() }.test { roomListService.postAllRooms(listOf(aRoomSummaryFilled())) - val loadedState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last() + skipItems(3) + val loadedState = awaitItem() // Test filtering with result + assertThat(loadedState.roomList.size).isEqualTo(1) loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3))) - val withFilteredRoomState = consumeItemsUntilPredicate { state -> state.filteredRoomList.size == 1 }.last() + skipItems(1) + val withFilteredRoomState = awaitItem() + assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1) assertThat(withFilteredRoomState.filter).isEqualTo(A_ROOM_NAME.substring(0, 3)) assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1) assertThat(withFilteredRoomState.filteredRoomList.first()) .isEqualTo(aRoomListRoomSummary) // Test filtering without result withFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada")) - val withNotFilteredRoomState = consumeItemsUntilPredicate { state -> state.filteredRoomList.size == 0 }.last() + skipItems(1) + val withNotFilteredRoomState = awaitItem() assertThat(withNotFilteredRoomState.filter).isEqualTo("tada") assertThat(withNotFilteredRoomState.filteredRoomList).isEmpty() scope.cancel()