Benoit Marty
3 weeks ago
7 changed files with 254 additions and 3 deletions
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.factories |
||||
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo |
||||
|
||||
fun anEventTimelineItemDebugInfo( |
||||
model: String = "model", |
||||
originalJson: String? = null, |
||||
latestEditJson: String? = null, |
||||
) = EventTimelineItemDebugInfo( |
||||
model = model, |
||||
originalJson = originalJson, |
||||
latestEditJson = latestEditJson |
||||
) |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes |
||||
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.anEventTimelineItemDebugInfo |
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID |
||||
import io.element.android.libraries.matrix.test.A_USER_ID |
||||
import org.matrix.rustcomponents.sdk.EventSendState |
||||
import org.matrix.rustcomponents.sdk.EventTimelineItem |
||||
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo |
||||
import org.matrix.rustcomponents.sdk.NoPointer |
||||
import org.matrix.rustcomponents.sdk.ProfileDetails |
||||
import org.matrix.rustcomponents.sdk.Reaction |
||||
import org.matrix.rustcomponents.sdk.Receipt |
||||
import org.matrix.rustcomponents.sdk.ShieldState |
||||
import org.matrix.rustcomponents.sdk.TimelineItemContent |
||||
import uniffi.matrix_sdk_ui.EventItemOrigin |
||||
|
||||
class FakeRustEventTimelineItem( |
||||
private val origin: EventItemOrigin? = null, |
||||
) : EventTimelineItem(NoPointer) { |
||||
override fun origin(): EventItemOrigin? = origin |
||||
override fun eventId(): String = AN_EVENT_ID.value |
||||
override fun transactionId(): String? = null |
||||
override fun isEditable(): Boolean = false |
||||
override fun canBeRepliedTo(): Boolean = false |
||||
override fun isLocal(): Boolean = false |
||||
override fun isOwn(): Boolean = false |
||||
override fun isRemote(): Boolean = false |
||||
override fun localSendState(): EventSendState? = null |
||||
override fun reactions(): List<Reaction> = emptyList() |
||||
override fun readReceipts(): Map<String, Receipt> = emptyMap() |
||||
override fun sender(): String = A_USER_ID.value |
||||
override fun senderProfile(): ProfileDetails = ProfileDetails.Unavailable |
||||
override fun timestamp(): ULong = 0u |
||||
override fun content(): TimelineItemContent = FakeRustTimelineItemContent() |
||||
override fun debugInfo(): EventTimelineItemDebugInfo = anEventTimelineItemDebugInfo() |
||||
override fun getShield(strict: Boolean): ShieldState? = null |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes |
||||
|
||||
import org.matrix.rustcomponents.sdk.NoPointer |
||||
import org.matrix.rustcomponents.sdk.TaskHandle |
||||
import org.matrix.rustcomponents.sdk.Timeline |
||||
import org.matrix.rustcomponents.sdk.TimelineDiff |
||||
import org.matrix.rustcomponents.sdk.TimelineListener |
||||
|
||||
class FakeRustTimeline( |
||||
) : Timeline(NoPointer) { |
||||
private var listener: TimelineListener? = null |
||||
override suspend fun addListener(listener: TimelineListener): TaskHandle { |
||||
this.listener = listener |
||||
return FakeRustTaskHandle() |
||||
} |
||||
|
||||
fun emitDiff(diff: List<TimelineDiff>) { |
||||
listener!!.onUpdate(diff) |
||||
} |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes |
||||
|
||||
import org.matrix.rustcomponents.sdk.Message |
||||
import org.matrix.rustcomponents.sdk.NoPointer |
||||
import org.matrix.rustcomponents.sdk.TimelineItemContent |
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind |
||||
|
||||
class FakeRustTimelineItemContent : TimelineItemContent(NoPointer) { |
||||
override fun asMessage(): Message? = null |
||||
override fun kind(): TimelineItemContentKind = TimelineItemContentKind.Message |
||||
} |
@ -0,0 +1,139 @@
@@ -0,0 +1,139 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.matrix.impl.timeline |
||||
|
||||
import app.cash.turbine.test |
||||
import com.google.common.truth.Truth.assertThat |
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem |
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustEventTimelineItem |
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimeline |
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineDiff |
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineItem |
||||
import io.element.android.tests.testutils.lambda.lambdaError |
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder |
||||
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope |
||||
import kotlinx.coroutines.CompletableDeferred |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||
import kotlinx.coroutines.flow.MutableSharedFlow |
||||
import kotlinx.coroutines.flow.MutableStateFlow |
||||
import kotlinx.coroutines.test.StandardTestDispatcher |
||||
import kotlinx.coroutines.test.TestScope |
||||
import kotlinx.coroutines.test.runCurrent |
||||
import org.junit.Test |
||||
import org.matrix.rustcomponents.sdk.Timeline |
||||
import org.matrix.rustcomponents.sdk.TimelineChange |
||||
import uniffi.matrix_sdk_ui.EventItemOrigin |
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class) |
||||
class TimelineItemsSubscriberTest { |
||||
@Test |
||||
fun `when timeline emits an empty list of items, the flow must emits an empty list`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope -> |
||||
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = |
||||
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE) |
||||
val timeline = FakeRustTimeline() |
||||
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber( |
||||
coroutineScope = cancellableScope, |
||||
timeline = timeline, |
||||
timelineItems = timelineItems, |
||||
) |
||||
timelineItems.test { |
||||
timelineItemsSubscriber.subscribeIfNeeded() |
||||
// Wait for the listener to be set. |
||||
testScope.runCurrent() |
||||
timeline.emitDiff(listOf(FakeRustTimelineDiff(item = null, change = TimelineChange.RESET))) |
||||
val final = awaitItem() |
||||
assertThat(final).isEmpty() |
||||
timelineItemsSubscriber.unsubscribeIfNeeded() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `when timeline emits a non empty list of items, the flow must emits a non empty list`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope -> |
||||
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = |
||||
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE) |
||||
val timeline = FakeRustTimeline() |
||||
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber( |
||||
coroutineScope = cancellableScope, |
||||
timeline = timeline, |
||||
timelineItems = timelineItems, |
||||
) |
||||
timelineItems.test { |
||||
timelineItemsSubscriber.subscribeIfNeeded() |
||||
// Wait for the listener to be set. |
||||
testScope.runCurrent() |
||||
timeline.emitDiff(listOf(FakeRustTimelineDiff(item = FakeRustTimelineItem(), change = TimelineChange.RESET))) |
||||
val final = awaitItem() |
||||
assertThat(final).isNotEmpty() |
||||
timelineItemsSubscriber.unsubscribeIfNeeded() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `when timeline emits an item with SYNC origin, the callback onNewSyncedEvent is invoked`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope -> |
||||
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = |
||||
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE) |
||||
val timeline = FakeRustTimeline() |
||||
val onNewSyncedEventRecorder = lambdaRecorder<Unit> { } |
||||
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber( |
||||
coroutineScope = cancellableScope, |
||||
timeline = timeline, |
||||
timelineItems = timelineItems, |
||||
onNewSyncedEvent = onNewSyncedEventRecorder, |
||||
) |
||||
timelineItems.test { |
||||
timelineItemsSubscriber.subscribeIfNeeded() |
||||
// Wait for the listener to be set. |
||||
testScope.runCurrent() |
||||
timeline.emitDiff( |
||||
listOf( |
||||
FakeRustTimelineDiff( |
||||
item = FakeRustTimelineItem( |
||||
asEventResult = FakeRustEventTimelineItem(origin = EventItemOrigin.SYNC) |
||||
), |
||||
change = TimelineChange.RESET, |
||||
) |
||||
) |
||||
) |
||||
val final = awaitItem() |
||||
assertThat(final).isNotEmpty() |
||||
timelineItemsSubscriber.unsubscribeIfNeeded() |
||||
} |
||||
onNewSyncedEventRecorder.assertions().isCalledOnce() |
||||
} |
||||
|
||||
@Test |
||||
fun `multiple subscriptions does not have side effect`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope -> |
||||
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber( |
||||
coroutineScope = cancellableScope, |
||||
) |
||||
timelineItemsSubscriber.subscribeIfNeeded() |
||||
timelineItemsSubscriber.subscribeIfNeeded() |
||||
timelineItemsSubscriber.unsubscribeIfNeeded() |
||||
timelineItemsSubscriber.unsubscribeIfNeeded() |
||||
} |
||||
|
||||
private fun TestScope.createTimelineItemsSubscriber( |
||||
coroutineScope: CoroutineScope, |
||||
timeline: Timeline = FakeRustTimeline(), |
||||
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE), |
||||
initLatch: CompletableDeferred<Unit> = CompletableDeferred(), |
||||
isTimelineInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false), |
||||
onNewSyncedEvent: () -> Unit = { lambdaError() }, |
||||
): TimelineItemsSubscriber { |
||||
return TimelineItemsSubscriber( |
||||
timelineCoroutineScope = coroutineScope, |
||||
dispatcher = StandardTestDispatcher(testScheduler), |
||||
timeline = timeline, |
||||
timelineDiffProcessor = createMatrixTimelineDiffProcessor(timelineItems), |
||||
initLatch = initLatch, |
||||
isTimelineInitialized = isTimelineInitialized, |
||||
onNewSyncedEvent = onNewSyncedEvent, |
||||
) |
||||
} |
||||
} |
Loading…
Reference in new issue