Skip to content

Commit b7c6369

Browse files
committed
Add test on TimelineItemsSubscriber.
1 parent 738ac54 commit b7c6369

File tree

7 files changed

+254
-3
lines changed

7 files changed

+254
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.matrix.impl.fixtures.factories
9+
10+
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo
11+
12+
fun anEventTimelineItemDebugInfo(
13+
model: String = "model",
14+
originalJson: String? = null,
15+
latestEditJson: String? = null,
16+
) = EventTimelineItemDebugInfo(
17+
model = model,
18+
originalJson = originalJson,
19+
latestEditJson = latestEditJson
20+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.matrix.impl.fixtures.fakes
9+
10+
import io.element.android.libraries.matrix.impl.fixtures.factories.anEventTimelineItemDebugInfo
11+
import io.element.android.libraries.matrix.test.AN_EVENT_ID
12+
import io.element.android.libraries.matrix.test.A_USER_ID
13+
import org.matrix.rustcomponents.sdk.EventSendState
14+
import org.matrix.rustcomponents.sdk.EventTimelineItem
15+
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo
16+
import org.matrix.rustcomponents.sdk.NoPointer
17+
import org.matrix.rustcomponents.sdk.ProfileDetails
18+
import org.matrix.rustcomponents.sdk.Reaction
19+
import org.matrix.rustcomponents.sdk.Receipt
20+
import org.matrix.rustcomponents.sdk.ShieldState
21+
import org.matrix.rustcomponents.sdk.TimelineItemContent
22+
import uniffi.matrix_sdk_ui.EventItemOrigin
23+
24+
class FakeRustEventTimelineItem(
25+
private val origin: EventItemOrigin? = null,
26+
) : EventTimelineItem(NoPointer) {
27+
override fun origin(): EventItemOrigin? = origin
28+
override fun eventId(): String = AN_EVENT_ID.value
29+
override fun transactionId(): String? = null
30+
override fun isEditable(): Boolean = false
31+
override fun canBeRepliedTo(): Boolean = false
32+
override fun isLocal(): Boolean = false
33+
override fun isOwn(): Boolean = false
34+
override fun isRemote(): Boolean = false
35+
override fun localSendState(): EventSendState? = null
36+
override fun reactions(): List<Reaction> = emptyList()
37+
override fun readReceipts(): Map<String, Receipt> = emptyMap()
38+
override fun sender(): String = A_USER_ID.value
39+
override fun senderProfile(): ProfileDetails = ProfileDetails.Unavailable
40+
override fun timestamp(): ULong = 0u
41+
override fun content(): TimelineItemContent = FakeRustTimelineItemContent()
42+
override fun debugInfo(): EventTimelineItemDebugInfo = anEventTimelineItemDebugInfo()
43+
override fun getShield(strict: Boolean): ShieldState? = null
44+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.matrix.impl.fixtures.fakes
9+
10+
import org.matrix.rustcomponents.sdk.NoPointer
11+
import org.matrix.rustcomponents.sdk.TaskHandle
12+
import org.matrix.rustcomponents.sdk.Timeline
13+
import org.matrix.rustcomponents.sdk.TimelineDiff
14+
import org.matrix.rustcomponents.sdk.TimelineListener
15+
16+
class FakeRustTimeline(
17+
) : Timeline(NoPointer) {
18+
private var listener: TimelineListener? = null
19+
override suspend fun addListener(listener: TimelineListener): TaskHandle {
20+
this.listener = listener
21+
return FakeRustTaskHandle()
22+
}
23+
24+
fun emitDiff(diff: List<TimelineDiff>) {
25+
listener!!.onUpdate(diff)
26+
}
27+
}

libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimelineItem.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import org.matrix.rustcomponents.sdk.NoPointer
1212
import org.matrix.rustcomponents.sdk.TimelineItem
1313
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
1414

15-
class FakeRustTimelineItem : TimelineItem(NoPointer) {
16-
override fun asEvent(): EventTimelineItem? = null
15+
class FakeRustTimelineItem(
16+
private val asEventResult: EventTimelineItem? = null,
17+
) : TimelineItem(NoPointer) {
18+
override fun asEvent(): EventTimelineItem? = asEventResult
1719
override fun asVirtual(): VirtualTimelineItem? = null
1820
override fun fmtDebug(): String = "fmtDebug"
1921
override fun uniqueId(): String = "uniqueId"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.matrix.impl.fixtures.fakes
9+
10+
import org.matrix.rustcomponents.sdk.Message
11+
import org.matrix.rustcomponents.sdk.NoPointer
12+
import org.matrix.rustcomponents.sdk.TimelineItemContent
13+
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
14+
15+
class FakeRustTimelineItemContent : TimelineItemContent(NoPointer) {
16+
override fun asMessage(): Message? = null
17+
override fun kind(): TimelineItemContentKind = TimelineItemContentKind.Message
18+
}

libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTim
1616
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
1717
import io.element.android.libraries.matrix.test.A_UNIQUE_ID_2
1818
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
19+
import kotlinx.coroutines.flow.MutableSharedFlow
1920
import kotlinx.coroutines.flow.MutableStateFlow
2021
import kotlinx.coroutines.test.TestScope
2122
import kotlinx.coroutines.test.runTest
@@ -155,7 +156,7 @@ class MatrixTimelineDiffProcessorTest {
155156
}
156157

157158
internal fun TestScope.createMatrixTimelineDiffProcessor(
158-
timelineItems: MutableStateFlow<List<MatrixTimelineItem>>,
159+
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>>,
159160
): MatrixTimelineDiffProcessor {
160161
val timelineEventContentMapper = TimelineEventContentMapper()
161162
val timelineItemMapper = MatrixTimelineItemMapper(
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.matrix.impl.timeline
9+
10+
import app.cash.turbine.test
11+
import com.google.common.truth.Truth.assertThat
12+
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
13+
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustEventTimelineItem
14+
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimeline
15+
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineDiff
16+
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineItem
17+
import io.element.android.tests.testutils.lambda.lambdaError
18+
import io.element.android.tests.testutils.lambda.lambdaRecorder
19+
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope
20+
import kotlinx.coroutines.CompletableDeferred
21+
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.ExperimentalCoroutinesApi
23+
import kotlinx.coroutines.flow.MutableSharedFlow
24+
import kotlinx.coroutines.flow.MutableStateFlow
25+
import kotlinx.coroutines.test.StandardTestDispatcher
26+
import kotlinx.coroutines.test.TestScope
27+
import kotlinx.coroutines.test.runCurrent
28+
import org.junit.Test
29+
import org.matrix.rustcomponents.sdk.Timeline
30+
import org.matrix.rustcomponents.sdk.TimelineChange
31+
import uniffi.matrix_sdk_ui.EventItemOrigin
32+
33+
@OptIn(ExperimentalCoroutinesApi::class)
34+
class TimelineItemsSubscriberTest {
35+
@Test
36+
fun `when timeline emits an empty list of items, the flow must emits an empty list`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
37+
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
38+
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
39+
val timeline = FakeRustTimeline()
40+
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber(
41+
coroutineScope = cancellableScope,
42+
timeline = timeline,
43+
timelineItems = timelineItems,
44+
)
45+
timelineItems.test {
46+
timelineItemsSubscriber.subscribeIfNeeded()
47+
// Wait for the listener to be set.
48+
testScope.runCurrent()
49+
timeline.emitDiff(listOf(FakeRustTimelineDiff(item = null, change = TimelineChange.RESET)))
50+
val final = awaitItem()
51+
assertThat(final).isEmpty()
52+
timelineItemsSubscriber.unsubscribeIfNeeded()
53+
}
54+
}
55+
56+
@Test
57+
fun `when timeline emits a non empty list of items, the flow must emits a non empty list`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
58+
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
59+
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
60+
val timeline = FakeRustTimeline()
61+
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber(
62+
coroutineScope = cancellableScope,
63+
timeline = timeline,
64+
timelineItems = timelineItems,
65+
)
66+
timelineItems.test {
67+
timelineItemsSubscriber.subscribeIfNeeded()
68+
// Wait for the listener to be set.
69+
testScope.runCurrent()
70+
timeline.emitDiff(listOf(FakeRustTimelineDiff(item = FakeRustTimelineItem(), change = TimelineChange.RESET)))
71+
val final = awaitItem()
72+
assertThat(final).isNotEmpty()
73+
timelineItemsSubscriber.unsubscribeIfNeeded()
74+
}
75+
}
76+
77+
@Test
78+
fun `when timeline emits an item with SYNC origin, the callback onNewSyncedEvent is invoked`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
79+
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
80+
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
81+
val timeline = FakeRustTimeline()
82+
val onNewSyncedEventRecorder = lambdaRecorder<Unit> { }
83+
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber(
84+
coroutineScope = cancellableScope,
85+
timeline = timeline,
86+
timelineItems = timelineItems,
87+
onNewSyncedEvent = onNewSyncedEventRecorder,
88+
)
89+
timelineItems.test {
90+
timelineItemsSubscriber.subscribeIfNeeded()
91+
// Wait for the listener to be set.
92+
testScope.runCurrent()
93+
timeline.emitDiff(
94+
listOf(
95+
FakeRustTimelineDiff(
96+
item = FakeRustTimelineItem(
97+
asEventResult = FakeRustEventTimelineItem(origin = EventItemOrigin.SYNC)
98+
),
99+
change = TimelineChange.RESET,
100+
)
101+
)
102+
)
103+
val final = awaitItem()
104+
assertThat(final).isNotEmpty()
105+
timelineItemsSubscriber.unsubscribeIfNeeded()
106+
}
107+
onNewSyncedEventRecorder.assertions().isCalledOnce()
108+
}
109+
110+
@Test
111+
fun `multiple subscriptions does not have side effect`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
112+
val timelineItemsSubscriber = testScope.createTimelineItemsSubscriber(
113+
coroutineScope = cancellableScope,
114+
)
115+
timelineItemsSubscriber.subscribeIfNeeded()
116+
timelineItemsSubscriber.subscribeIfNeeded()
117+
timelineItemsSubscriber.unsubscribeIfNeeded()
118+
timelineItemsSubscriber.unsubscribeIfNeeded()
119+
}
120+
121+
private fun TestScope.createTimelineItemsSubscriber(
122+
coroutineScope: CoroutineScope,
123+
timeline: Timeline = FakeRustTimeline(),
124+
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE),
125+
initLatch: CompletableDeferred<Unit> = CompletableDeferred(),
126+
isTimelineInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false),
127+
onNewSyncedEvent: () -> Unit = { lambdaError() },
128+
): TimelineItemsSubscriber {
129+
return TimelineItemsSubscriber(
130+
timelineCoroutineScope = coroutineScope,
131+
dispatcher = StandardTestDispatcher(testScheduler),
132+
timeline = timeline,
133+
timelineDiffProcessor = createMatrixTimelineDiffProcessor(timelineItems),
134+
initLatch = initLatch,
135+
isTimelineInitialized = isTimelineInitialized,
136+
onNewSyncedEvent = onNewSyncedEvent,
137+
)
138+
}
139+
}

0 commit comments

Comments
 (0)