Commit 7e12d25b authored by Shivansh Saini's avatar Shivansh Saini

UI test: StorageBrowser and filtering in FileBrowser

Signed-off-by: Shivansh Saini's avatarShivansh Saini <shivanshs9@gmail.com>
parent 3ec8d068
package org.videolan.vlc
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewGroup
......@@ -10,13 +11,24 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.matcher.BoundedMatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.hamcrest.BaseMatcher
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.gui.browser.BaseBrowserAdapter
import org.videolan.vlc.gui.helpers.SelectorViewHolder
import org.hamcrest.BaseMatcher
import org.videolan.vlc.gui.helpers.ThreeStatesCheckbox
import android.graphics.drawable.BitmapDrawable
import android.graphics.Bitmap
import android.graphics.drawable.StateListDrawable
import android.graphics.drawable.Drawable
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.appcompat.view.menu.ActionMenuItemView
......@@ -155,7 +167,7 @@ fun sizeOfAtLeast(minSize: Int): Matcher<in View> {
fun withCount(matcher: Matcher<Int>): Matcher<in View> {
return object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("Recycler view has count with ${matcher.describeTo(description)}")
description.appendText("Recycler view has count with $matcher")
}
override fun matchesSafely(view: View): Boolean {
......@@ -163,3 +175,88 @@ fun withCount(matcher: Matcher<Int>): Matcher<in View> {
}
}
}
fun withCheckBoxState(state: Matcher<Int>): Matcher<in View> {
return object : TypeSafeMatcher<ThreeStatesCheckbox>() {
override fun describeTo(description: Description) {
description.appendText("checkbox with state $state")
}
override fun matchesSafely(item: ThreeStatesCheckbox?): Boolean = state.matches(item?.state)
} as Matcher<in View>
}
/*
Taken from https://gist.github.com/frankiesardo/7490059
*/
fun withBackground(resourceId: Int): Matcher<View> {
return object : TypeSafeMatcher<View>() {
public override fun matchesSafely(view: View): Boolean {
return sameBitmap(view.context, view.background, resourceId)
}
override fun describeTo(description: Description) {
description.appendText("has background resource $resourceId")
}
}
}
fun withCompoundDrawable(resourceId: Int): Matcher<View> {
return object : BoundedMatcher<View, TextView>(TextView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has compound drawable resource $resourceId")
}
public override fun matchesSafely(textView: TextView): Boolean {
for (drawable in textView.compoundDrawables) {
if (sameBitmap(textView.context, drawable, resourceId)) {
return true
}
}
return false
}
}
}
fun withImageDrawable(resourceId: Int): Matcher<View> {
return object : BoundedMatcher<View, ImageView>(ImageView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has image drawable resource $resourceId")
}
public override fun matchesSafely(imageView: ImageView): Boolean {
return sameBitmap(imageView.context, imageView.drawable, resourceId)
}
}
}
private fun sameBitmap(context: Context, drawable: Drawable?, resourceId: Int): Boolean {
var drawable = drawable
var otherDrawable = context.resources.getDrawable(resourceId)
if (drawable == null || otherDrawable == null) {
return false
}
if (drawable is StateListDrawable && otherDrawable is StateListDrawable) {
drawable = drawable.current
otherDrawable = otherDrawable.current
}
if (drawable is BitmapDrawable) {
val bitmap = drawable.bitmap
val otherBitmap = (otherDrawable as BitmapDrawable).bitmap
return bitmap.sameAs(otherBitmap)
}
return false
}
fun withActionIconDrawable(@DrawableRes resourceId: Int): Matcher<View> {
return object : BoundedMatcher<View, ActionMenuItemView>(ActionMenuItemView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has image drawable resource $resourceId")
}
public override fun matchesSafely(actionMenuItemView: ActionMenuItemView): Boolean {
return sameBitmap(actionMenuItemView.context, actionMenuItemView.itemData.icon, resourceId)
}
}
}
......@@ -2,6 +2,8 @@ package org.videolan.vlc.gui.browser
import android.content.Intent
import android.view.View
import android.widget.AutoCompleteTextView
import android.widget.SearchView
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
......@@ -9,6 +11,7 @@ import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.*
import androidx.test.espresso.contrib.RecyclerViewActions.*
import androidx.test.espresso.matcher.RootMatchers.isFocusable
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.rule.ActivityTestRule
......@@ -29,6 +32,7 @@ import org.videolan.vlc.gui.MainActivity
import org.videolan.vlc.gui.helpers.SelectorViewHolder
import org.videolan.vlc.util.EXTRA_TARGET
import org.videolan.vlc.util.Settings
import java.lang.Thread.sleep
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
......@@ -256,7 +260,7 @@ class FileBrowserFragmentUITest : BaseUITest() {
onView(withId(R.id.ctx_list))
.check(matches(isDisplayed()))
.check(matches(sizeOfAtLeast(3)))
.check(matches(withCount(equalTo(3))))
.check(matches(hasDescendant(withText(R.string.play))))
.check(matches(hasDescendant(withText(R.string.favorites_add))))
.check(matches(hasDescendant(withText(R.string.delete))))
......@@ -269,8 +273,8 @@ class FileBrowserFragmentUITest : BaseUITest() {
)).perform(click())
onView(allOf(
withId(R.id.item_more), isDescendantOfA(MediaRecyclerViewMatcher<SelectorViewHolder<ViewDataBinding>>(R.id.network_list).atGivenType(AbstractMediaWrapper.TYPE_DIR)), firstView()
)).perform(click())
MediaRecyclerViewMatcher<SelectorViewHolder<ViewDataBinding>>(R.id.network_list).atGivenType(AbstractMediaWrapper.TYPE_VIDEO), firstView()
)).perform(longClick())
onView(withId(R.id.action_mode_file_info))
.check(matches(isDisplayed()))
......@@ -296,7 +300,7 @@ class FileBrowserFragmentUITest : BaseUITest() {
onView(withId(R.id.ctx_list))
.check(matches(isDisplayed()))
.check(matches(sizeOfAtLeast(7)))
.check(matches(withCount(equalTo(7))))
.check(matches(hasDescendant(withText(R.string.play_all))))
.check(matches(hasDescendant(withText(R.string.play_as_audio))))
.check(matches(hasDescendant(withText(R.string.append))))
......@@ -307,10 +311,12 @@ class FileBrowserFragmentUITest : BaseUITest() {
}
@Test
fun whenAtInternalStorageAndContainsUnknownFile_checkShownOnlyIfSettingIsTrue() {
onView(withRecyclerView(R.id.network_list).atPosition(1)).perform(click())
fun whenAtInternalStorageAndContainsUnknownFile_checkShownIfSettingIsTrue() {
Settings.getInstance(context).edit()
.putBoolean("browser_show_all_files", true)
.commit()
val showAllFiles = Settings.getInstance(context).getBoolean("browser_show_all_files", true)
onView(withRecyclerView(R.id.network_list).atPosition(1)).perform(click())
val rvMatcher = withRecyclerView(R.id.network_list)
onView(rvMatcher.atPosition(0))
......@@ -319,15 +325,32 @@ class FileBrowserFragmentUITest : BaseUITest() {
val adapter = rvMatcher.recyclerView?.adapter as? DiffUtilAdapter<MediaLibraryItem, RecyclerView.ViewHolder>
val pos = findFirstPosition(adapter, withMediaType(AbstractMediaWrapper.TYPE_ALL))
if (showAllFiles) {
assertThat(pos, not(equalTo(-1)))
onView(withId(R.id.network_list))
.perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(pos, longClick()))
assertThat(pos, not(equalTo(-1)))
assertThat(activity.supportFragmentManager.findFragmentByTag("context"), notNullValue())
} else {
assertThat(pos, equalTo(-1))
}
// TODO: Fails because it doesn't scroll completely as required
onView(withId(R.id.network_list))
.perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(pos, longClick())) // FAILED
assertThat(activity.supportFragmentManager.findFragmentByTag("context"), notNullValue())
}
@Test
fun whenAtInternalStorageAndContainsUnknownFile_checkNotShownIfSettingIsFalse() {
// TODO: Fails, because this preference value doesn't get reflected in the provider
Settings.getInstance(context).edit()
.putBoolean("browser_show_all_files", false)
.commit()
onView(withRecyclerView(R.id.network_list).atPosition(1)).perform(click())
val rvMatcher = withRecyclerView(R.id.network_list)
onView(rvMatcher.atPosition(0))
.check(matches(isDisplayed()))
val adapter = rvMatcher.recyclerView?.adapter as? DiffUtilAdapter<MediaLibraryItem, RecyclerView.ViewHolder>
val pos = findFirstPosition(adapter, withMediaType(AbstractMediaWrapper.TYPE_ALL))
assertThat(pos, equalTo(-1)) // FAILED
}
@Test
......@@ -370,4 +393,70 @@ class FileBrowserFragmentUITest : BaseUITest() {
onView(rvMatcher.atPosition(1)).check(matches(not(withBgColor(context.getColor(R.color.orange200transparent)))))
onView(rvMatcher.atPosition(3)).check(matches(not(withBgColor(context.getColor(R.color.orange200transparent)))))
}
@Test
fun whenAtSomeFolderAndFavorited_checkQuickAccessIsUpdatedAtRoot() {
val rvMatcher = withRecyclerView(R.id.network_list)
onView(rvMatcher.atPosition(1))
.check(matches(isDisplayed()))
val oldCount = rvMatcher.recyclerView?.adapter?.itemCount ?: 0
onView(withRecyclerView(R.id.network_list).atPosition(1)).perform(click())
onView(withRecyclerView(R.id.network_list).atPosition(0)).perform(click())
onView(withId(R.id.ml_menu_save))
.check(matches(withActionIconDrawable(R.drawable.ic_menu_bookmark_outline_w)))
.perform(click())
.check(matches(withActionIconDrawable(R.drawable.ic_menu_bookmark_w)))
onView(isRoot()).perform(pressBack())
onView(isRoot()).perform(pressBack())
onView(withId(R.id.network_list))
.check(matches(withCount(equalTo(oldCount + 1))))
}
@Test
fun whenAtSomeQuickAccessFolderAndDefavorited_checkQuickAccessIsUpdatedAtRoot() {
val rvMatcher = withRecyclerView(R.id.network_list)
onView(rvMatcher.atPosition(1))
.check(matches(isDisplayed()))
val oldCount = rvMatcher.recyclerView?.adapter?.itemCount ?: 0
onView(withRecyclerView(R.id.network_list).atPosition(3)).perform(click())
onView(withId(R.id.ml_menu_save))
.check(matches(withActionIconDrawable(R.drawable.ic_menu_bookmark_w)))
.perform(click())
.check(matches(withActionIconDrawable(R.drawable.ic_menu_bookmark_outline_w)))
onView(isRoot()).perform(pressBack())
onView(withId(R.id.network_list))
.check(matches(withCount(equalTo(oldCount - 1))))
}
@Test
fun whenAtSomeFolderAndFiltered_checkItemsAreFiltered() {
onView(withRecyclerView(R.id.network_list).atPosition(1)).perform(click())
onView(withId(R.id.ml_menu_filter))
.perform(click())
onView(isAssignableFrom(AutoCompleteTextView::class.java))
.perform(typeTextIntoFocusedView("anasjfd"))
onView(withId(R.id.network_list))
.check(matches(withCount(equalTo(0))))
onView(isAssignableFrom(AutoCompleteTextView::class.java))
.perform(clearText())
.perform(typeTextIntoFocusedView("Music"))
onView(withId(R.id.network_list))
.check(matches(sizeOfAtLeast(1)))
}
}
\ No newline at end of file
package org.videolan.vlc.gui.browser
import android.content.Intent
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.*
import androidx.test.espresso.contrib.RecyclerViewActions.*
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.rule.ActivityTestRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.videolan.vlc.BaseUITest
import org.videolan.vlc.R
import org.videolan.vlc.gui.SecondaryActivity
import org.videolan.vlc.*
import org.videolan.vlc.gui.helpers.ThreeStatesCheckbox
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class StorageBrowserFragmentUITest : BaseUITest() {
@Rule
@JvmField
val activityTestRule = ActivityTestRule(SecondaryActivity::class.java, true, false)
lateinit var activity: SecondaryActivity
@Before
fun init() {
val intent = Intent().apply {
putExtra(SecondaryActivity.KEY_FRAGMENT, SecondaryActivity.STORAGE_BROWSER)
}
activityTestRule.launchActivity(intent)
activity = activityTestRule.activity
}
private fun testRecyclerViewShownAndSizeGreaterThanSize(minSize: Int) {
onView(withId(R.id.network_list))
.check(matches(isDisplayed()))
.check(matches(sizeOfAtLeast(minSize)))
}
@Test
fun whenAtRoot_checkCorrectAppbar() {
onView(withId(R.id.main_toolbar))
.check(matches(
hasDescendant(withText(R.string.directories_summary))
))
openActionBarOverflowOrOptionsMenu(context)
onView(withText(R.string.add_custom_path))
.inRoot(isPlatformPopup())
.check(matches(isDisplayed()))
}
@Test
fun whenAtRootClickAddCustomPath_showDialog() {
openActionBarOverflowOrOptionsMenu(context)
onView(withText(R.string.add_custom_path))
.inRoot(isPlatformPopup())
.perform(click())
}
@Test
fun whenAtRoot_checkInternalStorageShown() {
testRecyclerViewShownAndSizeGreaterThanSize(1)
val rvMatcher = withRecyclerView(R.id.network_list)
onView(rvMatcher.atPosition(0))
.check(matches(hasDescendant(withText(R.string.internal_memory))))
}
@Test
fun whenAtRoot_togglingInternalStorageWorks() {
val rvMatcher = withRecyclerView(R.id.network_list)
onView(allOf(
withId(R.id.browser_checkbox), isDescendantOfA(rvMatcher.atPosition(0))
))
.check(matches(withCheckBoxState(equalTo(ThreeStatesCheckbox.STATE_UNCHECKED))))
.perform(click())
.check(matches(withCheckBoxState(equalTo(ThreeStatesCheckbox.STATE_CHECKED))))
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment