Browse Source

Merge pull request #3554 from element-hq/feature/bma/testEmptyTimeline

Add unit tests on TimelineItemsSubscriber
pull/3585/head
Benoit Marty 2 weeks ago committed by GitHub
parent
commit
29356b96c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 20
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemDebugInfo.kt
  2. 44
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustEventTimelineItem.kt
  3. 26
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimeline.kt
  4. 6
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimelineItem.kt
  5. 18
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimelineItemContent.kt
  6. 10
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryListTest.kt
  7. 8
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryServiceTest.kt
  8. 6
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt
  9. 10
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListServiceTest.kt
  10. 53
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt
  11. 139
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriberTest.kt
  12. 61
      services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt
  13. 42
      services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt
  14. 32
      tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RunCancellableTest.kt

20
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemDebugInfo.kt

@ -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
)

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

@ -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
}

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

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*
* 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)
}
}

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

@ -12,8 +12,10 @@ import org.matrix.rustcomponents.sdk.NoPointer @@ -12,8 +12,10 @@ import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.TimelineItem
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
class FakeRustTimelineItem : TimelineItem(NoPointer) {
override fun asEvent(): EventTimelineItem? = null
class FakeRustTimelineItem(
private val asEventResult: EventTimelineItem? = null,
) : TimelineItem(NoPointer) {
override fun asEvent(): EventTimelineItem? = asEventResult
override fun asVirtual(): VirtualTimelineItem? = null
override fun fmtDebug(): String = "fmtDebug"
override fun uniqueId(): String = "uniqueId"

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

@ -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
}

10
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryListTest.kt

@ -13,12 +13,12 @@ import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList @@ -13,12 +13,12 @@ import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomDescription
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRoomDirectorySearch
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
@ -26,15 +26,15 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate @@ -26,15 +26,15 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
@OptIn(ExperimentalCoroutinesApi::class)
class RustRoomDirectoryListTest {
@Test
fun `check that the state emits the expected values`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
fun `check that the state emits the expected values`() = runTest {
val fakeRoomDirectorySearch = FakeRoomDirectorySearch()
val mapper = RoomDescriptionMapper()
val sut = testScope.createRustRoomDirectoryList(
val sut = createRustRoomDirectoryList(
roomDirectorySearch = fakeRoomDirectorySearch,
scope = cancellableScope,
scope = backgroundScope,
)
// Let the mxCallback be ready
testScope.runCurrent()
runCurrent()
sut.state.test {
sut.filter("", 20)
fakeRoomDirectorySearch.emitResult(

8
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryServiceTest.kt

@ -8,18 +8,18 @@ @@ -8,18 +8,18 @@
package io.element.android.libraries.matrix.impl.roomdirectory
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustClient
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
class RustRoomDirectoryServiceTest {
@Test
fun test() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
fun test() = runTest {
val client = FakeRustClient()
val sut = RustRoomDirectoryService(
client = client,
sessionDispatcher = StandardTestDispatcher(testScope.testScheduler),
sessionDispatcher = StandardTestDispatcher(testScheduler),
)
sut.createRoomDirectoryList(cancellableScope)
sut.createRoomDirectoryList(backgroundScope)
}
}

6
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt

@ -9,16 +9,16 @@ package io.element.android.libraries.matrix.impl.roomlist @@ -9,16 +9,16 @@ package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomList
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.tests.testutils.runCancellableScopeTest
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.coroutines.EmptyCoroutineContext
class RoomListFactoryTest {
@Test
fun `createRoomList should work`() = runCancellableScopeTest {
fun `createRoomList should work`() = runTest {
val sut = RoomListFactory(
innerRoomListService = FakeRustRoomListService(),
sessionCoroutineScope = it,
sessionCoroutineScope = backgroundScope,
)
sut.createRoomList(
pageSize = 10,

10
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListServiceTest.kt

@ -11,13 +11,13 @@ import com.google.common.truth.Truth.assertThat @@ -11,13 +11,13 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
import org.matrix.rustcomponents.sdk.RoomListService as RustRoomListService
@ -25,14 +25,14 @@ import org.matrix.rustcomponents.sdk.RoomListService as RustRoomListService @@ -25,14 +25,14 @@ import org.matrix.rustcomponents.sdk.RoomListService as RustRoomListService
@OptIn(ExperimentalCoroutinesApi::class)
class RustRoomListServiceTest {
@Test
fun `syncIndicator should emit the expected values`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
fun `syncIndicator should emit the expected values`() = runTest {
val roomListService = FakeRustRoomListService()
val sut = testScope.createRustRoomListService(
sessionCoroutineScope = cancellableScope,
val sut = createRustRoomListService(
sessionCoroutineScope = backgroundScope,
roomListService = roomListService,
)
// Give time for mxCallback to setup
testScope.runCurrent()
runCurrent()
sut.syncIndicator.test {
assertThat(awaitItem()).isEqualTo(RoomListService.SyncIndicator.Hide)
roomListService.emitRoomListServiceSyncIndicator(RoomListServiceSyncIndicator.SHOW)

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

@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTim @@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTim
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.A_UNIQUE_ID_2
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@ -31,7 +32,7 @@ class MatrixTimelineDiffProcessorTest { @@ -31,7 +32,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `Append adds new entries at the end of the list`() = runTest {
timelineItems.value = listOf(anEvent)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.APPEND)))
assertThat(timelineItems.value.count()).isEqualTo(2)
assertThat(timelineItems.value).containsExactly(
@ -43,7 +44,7 @@ class MatrixTimelineDiffProcessorTest { @@ -43,7 +44,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `PushBack adds a new entry at the end of the list`() = runTest {
timelineItems.value = listOf(anEvent)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.PUSH_BACK)))
assertThat(timelineItems.value.count()).isEqualTo(2)
assertThat(timelineItems.value).containsExactly(
@ -55,7 +56,7 @@ class MatrixTimelineDiffProcessorTest { @@ -55,7 +56,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `PushFront inserts a new entry at the start of the list`() = runTest {
timelineItems.value = listOf(anEvent)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.PUSH_FRONT)))
assertThat(timelineItems.value.count()).isEqualTo(2)
assertThat(timelineItems.value).containsExactly(
@ -67,7 +68,7 @@ class MatrixTimelineDiffProcessorTest { @@ -67,7 +68,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `Set replaces an entry at some index`() = runTest {
timelineItems.value = listOf(anEvent, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.SET)))
assertThat(timelineItems.value.count()).isEqualTo(2)
assertThat(timelineItems.value).containsExactly(
@ -79,7 +80,7 @@ class MatrixTimelineDiffProcessorTest { @@ -79,7 +80,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `Insert inserts a new entry at the provided index`() = runTest {
timelineItems.value = listOf(anEvent, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.INSERT)))
assertThat(timelineItems.value.count()).isEqualTo(3)
assertThat(timelineItems.value).containsExactly(
@ -92,7 +93,7 @@ class MatrixTimelineDiffProcessorTest { @@ -92,7 +93,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `Remove removes an entry at some index`() = runTest {
timelineItems.value = listOf(anEvent, MatrixTimelineItem.Other, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.REMOVE)))
assertThat(timelineItems.value.count()).isEqualTo(2)
assertThat(timelineItems.value).containsExactly(
@ -104,7 +105,7 @@ class MatrixTimelineDiffProcessorTest { @@ -104,7 +105,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `PopBack removes an entry at the end of the list`() = runTest {
timelineItems.value = listOf(anEvent, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.POP_BACK)))
assertThat(timelineItems.value.count()).isEqualTo(1)
assertThat(timelineItems.value).containsExactly(
@ -115,7 +116,7 @@ class MatrixTimelineDiffProcessorTest { @@ -115,7 +116,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `PopFront removes an entry at the start of the list`() = runTest {
timelineItems.value = listOf(anEvent, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.POP_FRONT)))
assertThat(timelineItems.value.count()).isEqualTo(1)
assertThat(timelineItems.value).containsExactly(
@ -126,7 +127,7 @@ class MatrixTimelineDiffProcessorTest { @@ -126,7 +127,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `Clear removes all the entries`() = runTest {
timelineItems.value = listOf(anEvent, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.CLEAR)))
assertThat(timelineItems.value).isEmpty()
}
@ -134,7 +135,7 @@ class MatrixTimelineDiffProcessorTest { @@ -134,7 +135,7 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `Truncate removes all entries after the provided length`() = runTest {
timelineItems.value = listOf(anEvent, MatrixTimelineItem.Other, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.TRUNCATE)))
assertThat(timelineItems.value.count()).isEqualTo(1)
assertThat(timelineItems.value).containsExactly(
@ -145,27 +146,29 @@ class MatrixTimelineDiffProcessorTest { @@ -145,27 +146,29 @@ class MatrixTimelineDiffProcessorTest {
@Test
fun `Reset removes all entries and add the provided ones`() = runTest {
timelineItems.value = listOf(anEvent, MatrixTimelineItem.Other, anEvent2)
val processor = createProcessor()
val processor = createMatrixTimelineDiffProcessor(timelineItems)
processor.postDiffs(listOf(FakeRustTimelineDiff(change = TimelineChange.RESET)))
assertThat(timelineItems.value.count()).isEqualTo(1)
assertThat(timelineItems.value).containsExactly(
MatrixTimelineItem.Other,
)
}
}
private fun TestScope.createProcessor(): MatrixTimelineDiffProcessor {
val timelineEventContentMapper = TimelineEventContentMapper()
val timelineItemMapper = MatrixTimelineItemMapper(
fetchDetailsForEvent = { _ -> Result.success(Unit) },
coroutineScope = this,
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
eventTimelineItemMapper = EventTimelineItemMapper(
contentMapper = timelineEventContentMapper
)
)
return MatrixTimelineDiffProcessor(
timelineItems,
timelineItemFactory = timelineItemMapper,
internal fun TestScope.createMatrixTimelineDiffProcessor(
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>>,
): MatrixTimelineDiffProcessor {
val timelineEventContentMapper = TimelineEventContentMapper()
val timelineItemMapper = MatrixTimelineItemMapper(
fetchDetailsForEvent = { _ -> Result.success(Unit) },
coroutineScope = this,
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
eventTimelineItemMapper = EventTimelineItemMapper(
contentMapper = timelineEventContentMapper
)
}
)
return MatrixTimelineDiffProcessor(
timelineItems = timelineItems,
timelineItemFactory = timelineItemMapper,
)
}

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

@ -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 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 kotlinx.coroutines.test.runTest
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`() = runTest {
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
val timeline = FakeRustTimeline()
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
timeline = timeline,
timelineItems = timelineItems,
)
timelineItems.test {
timelineItemsSubscriber.subscribeIfNeeded()
// Wait for the listener to be set.
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`() = runTest {
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
val timeline = FakeRustTimeline()
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
timeline = timeline,
timelineItems = timelineItems,
)
timelineItems.test {
timelineItemsSubscriber.subscribeIfNeeded()
// Wait for the listener to be set.
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`() = runTest {
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
val timeline = FakeRustTimeline()
val onNewSyncedEventRecorder = lambdaRecorder<Unit> { }
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
timeline = timeline,
timelineItems = timelineItems,
onNewSyncedEvent = onNewSyncedEventRecorder,
)
timelineItems.test {
timelineItemsSubscriber.subscribeIfNeeded()
// Wait for the listener to be set.
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`() = runTest {
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
)
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,
)
}

61
services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt

@ -24,7 +24,6 @@ import io.element.android.services.analyticsproviders.api.AnalyticsProvider @@ -24,7 +24,6 @@ import io.element.android.services.analyticsproviders.api.AnalyticsProvider
import io.element.android.services.analyticsproviders.test.FakeAnalyticsProvider
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.runCancellableScopeTest
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -37,13 +36,13 @@ import org.junit.Test @@ -37,13 +36,13 @@ import org.junit.Test
class DefaultAnalyticsServiceTest {
@Test
fun `getAvailableAnalyticsProviders return the set of provider`() = runCancellableScopeTest {
fun `getAvailableAnalyticsProviders return the set of provider`() = runTest {
val providers = setOf(
FakeAnalyticsProvider(name = "provider1", stopLambda = { }),
FakeAnalyticsProvider(name = "provider2", stopLambda = { }),
)
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsProviders = providers
)
val result = sut.getAvailableAnalyticsProviders()
@ -51,17 +50,17 @@ class DefaultAnalyticsServiceTest { @@ -51,17 +50,17 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when consent is not provided, capture is no op`() = runCancellableScopeTest {
val sut = createDefaultAnalyticsService(it)
fun `when consent is not provided, capture is no op`() = runTest {
val sut = createDefaultAnalyticsService(backgroundScope)
sut.capture(anEvent)
}
@Test
fun `when consent is provided, capture is sent to the AnalyticsProvider`() = runCancellableScopeTest {
fun `when consent is provided, capture is sent to the AnalyticsProvider`() = runTest {
val initLambda = lambdaRecorder<Unit> { }
val captureLambda = lambdaRecorder<VectorAnalyticsEvent, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = FakeAnalyticsStore(defaultUserConsent = true),
analyticsProviders = setOf(
FakeAnalyticsProvider(
@ -76,17 +75,17 @@ class DefaultAnalyticsServiceTest { @@ -76,17 +75,17 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when consent is not provided, screen is no op`() = runCancellableScopeTest {
val sut = createDefaultAnalyticsService(it)
fun `when consent is not provided, screen is no op`() = runTest {
val sut = createDefaultAnalyticsService(backgroundScope)
sut.screen(aScreen)
}
@Test
fun `when consent is provided, screen is sent to the AnalyticsProvider`() = runCancellableScopeTest {
fun `when consent is provided, screen is sent to the AnalyticsProvider`() = runTest {
val initLambda = lambdaRecorder<Unit> { }
val screenLambda = lambdaRecorder<VectorAnalyticsScreen, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = FakeAnalyticsStore(defaultUserConsent = true),
analyticsProviders = setOf(
FakeAnalyticsProvider(
@ -101,17 +100,17 @@ class DefaultAnalyticsServiceTest { @@ -101,17 +100,17 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when consent is not provided, trackError is no op`() = runCancellableScopeTest {
val sut = createDefaultAnalyticsService(it)
fun `when consent is not provided, trackError is no op`() = runTest {
val sut = createDefaultAnalyticsService(backgroundScope)
sut.trackError(anError)
}
@Test
fun `when consent is provided, trackError is sent to the AnalyticsProvider`() = runCancellableScopeTest {
fun `when consent is provided, trackError is sent to the AnalyticsProvider`() = runTest {
val initLambda = lambdaRecorder<Unit> { }
val trackErrorLambda = lambdaRecorder<Throwable, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = FakeAnalyticsStore(defaultUserConsent = true),
analyticsProviders = setOf(
FakeAnalyticsProvider(
@ -126,10 +125,10 @@ class DefaultAnalyticsServiceTest { @@ -126,10 +125,10 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `setUserConsent is sent to the store`() = runCancellableScopeTest {
fun `setUserConsent is sent to the store`() = runTest {
val store = FakeAnalyticsStore()
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = store,
)
assertThat(store.userConsentFlow.first()).isFalse()
@ -140,10 +139,10 @@ class DefaultAnalyticsServiceTest { @@ -140,10 +139,10 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `setAnalyticsId is sent to the store`() = runCancellableScopeTest {
fun `setAnalyticsId is sent to the store`() = runTest {
val store = FakeAnalyticsStore()
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = store,
)
assertThat(store.analyticsIdFlow.first()).isEqualTo("")
@ -154,10 +153,10 @@ class DefaultAnalyticsServiceTest { @@ -154,10 +153,10 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `setDidAskUserConsent is sent to the store`() = runCancellableScopeTest {
fun `setDidAskUserConsent is sent to the store`() = runTest {
val store = FakeAnalyticsStore()
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = store,
)
assertThat(store.didAskUserConsentFlow.first()).isFalse()
@ -168,13 +167,13 @@ class DefaultAnalyticsServiceTest { @@ -168,13 +167,13 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when a session is deleted, the store is reset`() = runCancellableScopeTest {
fun `when a session is deleted, the store is reset`() = runTest {
val resetLambda = lambdaRecorder<Unit> { }
val store = FakeAnalyticsStore(
resetLambda = resetLambda,
)
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = store,
)
sut.onSessionDeleted("userId")
@ -182,12 +181,12 @@ class DefaultAnalyticsServiceTest { @@ -182,12 +181,12 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when reset is invoked, the user consent is reset`() = runCancellableScopeTest {
fun `when reset is invoked, the user consent is reset`() = runTest {
val store = FakeAnalyticsStore(
defaultDidAskUserConsent = true,
)
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsStore = store,
)
assertThat(store.didAskUserConsentFlow.first()).isTrue()
@ -196,9 +195,9 @@ class DefaultAnalyticsServiceTest { @@ -196,9 +195,9 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when a session is added, nothing happen`() = runCancellableScopeTest {
fun `when a session is added, nothing happen`() = runTest {
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
)
sut.onSessionCreated("userId")
}
@ -231,10 +230,10 @@ class DefaultAnalyticsServiceTest { @@ -231,10 +230,10 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when consent is provided, updateUserProperties is sent to the provider`() = runCancellableScopeTest {
fun `when consent is provided, updateUserProperties is sent to the provider`() = runTest {
val updateUserPropertiesLambda = lambdaRecorder<UserProperties, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsProviders = setOf(
FakeAnalyticsProvider(
initLambda = { },
@ -248,10 +247,10 @@ class DefaultAnalyticsServiceTest { @@ -248,10 +247,10 @@ class DefaultAnalyticsServiceTest {
}
@Test
fun `when super properties are updated, updateSuperProperties is sent to the provider`() = runCancellableScopeTest {
fun `when super properties are updated, updateSuperProperties is sent to the provider`() = runTest {
val updateSuperPropertiesLambda = lambdaRecorder<SuperProperties, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
coroutineScope = backgroundScope,
analyticsProviders = setOf(
FakeAnalyticsProvider(
initLambda = { },

42
services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt

@ -23,9 +23,9 @@ import io.element.android.services.appnavstate.test.A_SESSION_OWNER @@ -23,9 +23,9 @@ import io.element.android.services.appnavstate.test.A_SESSION_OWNER
import io.element.android.services.appnavstate.test.A_SPACE_OWNER
import io.element.android.services.appnavstate.test.A_THREAD_OWNER
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.tests.testutils.runCancellableScopeTest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultNavigationStateServiceTest {
@ -51,8 +51,8 @@ class DefaultNavigationStateServiceTest { @@ -51,8 +51,8 @@ class DefaultNavigationStateServiceTest {
)
@Test
fun testNavigation() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testNavigation() = runTest {
val service = createStateService(backgroundScope)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)
service.onNavigateToSession(A_SESSION_OWNER, A_SESSION_ID)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateSession)
@ -74,15 +74,15 @@ class DefaultNavigationStateServiceTest { @@ -74,15 +74,15 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testFailure() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testFailure() = runTest {
val service = createStateService(backgroundScope)
service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID)
assertThat(service.appNavigationState.value.navigationState).isEqualTo(NavigationState.Root)
}
@Test
fun testOnNavigateToThread() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnNavigateToThread() = runTest {
val service = createStateService(backgroundScope)
// From root (no effect)
service.onNavigateToThread(A_THREAD_OWNER, A_THREAD_ID)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)
@ -110,8 +110,8 @@ class DefaultNavigationStateServiceTest { @@ -110,8 +110,8 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testOnNavigateToRoom() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnNavigateToRoom() = runTest {
val service = createStateService(backgroundScope)
// From root (no effect)
service.onNavigateToRoom(A_ROOM_OWNER, A_ROOM_ID)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)
@ -139,8 +139,8 @@ class DefaultNavigationStateServiceTest { @@ -139,8 +139,8 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testOnNavigateToSpace() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnNavigateToSpace() = runTest {
val service = createStateService(backgroundScope)
// From root (no effect)
service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)
@ -168,8 +168,8 @@ class DefaultNavigationStateServiceTest { @@ -168,8 +168,8 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testOnNavigateToSession() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnNavigateToSession() = runTest {
val service = createStateService(backgroundScope)
// From root
service.onNavigateToSession(A_SESSION_OWNER, A_SESSION_ID)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateSession)
@ -197,8 +197,8 @@ class DefaultNavigationStateServiceTest { @@ -197,8 +197,8 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testOnLeavingThread() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnLeavingThread() = runTest {
val service = createStateService(backgroundScope)
// From root (no effect)
service.onLeavingThread(A_THREAD_OWNER)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)
@ -225,8 +225,8 @@ class DefaultNavigationStateServiceTest { @@ -225,8 +225,8 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testOnLeavingRoom() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnLeavingRoom() = runTest {
val service = createStateService(backgroundScope)
// From root (no effect)
service.onLeavingRoom(A_ROOM_OWNER)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)
@ -253,8 +253,8 @@ class DefaultNavigationStateServiceTest { @@ -253,8 +253,8 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testOnLeavingSpace() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnLeavingSpace() = runTest {
val service = createStateService(backgroundScope)
// From root (no effect)
service.onLeavingSpace(A_SPACE_OWNER)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)
@ -281,8 +281,8 @@ class DefaultNavigationStateServiceTest { @@ -281,8 +281,8 @@ class DefaultNavigationStateServiceTest {
}
@Test
fun testOnLeavingSession() = runCancellableScopeTest { scope ->
val service = createStateService(scope)
fun testOnLeavingSession() = runTest {
val service = createStateService(backgroundScope)
// From root
service.onLeavingSession(A_SESSION_OWNER)
assertThat(service.appNavigationState.first().navigationState).isEqualTo(navigationStateRoot)

32
tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RunCancellableTest.kt

@ -1,32 +0,0 @@ @@ -1,32 +0,0 @@
/*
* Copyright 2023, 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.tests.testutils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
/**
* Run a test with a [CoroutineScope] that will be cancelled automatically and avoiding failing the test.
*/
fun runCancellableScopeTest(block: suspend (CoroutineScope) -> Unit) = runTest {
val scope = CoroutineScope(coroutineContext + SupervisorJob())
block(scope)
scope.cancel()
}
/**
* Run a test with a [CoroutineScope] that will be cancelled automatically and avoiding failing the test.
*/
fun runCancellableScopeTestWithTestScope(block: suspend (testScope: TestScope, cancellableScope: CoroutineScope) -> Unit) = runTest {
val scope = CoroutineScope(coroutineContext + SupervisorJob())
block(this, scope)
scope.cancel()
}
Loading…
Cancel
Save