Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
For #14980 - Effectively disable tabs tray STATE_HALF_EXPANDED (#16052)
Browse files Browse the repository at this point in the history
STATE_HALF_EXPANDED cannot be disabled while also keeping fitToContents = true
based on which the tabs tray layout is currently set.
To work around this we'll set a a minuscule height for the tab tray when in
this state and then immediately advance to STATE_HIDDEN so to make it
imperceptible to the users.
Since I couldn't write unit tests because of InflateExceptions in Robolectric
I've written UI tests to protect against regressions.
  • Loading branch information
Mugurell committed Nov 4, 2020
1 parent c22ffd3 commit 172977f
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.helpers.idlingresource

import android.view.View
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.IdlingResource.ResourceCallback
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback

class BottomSheetBehaviorStateIdlingResource(behavior: BottomSheetBehavior<*>) :
BottomSheetCallback(), IdlingResource {

private var isIdle: Boolean
private var callback: ResourceCallback? = null

override fun onStateChanged(bottomSheet: View, newState: Int) {
val wasIdle = isIdle
isIdle = isIdleState(newState)
if (!wasIdle && isIdle && callback != null) {
callback!!.onTransitionToIdle()
}
}

override fun onSlide(bottomSheet: View, slideOffset: Float) {
// no-op
}

override fun getName(): String {
return BottomSheetBehaviorStateIdlingResource::class.java.simpleName
}

override fun isIdleNow(): Boolean {
return isIdle
}

override fun registerIdleTransitionCallback(callback: ResourceCallback) {
this.callback = callback
}

private fun isIdleState(state: Int): Boolean {
return state != BottomSheetBehavior.STATE_DRAGGING &&
state != BottomSheetBehavior.STATE_SETTLING &&
// When detecting STATE_HALF_EXPANDED we immediately transit to STATE_HIDDEN.
// Consider this also an intermediary state so not idling.
state != BottomSheetBehavior.STATE_HALF_EXPANDED
}

init {
behavior.addBottomSheetCallback(this)
val state = behavior.state
isIdle = isIdleState(state)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.helpers.matchers

import android.view.View
import androidx.test.espresso.matcher.BoundedMatcher
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.hamcrest.Description

class BottomSheetBehaviorStateMatcher(private val expectedState: Int) :
BoundedMatcher<View, View>(View::class.java) {

override fun describeTo(description: Description?) {
description?.appendText("BottomSheetBehavior in state: \"$expectedState\"")
}

override fun matchesSafely(item: View): Boolean {
val behavior = BottomSheetBehavior.from(item)
return behavior.state == expectedState
}
}

class BottomSheetBehaviorHalfExpandedMaxRatioMatcher(private val maxHalfExpandedRatio: Float) :
BoundedMatcher<View, View>(View::class.java) {

override fun describeTo(description: Description?) {
description?.appendText(
"BottomSheetBehavior with an at max halfExpandedRation: " +
"$maxHalfExpandedRatio"
)
}

override fun matchesSafely(item: View): Boolean {
val behavior = BottomSheetBehavior.from(item)
return behavior.halfExpandedRatio <= maxHalfExpandedRatio
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package org.mozilla.fenix.ui

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.google.android.material.bottomsheet.BottomSheetBehavior
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
Expand Down Expand Up @@ -254,6 +255,31 @@ class TabbedBrowsingTest {
}
}

@Test
fun verifyTabTrayNotShowingStateHalfExpanded() {
homeScreen { }.dismissOnboarding()

navigationToolbar {
}.openTabTray {
verifyNoTabsOpened()
// With no tabs opened the state should be STATE_COLLAPSED.
verifyBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)
// Need to ensure the halfExpandedRatio is very small so that when in STATE_HALF_EXPANDED
// the tabTray will actually have a very small height (for a very short time) akin to being hidden.
verifyHalfExpandedRatio()
}.clickTopBar {
}.waitForTabTrayBehaviorToIdle {
// Touching the topBar would normally advance the tabTray to the next state.
// We don't want that.
verifyBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)
}.advanceToHalfExpandedState {
}.waitForTabTrayBehaviorToIdle {
// TabTray should not be displayed in STATE_HALF_EXPANDED.
// When advancing to this state it should immediately be hidden.
verifyTabTrayIsClosed()
}
}

@Test
fun verifyEmptyTabTray() {
homeScreen { }.dismissOnboarding()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
package org.mozilla.fenix.ui.robots

import android.content.Context
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers
Expand All @@ -28,14 +32,19 @@ import androidx.test.uiautomator.By.text
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import androidx.test.uiautomator.Until.findObject
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.Matcher
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.idlingresource.BottomSheetBehaviorStateIdlingResource
import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorHalfExpandedMaxRatioMatcher
import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorStateMatcher

/**
* Implementation of Robot Pattern for the home screen menu.
Expand All @@ -52,6 +61,10 @@ class TabDrawerRobot {
fun verifyNewTabButton() = assertNewTabButton()
fun verifyTabTrayOverflowMenu(visibility: Boolean) = assertTabTrayOverflowButton(visibility)

fun verifyTabTrayIsClosed() = assertTabTrayDoesNotExist()
fun verifyHalfExpandedRatio() = assertMinisculeHalfExpandedRatio()
fun verifyBehaviorState(expectedState: Int) = assertBehaviorState(expectedState)

fun closeTab() {
closeTabButton().click()
}
Expand Down Expand Up @@ -126,9 +139,7 @@ class TabDrawerRobot {
fun closeTabDrawer(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitForIdle(waitingTime)

// Dismisses the tab tray bottom sheet with 2 handle clicks
onView(withId(R.id.handle)).perform(
click(),
click()
)
BrowserRobot().interact()
Expand Down Expand Up @@ -169,6 +180,52 @@ class TabDrawerRobot {
BrowserRobot().interact()
return BrowserRobot.Transition()
}

fun clickTopBar(interact: TabDrawerRobot.() -> Unit): Transition {
onView(withId(R.id.topBar)).click()
TabDrawerRobot().interact()
return Transition()
}

fun advanceToHalfExpandedState(interact: TabDrawerRobot.() -> Unit): Transition {
onView(withId(R.id.tab_wrapper)).perform(object : ViewAction {
override fun getDescription(): String {
return "Advance a BottomSheetBehavior to STATE_HALF_EXPANDED"
}

override fun getConstraints(): Matcher<View> {
return ViewMatchers.isAssignableFrom(View::class.java)
}

override fun perform(uiController: UiController?, view: View?) {
val behavior = BottomSheetBehavior.from(view!!)
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
})
TabDrawerRobot().interact()
return Transition()
}

fun waitForTabTrayBehaviorToIdle(interact: TabDrawerRobot.() -> Unit): Transition {
var behavior: BottomSheetBehavior<*>? = null
onView(withId(R.id.tab_wrapper)).perform(object : ViewAction {
override fun getDescription(): String {
return "Postpone actions to after the BottomSheetBehavior has settled"
}

override fun getConstraints(): Matcher<View> {
return ViewMatchers.isAssignableFrom(View::class.java)
}

override fun perform(uiController: UiController?, view: View?) {
behavior = BottomSheetBehavior.from(view!!)
}
})
runWithIdleRes(BottomSheetBehaviorStateIdlingResource(behavior!!)) {
TabDrawerRobot().interact()
}
return Transition()
}
}
}

Expand Down Expand Up @@ -239,6 +296,21 @@ private fun assertTabTrayOverflowButton(visible: Boolean) =
onView(withId(R.id.tab_tray_overflow))
.check(matches(withEffectiveVisibility(visibleOrGone(visible))))

private fun assertTabTrayDoesNotExist() {
onView(withId(R.id.tab_wrapper))
.check(doesNotExist())
}

private fun assertMinisculeHalfExpandedRatio() {
onView(withId(R.id.tab_wrapper))
.check(matches(BottomSheetBehaviorHalfExpandedMaxRatioMatcher(0.001f)))
}

private fun assertBehaviorState(expectedState: Int) {
onView(withId(R.id.tab_wrapper))
.check(matches(BottomSheetBehaviorStateMatcher(expectedState)))
}

private fun tab(title: String) =
onView(
allOf(
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ class TabTrayView(

toggleFabText(isPrivate)

view.topBar.setOnClickListener {
// no-op, consume the touch event to prevent it advancing the tray to the next state.
}
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
if (interactor.onModeRequested() is Mode.Normal && !hasAccessibilityEnabled) {
Expand All @@ -133,6 +136,10 @@ class TabTrayView(
components.analytics.metrics.track(Event.TabsTrayClosed)
interactor.onTabTrayDismissed()
}
// We only support expanded and collapsed states. Don't allow STATE_HALF_EXPANDED.
else if (newState == BottomSheetBehavior.STATE_HALF_EXPANDED) {
behavior.state = BottomSheetBehavior.STATE_HIDDEN
}
}
})

Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,8 @@
<item name="behavior_fitToContents">false</item>
<item name="behavior_expandedOffset">80</item>
<item name="behavior_skipCollapsed">false</item>
<item name="behavior_halfExpandedRatio">0.4</item>
<!-- Effectively disable STATE_HALF_EXPANDED by having the tray have a minuscule height in this state -->
<item name="behavior_halfExpandedRatio">0.001</item>
</style>

<style name="TopSite.Favicon" parent="Mozac.Widgets.Favicon">
Expand Down

0 comments on commit 172977f

Please sign in to comment.