forked from GitHub-Mirror/riotX-android
Start testing timeline. Not working at the moment, have to figure it out.
This commit is contained in:
parent
9af466c24b
commit
faa68f4d52
@ -10,7 +10,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "io.realm:realm-gradle-plugin:5.8.0"
|
classpath "io.realm:realm-gradle-plugin:5.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,12 +42,6 @@ android {
|
|||||||
adbOptions {
|
adbOptions {
|
||||||
installOptions "-g"
|
installOptions "-g"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -115,12 +109,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 "android.arch.core:core-testing:$lifecycle_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 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
androidTestImplementation "com.android.support.test:rules:1.0.2"
|
androidTestImplementation "com.android.support.test:rules:1.0.2"
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
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 "android.arch.core:core-testing:$lifecycle_version"
|
||||||
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,195 @@
|
|||||||
|
package im.vector.matrix.android;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.arch.lifecycle.Observer;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public final class LiveDataTestObserver<T> implements Observer<T> {
|
||||||
|
private final List<T> valueHistory = new ArrayList<>();
|
||||||
|
private final List<Observer<T>> childObservers = new ArrayList<>();
|
||||||
|
|
||||||
|
@Deprecated // will be removed in version 1.0
|
||||||
|
private final LiveData<T> observedLiveData;
|
||||||
|
|
||||||
|
private CountDownLatch valueLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
private LiveDataTestObserver(LiveData<T> observedLiveData) {
|
||||||
|
this.observedLiveData = observedLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable T value) {
|
||||||
|
valueHistory.add(value);
|
||||||
|
valueLatch.countDown();
|
||||||
|
for (Observer<T> childObserver : childObservers) {
|
||||||
|
childObserver.onChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T value() {
|
||||||
|
assertHasValue();
|
||||||
|
return valueHistory.get(valueHistory.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> valueHistory() {
|
||||||
|
return Collections.unmodifiableList(valueHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes and removes observer from observed live data.
|
||||||
|
*
|
||||||
|
* @return This Observer
|
||||||
|
* @deprecated Please use {@link LiveData#removeObserver(Observer)} instead, will be removed in 1.0
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public LiveDataTestObserver<T> dispose() {
|
||||||
|
observedLiveData.removeObserver(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertHasValue() {
|
||||||
|
if (valueHistory.isEmpty()) {
|
||||||
|
throw fail("Observer never received any value");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertNoValue() {
|
||||||
|
if (!valueHistory.isEmpty()) {
|
||||||
|
throw fail("Expected no value, but received: " + value());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertHistorySize(int expectedSize) {
|
||||||
|
int size = valueHistory.size();
|
||||||
|
if (size != expectedSize) {
|
||||||
|
throw fail("History size differ; Expected: " + expectedSize + ", Actual: " + size);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertValue(T expected) {
|
||||||
|
T value = value();
|
||||||
|
|
||||||
|
if (expected == null && value == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value.equals(expected)) {
|
||||||
|
throw fail("Expected: " + valueAndClass(expected) + ", Actual: " + valueAndClass(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertValue(Function<T, Boolean> valuePredicate) {
|
||||||
|
T value = value();
|
||||||
|
|
||||||
|
if (!valuePredicate.apply(value)) {
|
||||||
|
throw fail("Value not present");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertNever(Function<T, Boolean> valuePredicate) {
|
||||||
|
int size = valueHistory.size();
|
||||||
|
for (int valueIndex = 0; valueIndex < size; valueIndex++) {
|
||||||
|
T value = this.valueHistory.get(valueIndex);
|
||||||
|
if (valuePredicate.apply(value)) {
|
||||||
|
throw fail("Value at position " + valueIndex + " matches predicate "
|
||||||
|
+ valuePredicate.toString() + ", which was not expected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits until this TestObserver has any value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then this method returns immediately.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitValue() throws InterruptedException {
|
||||||
|
valueLatch.await();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits the specified amount of time or until this TestObserver has any value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then this method returns immediately.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitValue(long timeout, TimeUnit timeUnit) throws InterruptedException {
|
||||||
|
valueLatch.await(timeout, timeUnit);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits until this TestObserver receives next value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then it awaits for another one.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitNextValue() throws InterruptedException {
|
||||||
|
return withNewLatch().awaitValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits the specified amount of time or until this TestObserver receives next value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then it awaits for another one.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitNextValue(long timeout, TimeUnit timeUnit) throws InterruptedException {
|
||||||
|
return withNewLatch().awaitValue(timeout, timeUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveDataTestObserver<T> withNewLatch() {
|
||||||
|
valueLatch = new CountDownLatch(1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AssertionError fail(String message) {
|
||||||
|
return new AssertionError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String valueAndClass(Object value) {
|
||||||
|
if (value != null) {
|
||||||
|
return value + " (class: " + value.getClass().getSimpleName() + ")";
|
||||||
|
}
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> LiveDataTestObserver<T> create() {
|
||||||
|
return new LiveDataTestObserver<>(new MutableLiveData<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> LiveDataTestObserver<T> test(LiveData<T> liveData) {
|
||||||
|
LiveDataTestObserver<T> observer = new LiveDataTestObserver<>(liveData);
|
||||||
|
liveData.observeForever(observer);
|
||||||
|
return observer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package im.vector.matrix.android
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
|
|
||||||
|
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main)
|
@ -0,0 +1,24 @@
|
|||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
internal class FakeGetContextOfEventTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
||||||
|
|
||||||
|
override fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEvent> {
|
||||||
|
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
|
val tokenChunkEvent = FakeTokenChunkEvent(
|
||||||
|
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||||
|
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||||
|
fakeEvents
|
||||||
|
)
|
||||||
|
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
|
||||||
|
.map { tokenChunkEvent }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
internal class FakePaginationTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
||||||
|
|
||||||
|
override fun execute(params: PaginationTask.Params): Try<TokenChunkEvent> {
|
||||||
|
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
|
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||||
|
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||||
|
.map { tokenChunkEvent }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
|
||||||
|
|
||||||
|
internal data class FakeTokenChunkEvent(override val start: String?,
|
||||||
|
override val end: String?,
|
||||||
|
override val events: List<Event> = emptyList(),
|
||||||
|
override val stateEvents: List<Event> = emptyList()
|
||||||
|
) : TokenChunkEvent
|
@ -0,0 +1,43 @@
|
|||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
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.room.model.MyMembership
|
||||||
|
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.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
object RoomDataHelper {
|
||||||
|
|
||||||
|
fun createFakeListOfEvents(size: Int = 10): List<Event> {
|
||||||
|
return (0 until size).map { createFakeEvent(Random.nextBoolean()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
||||||
|
roomEntity.membership = MyMembership.JOINED
|
||||||
|
val eventList = createFakeListOfEvents(30)
|
||||||
|
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
||||||
|
nextToken = null
|
||||||
|
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
||||||
|
isLast = true
|
||||||
|
}
|
||||||
|
chunkEntity.addAll(eventList, PaginationDirection.FORWARDS)
|
||||||
|
roomEntity.addOrUpdate(chunkEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import android.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
|
import android.support.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.api.thread.MainThreadExecutor
|
||||||
|
import im.vector.matrix.android.internal.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
|
import im.vector.matrix.android.internal.util.PagingRequestHelper
|
||||||
|
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.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)
|
||||||
|
val boundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, PagingRequestHelper(MainThreadExecutor()))
|
||||||
|
|
||||||
|
RoomDataHelper.fakeInitialSync(monarchy, roomId)
|
||||||
|
val timelineHolder = DefaultTimelineHolder(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask)
|
||||||
|
val timelineObserver = LiveDataTestObserver.test(timelineHolder.timeline())
|
||||||
|
timelineObserver.awaitNextValue().assertHasValue()
|
||||||
|
var pagedList = timelineObserver.value()
|
||||||
|
pagedList.size shouldEqual 30
|
||||||
|
(0 until pagedList.size).map {
|
||||||
|
pagedList.loadAround(it)
|
||||||
|
}
|
||||||
|
timelineObserver.awaitNextValue().assertHasValue()
|
||||||
|
pagedList = timelineObserver.value()
|
||||||
|
pagedList.size shouldEqual 60
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user