Timeline : make tests compile and pass
This commit is contained in:
parent
94db36d6c4
commit
be6a4efacb
|
@ -28,6 +28,7 @@ android {
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
multiDexEnabled true
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -104,17 +105,17 @@ dependencies {
|
||||||
testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||||
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
||||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||||
testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
androidTestImplementation "org.koin:koin-test:$koin_version"
|
androidTestImplementation "org.koin:koin-test:$koin_version"
|
||||||
|
androidTestImplementation 'androidx.test:core:1.1.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||||
androidTestImplementation 'androidx.test:rules:1.1.1'
|
androidTestImplementation 'androidx.test:rules:1.1.1'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||||
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
||||||
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,9 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.session.room.timeline
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
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.add
|
||||||
import im.vector.matrix.android.internal.database.helper.addAll
|
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.isUnlinked
|
||||||
|
@ -27,6 +26,9 @@ 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.helper.merge
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||||
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||||
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeRoomMemberEvent
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
|
@ -35,9 +37,10 @@ import org.amshove.kluent.shouldBeTrue
|
||||||
import org.amshove.kluent.shouldEqual
|
import org.amshove.kluent.shouldEqual
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.random.Random
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
internal class ChunkEntityTest : InstrumentedTest {
|
internal class ChunkEntityTest : InstrumentedTest {
|
||||||
|
|
||||||
private lateinit var monarchy: Monarchy
|
private lateinit var monarchy: Monarchy
|
||||||
|
@ -54,7 +57,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
val fakeEvent = createFakeEvent(false)
|
val fakeEvent = createFakeMessageEvent()
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
chunk.events.size shouldEqual 1
|
chunk.events.size shouldEqual 1
|
||||||
}
|
}
|
||||||
|
@ -64,7 +67,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
val fakeEvent = createFakeEvent(false)
|
val fakeEvent = createFakeMessageEvent()
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
chunk.events.size shouldEqual 1
|
chunk.events.size shouldEqual 1
|
||||||
|
@ -75,7 +78,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
|
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
val fakeEvent = createFakeEvent(true)
|
val fakeEvent = createFakeRoomMemberEvent()
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
|
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
|
||||||
}
|
}
|
||||||
|
@ -85,7 +88,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
|
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
val fakeEvent = createFakeEvent(false)
|
val fakeEvent = createFakeMessageEvent()
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
|
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
|
||||||
}
|
}
|
||||||
|
@ -196,15 +199,4 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun createFakeListOfEvents(size: Int = 10): List<Event> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -17,9 +17,15 @@
|
||||||
package im.vector.matrix.android.session.room.timeline
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.MyMembership
|
import im.vector.matrix.android.api.session.room.model.MyMembership
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.internal.database.helper.addAll
|
import im.vector.matrix.android.internal.database.helper.addAll
|
||||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
@ -30,27 +36,56 @@ import kotlin.random.Random
|
||||||
|
|
||||||
object RoomDataHelper {
|
object RoomDataHelper {
|
||||||
|
|
||||||
|
private const val FAKE_TEST_SENDER = "@sender:test.org"
|
||||||
|
private val EVENT_FACTORIES = hashMapOf(
|
||||||
|
0 to { createFakeMessageEvent() },
|
||||||
|
1 to { createFakeRoomMemberEvent() }
|
||||||
|
)
|
||||||
|
|
||||||
fun createFakeListOfEvents(size: Int = 10): List<Event> {
|
fun createFakeListOfEvents(size: Int = 10): List<Event> {
|
||||||
return (0 until size).map { createFakeEvent(Random.nextBoolean()) }
|
return (0 until size).mapNotNull {
|
||||||
|
val nextInt = Random.nextInt(EVENT_FACTORIES.size)
|
||||||
|
EVENT_FACTORIES[nextInt]?.invoke()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFakeEvent(asStateEvent: Boolean = false): Event {
|
fun createFakeEvent(type: String,
|
||||||
val eventId = Random.nextLong(System.currentTimeMillis()).toString()
|
content: Content? = null,
|
||||||
val type = if (asStateEvent) EventType.STATE_ROOM_NAME else EventType.MESSAGE
|
prevContent: Content? = null,
|
||||||
return Event(type, eventId)
|
sender: String = FAKE_TEST_SENDER,
|
||||||
|
stateKey: String = FAKE_TEST_SENDER
|
||||||
|
): Event {
|
||||||
|
return Event(
|
||||||
|
type = type,
|
||||||
|
eventId = Random.nextLong().toString(),
|
||||||
|
content = content,
|
||||||
|
prevContent = prevContent,
|
||||||
|
sender = sender,
|
||||||
|
stateKey = stateKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFakeMessageEvent(): Event {
|
||||||
|
val message = MessageTextContent(MessageType.MSGTYPE_TEXT, "Fake message #${Random.nextLong()}").toContent()
|
||||||
|
return createFakeEvent(EventType.MESSAGE, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFakeRoomMemberEvent(): Event {
|
||||||
|
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||||
|
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
||||||
roomEntity.membership = MyMembership.JOINED
|
roomEntity.membership = MyMembership.JOINED
|
||||||
val eventList = createFakeListOfEvents(30)
|
val eventList = createFakeListOfEvents(10)
|
||||||
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
||||||
nextToken = null
|
nextToken = null
|
||||||
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
||||||
isLastForward = true
|
isLastForward = true
|
||||||
}
|
}
|
||||||
chunkEntity.addAll("roomId", eventList, PaginationDirection.FORWARDS)
|
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS)
|
||||||
roomEntity.addOrUpdate(chunkEntity)
|
roomEntity.addOrUpdate(chunkEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.matrix.android.session.room.timeline
|
|
||||||
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|
||||||
import androidx.test.annotation.UiThreadTest
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
|
||||||
import im.vector.matrix.android.LiveDataTestObserver
|
|
||||||
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
|
||||||
import im.vector.matrix.android.testCoroutineDispatchers
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
internal class TimelineHolderTest : InstrumentedTest {
|
|
||||||
|
|
||||||
@get:Rule val testRule = InstantTaskExecutorRule()
|
|
||||||
private lateinit var monarchy: Monarchy
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
Realm.init(context())
|
|
||||||
val testConfiguration = RealmConfiguration.Builder().name("test-realm").build()
|
|
||||||
Realm.deleteRealm(testConfiguration)
|
|
||||||
monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@UiThreadTest
|
|
||||||
fun backPaginate_shouldLoadMoreEvents_whenLoadAroundIsCalled() {
|
|
||||||
val roomId = "roomId"
|
|
||||||
val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
|
||||||
val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy)
|
|
||||||
val paginationTask = FakePaginationTask(tokenChunkEventPersistor)
|
|
||||||
val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor)
|
|
||||||
RoomDataHelper.fakeInitialSync(monarchy, roomId)
|
|
||||||
val timelineHolder = DefaultTimelineService(roomId, monarchy, taskExecutor, getContextOfEventTask, RoomMemberExtractor(roomId))
|
|
||||||
val timelineObserver = LiveDataTestObserver.test(timelineHolder.timeline())
|
|
||||||
timelineObserver.awaitNextValue().assertHasValue()
|
|
||||||
var timelineData = timelineObserver.value()
|
|
||||||
timelineData.events.size shouldEqual 30
|
|
||||||
(0 until timelineData.events.size).map {
|
|
||||||
timelineData.events.loadAround(it)
|
|
||||||
}
|
|
||||||
timelineObserver.awaitNextValue().assertHasValue()
|
|
||||||
timelineData = timelineObserver.value()
|
|
||||||
timelineData.events.size shouldEqual 60
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.room.timeline.Timeline
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.testCoroutineDispatchers
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import org.amshove.kluent.shouldEqual
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
internal class TimelineTest : InstrumentedTest {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ROOM_ID = "roomId"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var monarchy: Monarchy
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
Realm.init(context())
|
||||||
|
val testConfiguration = RealmConfiguration.Builder().name("test-realm").build()
|
||||||
|
Realm.deleteRealm(testConfiguration)
|
||||||
|
monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
||||||
|
RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTimeline(initialEventId: String? = null): Timeline {
|
||||||
|
val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
||||||
|
val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy)
|
||||||
|
val paginationTask = FakePaginationTask(tokenChunkEventPersistor)
|
||||||
|
val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor)
|
||||||
|
val roomMemberExtractor = RoomMemberExtractor(ROOM_ID)
|
||||||
|
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor)
|
||||||
|
return DefaultTimeline(ROOM_ID, initialEventId, monarchy.realmConfiguration, taskExecutor, getContextOfEventTask, timelineEventFactory, paginationTask, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun backPaginate_shouldLoadMoreEvents_whenPaginateIsCalled() {
|
||||||
|
val timeline = createTimeline()
|
||||||
|
timeline.start()
|
||||||
|
val paginationCount = 30
|
||||||
|
var initialLoad = 0
|
||||||
|
val latch = CountDownLatch(2)
|
||||||
|
var timelineEvents: List<TimelineEvent> = emptyList()
|
||||||
|
timeline.listener = object : Timeline.Listener {
|
||||||
|
override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
if (snapshot.isNotEmpty()) {
|
||||||
|
if (initialLoad == 0) {
|
||||||
|
initialLoad = snapshot.size
|
||||||
|
}
|
||||||
|
timelineEvents = snapshot
|
||||||
|
latch.countDown()
|
||||||
|
timeline.paginate(Timeline.Direction.BACKWARDS, paginationCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
latch.await()
|
||||||
|
timelineEvents.size shouldEqual initialLoad + paginationCount
|
||||||
|
timeline.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,18 @@ inline fun <reified T> Content?.toModel(): T? {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This methods is a facility method to map a model to a json Content
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
inline fun <reified T> T?.toContent(): Content? {
|
||||||
|
return this?.let {
|
||||||
|
val moshi = MoshiProvider.providesMoshi()
|
||||||
|
val moshiAdapter = moshi.adapter(T::class.java)
|
||||||
|
return moshiAdapter.toJsonValue(it) as Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic event class with all possible fields for events.
|
* Generic event class with all possible fields for events.
|
||||||
* The content and prevContent json fields can easily be mapped to a model with [toModel] method.
|
* The content and prevContent json fields can easily be mapped to a model with [toModel] method.
|
||||||
|
|
|
@ -54,10 +54,14 @@ internal fun ChunkEntity.merge(roomId: String,
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
this.nextToken = chunkToMerge.nextToken
|
this.nextToken = chunkToMerge.nextToken
|
||||||
this.isLastForward = chunkToMerge.isLastForward
|
this.isLastForward = chunkToMerge.isLastForward
|
||||||
|
this.forwardsStateIndex = chunkToMerge.forwardsStateIndex
|
||||||
|
this.forwardsDisplayIndex = chunkToMerge.forwardsDisplayIndex
|
||||||
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
} else {
|
} else {
|
||||||
this.prevToken = chunkToMerge.prevToken
|
this.prevToken = chunkToMerge.prevToken
|
||||||
this.isLastBackward = chunkToMerge.isLastBackward
|
this.isLastBackward = chunkToMerge.isLastBackward
|
||||||
|
this.backwardsStateIndex = chunkToMerge.backwardsStateIndex
|
||||||
|
this.backwardsDisplayIndex = chunkToMerge.backwardsDisplayIndex
|
||||||
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
}
|
}
|
||||||
eventsToMerge.forEach {
|
eventsToMerge.forEach {
|
||||||
|
@ -111,8 +115,7 @@ internal fun ChunkEntity.add(roomId: String,
|
||||||
this.displayIndex = currentDisplayIndex
|
this.displayIndex = currentDisplayIndex
|
||||||
}
|
}
|
||||||
// We are not using the order of the list, but will be sorting with displayIndex field
|
// We are not using the order of the list, but will be sorting with displayIndex field
|
||||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
|
events.add(eventEntity)
|
||||||
events.add(position, eventEntity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ChunkEntity.assertIsManaged() {
|
private fun ChunkEntity.assertIsManaged() {
|
||||||
|
|
|
@ -48,7 +48,6 @@ internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI,
|
||||||
return if (areAllMembersAlreadyLoaded(params.roomId)) {
|
return if (areAllMembersAlreadyLoaded(params.roomId)) {
|
||||||
Try.just(true)
|
Try.just(true)
|
||||||
} else {
|
} else {
|
||||||
//TODO use this token
|
|
||||||
val lastToken = syncTokenStore.getLastToken()
|
val lastToken = syncTokenStore.getLastToken()
|
||||||
executeRequest<RoomMembersResponse> {
|
executeRequest<RoomMembersResponse> {
|
||||||
apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
|
apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
|
||||||
|
|
|
@ -36,7 +36,12 @@ import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoo
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import io.realm.*
|
import io.realm.OrderedRealmCollectionChangeListener
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import io.realm.Sort
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
@ -97,10 +102,14 @@ internal class DefaultTimeline(
|
||||||
val state = getPaginationState(direction)
|
val state = getPaginationState(direction)
|
||||||
if (state.isPaginating) {
|
if (state.isPaginating) {
|
||||||
// We are getting new items from pagination
|
// We are getting new items from pagination
|
||||||
paginateInternal(startDisplayIndex, direction, state.requestedCount)
|
val shouldPostSnapshot = paginateInternal(startDisplayIndex, direction, state.requestedCount)
|
||||||
|
if (shouldPostSnapshot) {
|
||||||
|
postSnapshot()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// We are getting new items from sync
|
// We are getting new items from sync
|
||||||
buildTimelineEvents(startDisplayIndex, direction, range.length.toLong())
|
buildTimelineEvents(startDisplayIndex, direction, range.length.toLong())
|
||||||
|
postSnapshot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +123,10 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
Timber.v("Paginate $direction of $count items")
|
Timber.v("Paginate $direction of $count items")
|
||||||
val startDisplayIndex = if (direction == Timeline.Direction.BACKWARDS) prevDisplayIndex else nextDisplayIndex
|
val startDisplayIndex = if (direction == Timeline.Direction.BACKWARDS) prevDisplayIndex else nextDisplayIndex
|
||||||
paginateInternal(startDisplayIndex, direction, count)
|
val shouldPostSnapshot = paginateInternal(startDisplayIndex, direction, count)
|
||||||
|
if (shouldPostSnapshot) {
|
||||||
|
postSnapshot()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,13 +203,15 @@ internal class DefaultTimeline(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This has to be called on TimelineThread as it access realm live results
|
* This has to be called on TimelineThread as it access realm live results
|
||||||
|
* @return true if snapshot should be posted
|
||||||
*/
|
*/
|
||||||
private fun paginateInternal(startDisplayIndex: Int,
|
private fun paginateInternal(startDisplayIndex: Int,
|
||||||
direction: Timeline.Direction,
|
direction: Timeline.Direction,
|
||||||
count: Int) {
|
count: Int): Boolean {
|
||||||
updatePaginationState(direction) { it.copy(requestedCount = count, isPaginating = true) }
|
updatePaginationState(direction) { it.copy(requestedCount = count, isPaginating = true) }
|
||||||
val builtCount = buildTimelineEvents(startDisplayIndex, direction, count.toLong())
|
val builtCount = buildTimelineEvents(startDisplayIndex, direction, count.toLong())
|
||||||
if (builtCount < count && !hasReachedEnd(direction)) {
|
val shouldFetchMore = builtCount < count && !hasReachedEnd(direction)
|
||||||
|
if (shouldFetchMore) {
|
||||||
val newRequestedCount = count - builtCount
|
val newRequestedCount = count - builtCount
|
||||||
updatePaginationState(direction) { it.copy(requestedCount = newRequestedCount) }
|
updatePaginationState(direction) { it.copy(requestedCount = newRequestedCount) }
|
||||||
val fetchingCount = Math.max(MIN_FETCHING_COUNT, newRequestedCount)
|
val fetchingCount = Math.max(MIN_FETCHING_COUNT, newRequestedCount)
|
||||||
|
@ -205,6 +219,7 @@ internal class DefaultTimeline(
|
||||||
} else {
|
} else {
|
||||||
updatePaginationState(direction) { it.copy(isPaginating = false, requestedCount = 0) }
|
updatePaginationState(direction) { it.copy(isPaginating = false, requestedCount = 0) }
|
||||||
}
|
}
|
||||||
|
return !shouldFetchMore
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun snapshot(): List<TimelineEvent> {
|
private fun snapshot(): List<TimelineEvent> {
|
||||||
|
@ -252,12 +267,13 @@ internal class DefaultTimeline(
|
||||||
} else {
|
} else {
|
||||||
val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size)
|
val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size)
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
paginate(Timeline.Direction.BACKWARDS, count)
|
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
||||||
} else {
|
} else {
|
||||||
paginate(Timeline.Direction.FORWARDS, count / 2)
|
paginateInternal(initialDisplayIndex, Timeline.Direction.FORWARDS, count / 2)
|
||||||
paginate(Timeline.Direction.BACKWARDS, count / 2)
|
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count / 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -336,8 +352,6 @@ internal class DefaultTimeline(
|
||||||
builtEvents.add(position, timelineEvent)
|
builtEvents.add(position, timelineEvent)
|
||||||
}
|
}
|
||||||
Timber.v("Built ${offsetResults.size} items from db")
|
Timber.v("Built ${offsetResults.size} items from db")
|
||||||
val snapshot = snapshot()
|
|
||||||
mainHandler.post { listener?.onUpdated(snapshot) }
|
|
||||||
return offsetResults.size
|
return offsetResults.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,6 +413,11 @@ internal class DefaultTimeline(
|
||||||
contextOfEventTask.configureWith(params).executeBy(taskExecutor)
|
contextOfEventTask.configureWith(params).executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun postSnapshot() {
|
||||||
|
val snapshot = snapshot()
|
||||||
|
mainHandler.post { listener?.onUpdated(snapshot) }
|
||||||
|
}
|
||||||
|
|
||||||
// Extension methods ***************************************************************************
|
// Extension methods ***************************************************************************
|
||||||
|
|
||||||
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
||||||
|
|
Loading…
Reference in New Issue