mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge pull request #1449 from psh/consolidate-networking-libraries-step-1
Code cleanup + automated testing around GPS Categories
This commit is contained in:
commit
f0b8039778
21 changed files with 619 additions and 313 deletions
|
|
@ -11,7 +11,6 @@ dependencies {
|
|||
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||
implementation 'in.yuvi:http.fluent:1.3'
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
||||
implementation 'com.android.volley:volley:1.0.0'
|
||||
implementation 'ch.acra:acra:4.9.2'
|
||||
implementation 'org.mediawiki:api:1.3'
|
||||
implementation 'commons-codec:commons-codec:1.10'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
-dontobfuscate
|
||||
-keep class org.apache.http.** { *; }
|
||||
-dontwarn org.apache.http.**
|
||||
-keep class fr.free.nrw.commons.upload.MwVolleyApi$Page {*;}
|
||||
-keep class android.support.v7.widget.ShareActionProvider { *; }
|
||||
|
|
@ -7,18 +7,25 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||
import timber.log.Timber;
|
||||
|
||||
@Singleton
|
||||
public class CacheController {
|
||||
|
||||
private final GpsCategoryModel gpsCategoryModel;
|
||||
private final QuadTree<List<String>> quadTree;
|
||||
private double x, y;
|
||||
private QuadTree<List<String>> quadTree;
|
||||
private double xMinus, xPlus, yMinus, yPlus;
|
||||
|
||||
private static final int EARTH_RADIUS = 6378137;
|
||||
|
||||
public CacheController() {
|
||||
@Inject
|
||||
CacheController(GpsCategoryModel gpsCategoryModel) {
|
||||
this.gpsCategoryModel = gpsCategoryModel;
|
||||
quadTree = new QuadTree<>(-180, -90, +180, +90);
|
||||
}
|
||||
|
||||
|
|
@ -31,8 +38,8 @@ public class CacheController {
|
|||
|
||||
public void cacheCategory() {
|
||||
List<String> pointCatList = new ArrayList<>();
|
||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
||||
pointCatList.addAll(MwVolleyApi.getGpsCat());
|
||||
if (gpsCategoryModel.getGpsCatExists()) {
|
||||
pointCatList.addAll(gpsCategoryModel.getCategoryList());
|
||||
Timber.d("Categories being cached: %s", pointCatList);
|
||||
} else {
|
||||
Timber.d("No categories found, so no categories cached");
|
||||
|
|
@ -65,7 +72,7 @@ public class CacheController {
|
|||
}
|
||||
|
||||
//Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
|
||||
public void convertCoordRange() {
|
||||
private void convertCoordRange() {
|
||||
//Position, decimal degrees
|
||||
double lat = y;
|
||||
double lon = x;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import butterknife.ButterKnife;
|
|||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.Observable;
|
||||
|
|
@ -73,6 +73,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
|||
@Inject @Named("prefs") SharedPreferences prefsPrefs;
|
||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||
@Inject CategoryDao categoryDao;
|
||||
@Inject GpsCategoryModel gpsCategoryModel;
|
||||
|
||||
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
||||
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
||||
|
|
@ -253,7 +254,6 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
|
||||
private Observable<CategoryItem> defaultCategories() {
|
||||
|
||||
Observable<CategoryItem> directCat = directCategories();
|
||||
if (hasDirectCategories) {
|
||||
Timber.d("Image has direct Cat");
|
||||
|
|
@ -287,9 +287,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
|
||||
private Observable<CategoryItem> gpsCategories() {
|
||||
return Observable.fromIterable(
|
||||
MwVolleyApi.GpsCatExists.getGpsCatExists()
|
||||
? MwVolleyApi.getGpsCat() : new ArrayList<>())
|
||||
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,17 +9,16 @@ import dagger.android.support.AndroidSupportInjectionModule;
|
|||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.MediaWikiImageView;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
|
||||
import fr.free.nrw.commons.delete.DeleteTask;
|
||||
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
|
||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
CommonsApplicationModule.class,
|
||||
NetworkingModule.class,
|
||||
AndroidInjectionModule.class,
|
||||
AndroidSupportInjectionModule.class,
|
||||
ActivityBuilderModule.class,
|
||||
|
|
|
|||
|
|
@ -6,21 +6,15 @@ import android.content.SharedPreferences;
|
|||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.util.LruCache;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.auth.AccountUtil;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.caching.CacheController;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||
import fr.free.nrw.commons.upload.UploadController;
|
||||
|
|
@ -33,7 +27,6 @@ import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MOD
|
|||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public class CommonsApplicationModule {
|
||||
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
|
||||
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
private Context applicationContext;
|
||||
|
||||
|
|
@ -117,37 +110,12 @@ public class CommonsApplicationModule {
|
|||
return new SessionManager(context, mediaWikiApi, sharedPreferences);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public MediaWikiApi provideMediaWikiApi(Context context,
|
||||
@Named("default_preferences") SharedPreferences defaultPreferences,
|
||||
@Named("category_prefs") SharedPreferences categoryPrefs,
|
||||
Gson gson) {
|
||||
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public LocationServiceManager provideLocationServiceManager(Context context) {
|
||||
return new LocationServiceManager(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
||||
* @return returns a singleton Gson instance
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
public Gson provideGson() {
|
||||
return new Gson();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public CacheController provideCacheController() {
|
||||
return new CacheController();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public DBOpenHelper provideDBOpenHelper(Context context) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package fr.free.nrw.commons.di;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@Module
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public class NetworkingModule {
|
||||
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public OkHttpClient provideOkHttpClient() {
|
||||
return new OkHttpClient.Builder().build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public MediaWikiApi provideMediaWikiApi(Context context,
|
||||
@Named("default_preferences") SharedPreferences defaultPreferences,
|
||||
@Named("category_prefs") SharedPreferences categoryPrefs,
|
||||
Gson gson) {
|
||||
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("commons_mediawiki_url")
|
||||
@NonNull
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public HttpUrl provideMwUrl() {
|
||||
return HttpUrl.parse(BuildConfig.COMMONS_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
||||
* @return returns a singleton Gson instance
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
public Gson provideGson() {
|
||||
return new GsonBuilder().create();
|
||||
}
|
||||
|
||||
}
|
||||
101
app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java
Normal file
101
app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.mwapi.model.ApiResponse;
|
||||
import fr.free.nrw.commons.mwapi.model.Page;
|
||||
import fr.free.nrw.commons.mwapi.model.PageCategory;
|
||||
import io.reactivex.Single;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates
|
||||
* with nearby Commons categories. Parses the results using GSON to obtain a list of relevant
|
||||
* categories. Note: that caller is responsible for executing the request() method on a background
|
||||
* thread.
|
||||
*/
|
||||
public class CategoryApi {
|
||||
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final HttpUrl mwUrl;
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public CategoryApi(OkHttpClient okHttpClient, Gson gson,
|
||||
@Named("commons_mediawiki_url") HttpUrl mwUrl) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.mwUrl = mwUrl;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
public Single<List<String>> request(String coords) {
|
||||
return Single.fromCallable(() -> {
|
||||
HttpUrl apiUrl = buildUrl(coords);
|
||||
Timber.d("URL: %s", apiUrl.toString());
|
||||
|
||||
Request request = new Request.Builder().get().url(apiUrl).build();
|
||||
Response response = okHttpClient.newCall(request).execute();
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ApiResponse apiResponse = gson.fromJson(body.charStream(), ApiResponse.class);
|
||||
Set<String> categories = new LinkedHashSet<>();
|
||||
if (apiResponse != null && apiResponse.hasPages()) {
|
||||
for (Page page : apiResponse.query.pages) {
|
||||
for (PageCategory category : page.getCategories()) {
|
||||
categories.add(category.withoutPrefix());
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(categories);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds URL with image coords for MediaWiki API calls
|
||||
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
|
||||
*
|
||||
* @param coords Coordinates to build query with
|
||||
* @return URL for API query
|
||||
*/
|
||||
private HttpUrl buildUrl(String coords) {
|
||||
return mwUrl.newBuilder()
|
||||
.addPathSegment("w")
|
||||
.addPathSegment("api.php")
|
||||
.addQueryParameter("action", "query")
|
||||
.addQueryParameter("prop", "categories|coordinates|pageprops")
|
||||
.addQueryParameter("format", "json")
|
||||
.addQueryParameter("clshow", "!hidden")
|
||||
.addQueryParameter("coprop", "type|name|dim|country|region|globe")
|
||||
.addQueryParameter("codistancefrompoint", coords)
|
||||
.addQueryParameter("generator", "geosearch")
|
||||
.addQueryParameter("ggscoord", coords)
|
||||
.addQueryParameter("ggsradius", "10000")
|
||||
.addQueryParameter("ggslimit", "10")
|
||||
.addQueryParameter("ggsnamespace", "6")
|
||||
.addQueryParameter("ggsprop", "type|name|dim|country|region|globe")
|
||||
.addQueryParameter("ggsprimary", "all")
|
||||
.addQueryParameter("formatversion", "2")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package fr.free.nrw.commons.mwapi.model;
|
||||
|
||||
public class ApiResponse {
|
||||
public Query query;
|
||||
|
||||
public ApiResponse() {
|
||||
}
|
||||
|
||||
public boolean hasPages() {
|
||||
return query != null && query.pages != null;
|
||||
}
|
||||
}
|
||||
17
app/src/main/java/fr/free/nrw/commons/mwapi/model/Page.java
Normal file
17
app/src/main/java/fr/free/nrw/commons/mwapi/model/Page.java
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package fr.free.nrw.commons.mwapi.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class Page {
|
||||
public String title;
|
||||
public PageCategory[] categories;
|
||||
public PageCategory category;
|
||||
|
||||
public Page() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public PageCategory[] getCategories() {
|
||||
return categories != null ? categories : new PageCategory[0];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package fr.free.nrw.commons.mwapi.model;
|
||||
|
||||
public class PageCategory {
|
||||
public String title;
|
||||
|
||||
public PageCategory() {
|
||||
}
|
||||
|
||||
public String withoutPrefix() {
|
||||
return title != null ? title.replace("Category:", "") : "";
|
||||
}
|
||||
}
|
||||
10
app/src/main/java/fr/free/nrw/commons/mwapi/model/Query.java
Normal file
10
app/src/main/java/fr/free/nrw/commons/mwapi/model/Query.java
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package fr.free.nrw.commons.mwapi.model;
|
||||
|
||||
public class Query {
|
||||
public Page[] pages;
|
||||
|
||||
public Query() {
|
||||
pages = new Page[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class GpsCategoryModel {
|
||||
private Set<String> categorySet;
|
||||
|
||||
@Inject
|
||||
public GpsCategoryModel() {
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
categorySet = new HashSet<>();
|
||||
}
|
||||
|
||||
public boolean getGpsCatExists() {
|
||||
return !categorySet.isEmpty();
|
||||
}
|
||||
|
||||
public List<String> getCategoryList() {
|
||||
return new ArrayList<>(categorySet);
|
||||
}
|
||||
|
||||
public void setCategoryList(List<String> categoryList) {
|
||||
clear();
|
||||
categorySet.addAll(categoryList != null ? categoryList : new ArrayList<>());
|
||||
}
|
||||
|
||||
public void add(String categoryString) {
|
||||
categorySet.add(categoryString);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.volley.Cache;
|
||||
import com.android.volley.NetworkResponse;
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.HttpHeaderParser;
|
||||
import com.android.volley.toolbox.JsonRequest;
|
||||
import com.android.volley.toolbox.Volley;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match
|
||||
* GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list
|
||||
* of relevant categories.
|
||||
*/
|
||||
public class MwVolleyApi {
|
||||
|
||||
private static RequestQueue REQUEST_QUEUE;
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
|
||||
private static Set<String> categorySet;
|
||||
private static List<String> categoryList;
|
||||
|
||||
private static final String MWURL = "https://commons.wikimedia.org/";
|
||||
private final Context context;
|
||||
|
||||
public MwVolleyApi(Context context) {
|
||||
this.context = context;
|
||||
categorySet = new HashSet<>();
|
||||
}
|
||||
|
||||
public static List<String> getGpsCat() {
|
||||
return categoryList;
|
||||
}
|
||||
|
||||
public static void setGpsCat(List<String> cachedList) {
|
||||
categoryList = new ArrayList<>();
|
||||
categoryList.addAll(cachedList);
|
||||
Timber.d("Setting GPS cats from cache: %s", categoryList);
|
||||
}
|
||||
|
||||
public void request(String coords) {
|
||||
String apiUrl = buildUrl(coords);
|
||||
Timber.d("URL: %s", apiUrl);
|
||||
|
||||
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
|
||||
new LogResponseListener<>(), new LogResponseErrorListener());
|
||||
getQueue().add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds URL with image coords for MediaWiki API calls
|
||||
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
|
||||
* @param coords Coordinates to build query with
|
||||
* @return URL for API query
|
||||
*/
|
||||
private String buildUrl(String coords) {
|
||||
|
||||
Uri.Builder builder = Uri.parse(MWURL).buildUpon();
|
||||
|
||||
builder.appendPath("w")
|
||||
.appendPath("api.php")
|
||||
.appendQueryParameter("action", "query")
|
||||
.appendQueryParameter("prop", "categories|coordinates|pageprops")
|
||||
.appendQueryParameter("format", "json")
|
||||
.appendQueryParameter("clshow", "!hidden")
|
||||
.appendQueryParameter("coprop", "type|name|dim|country|region|globe")
|
||||
.appendQueryParameter("codistancefrompoint", coords)
|
||||
.appendQueryParameter("generator", "geosearch")
|
||||
.appendQueryParameter("ggscoord", coords)
|
||||
.appendQueryParameter("ggsradius", "10000")
|
||||
.appendQueryParameter("ggslimit", "10")
|
||||
.appendQueryParameter("ggsnamespace", "6")
|
||||
.appendQueryParameter("ggsprop", "type|name|dim|country|region|globe")
|
||||
.appendQueryParameter("ggsprimary", "all")
|
||||
.appendQueryParameter("formatversion", "2");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private synchronized RequestQueue getQueue() {
|
||||
if (REQUEST_QUEUE == null) {
|
||||
REQUEST_QUEUE = Volley.newRequestQueue(context);
|
||||
}
|
||||
return REQUEST_QUEUE;
|
||||
}
|
||||
|
||||
private static class LogResponseListener<T> implements Response.Listener<T> {
|
||||
|
||||
@Override
|
||||
public void onResponse(T response) {
|
||||
Timber.d(response.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class LogResponseErrorListener implements Response.ErrorListener {
|
||||
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
Timber.e(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class QueryRequest extends JsonRequest<QueryResponse> {
|
||||
|
||||
public QueryRequest(String url,
|
||||
Response.Listener<QueryResponse> listener,
|
||||
Response.ErrorListener errorListener) {
|
||||
super(Request.Method.GET, url, null, listener, errorListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response<QueryResponse> parseNetworkResponse(NetworkResponse response) {
|
||||
String json = parseString(response);
|
||||
QueryResponse queryResponse = GSON.fromJson(json, QueryResponse.class);
|
||||
return Response.success(queryResponse, cacheEntry(response));
|
||||
}
|
||||
|
||||
private Cache.Entry cacheEntry(NetworkResponse response) {
|
||||
return HttpHeaderParser.parseCacheHeaders(response);
|
||||
}
|
||||
|
||||
private String parseString(NetworkResponse response) {
|
||||
try {
|
||||
return new String(response.data, HttpHeaderParser.parseCharset(response.headers));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return new String(response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GpsCatExists {
|
||||
private static boolean gpsCatExists;
|
||||
|
||||
public static void setGpsCatExists(boolean gpsCat) {
|
||||
gpsCatExists = gpsCat;
|
||||
}
|
||||
|
||||
public static boolean getGpsCatExists() {
|
||||
return gpsCatExists;
|
||||
}
|
||||
}
|
||||
|
||||
private static class QueryResponse {
|
||||
private Query query = new Query();
|
||||
|
||||
private String printSet() {
|
||||
if (categorySet == null || categorySet.isEmpty()) {
|
||||
GpsCatExists.setGpsCatExists(false);
|
||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
||||
return "No collection of categories";
|
||||
} else {
|
||||
GpsCatExists.setGpsCatExists(true);
|
||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
||||
return "CATEGORIES FOUND" + categorySet.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (query != null) {
|
||||
return "query=" + query.toString() + "\n" + printSet();
|
||||
} else {
|
||||
return "No pages found";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Query {
|
||||
private Page [] pages;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("pages=" + "\n");
|
||||
if (pages != null) {
|
||||
for (Page page : pages) {
|
||||
builder.append(page.toString());
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.replace(builder.length() - 1, builder.length(), "");
|
||||
return builder.toString();
|
||||
} else {
|
||||
return "No pages found";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Page {
|
||||
private int pageid;
|
||||
private int ns;
|
||||
private String title;
|
||||
private Category[] categories;
|
||||
private Category category;
|
||||
|
||||
public Page() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuilder builder = new StringBuilder("PAGEID=" + pageid + " ns=" + ns + " title=" + title + "\n" + " CATEGORIES= ");
|
||||
|
||||
if (categories == null || categories.length == 0) {
|
||||
builder.append("no categories exist\n");
|
||||
} else {
|
||||
for (Category category : categories) {
|
||||
builder.append(category.toString());
|
||||
builder.append("\n");
|
||||
if (category != null) {
|
||||
String categoryString = category.toString().replace("Category:", "");
|
||||
categorySet.add(categoryString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
categoryList = new ArrayList<>(categorySet);
|
||||
builder.replace(builder.length() - 1, builder.length(), "");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Category {
|
||||
private String title;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
|
|
@ -73,8 +74,10 @@ import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
|||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import fr.free.nrw.commons.mwapi.CategoryApi;
|
||||
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
@ -123,8 +126,12 @@ public class ShareActivity
|
|||
@Inject
|
||||
ModifierSequenceDao modifierSequenceDao;
|
||||
@Inject
|
||||
CategoryApi apiCall;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
SharedPreferences prefs;
|
||||
@Inject
|
||||
GpsCategoryModel gpsCategoryModel;
|
||||
|
||||
private String source;
|
||||
private String mimeType;
|
||||
|
|
@ -685,8 +692,9 @@ public class ShareActivity
|
|||
|
||||
/**
|
||||
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
||||
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
|
||||
* Then initiates the calls to MediaWiki API through an instance of CategpryApi.
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
public void useImageCoords() {
|
||||
if (decimalCoords != null) {
|
||||
Timber.d("Decimal coords of image: %s", decimalCoords);
|
||||
|
|
@ -699,20 +707,27 @@ public class ShareActivity
|
|||
cacheController.setQtPoint(decLongitude, decLatitude);
|
||||
}
|
||||
|
||||
MwVolleyApi apiCall = new MwVolleyApi(this);
|
||||
|
||||
List<String> displayCatList = cacheController.findCategory();
|
||||
boolean catListEmpty = displayCatList.isEmpty();
|
||||
|
||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
||||
if (catListEmpty) {
|
||||
cacheFound = false;
|
||||
apiCall.request(decimalCoords);
|
||||
apiCall.request(decimalCoords)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(
|
||||
gpsCategoryModel::setCategoryList,
|
||||
throwable -> {
|
||||
Timber.e(throwable);
|
||||
gpsCategoryModel.clear();
|
||||
}
|
||||
);
|
||||
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
||||
} else {
|
||||
cacheFound = true;
|
||||
Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
|
||||
MwVolleyApi.setGpsCat(displayCatList);
|
||||
Timber.d("Cache found, setting categoryList in model to %s", displayCatList);
|
||||
gpsCategoryModel.setCategoryList(displayCatList);
|
||||
}
|
||||
}else{
|
||||
Timber.d("EXIF: no coords");
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import android.content.ContentProviderClient
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.support.v4.util.LruCache
|
||||
import com.google.gson.Gson
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.squareup.leakcanary.RefWatcher
|
||||
import fr.free.nrw.commons.auth.AccountUtil
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.caching.CacheController
|
||||
import fr.free.nrw.commons.data.DBOpenHelper
|
||||
import fr.free.nrw.commons.di.CommonsApplicationComponent
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
|
|
@ -33,21 +32,30 @@ class TestCommonsApplication : CommonsApplication() {
|
|||
override fun setupLeakCanary(): RefWatcher = RefWatcher.DISABLED
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModule(appContext) {
|
||||
val accountUtil: AccountUtil = mock()
|
||||
val appSharedPreferences: SharedPreferences = mock()
|
||||
val defaultSharedPreferences: SharedPreferences = mock()
|
||||
val categorySharedPreferences: SharedPreferences = mock()
|
||||
val otherSharedPreferences: SharedPreferences = mock()
|
||||
val uploadController: UploadController = mock()
|
||||
val mockSessionManager: SessionManager = mock()
|
||||
val mediaWikiApi: MediaWikiApi = mock()
|
||||
val locationServiceManager: LocationServiceManager = mock()
|
||||
val cacheController: CacheController = mock()
|
||||
val mockDbOpenHelper: DBOpenHelper = mock()
|
||||
val nearbyPlaces: NearbyPlaces = mock()
|
||||
val lruCache: LruCache<String, String> = mock()
|
||||
val gson: Gson = Gson()
|
||||
val categoryClient: ContentProviderClient = mock()
|
||||
val contributionClient: ContentProviderClient = mock()
|
||||
val modificationClient: ContentProviderClient = mock()
|
||||
val uploadPrefs: SharedPreferences = mock()
|
||||
|
||||
override fun provideCategoryContentProviderClient(context: Context?): ContentProviderClient = categoryClient
|
||||
|
||||
override fun provideContributionContentProviderClient(context: Context?): ContentProviderClient = contributionClient
|
||||
|
||||
override fun provideModificationContentProviderClient(context: Context?): ContentProviderClient = modificationClient
|
||||
|
||||
override fun providesDirectNearbyUploadPreferences(context: Context?): SharedPreferences = uploadPrefs
|
||||
|
||||
override fun providesAccountUtil(context: Context): AccountUtil = accountUtil
|
||||
|
||||
|
|
@ -61,12 +69,8 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu
|
|||
|
||||
override fun providesSessionManager(context: Context, mediaWikiApi: MediaWikiApi, sharedPreferences: SharedPreferences): SessionManager = mockSessionManager
|
||||
|
||||
override fun provideMediaWikiApi(context: Context, sharedPreferences: SharedPreferences, categorySharedPreferences: SharedPreferences, gson: Gson): MediaWikiApi = mediaWikiApi
|
||||
|
||||
override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager
|
||||
|
||||
override fun provideCacheController(): CacheController = cacheController
|
||||
|
||||
override fun provideDBOpenHelper(context: Context): DBOpenHelper = mockDbOpenHelper
|
||||
|
||||
override fun provideNearbyPlaces(): NearbyPlaces = nearbyPlaces
|
||||
|
|
|
|||
178
app/src/test/kotlin/fr/free/nrw/commons/mwapi/CategoryApiTest.kt
Normal file
178
app/src/test/kotlin/fr/free/nrw/commons/mwapi/CategoryApiTest.kt
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
package fr.free.nrw.commons.mwapi
|
||||
|
||||
import com.google.gson.Gson
|
||||
import fr.free.nrw.commons.mwapi.model.Page
|
||||
import fr.free.nrw.commons.mwapi.model.PageCategory
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class CategoryApiTest {
|
||||
private lateinit var server: MockWebServer
|
||||
private lateinit var url: String
|
||||
private lateinit var categoryApi: CategoryApi
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
server = MockWebServer()
|
||||
url = "http://${server.hostName}:${server.port}/"
|
||||
categoryApi = CategoryApi(OkHttpClient.Builder().build(), Gson(), HttpUrl.parse(url))
|
||||
}
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
server.shutdown()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun apiReturnsEmptyListWhenError() {
|
||||
server.enqueue(MockResponse().setResponseCode(400).setBody(""))
|
||||
|
||||
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun apiReturnsEmptyWhenTheresNoQuery() {
|
||||
server.success(emptyMap())
|
||||
|
||||
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun apiReturnsEmptyWhenQueryHasNoPages() {
|
||||
server.success(mapOf("query" to emptyMap<String, Any>()))
|
||||
|
||||
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun apiReturnsEmptyWhenQueryHasPagesButTheyreEmpty() {
|
||||
server.success(mapOf("query" to
|
||||
mapOf("pages" to emptyList<String>())))
|
||||
|
||||
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singlePageSingleCategory() {
|
||||
server.success(mapOf("query" to
|
||||
mapOf("pages" to listOf(
|
||||
page(listOf("one"))
|
||||
))))
|
||||
|
||||
val response = categoryApi.request("foo").blockingGet()
|
||||
|
||||
assertEquals(1, response.size)
|
||||
assertEquals("one", response[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiplePagesSingleCategory() {
|
||||
server.success(mapOf("query" to
|
||||
mapOf("pages" to listOf(
|
||||
page(listOf("one")),
|
||||
page(listOf("two"))
|
||||
))))
|
||||
|
||||
val response = categoryApi.request("foo").blockingGet()
|
||||
|
||||
assertEquals(2, response.size)
|
||||
assertEquals("one", response[0])
|
||||
assertEquals("two", response[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singlePageMultipleCategories() {
|
||||
server.success(mapOf("query" to
|
||||
mapOf("pages" to listOf(
|
||||
page(listOf("one", "two"))
|
||||
))))
|
||||
|
||||
val response = categoryApi.request("foo").blockingGet()
|
||||
|
||||
assertEquals(2, response.size)
|
||||
assertEquals("one", response[0])
|
||||
assertEquals("two", response[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiplePagesMultipleCategories() {
|
||||
server.success(mapOf("query" to
|
||||
mapOf("pages" to listOf(
|
||||
page(listOf("one", "two")),
|
||||
page(listOf("three", "four"))
|
||||
))))
|
||||
|
||||
val response = categoryApi.request("foo").blockingGet()
|
||||
|
||||
assertEquals(4, response.size)
|
||||
assertEquals("one", response[0])
|
||||
assertEquals("two", response[1])
|
||||
assertEquals("three", response[2])
|
||||
assertEquals("four", response[3])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiplePagesMultipleCategories_duplicatesRemoved() {
|
||||
server.success(mapOf("query" to
|
||||
mapOf("pages" to listOf(
|
||||
page(listOf("one", "two", "three")),
|
||||
page(listOf("three", "four", "one"))
|
||||
))))
|
||||
|
||||
val response = categoryApi.request("foo").blockingGet()
|
||||
|
||||
assertEquals(4, response.size)
|
||||
assertEquals("one", response[0])
|
||||
assertEquals("two", response[1])
|
||||
assertEquals("three", response[2])
|
||||
assertEquals("four", response[3])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requestSendsWhatWeExpect() {
|
||||
server.success(mapOf("query" to mapOf("pages" to emptyList<String>())))
|
||||
|
||||
val coords = "foo,bar"
|
||||
categoryApi.request(coords).blockingGet()
|
||||
|
||||
server.takeRequest().let { request ->
|
||||
assertEquals("GET", request.method)
|
||||
assertEquals("/w/api.php", request.requestUrl.encodedPath())
|
||||
request.requestUrl.let { url ->
|
||||
assertEquals("query", url.queryParameter("action"))
|
||||
assertEquals("categories|coordinates|pageprops", url.queryParameter("prop"))
|
||||
assertEquals("json", url.queryParameter("format"))
|
||||
assertEquals("!hidden", url.queryParameter("clshow"))
|
||||
assertEquals("type|name|dim|country|region|globe", url.queryParameter("coprop"))
|
||||
assertEquals(coords, url.queryParameter("codistancefrompoint"))
|
||||
assertEquals("geosearch", url.queryParameter("generator"))
|
||||
assertEquals(coords, url.queryParameter("ggscoord"))
|
||||
assertEquals("10000", url.queryParameter("ggsradius"))
|
||||
assertEquals("10", url.queryParameter("ggslimit"))
|
||||
assertEquals("6", url.queryParameter("ggsnamespace"))
|
||||
assertEquals("type|name|dim|country|region|globe", url.queryParameter("ggsprop"))
|
||||
assertEquals("all", url.queryParameter("ggsprimary"))
|
||||
assertEquals("2", url.queryParameter("formatversion"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun page(catList: List<String>) = Page().apply {
|
||||
categories = catList.map {
|
||||
PageCategory().apply {
|
||||
title = "Category:$it"
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
fun MockWebServer.success(response: Map<String, Any>) {
|
||||
enqueue(MockResponse().setResponseCode(200).setBody(Gson().toJson(response)))
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package fr.free.nrw.commons.mwapi.model
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class ApiResponseTest {
|
||||
@Test
|
||||
fun hasPages_whenQueryIsNull() {
|
||||
val response = ApiResponse()
|
||||
assertFalse(response.hasPages())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasPages_whenPagesIsNull() {
|
||||
val response = ApiResponse()
|
||||
response.query = Query()
|
||||
response.query.pages = null
|
||||
assertFalse(response.hasPages())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasPages_defaultsToSafeValue() {
|
||||
val response = ApiResponse()
|
||||
response.query = Query()
|
||||
assertNotNull(response.query.pages)
|
||||
assertTrue(response.hasPages())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package fr.free.nrw.commons.mwapi.model
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class PageCategoryTest {
|
||||
@Test
|
||||
fun stripPrefix_whenPresent() {
|
||||
val testObject = PageCategory()
|
||||
testObject.title = "Category:Foo"
|
||||
assertEquals("Foo", testObject.withoutPrefix())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stripPrefix_prefixAbsent() {
|
||||
val testObject = PageCategory()
|
||||
testObject.title = "Foo_Bar"
|
||||
assertEquals("Foo_Bar", testObject.withoutPrefix())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package fr.free.nrw.commons.mwapi.model
|
||||
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
|
||||
class PageTest {
|
||||
@Test
|
||||
fun categoriesDefaultToSafeValue() {
|
||||
val page = Page()
|
||||
assertNotNull(page.getCategories())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class GpsCategoryModelTest {
|
||||
|
||||
private lateinit var testObject : GpsCategoryModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
testObject = GpsCategoryModel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initiallyTheModelIsEmpty() {
|
||||
assertFalse(testObject.gpsCatExists)
|
||||
assertTrue(testObject.categoryList.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addingCategoriesToTheModel() {
|
||||
testObject.add("one")
|
||||
assertTrue(testObject.gpsCatExists)
|
||||
assertFalse(testObject.categoryList.isEmpty())
|
||||
assertEquals(listOf("one"), testObject.categoryList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun duplicatesAreIgnored() {
|
||||
testObject.add("one")
|
||||
testObject.add("one")
|
||||
assertEquals(listOf("one"), testObject.categoryList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun modelProtectsAgainstExternalModification() {
|
||||
testObject.add("one")
|
||||
|
||||
val list = testObject.categoryList
|
||||
list.add("two")
|
||||
|
||||
assertEquals(listOf("one"), testObject.categoryList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clearingTheModel() {
|
||||
testObject.add("one")
|
||||
|
||||
testObject.clear()
|
||||
assertFalse(testObject.gpsCatExists)
|
||||
assertTrue(testObject.categoryList.isEmpty())
|
||||
|
||||
testObject.add("two")
|
||||
assertEquals(listOf("two"), testObject.categoryList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingTheListHandlesNull() {
|
||||
testObject.add("one")
|
||||
|
||||
testObject.categoryList = null
|
||||
|
||||
assertFalse(testObject.gpsCatExists)
|
||||
assertTrue(testObject.categoryList.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setttingTheListOverwritesExistingValues() {
|
||||
testObject.add("one")
|
||||
|
||||
testObject.categoryList = listOf("two")
|
||||
|
||||
assertEquals(listOf("two"), testObject.categoryList)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue