forked from Mirroring/Kore
Implemented integration tests to test restoring search query
* Added perl tools to generate json files for music and movie data * Upgraded build tools version, sdk compile version, and support libraries as this is required by the test packages. * Added new product flavor instrumentationTest to make sure assets and settings required for testing do not also ship with a release version of Koremaster
parent
e338636bb7
commit
6f60442e7b
|
@ -38,6 +38,13 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
instrumentationTest {
|
||||
applicationId "org.xbmc.kore.instrumentationtest"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
// debug {
|
||||
// minifyEnabled true
|
||||
|
@ -94,5 +101,32 @@ dependencies {
|
|||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
||||
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
androidTestCompile 'com.android.support.test:rules:0.5'
|
||||
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-idling-resource:2.2.2'
|
||||
androidTestCompile 'com.android.support:support-v13:23.4.0'
|
||||
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
// Get the path to ADB. Required when running tests directly from Android Studio.
|
||||
// Source: http://stackoverflow.com/a/26771087/112705
|
||||
def adb = android.getAdbExe().toString()
|
||||
|
||||
// Source: http://stackoverflow.com/q/29908110/112705
|
||||
afterEvaluate {
|
||||
task grantAnimationPermissionDev(type: Exec, dependsOn: 'installInstrumentationTestDebug') {
|
||||
println("Executing: $adb shell pm grant $android.productFlavors.instrumentationTest.applicationId android.permission.SET_ANIMATION_SCALE")
|
||||
commandLine "$adb shell pm grant $android.productFlavors.instrumentationTest.applicationId android.permission.SET_ANIMATION_SCALE".split(' ')
|
||||
}
|
||||
|
||||
// When launching individual tests from Android Studio, it seems that only the assemble tasks
|
||||
// get called directly, not the install* versions
|
||||
tasks.each { task ->
|
||||
if (task.name.startsWith('assembleInstrumentationTestDebugAndroidTest')) {
|
||||
task.dependsOn grantAnimationPermissionDev
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package org.xbmc.kore;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
|
||||
import org.xbmc.kore.host.HostInfo;
|
||||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.jsonrpc.ApiException;
|
||||
import org.xbmc.kore.jsonrpc.method.AudioLibrary;
|
||||
import org.xbmc.kore.jsonrpc.method.VideoLibrary;
|
||||
import org.xbmc.kore.jsonrpc.type.AudioType;
|
||||
import org.xbmc.kore.jsonrpc.type.LibraryType;
|
||||
import org.xbmc.kore.jsonrpc.type.VideoType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaProvider;
|
||||
import org.xbmc.kore.service.library.SyncUtils;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Database {
|
||||
public static final String TAG = LogUtils.makeLogTag(Database.class);
|
||||
|
||||
public static HostInfo fill(Context context) throws ApiException, IOException {
|
||||
MediaProvider mediaProvider = new MediaProvider();
|
||||
mediaProvider.setContext(context);
|
||||
mediaProvider.onCreate();
|
||||
|
||||
HostInfo hostInfo = addHost(context);
|
||||
|
||||
insertMovies(context, hostInfo.getId());
|
||||
insertArtists(context, hostInfo.getId());
|
||||
insertGenres(context, hostInfo.getId());
|
||||
insertAlbums(context, hostInfo.getId());
|
||||
insertSongs(context, hostInfo.getId());
|
||||
|
||||
return hostInfo;
|
||||
}
|
||||
|
||||
public static void flush(Context context, HostInfo hostInfo) {
|
||||
context.getContentResolver()
|
||||
.delete(MediaContract.Hosts.buildHostUri(hostInfo.getId()), null, null);
|
||||
}
|
||||
|
||||
private static HostInfo addHost(Context context) {
|
||||
return HostManager.getInstance(context).addHost("TestHost", "127.0.0.1", 1, 80, 9090, null, null, "52:54:00:12:35:02", 9, false, 9777);
|
||||
}
|
||||
|
||||
private static void insertMovies(Context context, int hostId) throws ApiException, IOException {
|
||||
VideoLibrary.GetMovies getMovies = new VideoLibrary.GetMovies();
|
||||
String result = Utils.readFile(context, "Video.Details.Movie.json");
|
||||
ArrayList<VideoType.DetailsMovie> movieList = (ArrayList) getMovies.resultFromJson(result);
|
||||
|
||||
|
||||
ContentValues movieValuesBatch[] = new ContentValues[movieList.size()];
|
||||
int castCount = 0;
|
||||
|
||||
// Iterate on each movie
|
||||
for (int i = 0; i < movieList.size(); i++) {
|
||||
VideoType.DetailsMovie movie = movieList.get(i);
|
||||
movieValuesBatch[i] = SyncUtils.contentValuesFromMovie(hostId, movie);
|
||||
castCount += movie.cast.size();
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.Movies.CONTENT_URI, movieValuesBatch);
|
||||
|
||||
ContentValues movieCastValuesBatch[] = new ContentValues[castCount];
|
||||
int count = 0;
|
||||
// Iterate on each movie/cast
|
||||
for (VideoType.DetailsMovie movie : movieList) {
|
||||
for (VideoType.Cast cast : movie.cast) {
|
||||
movieCastValuesBatch[count] = SyncUtils.contentValuesFromCast(hostId, cast);
|
||||
movieCastValuesBatch[count].put(MediaContract.MovieCastColumns.MOVIEID, movie.movieid);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.MovieCast.CONTENT_URI, movieCastValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertArtists(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetArtists getArtists = new AudioLibrary.GetArtists(false);
|
||||
String result = Utils.readFile(context, "AudioLibrary.GetArtists.json");
|
||||
ArrayList<AudioType.DetailsArtist> artistList = (ArrayList) getArtists.resultFromJson(result).items;
|
||||
|
||||
ContentValues artistValuesBatch[] = new ContentValues[artistList.size()];
|
||||
for (int i = 0; i < artistList.size(); i++) {
|
||||
AudioType.DetailsArtist artist = artistList.get(i);
|
||||
artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist);
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertGenres(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetGenres getGenres = new AudioLibrary.GetGenres();
|
||||
ArrayList<LibraryType.DetailsGenre> genreList = (ArrayList) getGenres.resultFromJson(Utils.readFile(context, "AudioLibrary.GetGenres.json"));
|
||||
|
||||
ContentValues genresValuesBatch[] = new ContentValues[genreList.size()];
|
||||
for (int i = 0; i < genreList.size(); i++) {
|
||||
LibraryType.DetailsGenre genre = genreList.get(i);
|
||||
genresValuesBatch[i] = SyncUtils.contentValuesFromAudioGenre(hostId, genre);
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.AudioGenres.CONTENT_URI, genresValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertAlbums(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetAlbums getAlbums = new AudioLibrary.GetAlbums();
|
||||
String result = Utils.readFile(context, "AudioLibrary.GetAlbums.json");
|
||||
ArrayList<AudioType.DetailsAlbum> albumList = (ArrayList) getAlbums.resultFromJson(result).items;
|
||||
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
ContentValues albumValuesBatch[] = new ContentValues[albumList.size()];
|
||||
int artistsCount = 0, genresCount = 0;
|
||||
for (int i = 0; i < albumList.size(); i++) {
|
||||
AudioType.DetailsAlbum album = albumList.get(i);
|
||||
albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album);
|
||||
|
||||
artistsCount += album.artistid.size();
|
||||
genresCount += album.genreid.size();
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch);
|
||||
|
||||
// Iterate on each album, collect the artists and the genres and insert them
|
||||
ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount];
|
||||
ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount];
|
||||
int artistCount = 0, genreCount = 0;
|
||||
for (AudioType.DetailsAlbum album : albumList) {
|
||||
for (int artistId : album.artistid) {
|
||||
albumArtistsValuesBatch[artistCount] = new ContentValues();
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId);
|
||||
artistCount++;
|
||||
}
|
||||
|
||||
for (int genreId : album.genreid) {
|
||||
albumGenresValuesBatch[genreCount] = new ContentValues();
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId);
|
||||
genreCount++;
|
||||
}
|
||||
}
|
||||
|
||||
contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch);
|
||||
contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertSongs(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetSongs getSongs = new AudioLibrary.GetSongs();
|
||||
ArrayList<AudioType.DetailsSong> songList = (ArrayList) getSongs.resultFromJson(Utils.readFile(context, "AudioLibrary.GetSongs.json")).items;
|
||||
|
||||
ContentValues songValuesBatch[] = new ContentValues[songList.size()];
|
||||
for (int i = 0; i < songList.size(); i++) {
|
||||
AudioType.DetailsSong song = songList.get(i);
|
||||
songValuesBatch[i] = SyncUtils.contentValuesFromSong(hostId, song);
|
||||
}
|
||||
context.getContentResolver().bulkInsert(MediaContract.Songs.CONTENT_URI, songValuesBatch);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.espresso.NoMatchingViewException;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
import org.xbmc.kore.R;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onData;
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
|
||||
import static android.support.test.espresso.action.ViewActions.clearText;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
public class EspressoTestUtils {
|
||||
|
||||
public static void rotateDevice(Activity activity) {
|
||||
int orientation
|
||||
= activity.getResources().getConfiguration().orientation;
|
||||
activity.setRequestedOrientation(
|
||||
(orientation == Configuration.ORIENTATION_PORTRAIT) ?
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks a menu item regardless if it is in the overflow menu or
|
||||
* visible as icon in the action bar
|
||||
* @param activity
|
||||
* @param name Name of the menu item in the overflow menu
|
||||
* @param resourceId Resource identifier of the menu item
|
||||
*/
|
||||
public static void clickMenuItem(Activity activity, String name, int resourceId) {
|
||||
try {
|
||||
onView(withId(resourceId)).perform(click());
|
||||
} catch (NoMatchingViewException e) {
|
||||
openActionBarOverflowOrOptionsMenu(activity);
|
||||
//Use onData as item might not be visible in the View without scrolling
|
||||
onData(allOf(
|
||||
Matchers.withMenuTitle(name)))
|
||||
.perform(click());
|
||||
}
|
||||
}
|
||||
|
||||
public static void clickHomeButton() {
|
||||
onView(withId(android.R.id.home)).perform(click());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the search menu item and enters the given search query
|
||||
* @param activity
|
||||
* @param query
|
||||
*/
|
||||
public static void enterSearchQuery(Activity activity, String query) {
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class))
|
||||
.perform(click(), typeText(query));
|
||||
|
||||
Espresso.closeSoftKeyboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the search menu item and clears the search query by entering the empty string
|
||||
* @param activity
|
||||
*/
|
||||
public static void clearSearchQuery(Activity activity) {
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class))
|
||||
.perform(click(), clearText());
|
||||
|
||||
Espresso.closeSoftKeyboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the search query by pressing the X button
|
||||
* @param activity
|
||||
*/
|
||||
public static void clearSearchQueryXButton(Activity activity) {
|
||||
try {
|
||||
onView(withId(R.id.search_close_btn)).perform(click());
|
||||
} catch (NoMatchingViewException e) {
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
onView(withId(R.id.search_close_btn)).perform(click());
|
||||
}
|
||||
Espresso.closeSoftKeyboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a click on an item in an adapter view, such as GridView or ListView
|
||||
* @param position
|
||||
* @param resourceId
|
||||
*/
|
||||
public static void clickAdapterViewItem(int position, int resourceId) {
|
||||
onData(anything()).inAdapterView(allOf(withId(resourceId), isDisplayed()))
|
||||
.atPosition(position).perform(click());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that SearchView contains the given text
|
||||
* @param query text that SearchView should contain
|
||||
*/
|
||||
public static void checkTextInSearchQuery(String query) {
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class)).check(matches(withText(query)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the list contains item(s) matching search query
|
||||
* @param query text each element must contain
|
||||
* @param listSize amount of elements expected in list
|
||||
*/
|
||||
public static void checkListMatchesSearchQuery(String query, int listSize, int resourceId) {
|
||||
onView(allOf(withId(resourceId), isDisplayed()))
|
||||
.check(matches(Matchers.withOnlyMatchingDataItems(Matchers.withItemContent(containsString(query)))));
|
||||
onView(allOf(withId(resourceId), isDisplayed()))
|
||||
.check(matches(Matchers.withAdapterSize(listSize)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if search action view does not exist in the current view hierarchy
|
||||
*/
|
||||
public static void checkSearchMenuCollapsed() {
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class)).check(doesNotExist());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.support.test.espresso.IdlingResource;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
|
||||
public class LoaderIdlingResource implements IdlingResource {
|
||||
|
||||
private ResourceCallback mResourceCallback;
|
||||
private LoaderManager loaderManager;
|
||||
|
||||
public LoaderIdlingResource(LoaderManager loaderManager) {
|
||||
this.loaderManager = loaderManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return LoaderIdlingResource.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdleNow() {
|
||||
boolean idle = !loaderManager.hasRunningLoaders();
|
||||
if (idle && mResourceCallback != null) {
|
||||
mResourceCallback.onTransitionToIdle();
|
||||
}
|
||||
return idle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
|
||||
mResourceCallback = resourceCallback;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.support.test.espresso.matcher.BoundedMatcher;
|
||||
import android.support.test.espresso.matcher.CursorMatchers;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
public class Matchers {
|
||||
public static MenuItemTitleMatcher withMenuTitle(String title) {
|
||||
return new MenuItemTitleMatcher(title);
|
||||
}
|
||||
|
||||
public static class MenuItemTitleMatcher extends BaseMatcher<Object> {
|
||||
private final String title;
|
||||
public MenuItemTitleMatcher(String title) { this.title = title; }
|
||||
|
||||
@Override
|
||||
public boolean matches(Object o) {
|
||||
if (o instanceof MenuItem) {
|
||||
return ((MenuItem) o).getTitle().equals(title);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void describeTo(Description description) { }
|
||||
}
|
||||
|
||||
public static Matcher<View> withListSize(final int size) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override public boolean matchesSafely(final View view) {
|
||||
if (!(view instanceof ViewGroup))
|
||||
return false;
|
||||
|
||||
return ((ViewGroup) view).getChildCount() == size;
|
||||
}
|
||||
|
||||
@Override public void describeTo(final Description description) {
|
||||
description.appendText("List should have " + size + " item(s)");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<View> withAdapterSize(final int size) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(View view) {
|
||||
if (!(view instanceof AdapterView))
|
||||
return false;
|
||||
|
||||
return ((AdapterView) view).getCount() == size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Adapter should have " + size + " item(s)");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<View> withOnlyMatchingDataItems(final Matcher<Object> dataMatcher) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(View view) {
|
||||
if (!(view instanceof AdapterView))
|
||||
return false;
|
||||
|
||||
Adapter adapter = ((AdapterView) view).getAdapter();
|
||||
for (int i = 0; i < adapter.getCount(); i++) {
|
||||
if (! dataMatcher.matches(adapter.getItem(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("withOnlyMatchingDataItems: ");
|
||||
dataMatcher.describeTo(description);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<Object> withItemContent(final Matcher<String> textMatcher) {
|
||||
return new BoundedMatcher<Object, Cursor>(Cursor.class) {
|
||||
@Override
|
||||
protected boolean matchesSafely(Cursor item) {
|
||||
for (int i = 0; i < item.getColumnCount();i++) {
|
||||
switch (item.getType(i)) {
|
||||
case Cursor.FIELD_TYPE_STRING:
|
||||
if (CursorMatchers.withRowString(i, textMatcher).matches(item))
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("withItemContent: ");
|
||||
textMatcher.describeTo(description);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xbmc.kore.R;
|
||||
import org.xbmc.kore.host.HostInfo;
|
||||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = LogUtils.makeLogTag(Utils.class);
|
||||
|
||||
private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
|
||||
private static final float DISABLED = 0.0f;
|
||||
private static final float DEFAULT = 1.0f;
|
||||
|
||||
private static boolean isInitialized;
|
||||
|
||||
private static HostInfo hostInfo;
|
||||
private static Context context;
|
||||
|
||||
public static String readFile(Context context, String filename) throws IOException {
|
||||
InputStream is = context.getAssets().open(filename);
|
||||
|
||||
int size = is.available();
|
||||
|
||||
byte[] buffer = new byte[size];
|
||||
|
||||
is.read(buffer);
|
||||
|
||||
is.close();
|
||||
|
||||
return new String(buffer, "UTF-8");
|
||||
}
|
||||
|
||||
public static void closeDrawer(final ActivityTestRule<?> activityTestRule) throws Throwable {
|
||||
activityTestRule.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DrawerLayout drawerLayout = (DrawerLayout) activityTestRule.getActivity().findViewById(R.id.drawer_layout);
|
||||
drawerLayout.closeDrawers();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void initialize(ActivityTestRule<?> activityTestRule) throws Throwable {
|
||||
if (isInitialized)
|
||||
return;
|
||||
|
||||
context = activityTestRule.getActivity();
|
||||
|
||||
disableAnimations();
|
||||
|
||||
hostInfo = Database.fill(context);
|
||||
|
||||
HostManager.getInstance(context).switchHost(hostInfo);
|
||||
Utils.closeDrawer(activityTestRule);
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
public static void cleanup() {
|
||||
Database.flush(context, hostInfo);
|
||||
|
||||
enableAnimations();
|
||||
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
private static void disableAnimations() {
|
||||
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
|
||||
if (permStatus == PackageManager.PERMISSION_GRANTED) {
|
||||
setSystemAnimationsScale(DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
private static void enableAnimations() {
|
||||
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
|
||||
if (permStatus == PackageManager.PERMISSION_GRANTED) {
|
||||
setSystemAnimationsScale(DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setSystemAnimationsScale(float animationScale) {
|
||||
try {
|
||||
Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
|
||||
Method asInterface = windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
|
||||
Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
|
||||
Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
|
||||
Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
|
||||
Method setAnimationScales = windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
|
||||
Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");
|
||||
|
||||
IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
|
||||
Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
|
||||
float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
|
||||
for (int i = 0; i < currentScales.length; i++) {
|
||||
currentScales[i] = animationScale;
|
||||
}
|
||||
setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
|
||||
} catch (Exception e) {
|
||||
Log.e("SystemAnimations", "Could not change animation scale to " + animationScale + " :'(");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.tests.ui;
|
||||
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xbmc.kore.R;
|
||||
import org.xbmc.kore.testhelpers.EspressoTestUtils;
|
||||
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
|
||||
import org.xbmc.kore.testhelpers.Utils;
|
||||
import org.xbmc.kore.ui.MoviesActivity;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class RestoreSearchQueryListFragmentTest {
|
||||
|
||||
private final String SEARCH_QUERY = "Room";
|
||||
private final int SEARCH_QUERY_LIST_SIZE = 2;
|
||||
private final int COMPLETE_LIST_SIZE = 300;
|
||||
|
||||
private LoaderIdlingResource loaderIdlingResource;
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MoviesActivity> mActivityRule = new ActivityTestRule<>(
|
||||
MoviesActivity.class);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Throwable {
|
||||
Utils.initialize(mActivityRule);
|
||||
loaderIdlingResource = new LoaderIdlingResource(mActivityRule.getActivity().getSupportLoaderManager());
|
||||
Espresso.registerIdlingResources(loaderIdlingResource);
|
||||
Utils.closeDrawer(mActivityRule);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Espresso.unregisterIdlingResources(loaderIdlingResource);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() {
|
||||
Utils.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query results in expected item(s)
|
||||
*/
|
||||
@Test
|
||||
public void simpleSearchTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query is restored after device rotate
|
||||
*/
|
||||
@Test
|
||||
public void simpleRotateTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Press back
|
||||
* 4. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackTest() {
|
||||
EspressoTestUtils.clearSearchQuery(mActivityRule.getActivity());
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment when device is rotated while on detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Rotate device
|
||||
* 4. Press back
|
||||
* 5. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickRotateBackTest() {
|
||||
EspressoTestUtils.clearSearchQuery(mActivityRule.getActivity());
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if saved search query is cleared when user clears the
|
||||
* search query view
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Return to list
|
||||
* 4. Clear search query
|
||||
* 5. Click on list item
|
||||
* 6. Return to list
|
||||
* 7. Result: search query should be empty and collapsed
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackClearSearchClickBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.clearSearchQuery(mActivityRule.getActivity());
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if after restoring search query the search query is cleared
|
||||
* when user presses back again.
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Return to list
|
||||
* 4. Press back
|
||||
* 7. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if pressing back clears a previous search
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Press back
|
||||
* 3. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
public void searchBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if after restoring the search query pressing home button up clears a previous search
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Press back
|
||||
* 4. Press home button
|
||||
* 5. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
public void searchClickBackUpTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.clickHomeButton();
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if pressing home button up clears a previous search
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Press home button
|
||||
* 3. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
public void searchUpTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.clickHomeButton();
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.tests.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xbmc.kore.R;
|
||||
import org.xbmc.kore.testhelpers.EspressoTestUtils;
|
||||
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
|
||||
import org.xbmc.kore.testhelpers.Utils;
|
||||
import org.xbmc.kore.ui.MusicActivity;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.action.ViewActions.swipeLeft;
|
||||
import static android.support.test.espresso.action.ViewActions.swipeRight;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class RestoreSearchQueryViewPagerTest {
|
||||
|
||||
private final String ARTIST_SEARCH_QUERY = "Ben";
|
||||
private final int ARTIST_SEARCH_QUERY_LIST_SIZE = 2;
|
||||
private final String ALBUMS_SEARCH_QUERY = "tes";
|
||||
private final int ALBUM_SEARCH_QUERY_LIST_SIZE = 3;
|
||||
private final int ARTIST_COMPLETE_LIST_SIZE = 224;
|
||||
private final int ALBUM_COMPLETE_LIST_SIZE = 231;
|
||||
|
||||
private LoaderIdlingResource loaderIdlingResource;
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MusicActivity> mActivityRule = new ActivityTestRule<>(
|
||||
MusicActivity.class);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Throwable {
|
||||
Utils.initialize(mActivityRule);
|
||||
loaderIdlingResource = new LoaderIdlingResource(mActivityRule.getActivity().getSupportLoaderManager());
|
||||
Espresso.registerIdlingResources(loaderIdlingResource);
|
||||
Utils.closeDrawer(mActivityRule);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Espresso.unregisterIdlingResources(loaderIdlingResource);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() {
|
||||
Utils.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query results in expected item(s)
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void simpleSearchTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query is restored after device rotate
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Rotate device
|
||||
* 3. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void simpleRotateTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Press back
|
||||
* 4. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment when device is rotated while on detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Rotate device
|
||||
* 4. Press back
|
||||
* 5. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickRotateBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is cleared when switching to
|
||||
* different tab in the TabAdapter
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Result: search query should be cleared
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabTest() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search query is still cleared when
|
||||
* device is rotated after switching to a different tab
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Rotate device
|
||||
* 4. Open search menu item
|
||||
* 5. Result: search query should be cleared
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabRotateTest() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
EspressoTestUtils.rotateDevice(activity);
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", ALBUM_COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search query is restored when returning
|
||||
* to the original tab
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Switch to Artists tab
|
||||
* 4. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabReturnTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tests if search query is restored when returning
|
||||
* to the original tab after switching to a different
|
||||
* tab and rotating the device
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Rotated device
|
||||
* 4. Switch to Artists tab
|
||||
* 5. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabRotateReturnTest() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
EspressoTestUtils.rotateDevice(activity);
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search query is still cleared when user clears a previous
|
||||
* search query and switches to a different tab and returns to the
|
||||
* original tab
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Clear search query
|
||||
* 3. Switch to Albums tab
|
||||
* 4. Switch to Artists tab
|
||||
* 5. Click search menu item
|
||||
* 6. Result: search query should be cleared and list should contain all items
|
||||
*/
|
||||
@Test
|
||||
public void searchClearSwitchTabSwitchBack() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.clearSearchQuery(activity);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", ARTIST_COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same test as {@link #searchClearSwitchTabSwitchBack()} but this time clearing performed using X button
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Clear search query
|
||||
* 3. Switch to Albums tab
|
||||
* 4. Switch to Artists tab
|
||||
* 5. Click search menu item using X button
|
||||
* 6. Result: search query should be cleared and list should contain all items
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabSwitchBackClearUsingXButtonSwitchTabSwitchBack() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
EspressoTestUtils.clearSearchQueryXButton(activity);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", ARTIST_COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search queries for separate tabs are restored correctly
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query artists tab
|
||||
* 2. Enter search query albums tab
|
||||
* 3. Switch to Artists tab
|
||||
* 4. Result: search query entered at 1. should show in search field and list should match search query
|
||||
* 5. Switch to Albums tab
|
||||
* 6. Result: search query entered at 2. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void searchArtistsSearchAlbumsSwitchArtists() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
EspressoTestUtils.enterSearchQuery(activity, ALBUMS_SEARCH_QUERY);
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
|
||||
clickAlbumsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ALBUMS_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ALBUMS_SEARCH_QUERY, ALBUM_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
private void clickAlbumsTab() {
|
||||
onView(withId(R.id.pager_tab_strip)).perform(swipeLeft());
|
||||
onView(withText(R.string.albums)).perform(click());
|
||||
}
|
||||
|
||||
private void clickArtistsTab() {
|
||||
onView(withId(R.id.pager_tab_strip)).perform(swipeRight());
|
||||
onView(withText(R.string.artists)).perform(click());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.xbmc.kore">
|
||||
|
||||
<!-- For espresso testing purposes, this is removed in live builds, but not in dev builds -->
|
||||
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
|
||||
|
||||
</manifest>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"id" : "libGenres",
|
||||
"result" : {
|
||||
"limits" : {
|
||||
"total" : 39,
|
||||
"start" : 0,
|
||||
"end" : 39
|
||||
},
|
||||
"genres" : [
|
||||
{
|
||||
"genreid" : 9,
|
||||
"title" : "Ambient",
|
||||
"label" : "Ambient",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"label" : "Bluegrass",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 33,
|
||||
"title" : "Bluegrass"
|
||||
},
|
||||
{
|
||||
"title" : "Blues",
|
||||
"genreid" : 1,
|
||||
"thumbnail" : "",
|
||||
"label" : "Blues"
|
||||
},
|
||||
{
|
||||
"label" : "Blues Compilation",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 35,
|
||||
"title" : "Blues Compilation"
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Brutal Death Metal",
|
||||
"title" : "Brutal Death Metal",
|
||||
"genreid" : 32
|
||||
},
|
||||
{
|
||||
"title" : "Celtic",
|
||||
"genreid" : 24,
|
||||
"thumbnail" : "",
|
||||
"label" : "Celtic"
|
||||
},
|
||||
{
|
||||
"genreid" : 11,
|
||||
"title" : "Classic for Kids",
|
||||
"label" : "Classic for Kids",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Classical",
|
||||
"title" : "Classical",
|
||||
"genreid" : 3
|
||||
},
|
||||
{
|
||||
"genreid" : 13,
|
||||
"title" : "Country",
|
||||
"label" : "Country",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"title" : "Dancehall",
|
||||
"genreid" : 31,
|
||||
"thumbnail" : "",
|
||||
"label" : "Dancehall"
|
||||
},
|
||||
{
|
||||
"title" : "Easy Listening",
|
||||
"genreid" : 20,
|
||||
"thumbnail" : "",
|
||||
"label" : "Easy Listening"
|
||||
},
|
||||
{
|
||||
"title" : "Folk",
|
||||
"genreid" : 4,
|
||||
"thumbnail" : "",
|
||||
"label" : "Folk"
|
||||
},
|
||||
{
|
||||
"label" : "Folklore",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 22,
|
||||
"title" : "Folklore"
|
||||
},
|
||||
{
|
||||
"label" : "Goregrind",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 14,
|
||||
"title" : "Goregrind"
|
||||
},
|
||||
{
|
||||
"genreid" : 8,
|
||||
"title" : "Grind Core",
|
||||
"label" : "Grind Core",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"title" : "Grindcore",
|
||||
"genreid" : 18,
|
||||
"thumbnail" : "",
|
||||
"label" : "Grindcore"
|
||||
},
|
||||
{
|
||||
"label" : "Hardcore",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 12,
|
||||
"title" : "Hardcore"
|
||||
},
|
||||
{
|
||||
"label" : "Hardcore Thrash",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 30,
|
||||
"title" : "Hardcore Thrash"
|
||||
},
|
||||
{
|
||||
"title" : "Hip-Hop",
|
||||
"genreid" : 17,
|
||||
"thumbnail" : "",
|
||||
"label" : "Hip-Hop"
|
||||
},
|
||||
{
|
||||
"title" : "Instrumental",
|
||||
"genreid" : 39,
|
||||
"thumbnail" : "",
|
||||
"label" : "Instrumental"
|
||||
},
|
||||
{
|
||||
"genreid" : 5,
|
||||
"title" : "Jazz",
|
||||
"label" : "Jazz",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"label" : "Jazz+Funk",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 21,
|
||||
"title" : "Jazz+Funk"
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Metal",
|
||||
"title" : "Metal",
|
||||
"genreid" : 10
|
||||
},
|
||||
{
|
||||
"label" : "Oldies",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 36,
|
||||
"title" : "Oldies"
|
||||
},
|
||||
{
|
||||
"genreid" : 23,
|
||||
"title" : "Other",
|
||||
"label" : "Other",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"title" : "Pop",
|
||||
"genreid" : 25,
|
||||
"thumbnail" : "",
|
||||
"label" : "Pop"
|
||||