#3120 Suggest categories based on depictions (#3736)

This commit is contained in:
Seán Mac Gillicuddy 2020-05-20 15:26:16 +01:00 committed by GitHub
parent 01839dec6e
commit de3377c0fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 99 additions and 84 deletions

View file

@ -1,11 +1,6 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
import org.wikipedia.dataclient.okhttp.HttpStatusException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import okhttp3.Cache; import okhttp3.Cache;

View file

@ -2,9 +2,10 @@ package fr.free.nrw.commons.category
import android.text.TextUtils import android.text.TextUtils
import fr.free.nrw.commons.upload.GpsCategoryModel import fr.free.nrw.commons.upload.GpsCategoryModel
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import fr.free.nrw.commons.utils.StringSortingUtils import fr.free.nrw.commons.utils.StringSortingUtils
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.Function3 import io.reactivex.functions.Function4
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -67,30 +68,42 @@ class CategoriesModel @Inject constructor(
* @param imageTitleList * @param imageTitleList
* @return * @return
*/ */
fun searchAll(term: String, imageTitleList: List<String>): Observable<List<CategoryItem>> { fun searchAll(
return suggestionsOrSearch(term, imageTitleList) term: String,
imageTitleList: List<String>,
selectedDepictions: List<DepictedItem>
): Observable<List<CategoryItem>> {
return suggestionsOrSearch(term, imageTitleList, selectedDepictions)
.map { it.map { CategoryItem(it, false) } } .map { it.map { CategoryItem(it, false) } }
} }
private fun suggestionsOrSearch(term: String, imageTitleList: List<String>): private fun suggestionsOrSearch(
Observable<List<String>> { term: String,
imageTitleList: List<String>,
selectedDepictions: List<DepictedItem>
): Observable<List<String>> {
return if (TextUtils.isEmpty(term)) return if (TextUtils.isEmpty(term))
Observable.combineLatest( Observable.combineLatest(
categoriesFromDepiction(selectedDepictions),
gpsCategoryModel.categoriesFromLocation, gpsCategoryModel.categoriesFromLocation,
titleCategories(imageTitleList), titleCategories(imageTitleList),
Observable.just(categoryDao.recentCategories(SEARCH_CATS_LIMIT)), Observable.just(categoryDao.recentCategories(SEARCH_CATS_LIMIT)),
Function3(::combine) Function4(::combine)
) )
else else
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT) categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) } .map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
} }
private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>) =
Observable.just(selectedDepictions.map { it.commonsCategories }.flatten())
private fun combine( private fun combine(
depictionCategories: List<String>,
locationCategories: List<String>, locationCategories: List<String>,
titles: List<String>, titles: List<String>,
recents: List<String> recents: List<String>
) = locationCategories + titles + recents ) = depictionCategories + locationCategories + titles + recents
/** /**
@ -98,14 +111,13 @@ class CategoriesModel @Inject constructor(
* @param titleList * @param titleList
* @return * @return
*/ */
private fun titleCategories(titleList: List<String>): Observable<List<String>> { private fun titleCategories(titleList: List<String>) =
return if (titleList.isNotEmpty()) if (titleList.isNotEmpty())
Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults -> Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults ->
searchResults.map { it as List<String> }.flatten() searchResults.map { it as List<String> }.flatten()
} }
else else
Observable.just(emptyList()) Observable.just(emptyList())
}
/** /**
* Return category for single title * Return category for single title

View file

@ -155,5 +155,4 @@ public class SearchDepictionsFragmentPresenter extends CommonsDaggerSupportFragm
offset=queryList.size(); offset=queryList.size();
} }
} }
} }

View file

@ -171,7 +171,7 @@ public class MediaClient {
* @return caption for image using wikibaseIdentifier * @return caption for image using wikibaseIdentifier
*/ */
public Single<String> getCaptionByWikibaseIdentifier(String wikibaseIdentifier) { public Single<String> getCaptionByWikibaseIdentifier(String wikibaseIdentifier) {
return mediaDetailInterface.getCaptionForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier) return mediaDetailInterface.getEntityForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
.map(mediaDetailResponse -> { .map(mediaDetailResponse -> {
if (isSuccess(mediaDetailResponse)) { if (isSuccess(mediaDetailResponse)) {
for (Entity wikibaseItem : mediaDetailResponse.entities().values()) { for (Entity wikibaseItem : mediaDetailResponse.entities().values()) {

View file

@ -33,5 +33,5 @@ public interface MediaDetailInterface {
* @param wikibaseIdentifier pageId for the media * @param wikibaseIdentifier pageId for the media
*/ */
@GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki") @GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
Observable<Entities> getCaptionForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier); Observable<Entities> getEntityForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
} }

View file

@ -1,5 +1,13 @@
package fr.free.nrw.commons.nearby.fragments; package fr.free.nrw.commons.nearby.fragments;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
import static fr.free.nrw.commons.nearby.Label.TEXT_TO_DESCRIPTION;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import android.Manifest; import android.Manifest;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -26,14 +34,14 @@ import android.widget.RelativeLayout;
import android.widget.SearchView; import android.widget.SearchView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup; import com.google.android.material.chip.ChipGroup;
@ -58,17 +66,6 @@ import com.mapbox.mapboxsdk.maps.UiSettings;
import com.mapbox.pluginscalebar.ScaleBarOptions; import com.mapbox.pluginscalebar.ScaleBarOptions;
import com.mapbox.pluginscalebar.ScaleBarPlugin; import com.mapbox.pluginscalebar.ScaleBarPlugin;
import com.pedrogomez.renderers.RVRendererAdapter; import com.pedrogomez.renderers.RVRendererAdapter;
import fr.free.nrw.commons.utils.DialogUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
@ -92,6 +89,7 @@ import fr.free.nrw.commons.nearby.NearbyMarker;
import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract; import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter; import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.ExecutorUtils; import fr.free.nrw.commons.utils.ExecutorUtils;
import fr.free.nrw.commons.utils.LayoutUtils; import fr.free.nrw.commons.utils.LayoutUtils;
import fr.free.nrw.commons.utils.LocationUtils; import fr.free.nrw.commons.utils.LocationUtils;
@ -105,16 +103,13 @@ import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber; import timber.log.Timber;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
import static fr.free.nrw.commons.nearby.Label.TEXT_TO_DESCRIPTION;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
public class NearbyParentFragment extends CommonsDaggerSupportFragment public class NearbyParentFragment extends CommonsDaggerSupportFragment
implements NearbyParentFragmentContract.View, implements NearbyParentFragmentContract.View,

View file

@ -108,10 +108,12 @@ public class UploadRepository {
* *
* @param query * @param query
* @param imageTitleList * @param imageTitleList
* @param selectedDepictions
* @return * @return
*/ */
public Observable<List<CategoryItem>> searchAll(String query, List<String> imageTitleList) { public Observable<List<CategoryItem>> searchAll(String query, List<String> imageTitleList,
return categoriesModel.searchAll(query, imageTitleList); List<DepictedItem> selectedDepictions) {
return categoriesModel.searchAll(query, imageTitleList, selectedDepictions);
} }
/** /**

View file

@ -60,7 +60,7 @@ class CategoriesPresenter @Inject constructor(
} }
private fun searchResults(term: String) = private fun searchResults(term: String) =
repository.searchAll(term, getImageTitleList()) repository.searchAll(term, getImageTitleList(), repository.selectedDepictions)
.subscribeOn(ioScheduler) .subscribeOn(ioScheduler)
.map { it.filterNot { categoryItem -> repository.containsYear(categoryItem.name) } } .map { it.filterNot { categoryItem -> repository.containsYear(categoryItem.name) } }

View file

@ -107,7 +107,6 @@ class DepictsPresenter @Inject constructor(
view.noDepictionSelected() view.noDepictionSelected()
} }
} }
} }
inline fun <reified T> proxy() = Proxy inline fun <reified T> proxy() = Proxy

View file

@ -5,6 +5,7 @@ import fr.free.nrw.commons.explore.depictions.THUMB_IMAGE_SIZE
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.upload.WikidataItem import fr.free.nrw.commons.upload.WikidataItem
import fr.free.nrw.commons.wikidata.WikidataProperties import fr.free.nrw.commons.wikidata.WikidataProperties
import fr.free.nrw.commons.wikidata.WikidataProperties.*
import org.wikipedia.wikidata.DataValue import org.wikipedia.wikidata.DataValue
import org.wikipedia.wikidata.Entities import org.wikipedia.wikidata.Entities
import org.wikipedia.wikidata.Statement_partial import org.wikipedia.wikidata.Statement_partial
@ -17,6 +18,7 @@ data class DepictedItem constructor(
val description: String?, val description: String?,
val imageUrl: String?, val imageUrl: String?,
val instanceOfs: List<String>, val instanceOfs: List<String>,
val commonsCategories: List<String>,
var isSelected: Boolean, var isSelected: Boolean,
override val id: String override val id: String
) : WikidataItem { ) : WikidataItem {
@ -36,10 +38,12 @@ data class DepictedItem constructor(
constructor(entity: Entities.Entity, name: String, description: String) : this( constructor(entity: Entities.Entity, name: String, description: String) : this(
name, name,
description, description,
entity[WikidataProperties.IMAGE].primaryImageValue?.let { entity[IMAGE].primaryImageValue?.let {
getImageUrl(it.value, THUMB_IMAGE_SIZE) getImageUrl(it.value, THUMB_IMAGE_SIZE)
}, },
entity[WikidataProperties.INSTANCE_OF].toIds(), entity[INSTANCE_OF].toIds(),
entity[COMMONS_CATEGORY]?.map { (it.mainSnak.dataValue as DataValue.ValueString).value }
?: emptyList(),
false, false,
entity.id() entity.id()
) )
@ -57,7 +61,7 @@ data class DepictedItem constructor(
} }
private fun List<Statement_partial>?.toIds(): List<String> { private fun List<Statement_partial>?.toIds(): List<String> {
return this?.map { it.mainSnak.dataValue } return this?.map { it.mainSnak.dataValue }
?.filterIsInstance<DataValue.EntityId>() ?.filterIsInstance<DataValue.EntityId>()
?.map { it.value.id } ?.map { it.value.id }
?: emptyList() ?: emptyList()

View file

@ -3,6 +3,8 @@ package fr.free.nrw.commons.wikidata
import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.BuildConfig
enum class WikidataProperties(val propertyName: String) { enum class WikidataProperties(val propertyName: String) {
IMAGE("P18"), DEPICTS(BuildConfig.DEPICTS_PROPERTY), INSTANCE_OF("P31"); IMAGE("P18"),
DEPICTS(BuildConfig.DEPICTS_PROPERTY),
COMMONS_CATEGORY("P373"),
INSTANCE_OF("P31");
} }

View file

@ -0,0 +1,23 @@
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
fun depictedItem(
name: String = "label",
description: String = "desc",
imageUrl: String = "",
instanceOfs: List<String> = listOf(),
commonsCategories: List<String> = listOf(),
isSelected: Boolean = false,
id: String = "entityId"
) = DepictedItem(
name = name,
description = description,
imageUrl = imageUrl,
instanceOfs = instanceOfs,
commonsCategories = commonsCategories,
isSelected = isSelected,
id = id
)
fun categoryItem(name: String = "name", selected: Boolean = false) =
CategoryItem(name, selected)

View file

@ -1,7 +1,10 @@
package fr.free.nrw.commons.category package fr.free.nrw.commons.category
import categoryItem
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import depictedItem
import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.upload.GpsCategoryModel import fr.free.nrw.commons.upload.GpsCategoryModel
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.BehaviorSubject
@ -36,11 +39,11 @@ class CategoriesModelTest {
// Checking if both return "Test" // Checking if both return "Test"
val expectedItems = expectedList.map { CategoryItem(it, false) } val expectedItems = expectedList.map { CategoryItem(it, false) }
categoriesModel.searchAll("tes", emptyList()) categoriesModel.searchAll("tes", emptyList(), emptyList())
.test() .test()
.assertValues(expectedItems) .assertValues(expectedItems)
categoriesModel.searchAll("Tes", emptyList()) categoriesModel.searchAll("Tes", emptyList(), emptyList())
.test() .test()
.assertValues(expectedItems) .assertValues(expectedItems)
} }
@ -48,6 +51,7 @@ class CategoriesModelTest {
@Test @Test
fun `searchAll with empty search terms creates results from gps, title search & recents`() { fun `searchAll with empty search terms creates results from gps, title search & recents`() {
val gpsCategoryModel: GpsCategoryModel = mock() val gpsCategoryModel: GpsCategoryModel = mock()
val depictedItem = depictedItem(commonsCategories = listOf("depictionCategory"))
whenever(gpsCategoryModel.categoriesFromLocation) whenever(gpsCategoryModel.categoriesFromLocation)
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory"))) .thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory")))
@ -55,13 +59,14 @@ class CategoriesModelTest {
.thenReturn(Observable.just(listOf("titleSearch"))) .thenReturn(Observable.just(listOf("titleSearch")))
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories")) whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel) CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
.searchAll("", listOf("tes")) .searchAll("", listOf("tes"), listOf(depictedItem))
.test() .test()
.assertValue( .assertValue(
listOf( listOf(
CategoryItem("gpsCategory", false), categoryItem("depictionCategory"),
CategoryItem("titleSearch", false), categoryItem("gpsCategory"),
CategoryItem("recentCategories", false) categoryItem("titleSearch"),
categoryItem("recentCategories")
) )
) )
} }

View file

@ -1,10 +1,9 @@
package fr.free.nrw.commons.explore.depictions package fr.free.nrw.commons.explore.depictions
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import depictedItem
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.upload.depictedItem
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.schedulers.TestScheduler import io.reactivex.schedulers.TestScheduler
import org.junit.Before import org.junit.Before
@ -36,7 +35,6 @@ class SearchDepictionsPresenterTest {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
testScheduler = TestScheduler() testScheduler = TestScheduler()
val depictedItem: DepictedItem = depictedItem(instanceOfs = listOf())
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter( searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(
jsonKvStore, jsonKvStore,
recentSearchesDao, recentSearchesDao,
@ -56,5 +54,4 @@ class SearchDepictionsPresenterTest {
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view)?.onSuccess(expectedList) verify(view)?.onSuccess(expectedList)
} }
} }

View file

@ -1,8 +1,8 @@
package fr.free.nrw.commons.upload package fr.free.nrw.commons.upload
import categoryItem
import com.nhaarman.mockitokotlin2.* import com.nhaarman.mockitokotlin2.*
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.repository.UploadRepository import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.categories.CategoriesContract import fr.free.nrw.commons.upload.categories.CategoriesContract
import fr.free.nrw.commons.upload.categories.CategoriesPresenter import fr.free.nrw.commons.upload.categories.CategoriesPresenter
@ -27,11 +27,6 @@ class CategoriesPresenterTest {
private lateinit var testScheduler: TestScheduler private lateinit var testScheduler: TestScheduler
private val categoryItems: ArrayList<CategoryItem> = ArrayList()
@Mock
lateinit var categoryItem: CategoryItem
/** /**
* initial setup * initial setup
*/ */
@ -40,7 +35,6 @@ class CategoriesPresenterTest {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
testScheduler = TestScheduler() testScheduler = TestScheduler()
categoryItems.add(categoryItem)
categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler) categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler)
categoriesPresenter.onAttachView(view) categoriesPresenter.onAttachView(view)
} }
@ -62,7 +56,7 @@ class CategoriesPresenterTest {
emptyCaptionUploadItem emptyCaptionUploadItem
) )
) )
whenever(repository.searchAll("test", listOf("nonEmpty"))) whenever(repository.searchAll("test", listOf("nonEmpty"), repository.selectedDepictions))
.thenReturn( .thenReturn(
Observable.just( Observable.just(
listOf( listOf(
@ -87,7 +81,7 @@ class CategoriesPresenterTest {
@Test @Test
fun `searchForCategoriesTest sets Error when list is empty`() { fun `searchForCategoriesTest sets Error when list is empty`() {
whenever(repository.uploads).thenReturn(listOf()) whenever(repository.uploads).thenReturn(listOf())
whenever(repository.searchAll(any(), any())).thenReturn(Observable.just(listOf())) whenever(repository.searchAll(any(), any(), any())).thenReturn(Observable.just(listOf()))
whenever(repository.selectedCategories).thenReturn(listOf()) whenever(repository.selectedCategories).thenReturn(listOf())
categoriesPresenter.searchForCategories("test") categoriesPresenter.searchForCategories("test")
testScheduler.triggerActions() testScheduler.triggerActions()
@ -124,10 +118,8 @@ class CategoriesPresenterTest {
*/ */
@Test @Test
fun onCategoryItemClickedTest() { fun onCategoryItemClickedTest() {
val categoryItem = categoryItem()
categoriesPresenter.onCategoryItemClicked(categoryItem) categoriesPresenter.onCategoryItemClicked(categoryItem)
verify(repository).onCategoryClicked(categoryItem) verify(repository).onCategoryClicked(categoryItem)
} }
private fun categoryItem(name: String = "name", selected: Boolean = false) =
CategoryItem(name, selected)
} }

View file

@ -4,11 +4,11 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.jraska.livedata.test import com.jraska.livedata.test
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import depictedItem
import fr.free.nrw.commons.explore.depictions.DepictsClient import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.repository.UploadRepository import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.depicts.DepictsContract import fr.free.nrw.commons.upload.depicts.DepictsContract
import fr.free.nrw.commons.upload.depicts.DepictsPresenter import fr.free.nrw.commons.upload.depicts.DepictsPresenter
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
import io.reactivex.Flowable import io.reactivex.Flowable
import io.reactivex.schedulers.TestScheduler import io.reactivex.schedulers.TestScheduler
@ -62,8 +62,8 @@ class DepictsPresenterTest {
depictedItem(id="nonUnique"), depictedItem(id="nonUnique"),
depictedItem(id="nonUnique"), depictedItem(id="nonUnique"),
depictedItem( depictedItem(
id = "unique", instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id),
instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id) id = "unique"
) )
) )
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults)) whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
@ -78,6 +78,7 @@ class DepictsPresenterTest {
.assertValue(listOf(selectedItem, depictedItem(id="nonUnique"))) .assertValue(listOf(selectedItem, depictedItem(id="nonUnique")))
} }
@Test @Test
fun `empty search results with empty term do not show error`() { fun `empty search results with empty term do not show error`() {
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(emptyList())) whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(emptyList()))
@ -137,15 +138,4 @@ class DepictsPresenterTest {
depictsPresenter.verifyDepictions() depictsPresenter.verifyDepictions()
verify(view).noDepictionSelected() verify(view).noDepictionSelected()
} }
} }
fun depictedItem(
name: String = "label",
description: String = "desc",
imageUrl: String = "",
instanceOfs: List<String> = listOf(),
isSelected: Boolean = false,
id: String = "entityId"
) = DepictedItem(name, description, imageUrl, instanceOfs, isSelected, id)