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()
|
||||
}
|
||||
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 {
|
||||
installOptions "-g"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -115,12 +109,17 @@ dependencies {
|
||||
testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
||||
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 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation "com.android.support.test:rules:1.0.2"
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
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