#3822 Convert SubCategoryImagesListFragment to use Pagination - convert subcategories - add continuation support in category client - rely on interfaces for callbacks of PageableMediaFragments

This commit is contained in:
Sean Mac Gillicuddy 2020-06-18 14:28:19 +01:00
parent 0dad78358d
commit 5b87ed569c
26 changed files with 253 additions and 92 deletions

View file

@ -142,4 +142,9 @@ public class BookmarksActivity extends NavigationBaseActivity
@Override
public void requestMoreImages() { }
@Override
public void onMediaClicked(int position) {
//TODO use with pagination
}
}

View file

@ -93,6 +93,7 @@ class CategoriesModel @Inject constructor(
else
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
.toObservable()
}
private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>) =
@ -126,7 +127,7 @@ class CategoriesModel @Inject constructor(
* @return
*/
private fun getTitleCategories(title: String): Observable<List<String>> {
return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT)
return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT).toObservable()
}

View file

@ -1,16 +1,22 @@
package fr.free.nrw.commons.category
import io.reactivex.Observable
import io.reactivex.Single
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import javax.inject.Inject
import javax.inject.Singleton
const val CATEGORY_PREFIX = "Category:"
const val SUB_CATEGORY_CONTINUATION_PREFIX = "sub_category_"
/**
* Category Client to handle custom calls to Commons MediaWiki APIs
*/
@Singleton
class CategoryClient @Inject constructor(private val categoryInterface: CategoryInterface) {
private val continuationStore: MutableMap<String, Map<String, String>?> = mutableMapOf()
private val continuationExists: MutableMap<String, Boolean> = mutableMapOf()
/**
* Searches for categories containing the specified string.
*
@ -21,8 +27,10 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
*/
@JvmOverloads
fun searchCategories(filter: String?, itemLimit: Int, offset: Int = 0):
Observable<List<String>> {
return responseToCategoryName(categoryInterface.searchCategories(filter, itemLimit, offset))
Single<List<String>> {
return responseToCategoryName(
categoryInterface.searchCategories(filter, itemLimit, offset)
)
}
/**
@ -35,7 +43,7 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
*/
@JvmOverloads
fun searchCategoriesForPrefix(prefix: String?, itemLimit: Int, offset: Int = 0):
Observable<List<String>> {
Single<List<String>> {
return responseToCategoryName(
categoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset)
)
@ -48,8 +56,19 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
* @param categoryName Category name as defined on commons
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
*/
fun getSubCategoryList(categoryName: String?): Observable<List<String>> {
return responseToCategoryName(categoryInterface.getSubCategoryList(categoryName))
fun getSubCategoryList(categoryName: String?): Single<List<String>> {
val key = "$SUB_CATEGORY_CONTINUATION_PREFIX$categoryName"
return if (hasMorePagesFor(key)) {
responseToCategoryName(
categoryInterface.getSubCategoryList(
categoryName,
continuationStore[key] ?: emptyMap()
),
key
)
} else {
Single.just(emptyList())
}
}
/**
@ -59,7 +78,7 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
* @param categoryName Category name as defined on commons
* @return
*/
fun getParentCategoryList(categoryName: String?): Observable<List<String>> {
fun getParentCategoryList(categoryName: String?): Single<List<String>> {
return responseToCategoryName(categoryInterface.getParentCategoryList(categoryName))
}
@ -69,12 +88,30 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
* @param responseObservable The query response observable
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
*/
private fun responseToCategoryName(responseObservable: Observable<MwQueryResponse>): Observable<List<String>> {
private fun responseToCategoryName(
responseObservable: Single<MwQueryResponse>,
key: String? = null
): Single<List<String>> {
return responseObservable
.map { it.query()?.pages() ?: emptyList() }
.map {
if (key != null) {
continuationExists[key] =
it.continuation()?.let { continuation ->
continuationStore[key] = continuation
true
} ?: false
}
it.query()?.pages() ?: emptyList()
}
.map {
it.map { page -> page.title().replace(CATEGORY_PREFIX, "") }
}
}
private fun hasMorePagesFor(key: String) = continuationExists[key] ?: true
fun resetSubCategoryContinuation(category: String) {
continuationExists.remove("$SUB_CATEGORY_CONTINUATION_PREFIX$category")
continuationStore.remove("$SUB_CATEGORY_CONTINUATION_PREFIX$category")
}
}

View file

@ -20,6 +20,7 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.explore.ViewPagerAdapter;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import java.util.ArrayList;
@ -69,13 +70,12 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
categoriesMediaFragment = new CategoriesMediaFragment();
SubCategoryListFragment subCategoryListFragment = new SubCategoryListFragment();
SubCategoriesFragment subCategoryListFragment = new SubCategoriesFragment();
SubCategoryListFragment parentCategoryListFragment = new SubCategoryListFragment();
categoryName = getIntent().getStringExtra("categoryName");
if (getIntent() != null && categoryName != null) {
Bundle arguments = new Bundle();
arguments.putString("categoryName", categoryName);
arguments.putBoolean("isParentCategory", false);
categoriesMediaFragment.setArguments(arguments);
subCategoryListFragment.setArguments(arguments);
Bundle parentCategoryArguments = new Bundle();

View file

@ -195,4 +195,9 @@ public class CategoryImagesActivity
public void requestMoreImages() {
//unneeded
}
@Override
public void onMediaClicked(int position) {
// this class is unused and will be deleted
}
}

View file

@ -8,6 +8,7 @@ package fr.free.nrw.commons.category;
public interface CategoryImagesCallback {
void viewPagerNotifyDataSetChanged();
void requestMoreImages();
void onMediaClicked(int position);
}

View file

@ -1,10 +1,11 @@
package fr.free.nrw.commons.category;
import io.reactivex.Single;
import java.util.Map;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
/**
* Interface for interacting with Commons category related APIs
@ -20,7 +21,7 @@ public interface CategoryInterface {
*/
@GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=search&gsrnamespace=14")
Observable<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter,
Single<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter,
@Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset);
/**
@ -32,16 +33,17 @@ public interface CategoryInterface {
*/
@GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=allcategories")
Observable<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix,
Single<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix,
@Query("gaclimit") int itemLimit, @Query("gacoffset") int offset);
@GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=categorymembers&gcmtype=subcat"
+ "&prop=info&gcmlimit=500")
Observable<MwQueryResponse> getSubCategoryList(@Query("gcmtitle") String categoryName);
+ "&prop=info&gcmlimit=50")
Single<MwQueryResponse> getSubCategoryList(@Query("gcmtitle") String categoryName,
@QueryMap(encoded = true) Map<String, String> continuation);
@GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=categories&prop=info&gcllimit=500")
Observable<MwQueryResponse> getParentCategoryList(@Query("titles") String categoryName);
Single<MwQueryResponse> getParentCategoryList(@Query("titles") String categoryName);
}

View file

@ -8,6 +8,7 @@ import fr.free.nrw.commons.category.SubCategoryListFragment;
import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.contributions.ContributionsListFragment;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment;
import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment;
import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment;
import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment;
@ -99,4 +100,7 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract CategoriesMediaFragment bindCategoriesMediaFragment();
@ContributesAndroidInjector
abstract SubCategoriesFragment bindSubCategoriesFragment();
}

View file

@ -7,7 +7,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@ -31,8 +30,7 @@ import java.util.List;
public class ExploreActivity
extends NavigationBaseActivity
implements MediaDetailPagerFragment.MediaDetailProvider,
AdapterView.OnItemClickListener, CategoryImagesCallback {
implements MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
private static final String FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons";
private static final String MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android";
@ -177,7 +175,7 @@ public class ExploreActivity
* This method is called onClick of media inside category featured images or mobile uploads.
*/
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
public void onMediaClicked( int position) {
tabLayout.setVisibility(View.GONE);
viewPager.setVisibility(View.GONE);
mediaContainer.setVisibility(View.VISIBLE);
@ -195,7 +193,7 @@ public class ExploreActivity
// coming back to the explore activity. See https://github.com/commons-app/apps-android-commons/issues/1631
// https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550 supportFragmentManager.executePendingTransactions();
}
mediaDetails.showImage(i);
mediaDetails.showImage(position);
forceInitBackButton();
}

View file

@ -191,7 +191,8 @@ public class SearchActivity extends NavigationBaseActivity
* Open media detail pager fragment on click of image in search results
* @param index item index that should be opened
*/
public void onSearchImageClicked(int index) {
@Override
public void onMediaClicked(int index) {
ViewUtil.hideKeyboard(this.findViewById(R.id.searchBox));
toolbar.setVisibility(View.GONE);
tabLayout.setVisibility(View.GONE);

View file

@ -4,12 +4,18 @@ import dagger.Binds
import dagger.Module
import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenter
import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenterImpl
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesPresenter
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesPresenterImpl
@Module
abstract class CategoriesModule {
@Binds
abstract fun CategoryMediaPresenterImpl.bindsParentDepictionPresenter()
abstract fun CategoryMediaPresenterImpl.bindsCategoryMediaPresenter()
: CategoryMediaPresenter
@Binds
abstract fun SubCategoriesPresenterImpl.bindsSubCategoriesPresenter()
: SubCategoriesPresenter
}

View file

@ -3,8 +3,6 @@ package fr.free.nrw.commons.explore.categories.media
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.category.CATEGORY_PREFIX
import fr.free.nrw.commons.category.CategoryDetailsActivity
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.explore.media.PageableMediaFragment
import javax.inject.Inject
@ -20,12 +18,4 @@ class CategoriesMediaFragment : PageableMediaFragment() {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
}
override fun onItemClicked(position: Int) {
(activity as CategoryDetailsActivity).onMediaClicked(position)
}
override fun notifyViewPager() {
(activity as CategoryImagesCallback).viewPagerNotifyDataSetChanged()
}
}

View file

@ -11,6 +11,6 @@ class PageableCategoriesDataSource @Inject constructor(
) : PageableBaseDataSource<String>(liveDataConverter) {
override val loadFunction = { loadSize: Int, startPosition: Int ->
categoryClient.searchCategories(query, loadSize, startPosition).blockingFirst()
categoryClient.searchCategories(query, loadSize, startPosition).blockingGet()
}
}

View file

@ -0,0 +1,15 @@
package fr.free.nrw.commons.explore.categories.search
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryDetailsActivity
import fr.free.nrw.commons.explore.paging.BasePagingFragment
abstract class PageableCategoryFragment : BasePagingFragment<String>() {
override val errorTextId: Int = R.string.error_loading_categories
override val pagedListAdapter by lazy {
PagedSearchCategoriesAdapter {
CategoryDetailsActivity.startYourself(context, it)
}
}
}

View file

@ -8,20 +8,12 @@ import javax.inject.Inject
/**
* Displays the category search screen.
*/
class SearchCategoryFragment : BasePagingFragment<String>() {
class SearchCategoryFragment : PageableCategoryFragment() {
@Inject
lateinit var presenter: SearchCategoriesFragmentPresenter
override val errorTextId: Int = R.string.error_loading_categories
override val injectedPresenter
get() = presenter
override val pagedListAdapter by lazy {
PagedSearchCategoriesAdapter {
CategoryDetailsActivity.startYourself(context, it)
}
}
override fun getEmptyText(query: String) = getString(R.string.categories_not_found, query)
}

View file

@ -0,0 +1,19 @@
package fr.free.nrw.commons.explore.categories.sub
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
import javax.inject.Inject
class PageableSubCategoriesDataSource @Inject constructor(
liveDataConverter: LiveDataConverter,
val categoryClient: CategoryClient
) : PageableBaseDataSource<String>(liveDataConverter) {
override val loadFunction = { loadSize: Int, startPosition: Int ->
if (startPosition == 0) {
categoryClient.resetSubCategoryContinuation(query)
}
categoryClient.getSubCategoryList(query).blockingGet()
}
}

View file

@ -0,0 +1,25 @@
package fr.free.nrw.commons.explore.categories.sub
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CATEGORY_PREFIX
import fr.free.nrw.commons.explore.categories.search.PageableCategoryFragment
import fr.free.nrw.commons.explore.paging.PagingContract
import javax.inject.Inject
class SubCategoriesFragment : PageableCategoryFragment() {
@Inject lateinit var presenter: SubCategoriesPresenter
override val injectedPresenter: PagingContract.Presenter<String>
get() = presenter
override fun getEmptyText(query: String) = getString(R.string.no_subcategory_found)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
}
}

View file

@ -0,0 +1,16 @@
package fr.free.nrw.commons.explore.categories.sub
import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.paging.BasePagingPresenter
import fr.free.nrw.commons.explore.paging.PagingContract
import io.reactivex.Scheduler
import javax.inject.Inject
import javax.inject.Named
interface SubCategoriesPresenter : PagingContract.Presenter<String>
class SubCategoriesPresenterImpl @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableSubCategoriesDataSource
) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory),
SubCategoriesPresenter

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.explore.depictions.media
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity
import fr.free.nrw.commons.explore.media.PageableMediaFragment
import javax.inject.Inject
@ -18,11 +17,4 @@ class DepictedImagesFragment : PageableMediaFragment() {
onQueryUpdated(arguments!!.getString("entityId")!!)
}
override fun onItemClicked(position: Int) {
(activity as WikidataItemDetailsActivity).onMediaClicked(position)
}
override fun notifyViewPager() {
(activity as WikidataItemDetailsActivity).viewPagerNotifyDataSetChanged()
}
}

View file

@ -1,25 +1,38 @@
package fr.free.nrw.commons.explore.media
import android.content.Context
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.explore.paging.BasePagingFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
import kotlinx.android.synthetic.main.fragment_search_paginated.*
abstract class PageableMediaFragment : BasePagingFragment<Media>() {
override val pagedListAdapter by lazy { PagedMediaAdapter(::onItemClicked) }
abstract class PageableMediaFragment : BasePagingFragment<Media>(), MediaDetailProvider {
override val pagedListAdapter by lazy {
PagedMediaAdapter {
categoryImagesCallback.onMediaClicked(it)
}
}
override val errorTextId: Int = R.string.error_loading_images
override fun getEmptyText(query: String) = getString(R.string.no_images_found)
protected abstract fun onItemClicked(position: Int)
lateinit var categoryImagesCallback: CategoryImagesCallback
protected abstract fun notifyViewPager()
override fun onAttach(context: Context) {
super.onAttach(context)
categoryImagesCallback = (context as CategoryImagesCallback)
}
private val simpleDataObserver = SimpleDataObserver { notifyViewPager() }
private val simpleDataObserver = SimpleDataObserver {
categoryImagesCallback.viewPagerNotifyDataSetChanged()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -31,12 +44,12 @@ abstract class PageableMediaFragment : BasePagingFragment<Media>() {
pagedListAdapter.unregisterAdapterDataObserver(simpleDataObserver)
}
fun getMediaAtPosition(position: Int): Media? =
override fun getMediaAtPosition(position: Int): Media? =
pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null }
.also {
pagedListAdapter.currentList?.loadAround(position)
paginatedSearchResultsList.scrollToPosition(position)
}
fun getTotalMediaCount(): Int = pagedListAdapter.itemCount
override fun getTotalMediaCount(): Int = pagedListAdapter.itemCount
}

View file

@ -1,26 +1,15 @@
package fr.free.nrw.commons.explore.media
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.explore.SearchActivity
import javax.inject.Inject
/**
* Displays the image search screen.
*/
class SearchMediaFragment : PageableMediaFragment(){
class SearchMediaFragment : PageableMediaFragment() {
@Inject
lateinit var presenter: SearchMediaFragmentPresenter
override val injectedPresenter
get() = presenter
override fun onItemClicked(position: Int) {
(context as SearchActivity).onSearchImageClicked(position)
}
override fun notifyViewPager() {
(activity as CategoryImagesCallback).viewPagerNotifyDataSetChanged()
}
}

View file

@ -211,5 +211,6 @@ class MediaClient @Inject constructor(
fun resetCategoryContinuation(category: String) {
continuationExists.remove("$CATEGORY_CONTINUATION_PREFIX$category")
continuationStore.remove("$CATEGORY_CONTINUATION_PREFIX$category")
}
}

View file

@ -4,9 +4,8 @@ import categoryItem
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import depictedItem
import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.upload.GpsCategoryModel
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject
import org.junit.Before
import org.junit.Test
@ -35,7 +34,7 @@ class CategoriesModelTest {
val expectedList = listOf("Test")
whenever(categoryClient.searchCategoriesForPrefix("tes", 25))
.thenReturn(Observable.just(expectedList))
.thenReturn(Single.just(expectedList))
// Checking if both return "Test"
val expectedItems = expectedList.map { CategoryItem(it, false) }
@ -56,7 +55,7 @@ class CategoriesModelTest {
whenever(gpsCategoryModel.categoriesFromLocation)
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory")))
whenever(categoryClient.searchCategories("tes", 25))
.thenReturn(Observable.just(listOf("titleSearch")))
.thenReturn(Single.just(listOf("titleSearch")))
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
.searchAll("", listOf("tes"), listOf(depictedItem))

View file

@ -2,11 +2,10 @@ package fr.free.nrw.commons.category
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import io.reactivex.Observable
import io.reactivex.Single
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.*
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.mock
@ -32,7 +31,7 @@ class CategoryClientTest {
fun searchCategoriesFound() {
val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse))
.thenReturn(Single.just(mockResponse))
categoryClient.searchCategories("tes", 10)
.test()
.assertValues(listOf("Test"))
@ -45,7 +44,7 @@ class CategoryClientTest {
fun searchCategoriesNull() {
val mockResponse = withNullPages()
whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse))
.thenReturn(Single.just(mockResponse))
categoryClient.searchCategories("tes", 10)
.test()
.assertValues(emptyList())
@ -58,7 +57,7 @@ class CategoryClientTest {
fun searchCategoriesForPrefixFound() {
val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse))
.thenReturn(Single.just(mockResponse))
categoryClient.searchCategoriesForPrefix("tes", 10)
.test()
.assertValues(listOf("Test"))
@ -71,7 +70,7 @@ class CategoryClientTest {
fun searchCategoriesForPrefixNull() {
val mockResponse = withNullPages()
whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse))
.thenReturn(Single.just(mockResponse))
categoryClient.searchCategoriesForPrefix("tes", 10)
.test()
.assertValues(emptyList())
@ -84,7 +83,7 @@ class CategoryClientTest {
fun getParentCategoryListFound() {
val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.getParentCategoryList(anyString()))
.thenReturn(Observable.just(mockResponse))
.thenReturn(Single.just(mockResponse))
categoryClient.getParentCategoryList("tes")
.test()
.assertValues(listOf("Test"))
@ -94,7 +93,7 @@ class CategoryClientTest {
fun getParentCategoryListNull() {
val mockResponse = withNullPages()
whenever(categoryInterface.getParentCategoryList(anyString()))
.thenReturn(Observable.just(mockResponse))
.thenReturn(Single.just(mockResponse))
categoryClient.getParentCategoryList("tes")
.test()
.assertValues(emptyList())
@ -103,8 +102,8 @@ class CategoryClientTest {
@Test
fun getSubCategoryListFound() {
val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.getSubCategoryList("tes"))
.thenReturn(Observable.just(mockResponse))
whenever(categoryInterface.getSubCategoryList("tes", emptyMap()))
.thenReturn(Single.just(mockResponse))
categoryClient.getSubCategoryList("tes")
.test()
.assertValues(listOf("Test"))
@ -113,8 +112,11 @@ class CategoryClientTest {
@Test
fun getSubCategoryListNull() {
val mockResponse = withNullPages()
whenever(categoryInterface.getSubCategoryList(anyString()))
.thenReturn(Observable.just(mockResponse))
whenever(categoryInterface.getSubCategoryList(
anyString(),
anyMap()
))
.thenReturn(Single.just(mockResponse))
categoryClient.getSubCategoryList("tes")
.test()
.assertValues(emptyList())

View file

@ -0,0 +1,48 @@
package fr.free.nrw.commons.explore.categories.sub
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import io.reactivex.Single
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
class PageableSubCategoriesDataSourceTest{
@Mock
lateinit var categoryClient: CategoryClient
@Mock
lateinit var liveDataConverter: LiveDataConverter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@Test
fun `loadFunction calls reset at position 0`() {
val dataSource =
PageableSubCategoriesDataSource(liveDataConverter, categoryClient)
dataSource.onQueryUpdated("test")
whenever(categoryClient.getSubCategoryList("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 0), `is`(emptyList()))
verify(categoryClient).resetSubCategoryContinuation("test")
}
@Test
fun `loadFunction does not call reset at any other position`() {
val dataSource =
PageableSubCategoriesDataSource(liveDataConverter, categoryClient)
dataSource.onQueryUpdated("test")
whenever(categoryClient.getSubCategoryList("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 1), `is`(emptyList()))
verify(categoryClient, never()).resetSubCategoryContinuation("test")
}
}

View file

@ -4,7 +4,7 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.categories.search.PageableCategoriesDataSource
import io.reactivex.Observable
import io.reactivex.Single
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.junit.Test
@ -14,7 +14,7 @@ class PageableCategoriesDataSourceTest {
fun `loadFunction loads categories`() {
val categoryClient: CategoryClient = mock()
whenever(categoryClient.searchCategories("test", 0, 1))
.thenReturn(Observable.just(emptyList()))
.thenReturn(Single.just(emptyList()))
val pageableCategoriesDataSource = PageableCategoriesDataSource(mock(), categoryClient)
pageableCategoriesDataSource.onQueryUpdated("test")
assertThat(pageableCategoriesDataSource.loadFunction(0, 1), Matchers.`is`(emptyList()))