From f91d0688d8f2738b823c8a387c7bd65a12f35020 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Sep 2024 19:48:21 +0200 Subject: [PATCH] Add unit test on MatrixTimelineDiffProcessor --- .../timeline/MatrixTimelineDiffProcessor.kt | 9 +- .../MatrixTimelineDiffProcessorTest.kt | 199 ++++++++++++++++++ 2 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index 47be0fd07a..3523823d5e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.matrix.rustcomponents.sdk.TimelineChange -import org.matrix.rustcomponents.sdk.TimelineDiff +import org.matrix.rustcomponents.sdk.TimelineDiffInterface import org.matrix.rustcomponents.sdk.TimelineItem import timber.log.Timber @@ -36,7 +36,7 @@ internal class MatrixTimelineDiffProcessor( } } - suspend fun postDiffs(diffs: List) { + suspend fun postDiffs(diffs: List) { updateTimelineItems { Timber.v("Update timeline items from postDiffs (with ${diffs.size} items) on ${Thread.currentThread()}") diffs.forEach { diff -> @@ -52,7 +52,7 @@ internal class MatrixTimelineDiffProcessor( timelineItems.value = mutableTimelineItems } - private fun MutableList.applyDiff(diff: TimelineDiff) { + private fun MutableList.applyDiff(diff: TimelineDiffInterface) { when (diff.change()) { TimelineChange.APPEND -> { val items = diff.append()?.map { it.asMatrixTimelineItem() } ?: return @@ -100,7 +100,8 @@ internal class MatrixTimelineDiffProcessor( clear() } TimelineChange.TRUNCATE -> { - // Not supported + val index = diff.truncate() ?: return + subList(index.toInt(), size).clear() } } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt new file mode 100644 index 0000000000..84ce6e61f9 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt @@ -0,0 +1,199 @@ +/* + * 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 com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper +import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper +import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper +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.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.matrix.rustcomponents.sdk.EventTimelineItem +import org.matrix.rustcomponents.sdk.InsertData +import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.SetData +import org.matrix.rustcomponents.sdk.TimelineChange +import org.matrix.rustcomponents.sdk.TimelineDiffInterface +import org.matrix.rustcomponents.sdk.TimelineItem +import org.matrix.rustcomponents.sdk.VirtualTimelineItem + +open class FakeTimelineDiff( + private val change: TimelineChange, + private val item: TimelineItem? = FakeTimelineItem() +) : TimelineDiffInterface { + override fun change() = change + override fun append(): List? = item?.let { listOf(it) } + override fun insert(): InsertData? = item?.let { InsertData(1u, it) } + override fun pushBack(): TimelineItem? = item + override fun pushFront(): TimelineItem? = item + override fun remove(): UInt? = 1u + override fun reset(): List? = item?.let { listOf(it) } + override fun set(): SetData? = item?.let { SetData(1u, it) } + override fun truncate(): UInt? = 1u +} + +class MatrixTimelineDiffProcessorTest { + private val timelineItems = MutableStateFlow>(emptyList()) + + private val anEvent = MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()) + private val anEvent2 = MatrixTimelineItem.Event(A_UNIQUE_ID_2, anEventTimelineItem()) + + @Test + fun `Append adds new entries at the end of the list`() = runTest { + timelineItems.value = listOf(anEvent) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.APPEND))) + assertThat(timelineItems.value.count()).isEqualTo(2) + assertThat(timelineItems.value).containsExactly( + anEvent, + MatrixTimelineItem.Other, + ) + } + + @Test + fun `PushBack adds a new entry at the end of the list`() = runTest { + timelineItems.value = listOf(anEvent) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.PUSH_BACK))) + assertThat(timelineItems.value.count()).isEqualTo(2) + assertThat(timelineItems.value).containsExactly( + anEvent, + MatrixTimelineItem.Other, + ) + } + + @Test + fun `PushFront inserts a new entry at the start of the list`() = runTest { + timelineItems.value = listOf(anEvent) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.PUSH_FRONT))) + assertThat(timelineItems.value.count()).isEqualTo(2) + assertThat(timelineItems.value).containsExactly( + MatrixTimelineItem.Other, + anEvent, + ) + } + + @Test + fun `Set replaces an entry at some index`() = runTest { + timelineItems.value = listOf(anEvent, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.SET))) + assertThat(timelineItems.value.count()).isEqualTo(2) + assertThat(timelineItems.value).containsExactly( + anEvent, + MatrixTimelineItem.Other + ) + } + + @Test + fun `Insert inserts a new entry at the provided index`() = runTest { + timelineItems.value = listOf(anEvent, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.INSERT))) + assertThat(timelineItems.value.count()).isEqualTo(3) + assertThat(timelineItems.value).containsExactly( + anEvent, + MatrixTimelineItem.Other, + anEvent2, + ) + } + + @Test + fun `Remove removes an entry at some index`() = runTest { + timelineItems.value = listOf(anEvent, MatrixTimelineItem.Other, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.REMOVE))) + assertThat(timelineItems.value.count()).isEqualTo(2) + assertThat(timelineItems.value).containsExactly( + anEvent, + anEvent2, + ) + } + + @Test + fun `PopBack removes an entry at the end of the list`() = runTest { + timelineItems.value = listOf(anEvent, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.POP_BACK))) + assertThat(timelineItems.value.count()).isEqualTo(1) + assertThat(timelineItems.value).containsExactly( + anEvent, + ) + } + + @Test + fun `PopFront removes an entry at the start of the list`() = runTest { + timelineItems.value = listOf(anEvent, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.POP_FRONT))) + assertThat(timelineItems.value.count()).isEqualTo(1) + assertThat(timelineItems.value).containsExactly( + anEvent2, + ) + } + + @Test + fun `Clear removes all the entries`() = runTest { + timelineItems.value = listOf(anEvent, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.CLEAR))) + assertThat(timelineItems.value).isEmpty() + } + + @Test + fun `Truncate removes all entries after the provided length`() = runTest { + timelineItems.value = listOf(anEvent, MatrixTimelineItem.Other, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(change = TimelineChange.TRUNCATE))) + assertThat(timelineItems.value.count()).isEqualTo(1) + assertThat(timelineItems.value).containsExactly( + anEvent, + ) + } + + @Test + fun `Reset removes all entries and add the provided ones`() = runTest { + timelineItems.value = listOf(anEvent, MatrixTimelineItem.Other, anEvent2) + val processor = createProcessor() + processor.postDiffs(listOf(FakeTimelineDiff(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, + ) + } +} + +class FakeTimelineItem : TimelineItem(NoPointer) { + override fun asEvent(): EventTimelineItem? = null + override fun asVirtual(): VirtualTimelineItem? = null + override fun fmtDebug(): String = "fmtDebug" + override fun uniqueId(): String = "uniqueId" +}