diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 5417dc13d4..49a9a3b72f 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -20,6 +20,7 @@ repositories {
android {
compileSdkVersion 28
+ testOptions.unitTests.includeAndroidResources = true
defaultConfig {
minSdkVersion 21
@@ -45,6 +46,7 @@ dependencies {
def support_version = '28.0.0'
def moshi_version = '1.8.0'
def lifecycle_version = "1.1.1"
+ def powermock_version = "2.0.0-RC.4"
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@@ -94,7 +96,14 @@ dependencies {
testImplementation 'junit:junit:4.12'
+ testImplementation 'org.robolectric:robolectric:4.0.2'
+ testImplementation 'org.robolectric:shadows-support-v4:3.0'
+ testImplementation "io.mockk:mockk:1.8.13.kotlin13"
+ testImplementation 'org.amshove.kluent:kluent-android:1.44'
+
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
+
}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/ExampleInstrumentedTest.java b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/ExampleInstrumentedTest.java
deleted file mode 100644
index f9b3c62a4c..0000000000
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package im.vector.matrix.android;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("im.vector.matrix.android.test", appContext.getPackageName());
- }
-}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt
new file mode 100644
index 0000000000..c726b7eb0f
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt
@@ -0,0 +1,15 @@
+package im.vector.matrix.android
+
+import android.content.Context
+import android.support.test.InstrumentationRegistry
+import java.io.File
+
+abstract class InstrumentedTest {
+ fun context(): Context {
+ return InstrumentationRegistry.getTargetContext()
+ }
+
+ fun cacheDir(): File {
+ return context().cacheDir
+ }
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt
new file mode 100644
index 0000000000..146bc75210
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt
@@ -0,0 +1,147 @@
+package im.vector.matrix.android.session.room.timeline
+
+import com.zhuinden.monarchy.Monarchy
+import im.vector.matrix.android.InstrumentedTest
+import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.internal.database.helper.add
+import im.vector.matrix.android.internal.database.helper.addAll
+import im.vector.matrix.android.internal.database.helper.isUnlinked
+import im.vector.matrix.android.internal.database.helper.lastStateIndex
+import im.vector.matrix.android.internal.database.helper.merge
+import im.vector.matrix.android.internal.database.model.ChunkEntity
+import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import io.realm.kotlin.createObject
+import org.amshove.kluent.shouldEqual
+import org.junit.Before
+import org.junit.Test
+import kotlin.random.Random
+
+
+internal class ChunkEntityTest : InstrumentedTest() {
+
+ private lateinit var monarchy: Monarchy
+
+ @Before
+ fun setup() {
+ Realm.init(context())
+ val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build()
+ monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
+ }
+
+
+ @Test
+ fun add_shouldAdd_whenNotAlreadyIncluded() {
+ monarchy.runTransactionSync { realm ->
+ val chunk: ChunkEntity = realm.createObject()
+ val fakeEvent = createFakeEvent(false)
+ chunk.add(fakeEvent, PaginationDirection.FORWARDS)
+ chunk.events.size shouldEqual 1
+ }
+ }
+
+ @Test
+ fun add_shouldNotAdd_whenAlreadyIncluded() {
+ monarchy.runTransactionSync { realm ->
+ val chunk: ChunkEntity = realm.createObject()
+ val fakeEvent = createFakeEvent(false)
+ chunk.add(fakeEvent, PaginationDirection.FORWARDS)
+ chunk.add(fakeEvent, PaginationDirection.FORWARDS)
+ chunk.events.size shouldEqual 1
+ }
+ }
+
+ @Test
+ fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
+ monarchy.runTransactionSync { realm ->
+ val chunk: ChunkEntity = realm.createObject()
+ val fakeEvent = createFakeEvent(true)
+ chunk.add(fakeEvent, PaginationDirection.FORWARDS)
+ chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
+ }
+ }
+
+ @Test
+ fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
+ monarchy.runTransactionSync { realm ->
+ val chunk: ChunkEntity = realm.createObject()
+ val fakeEvent = createFakeEvent(false)
+ chunk.add(fakeEvent, PaginationDirection.FORWARDS)
+ chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
+ }
+ }
+
+ @Test
+ fun addAll_shouldStateIndexIncremented_whenStateEventsAreAddedForward() {
+ monarchy.runTransactionSync { realm ->
+ val chunk: ChunkEntity = realm.createObject()
+ val fakeEvents = createFakeListOfEvents(30)
+ val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
+ chunk.addAll(fakeEvents, PaginationDirection.FORWARDS)
+ chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual numberOfStateEvents
+ }
+ }
+
+ @Test
+ fun addAll_shouldStateIndexDecremented_whenStateEventsAreAddedBackward() {
+ monarchy.runTransactionSync { realm ->
+ val chunk: ChunkEntity = realm.createObject()
+ val fakeEvents = createFakeListOfEvents(30)
+ val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
+ val lastIsState = fakeEvents.last().isStateEvent()
+ val expectedStateIndex = if (lastIsState) -numberOfStateEvents + 1 else -numberOfStateEvents
+ chunk.addAll(fakeEvents, PaginationDirection.BACKWARDS)
+ chunk.lastStateIndex(PaginationDirection.BACKWARDS) shouldEqual expectedStateIndex
+ }
+ }
+
+ @Test
+ fun merge_shouldAddEvents_whenMergingBackward() {
+ monarchy.runTransactionSync { realm ->
+ val chunk1: ChunkEntity = realm.createObject()
+ val chunk2: ChunkEntity = realm.createObject()
+ chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
+ chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
+ chunk1.merge(chunk2, PaginationDirection.BACKWARDS)
+ chunk1.events.size shouldEqual 60
+ }
+ }
+
+ @Test
+ fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
+ monarchy.runTransactionSync { realm ->
+ val chunk1: ChunkEntity = realm.createObject()
+ val chunk2: ChunkEntity = realm.createObject()
+ chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
+ chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
+ chunk1.merge(chunk2, PaginationDirection.BACKWARDS)
+ chunk1.isUnlinked() shouldEqual false
+ }
+ }
+
+ @Test
+ fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
+ monarchy.runTransactionSync { realm ->
+ val chunk1: ChunkEntity = realm.createObject()
+ val chunk2: ChunkEntity = realm.createObject()
+ chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
+ chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
+ chunk1.merge(chunk2, PaginationDirection.BACKWARDS)
+ chunk1.isUnlinked() shouldEqual true
+ }
+ }
+
+
+ private fun createFakeListOfEvents(size: Int = 10): List {
+ return (0 until size).map { createFakeEvent(Random.nextBoolean()) }
+ }
+
+ private fun createFakeEvent(asStateEvent: Boolean = false): Event {
+ val eventId = Random.nextLong(System.currentTimeMillis()).toString()
+ val type = if (asStateEvent) EventType.STATE_ROOM_NAME else EventType.MESSAGE
+ return Event(type, eventId)
+ }
+
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
index 06a60d5ed5..f8d8207a3c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
@@ -2,7 +2,6 @@ package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
-import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
@@ -41,7 +40,7 @@ internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity,
eventsToMerge = chunkToMerge.events
}
eventsToMerge.forEach {
- add(it.asDomain(), direction, isUnlinked = isUnlinked)
+ add(it, direction, isUnlinked = isUnlinked)
}
}
@@ -63,16 +62,23 @@ internal fun ChunkEntity.add(event: Event,
direction: PaginationDirection,
stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) {
+ add(event.asEntity(), direction, stateIndexOffset, isUnlinked)
+}
+
+internal fun ChunkEntity.add(eventEntity: EventEntity,
+ direction: PaginationDirection,
+ stateIndexOffset: Int = 0,
+ isUnlinked: Boolean = false) {
if (!isManaged) {
throw IllegalStateException("Chunk entity should be managed to use fast contains")
}
- if (event.eventId == null || events.fastContains(event.eventId)) {
+ if (eventEntity.eventId.isEmpty() || events.fastContains(eventEntity.eventId)) {
return
}
var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset)
- if (direction == PaginationDirection.FORWARDS && event.isStateEvent()) {
+ if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(eventEntity.type)) {
currentStateIndex += 1
} else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) {
val lastEventType = events.last()?.type ?: ""
@@ -81,7 +87,6 @@ internal fun ChunkEntity.add(event: Event,
}
}
- val eventEntity = event.asEntity()
eventEntity.stateIndex = currentStateIndex
eventEntity.isUnlinked = isUnlinked
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
@@ -90,7 +95,7 @@ internal fun ChunkEntity.add(event: Event,
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
- PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
- PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
- } ?: defaultValue
+ PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
+ PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
+ } ?: defaultValue
}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt
index 7dfcc8ab1c..7b5822876d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt
@@ -69,6 +69,7 @@ internal fun RealmList.find(eventId: String): EventEntity? {
return this.where().equalTo(EventEntityFields.EVENT_ID, eventId).findFirst()
}
-internal fun RealmList.fastContains(eventId: String): Boolean {
+internal fun RealmList.
+ fastContains(eventId: String): Boolean {
return this.find(eventId) != null
}
diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/ExampleUnitTest.java b/matrix-sdk-android/src/test/java/im/vector/matrix/android/ExampleUnitTest.java
deleted file mode 100644
index 86ea905e61..0000000000
--- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/ExampleUnitTest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package im.vector.matrix.android;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file