Commit f3e7c81b authored by Sebastian Kaspari's avatar Sebastian Kaspari
Browse files

Issue #1514: Update unit tests.

parent ee6c20a4
......@@ -111,7 +111,11 @@ class BrowserMenu internal constructor(
/**
* Determines the orientation to be used for a menu based on the positioning of the [parent] in the layout.
*/
fun determineMenuOrientation(parent: View): Orientation {
fun determineMenuOrientation(parent: View?): Orientation {
if (parent == null) {
return DOWN
}
val params = parent.layoutParams
return if (params is CoordinatorLayout.LayoutParams) {
if ((params.gravity and Gravity.BOTTOM) == Gravity.BOTTOM) {
......
......@@ -12,6 +12,8 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import androidx.annotation.DrawableRes
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.core.view.forEach
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
......@@ -62,7 +64,7 @@ class BrowserToolbar @JvmOverloads constructor(
/**
* Toolbar in "display mode".
*/
val display = DisplayToolbar(
var display = DisplayToolbar(
context,
this,
LayoutInflater.from(context).inflate(
......@@ -71,11 +73,12 @@ class BrowserToolbar @JvmOverloads constructor(
false
)
)
@VisibleForTesting(otherwise = PRIVATE) internal set
/**
* Toolbar in "edit mode".
*/
val edit = EditToolbar(
var edit = EditToolbar(
context,
this,
LayoutInflater.from(context).inflate(
......@@ -84,6 +87,7 @@ class BrowserToolbar @JvmOverloads constructor(
false
)
)
@VisibleForTesting(otherwise = PRIVATE) internal set
override var title: String
get() = display.title
......
......@@ -14,6 +14,7 @@ import android.view.accessibility.AccessibilityEvent
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.annotation.ColorInt
import androidx.annotation.VisibleForTesting
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
......@@ -132,31 +133,36 @@ class DisplayToolbar internal constructor(
BOTTOM
}
private val views = object {
val browserActions = rootView.findViewById<ActionContainer>(
R.id.mozac_browser_toolbar_browser_actions)
val pageActions = rootView.findViewById<ActionContainer>(
R.id.mozac_browser_toolbar_page_actions)
val navigationActions = rootView.findViewById<ActionContainer>(
R.id.mozac_browser_toolbar_navigation_actions)
val background = rootView.findViewById<ImageView>(
R.id.mozac_browser_toolbar_background)
val separator = rootView.findViewById<ImageView>(
R.id.mozac_browser_toolbar_separator)
val emptyIndicator = rootView.findViewById<ImageView>(
R.id.mozac_browser_toolbar_empty_indicator)
val menu = rootView.findViewById<MenuButton>(
R.id.mozac_browser_toolbar_menu)
val progress = rootView.findViewById<ProgressBar>(
R.id.mozac_browser_toolbar_progress)
val securityIndicator = rootView.findViewById<SiteSecurityIconView>(
R.id.mozac_browser_toolbar_security_indicator)
val trackingProtectionIndicator = rootView.findViewById<TrackingProtectionIconView>(
R.id.mozac_browser_toolbar_tracking_protection_indicator)
val originView = rootView.findViewById<OriginView>(R.id.mozac_browser_toolbar_origin_view).also {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val views = DisplayToolbarViews(
browserActions = rootView.findViewById(R.id.mozac_browser_toolbar_browser_actions),
pageActions = rootView.findViewById(R.id.mozac_browser_toolbar_page_actions),
navigationActions = rootView.findViewById(R.id.mozac_browser_toolbar_navigation_actions),
background = rootView.findViewById(R.id.mozac_browser_toolbar_background),
separator = rootView.findViewById(R.id.mozac_browser_toolbar_separator),
emptyIndicator = rootView.findViewById(R.id.mozac_browser_toolbar_empty_indicator),
menu = rootView.findViewById(R.id.mozac_browser_toolbar_menu),
securityIndicator = rootView.findViewById(R.id.mozac_browser_toolbar_security_indicator),
trackingProtectionIndicator = rootView.findViewById(
R.id.mozac_browser_toolbar_tracking_protection_indicator
),
origin = rootView.findViewById<OriginView>(R.id.mozac_browser_toolbar_origin_view).also {
it.toolbar = toolbar
},
progress = rootView.findViewById<ProgressBar>(R.id.mozac_browser_toolbar_progress).apply {
accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityEvent(host: View?, event: AccessibilityEvent?) {
super.onInitializeAccessibilityEvent(host, event)
if (event?.eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
// Populate the scroll event with the current progress.
// See accessibility note in `updateProgress()`.
event.scrollY = progress
event.maxScrollY = max
}
}
}
}
}
)
/**
* Customizable colors in "display mode".
......@@ -166,9 +172,9 @@ class DisplayToolbar internal constructor(
securityIconInsecure = ContextCompat.getColor(context, R.color.photonWhite),
emptyIcon = ContextCompat.getColor(context, R.color.photonWhite),
menu = ContextCompat.getColor(context, R.color.photonWhite),
hint = views.originView.hintColor,
title = views.originView.titleColor,
text = views.originView.textColor,
hint = views.origin.hintColor,
title = views.origin.titleColor,
text = views.origin.textColor,
trackingProtection = null,
separator = ContextCompat.getColor(context, R.color.photonGrey80)
)
......@@ -178,9 +184,9 @@ class DisplayToolbar internal constructor(
updateSiteSecurityIcon()
views.emptyIndicator.setColorFilter(value.emptyIcon)
views.menu.setColorFilter(value.menu)
views.originView.hintColor = value.hint
views.originView.titleColor = value.title
views.originView.textColor = value.text
views.origin.hintColor = value.hint
views.origin.titleColor = value.title
views.origin.textColor = value.text
views.separator.setColorFilter(value.separator)
if (value.trackingProtection != null) {
......@@ -318,45 +324,45 @@ class DisplayToolbar internal constructor(
* <code>false</code> to not switch to editing mode and handle the click manually.
*/
var onUrlClicked: () -> Boolean
get() = views.originView.onUrlClicked
get() = views.origin.onUrlClicked
set(value) {
views.originView.onUrlClicked = value
views.origin.onUrlClicked = value
}
/**
* Sets the text to be displayed when the URL of the toolbar is empty.
*/
var hint: String
get() = views.originView.hint
get() = views.origin.hint
set(value) {
views.originView.hint = value
views.origin.hint = value
}
/**
* Sets the size of the text for the title displayed in the toolbar.
*/
var titleTextSize: Float
get() = views.originView.titleTextSize
get() = views.origin.titleTextSize
set(value) {
views.originView.titleTextSize = value
views.origin.titleTextSize = value
}
/**
* Sets the size of the text for the URL/search term displayed in the toolbar.
*/
var textSize: Float
get() = views.originView.textSize
get() = views.origin.textSize
set(value) {
views.originView.textSize = value
views.origin.textSize = value
}
/**
* Sets the typeface of the text for the URL/search term displayed in the toolbar.
*/
var typeface: Typeface
get() = views.originView.typeface
get() = views.origin.typeface
set(value) {
views.originView.typeface = value
views.origin.typeface = value
}
/**
......@@ -373,7 +379,7 @@ class DisplayToolbar internal constructor(
* Set a LongClickListener to the urlView of the toolbar.
*/
fun setOnUrlLongClickListener(handler: ((View) -> Boolean)?) {
views.originView.setOnLongClickListener(handler)
views.origin.setOnLongClickListener(handler)
}
private fun updateIndicatorVisibility() {
......@@ -385,19 +391,8 @@ class DisplayToolbar internal constructor(
View.GONE
}
views.trackingProtectionIndicator.visibility =
if (!urlEmpty && indicators.contains(Indicators.TRACKING_PROTECTION)) {
View.VISIBLE
} else {
View.GONE
}
views.separator.visibility = if (displayIndicatorSeparator && !urlEmpty && indicators.containsAll(
listOf(
Indicators.TRACKING_PROTECTION,
Indicators.SECURITY
)
)
views.trackingProtectionIndicator.visibility = if (
!urlEmpty && indicators.contains(Indicators.TRACKING_PROTECTION)
) {
View.VISIBLE
} else {
......@@ -409,24 +404,38 @@ class DisplayToolbar internal constructor(
} else {
View.GONE
}
updateSeparatorVisibility()
}
private fun updateSeparatorVisibility() {
views.separator.visibility = if (
displayIndicatorSeparator &&
views.trackingProtectionIndicator.isVisible &&
views.securityIndicator.isVisible
) {
View.VISIBLE
} else {
View.GONE
}
}
/**
* Updates the title to be displayed.
*/
internal var title: String
get() = views.originView.title
get() = views.origin.title
set(value) {
views.originView.title = value
views.origin.title = value
}
/**
* Updates the URL to be displayed.
*/
internal var url: CharSequence
get() = views.originView.url
get() = views.origin.url
set(value) {
views.originView.url = value
views.origin.url = value
updateIndicatorVisibility()
}
......@@ -459,6 +468,7 @@ class DisplayToolbar internal constructor(
}
views.trackingProtectionIndicator.siteTrackingProtection = state
updateSeparatorVisibility()
}
internal fun onStop() {
......@@ -550,3 +560,20 @@ class DisplayToolbar internal constructor(
views.navigationActions.addAction(action)
}
}
/**
* Internal holder for view references.
*/
internal class DisplayToolbarViews(
val browserActions: ActionContainer,
val pageActions: ActionContainer,
val navigationActions: ActionContainer,
val background: ImageView,
val separator: ImageView,
val emptyIndicator: ImageView,
val menu: MenuButton,
val securityIndicator: SiteSecurityIconView,
val trackingProtectionIndicator: TrackingProtectionIconView,
val origin: OriginView,
val progress: ProgressBar
)
......@@ -56,7 +56,7 @@ internal class MenuButton @JvmOverloads constructor(
val endAlwaysVisible = menuBuilder?.endOfMenuAlwaysVisible ?: false
menu?.show(
anchor = this,
orientation = BrowserMenu.determineMenuOrientation(parent as View),
orientation = BrowserMenu.determineMenuOrientation(parent as View?),
endOfMenuAlwaysVisible = endAlwaysVisible
) { menu = null }
......
......@@ -13,6 +13,7 @@ import android.view.Gravity
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.R
......@@ -37,7 +38,8 @@ internal class OriginView @JvmOverloads constructor(
private val textSizeTitle = context.resources.getDimension(
R.dimen.mozac_browser_toolbar_title_textsize)
private val urlView = TextView(context).apply {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val urlView = TextView(context).apply {
id = R.id.mozac_browser_toolbar_url_view
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizeUrlNormal)
gravity = Gravity.CENTER_VERTICAL
......@@ -60,7 +62,8 @@ internal class OriginView @JvmOverloads constructor(
isHorizontalFadingEdgeEnabled = fadingEdgeSize > 0
}
private val titleView = TextView(context).apply {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val titleView = TextView(context).apply {
id = R.id.mozac_browser_toolbar_title_view
visibility = View.GONE
......
......@@ -11,6 +11,8 @@ import android.view.KeyEvent
import android.view.View
import android.widget.ImageView
import androidx.annotation.ColorInt
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.core.content.ContextCompat
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.isVisible
......@@ -54,7 +56,7 @@ class EditToolbar internal constructor(
/**
* Data class holding the customizable colors in "edit mode".
*
* @property cancel Color tint used for the "cancel" icon to leave "edit mode".
* @property clear Color tint used for the "cancel" icon to leave "edit mode".
* @property icon Color tint of the icon displayed in front of the URL.
* @property hint Text color of the hint shown when the URL field is empty.
* @property text Text color of the URL.
......@@ -62,7 +64,7 @@ class EditToolbar internal constructor(
* @property suggestionForeground The foreground color used for autocomplete suggestions.
*/
data class Colors(
@ColorInt val cancel: Int,
@ColorInt val clear: Int,
@ColorInt val icon: Int?,
@ColorInt val hint: Int,
@ColorInt val text: Int,
......@@ -76,17 +78,17 @@ class EditToolbar internal constructor(
logger.error("Error while processing autocomplete input", throwable)
}
private val views = object {
val backgroundView = rootView.findViewById<ImageView>(R.id.mozac_browser_toolbar_background)
val iconView = rootView.findViewById<ImageView>(R.id.mozac_browser_toolbar_edit_icon)
val editActions = rootView.findViewById<ActionContainer>(R.id.mozac_browser_toolbar_edit_actions)
val cancelView = rootView.findViewById<ImageView>(R.id.mozac_browser_toolbar_cancel_view).apply {
@VisibleForTesting(otherwise = PRIVATE)
internal val views = EditToolbarViews(
background = rootView.findViewById<ImageView>(R.id.mozac_browser_toolbar_background),
icon = rootView.findViewById<ImageView>(R.id.mozac_browser_toolbar_edit_icon),
editActions = rootView.findViewById<ActionContainer>(R.id.mozac_browser_toolbar_edit_actions),
clear = rootView.findViewById<ImageView>(R.id.mozac_browser_toolbar_clear_view).apply {
setOnClickListener {
// We set text to an empty string instead of using clear to avoid #3612.
urlView.setText("")
onClear()
}
}
val urlView: InlineAutocompleteEditText = rootView.findViewById<InlineAutocompleteEditText>(
},
url = rootView.findViewById<InlineAutocompleteEditText>(
R.id.mozac_browser_toolbar_edit_url_view
).apply {
setOnCommitListener {
......@@ -98,8 +100,7 @@ class EditToolbar internal constructor(
}
setOnTextChangeListener { text, _ ->
cancelView.isVisible = text.isNotBlank()
editListener?.onTextChanged(text)
onTextChanged(text)
}
setOnDispatchKeyEventPreImeListener { event ->
......@@ -109,77 +110,77 @@ class EditToolbar internal constructor(
false
}
}
}
)
/**
* Customizable colors in "edit mode".
*/
var colors: Colors = Colors(
cancel = ContextCompat.getColor(context, R.color.photonWhite),
clear = ContextCompat.getColor(context, R.color.photonWhite),
icon = null,
hint = views.urlView.currentHintTextColor,
text = views.urlView.currentTextColor,
suggestionBackground = views.urlView.autoCompleteBackgroundColor,
suggestionForeground = views.urlView.autoCompleteForegroundColor
hint = views.url.currentHintTextColor,
text = views.url.currentTextColor,
suggestionBackground = views.url.autoCompleteBackgroundColor,
suggestionForeground = views.url.autoCompleteForegroundColor
)
set(value) {
field = value
views.cancelView.setColorFilter(value.cancel)
views.clear.setColorFilter(value.clear)
if (value.icon != null) {
views.iconView.setColorFilter(value.icon)
views.icon.setColorFilter(value.icon)
}
views.urlView.setHintTextColor(value.hint)
views.urlView.setTextColor(value.text)
views.urlView.autoCompleteBackgroundColor = value.suggestionBackground
views.urlView.autoCompleteForegroundColor = value.suggestionForeground
views.url.setHintTextColor(value.hint)
views.url.setTextColor(value.text)
views.url.autoCompleteBackgroundColor = value.suggestionBackground
views.url.autoCompleteForegroundColor = value.suggestionForeground
}
/**
* Sets the background that will be drawn behind the URL, icon and edit actions.
*/
fun setUrlBackground(background: Drawable?) {
views.backgroundView.setImageDrawable(background)
views.background.setImageDrawable(background)
}
/**
* Sets an icon that will be drawn in front of the URL.
*/
fun setIcon(icon: Drawable, contentDescription: String) {
views.iconView.setImageDrawable(icon)
views.iconView.contentDescription = contentDescription
views.icon.setImageDrawable(icon)
views.icon.contentDescription = contentDescription
}
/**
* Sets the text to be displayed when the URL of the toolbar is empty.
*/
var hint: String
get() = views.urlView.hint.toString()
set(value) { views.urlView.hint = value }
get() = views.url.hint.toString()
set(value) { views.url.hint = value }
/**
* Sets the size of the text for the URL/search term displayed in the toolbar.
*/
var textSize: Float
get() = views.urlView.textSize
get() = views.url.textSize
set(value) {
views.urlView.textSize = value
views.url.textSize = value
}
/**
* Sets the typeface of the text for the URL/search term displayed in the toolbar.
*/
var typeface: Typeface
get() = views.urlView.typeface
set(value) { views.urlView.typeface = value }
get() = views.url.typeface
set(value) { views.url.typeface = value }
/**
* Sets a listener to be invoked when focus of the URL input view (in edit mode) changed.
*/
internal fun setOnEditFocusChangeListener(listener: (Boolean) -> Unit) {
views.urlView.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
views.url.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
listener.invoke(hasFocus)
}
}
......@@ -188,7 +189,7 @@ class EditToolbar internal constructor(
* Focuses the url input field.
*/
fun focus() {
views.urlView.requestFocus()
views.url.requestFocus()
}
internal fun stopEditing() {
......@@ -202,8 +203,8 @@ class EditToolbar internal constructor(
internal var editListener: Toolbar.OnEditListener? = null
internal fun setAutocompleteListener(filter: suspend (String, AutocompleteDelegate) -> Unit) {
views.urlView.setOnFilterListener(
AsyncFilterListener(views.urlView, autocompleteDispatcher, filter)
views.url.setOnFilterListener(
AsyncFilterListener(views.url, autocompleteDispatcher, filter)
)
}
......@@ -216,14 +217,14 @@ class EditToolbar internal constructor(
}
internal fun updateUrl(url: String, shouldAutoComplete: Boolean = false) {
views.urlView.setText(url, shouldAutoComplete)
views.url.setText(url, shouldAutoComplete)
}
/**
* Select the entire text in the URL input field.
*/
internal fun selectAll() {
views.urlView.selectAll()
views.url.selectAll()
}
/**
......@@ -233,12 +234,33 @@ class EditToolbar internal constructor(
* model based on what the user typed.
*/
internal var private: Boolean
get() = (views.urlView.imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING) != 0
get() = (views.url.imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING) != 0
set(value) {
views.urlView.imeOptions = if (value) {
views.urlView.imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
views.url.imeOptions = if (value) {
views.url.imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
} else {
views.urlView.imeOptions and (EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv())
views.url.imeOptions and (EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv())
}
}
private fun onClear() {
// We set text to an empty string instead of using clear to avoid #3612.
views.url.setText("")
}
private fun onTextChanged(text: String) {
views.clear.isVisible = text.isNotBlank()
editListener?.onTextChanged(text)