mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
#3756 Convert SearchDepictionsFragment to use Pagination - test DataSource related classes
This commit is contained in:
parent
9af97c483e
commit
0fd81d09b1
8 changed files with 405 additions and 57 deletions
|
|
@ -8,13 +8,13 @@ import io.reactivex.schedulers.Schedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
class SearchDepictionsDataSource constructor(
|
data class SearchDepictionsDataSource constructor(
|
||||||
private val depictsClient: DepictsClient,
|
private val depictsClient: DepictsClient,
|
||||||
private val loadingStates: PublishProcessor<LoadingState>,
|
private val loadingStates: PublishProcessor<LoadingState>,
|
||||||
val query: String
|
private val query: String
|
||||||
) : PositionalDataSource<DepictedItem>() {
|
) : PositionalDataSource<DepictedItem>() {
|
||||||
|
|
||||||
var lastExecutedRequest: (() -> Boolean)? = null
|
private var lastExecutedRequest: (() -> Boolean)? = null
|
||||||
|
|
||||||
override fun loadInitial(
|
override fun loadInitial(
|
||||||
params: LoadInitialParams,
|
params: LoadInitialParams,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class SearchDepictionsFragmentPresenter @Inject constructor(
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.subscribe(::onLoadingState, Timber::e),
|
.subscribe(::onLoadingState, Timber::e),
|
||||||
searchableDataSourceFactory.noItemsLoadedEvent.subscribe {
|
searchableDataSourceFactory.noItemsLoadedEvent.subscribe {
|
||||||
currentQuery?.let(view::setEmptyViewText)
|
setEmptyViewText()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -42,13 +42,17 @@ class SearchDepictionsFragmentPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
LoadingState.InitialLoad -> view.showInitialLoadInProgress()
|
LoadingState.InitialLoad -> view.showInitialLoadInProgress()
|
||||||
LoadingState.Error -> {
|
LoadingState.Error -> {
|
||||||
currentQuery?.let(view::setEmptyViewText)
|
setEmptyViewText()
|
||||||
view.showSnackbar()
|
view.showSnackbar()
|
||||||
view.hideInitialLoadProgress()
|
view.hideInitialLoadProgress()
|
||||||
listFooterData.postValue(listOf(FooterItem.RefreshItem))
|
listFooterData.postValue(listOf(FooterItem.RefreshItem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setEmptyViewText() {
|
||||||
|
currentQuery?.let(view::setEmptyViewText)
|
||||||
|
}
|
||||||
|
|
||||||
override fun retryFailedRequest() {
|
override fun retryFailedRequest() {
|
||||||
searchableDataSourceFactory.retryFailedRequest()
|
searchableDataSourceFactory.retryFailedRequest()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ import javax.inject.Inject
|
||||||
private const val PAGE_SIZE = 50
|
private const val PAGE_SIZE = 50
|
||||||
private const val INITIAL_LOAD_SIZE = 50
|
private const val INITIAL_LOAD_SIZE = 50
|
||||||
|
|
||||||
class SearchableDepictionsDataSourceFactory @Inject constructor(val searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory) {
|
class SearchableDepictionsDataSourceFactory @Inject constructor(
|
||||||
|
val searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory,
|
||||||
|
val liveDataConverter: LiveDataConverter
|
||||||
|
) {
|
||||||
private val _loadingStates = PublishProcessor.create<LoadingState>()
|
private val _loadingStates = PublishProcessor.create<LoadingState>()
|
||||||
val loadingStates: Flowable<LoadingState> = _loadingStates
|
val loadingStates: Flowable<LoadingState> = _loadingStates
|
||||||
private val _searchResults = PublishProcessor.create<LiveData<PagedList<DepictedItem>>>()
|
private val _searchResults = PublishProcessor.create<LiveData<PagedList<DepictedItem>>>()
|
||||||
|
|
@ -21,24 +24,14 @@ class SearchableDepictionsDataSourceFactory @Inject constructor(val searchDepict
|
||||||
private val _noItemsLoadedEvent = PublishProcessor.create<Unit>()
|
private val _noItemsLoadedEvent = PublishProcessor.create<Unit>()
|
||||||
val noItemsLoadedEvent: Flowable<Unit> = _noItemsLoadedEvent
|
val noItemsLoadedEvent: Flowable<Unit> = _noItemsLoadedEvent
|
||||||
|
|
||||||
var currentFactory: SearchDepictionsDataSourceFactory? = null
|
private var currentFactory: SearchDepictionsDataSourceFactory? = null
|
||||||
|
|
||||||
fun onQueryUpdated(query: String) {
|
fun onQueryUpdated(query: String) {
|
||||||
_searchResults.offer(
|
_searchResults.offer(
|
||||||
searchDepictionsDataSourceFactoryFactory.create(query, _loadingStates)
|
liveDataConverter.convert(
|
||||||
.also { currentFactory = it }
|
searchDepictionsDataSourceFactoryFactory.create(query, _loadingStates)
|
||||||
.toLiveData(
|
.also { currentFactory = it }
|
||||||
Config(
|
) { _noItemsLoadedEvent.offer(Unit) }
|
||||||
pageSize = PAGE_SIZE,
|
|
||||||
initialLoadSizeHint = INITIAL_LOAD_SIZE,
|
|
||||||
enablePlaceholders = false
|
|
||||||
),
|
|
||||||
boundaryCallback = object : PagedList.BoundaryCallback<DepictedItem>() {
|
|
||||||
override fun onZeroItemsLoaded() {
|
|
||||||
_noItemsLoadedEvent.offer(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +40,27 @@ class SearchableDepictionsDataSourceFactory @Inject constructor(val searchDepict
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LiveDataConverter @Inject constructor() {
|
||||||
|
fun convert(
|
||||||
|
dataSourceFactory: SearchDepictionsDataSourceFactory,
|
||||||
|
zeroItemsLoadedFunction: () -> Unit
|
||||||
|
): LiveData<PagedList<DepictedItem>> {
|
||||||
|
return dataSourceFactory.toLiveData(
|
||||||
|
Config(
|
||||||
|
pageSize = PAGE_SIZE,
|
||||||
|
initialLoadSizeHint = INITIAL_LOAD_SIZE,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
boundaryCallback = object : PagedList.BoundaryCallback<DepictedItem>() {
|
||||||
|
override fun onZeroItemsLoaded() {
|
||||||
|
zeroItemsLoadedFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
interface SearchDepictionsDataSourceFactoryFactory {
|
interface SearchDepictionsDataSourceFactoryFactory {
|
||||||
fun create(query: String, loadingStates: PublishProcessor<LoadingState>)
|
fun create(query: String, loadingStates: PublishProcessor<LoadingState>)
|
||||||
: SearchDepictionsDataSourceFactory
|
: SearchDepictionsDataSourceFactory
|
||||||
|
|
@ -57,10 +71,9 @@ class SearchDepictionsDataSourceFactory constructor(
|
||||||
private val query: String,
|
private val query: String,
|
||||||
private val loadingStates: PublishProcessor<LoadingState>
|
private val loadingStates: PublishProcessor<LoadingState>
|
||||||
) : DataSource.Factory<Int, DepictedItem>() {
|
) : DataSource.Factory<Int, DepictedItem>() {
|
||||||
var currentDataSource: SearchDepictionsDataSource? = null
|
private var currentDataSource: SearchDepictionsDataSource? = null
|
||||||
override fun create() = SearchDepictionsDataSource(depictsClient, loadingStates, query).also {
|
override fun create() = SearchDepictionsDataSource(depictsClient, loadingStates, query)
|
||||||
currentDataSource = it
|
.also { currentDataSource = it }
|
||||||
}
|
|
||||||
|
|
||||||
fun retryFailedRequest() {
|
fun retryFailedRequest() {
|
||||||
currentDataSource?.retryFailedRequest()
|
currentDataSource?.retryFailedRequest()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.spy
|
||||||
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
|
import org.hamcrest.Matchers.`is`
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
class SearchDepictionsDataSourceFactoryTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var depictsClient: DepictsClient
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||||
|
private lateinit var factory: SearchDepictionsDataSourceFactory
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
factory = SearchDepictionsDataSourceFactory(depictsClient, "test", loadingStates)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create returns a dataSource`() {
|
||||||
|
assertThat(
|
||||||
|
factory.create(),
|
||||||
|
`is`(SearchDepictionsDataSource(depictsClient, loadingStates, "test"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest invokes method if not null`() {
|
||||||
|
val dataSource: SearchDepictionsDataSource = spy(factory.create())
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
verify(dataSource).retryFailedRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest does not invoke method if null`() {
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import androidx.paging.PositionalDataSource
|
||||||
|
import com.nhaarman.mockitokotlin2.*
|
||||||
|
import fr.free.nrw.commons.explore.depictions.LoadingState.*
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.plugins.RxJavaPlugins
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
class SearchDepictionsDataSourceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var depictsClient: DepictsClient
|
||||||
|
|
||||||
|
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||||
|
private lateinit var searchDepictionsDataSource: SearchDepictionsDataSource
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
loadingStates = PublishProcessor.create()
|
||||||
|
searchDepictionsDataSource =
|
||||||
|
SearchDepictionsDataSource(depictsClient, loadingStates, "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadInitial returns results and emits InitialLoad & Complete`() {
|
||||||
|
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||||
|
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||||
|
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||||
|
.thenReturn(Single.just(emptyList()))
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadInitial(params, callback)
|
||||||
|
verify(callback).onResult(emptyList(), 0)
|
||||||
|
testSubscriber.assertValues(InitialLoad, Complete)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadInitial onError does not return results and emits InitialLoad & Error`() {
|
||||||
|
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||||
|
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||||
|
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||||
|
.thenThrow(RuntimeException())
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadInitial(params, callback)
|
||||||
|
verify(callback, never()).onResult(any(), any())
|
||||||
|
testSubscriber.assertValues(InitialLoad, Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadRange returns results and emits Loading & Complete`() {
|
||||||
|
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||||
|
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||||
|
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||||
|
.thenReturn(Single.just(emptyList()))
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadRange(params, callback)
|
||||||
|
verify(callback).onResult(emptyList())
|
||||||
|
testSubscriber.assertValues(Loading, Complete)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadRange onError does not return results and emits Loading & Error`() {
|
||||||
|
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||||
|
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||||
|
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||||
|
.thenThrow(RuntimeException())
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadRange(params, callback)
|
||||||
|
verify(callback, never()).onResult(any())
|
||||||
|
testSubscriber.assertValues(Loading, Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest does nothing when null`() {
|
||||||
|
searchDepictionsDataSource.retryFailedRequest()
|
||||||
|
verifyNoMoreInteractions(depictsClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest retries last request`() {
|
||||||
|
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||||
|
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||||
|
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||||
|
.thenThrow(RuntimeException()).thenReturn(Single.just(emptyList()))
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadRange(params, callback)
|
||||||
|
verify(callback, never()).onResult(any())
|
||||||
|
searchDepictionsDataSource.retryFailedRequest()
|
||||||
|
verify(callback).onResult(emptyList())
|
||||||
|
testSubscriber.assertValues(Loading, Error, Loading, Complete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import com.jraska.livedata.test
|
||||||
|
import com.nhaarman.mockitokotlin2.*
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import io.reactivex.schedulers.TestScheduler
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
class SearchDepictionsFragmentPresenterTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
internal lateinit var view: SearchDepictionsFragmentContract.View
|
||||||
|
|
||||||
|
private lateinit var searchDepictionsFragmentPresenter: SearchDepictionsFragmentPresenter
|
||||||
|
|
||||||
|
private lateinit var testScheduler: TestScheduler
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var searchableDepictionsDataSourceFactory: SearchableDepictionsDataSourceFactory
|
||||||
|
|
||||||
|
private var loadingStates: PublishProcessor<LoadingState> = PublishProcessor.create()
|
||||||
|
|
||||||
|
private var searchResults: PublishProcessor<LiveData<PagedList<DepictedItem>>> =
|
||||||
|
PublishProcessor.create()
|
||||||
|
|
||||||
|
private var noItemLoadedEvent: PublishProcessor<Unit> = PublishProcessor.create()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(searchableDepictionsDataSourceFactory.searchResults).thenReturn(searchResults)
|
||||||
|
whenever(searchableDepictionsDataSourceFactory.loadingStates).thenReturn(loadingStates)
|
||||||
|
whenever(searchableDepictionsDataSourceFactory.noItemsLoadedEvent)
|
||||||
|
.thenReturn(noItemLoadedEvent)
|
||||||
|
testScheduler = TestScheduler()
|
||||||
|
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(
|
||||||
|
testScheduler,
|
||||||
|
searchableDepictionsDataSourceFactory
|
||||||
|
)
|
||||||
|
searchDepictionsFragmentPresenter.onAttachView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `searchResults emission updates the view`() {
|
||||||
|
val pagedListLiveData = mock<LiveData<PagedList<DepictedItem>>>()
|
||||||
|
searchResults.offer(pagedListLiveData)
|
||||||
|
verify(view).observeSearchResults(pagedListLiveData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Loading offers a loading list item`() {
|
||||||
|
onLoadingState(LoadingState.Loading)
|
||||||
|
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||||
|
.assertValue(listOf(FooterItem.LoadingItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Complete offers an empty list item and hides initial loader`() {
|
||||||
|
onLoadingState(LoadingState.Complete)
|
||||||
|
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||||
|
.assertValue(emptyList())
|
||||||
|
verify(view).hideInitialLoadProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `InitialLoad shows initial loader`() {
|
||||||
|
onLoadingState(LoadingState.InitialLoad)
|
||||||
|
verify(view).showInitialLoadInProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() {
|
||||||
|
searchDepictionsFragmentPresenter.onQueryUpdated("test")
|
||||||
|
onLoadingState(LoadingState.Error)
|
||||||
|
verify(view).setEmptyViewText("test")
|
||||||
|
verify(view).showSnackbar()
|
||||||
|
verify(view).hideInitialLoadProgress()
|
||||||
|
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||||
|
.assertValue(listOf(FooterItem.RefreshItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Error offers a refresh list item, hides initial loader and shows error with a unset text`() {
|
||||||
|
onLoadingState(LoadingState.Error)
|
||||||
|
verify(view, never()).setEmptyViewText(any())
|
||||||
|
verify(view).showSnackbar()
|
||||||
|
verify(view).hideInitialLoadProgress()
|
||||||
|
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||||
|
.assertValue(listOf(FooterItem.RefreshItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `no Items event sets empty view text`() {
|
||||||
|
searchDepictionsFragmentPresenter.onQueryUpdated("test")
|
||||||
|
noItemLoadedEvent.offer(Unit)
|
||||||
|
verify(view).setEmptyViewText("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest calls retry`() {
|
||||||
|
searchDepictionsFragmentPresenter.retryFailedRequest()
|
||||||
|
verify(searchableDepictionsDataSourceFactory).retryFailedRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onDetachView stops subscriptions`() {
|
||||||
|
searchDepictionsFragmentPresenter.onDetachView()
|
||||||
|
onLoadingState(LoadingState.Loading)
|
||||||
|
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||||
|
.assertValue(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onQueryUpdated updates dataSourceFactory`() {
|
||||||
|
searchDepictionsFragmentPresenter.onQueryUpdated("test")
|
||||||
|
verify(searchableDepictionsDataSourceFactory).onQueryUpdated("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onLoadingState(loadingState: LoadingState) {
|
||||||
|
loadingStates.offer(loadingState)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package fr.free.nrw.commons.explore.depictions
|
|
||||||
|
|
||||||
import io.reactivex.schedulers.TestScheduler
|
|
||||||
import org.junit.Before
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
|
|
||||||
class SearchDepictionsFragmentPresenterTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
internal lateinit var view: SearchDepictionsFragmentContract.View
|
|
||||||
|
|
||||||
private lateinit var searchDepictionsFragmentPresenter: SearchDepictionsFragmentPresenter
|
|
||||||
|
|
||||||
private lateinit var testScheduler: TestScheduler
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private lateinit var searchableDepictionsDataSourceFactory: SearchableDepictionsDataSourceFactory
|
|
||||||
|
|
||||||
@Before
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this)
|
|
||||||
testScheduler = TestScheduler()
|
|
||||||
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(
|
|
||||||
testScheduler,
|
|
||||||
searchableDepictionsDataSourceFactory
|
|
||||||
)
|
|
||||||
searchDepictionsFragmentPresenter.onAttachView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import com.nhaarman.mockitokotlin2.*
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
class SearchableDepictionsDataSourceFactoryTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var liveDataConverter: LiveDataConverter
|
||||||
|
|
||||||
|
private lateinit var factory: SearchableDepictionsDataSourceFactory
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
factory = SearchableDepictionsDataSourceFactory(
|
||||||
|
searchDepictionsDataSourceFactoryFactory,
|
||||||
|
liveDataConverter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onQueryUpdated emits new liveData`() {
|
||||||
|
val (_, liveData) = expectNewLiveData()
|
||||||
|
factory.searchResults.test()
|
||||||
|
.also { factory.onQueryUpdated("test") }
|
||||||
|
.assertValue(liveData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
|
||||||
|
val (captor, _) = expectNewLiveData()
|
||||||
|
factory.onQueryUpdated("test")
|
||||||
|
factory.noItemsLoadedEvent.test()
|
||||||
|
.also { captor.firstValue.invoke() }
|
||||||
|
.assertValue(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Just for coverage, no way to really assert this
|
||||||
|
* */
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest does nothing without a factory`() {
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest retries with a factory`() {
|
||||||
|
val (_, _, dataSourceFactory) = expectNewLiveData()
|
||||||
|
factory.onQueryUpdated("test")
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
verify(dataSourceFactory).retryFailedRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expectNewLiveData(): Triple<KArgumentCaptor<() -> Unit>, LiveData<PagedList<DepictedItem>>, SearchDepictionsDataSourceFactory> {
|
||||||
|
val dataSourceFactory: SearchDepictionsDataSourceFactory = mock()
|
||||||
|
whenever(
|
||||||
|
searchDepictionsDataSourceFactoryFactory.create(
|
||||||
|
"test",
|
||||||
|
factory.loadingStates as PublishProcessor<LoadingState>
|
||||||
|
)
|
||||||
|
).thenReturn(dataSourceFactory)
|
||||||
|
val captor = argumentCaptor<() -> Unit>()
|
||||||
|
val liveData: LiveData<PagedList<DepictedItem>> = mock()
|
||||||
|
whenever(liveDataConverter.convert(eq(dataSourceFactory), captor.capture()))
|
||||||
|
.thenReturn(liveData)
|
||||||
|
return Triple(captor, liveData, dataSourceFactory)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue