Issue #19792: Add content description for tab tray action button

upstream-sync
Jonathan Almeida 3 years ago committed by Jonathan Almeida
parent 2e4635334a
commit a64cac6c7f

@ -4,6 +4,7 @@
package org.mozilla.fenix.tabstray
import android.annotation.SuppressLint
import android.view.View
import android.widget.ImageButton
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -17,6 +18,8 @@ import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.utils.Settings
/**
* A binding for an accessible [actionButton] that is updated on the selected page.
*
* Do not show accessible new tab button when accessibility service is disabled
*
* This binding is coupled with [FloatingActionButtonBinding].
@ -26,13 +29,15 @@ import org.mozilla.fenix.utils.Settings
class AccessibleNewTabButtonBinding(
private val store: TabsTrayStore,
private val settings: Settings,
private val newTabButton: ImageButton,
private val actionButton: ImageButton,
private val browserTrayInteractor: BrowserTrayInteractor
) : AbstractBinding<TabsTrayState>(store) {
// suppressing for the intentional behaviour of this feature.
@SuppressLint("MissingSuperCall")
override fun start() {
if (!settings.accessibilityServicesEnabled) {
newTabButton.visibility = View.GONE
actionButton.visibility = View.GONE
return
}
super.start()
@ -54,8 +59,9 @@ class AccessibleNewTabButtonBinding(
private fun setAccessibleNewTabButton(selectedPage: Page, syncing: Boolean) {
when (selectedPage) {
Page.NormalTabs -> {
newTabButton.apply {
actionButton.apply {
visibility = View.VISIBLE
contentDescription = context.getString(R.string.add_tab)
setImageResource(R.drawable.ic_new)
setOnClickListener {
browserTrayInteractor.onFabClicked(false)
@ -63,8 +69,9 @@ class AccessibleNewTabButtonBinding(
}
}
Page.PrivateTabs -> {
newTabButton.apply {
actionButton.apply {
visibility = View.VISIBLE
contentDescription = context.getString(R.string.add_private_tab)
setImageResource(R.drawable.ic_new)
setOnClickListener {
browserTrayInteractor.onFabClicked(true)
@ -72,13 +79,12 @@ class AccessibleNewTabButtonBinding(
}
}
Page.SyncedTabs -> {
newTabButton.apply {
visibility =
when (syncing) {
true -> View.GONE
false -> View.VISIBLE
}
actionButton.apply {
visibility = when (syncing) {
true -> View.GONE
false -> View.VISIBLE
}
contentDescription = context.getString(R.string.tab_drawer_fab_sync)
setImageResource(R.drawable.ic_fab_sync)
setOnClickListener {
// Notify the store observers (one of which is the SyncedTabsFeature), that

@ -16,6 +16,8 @@ import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.utils.Settings
/**
* A binding for an accessible [actionButton] that is updated on the selected page.
*
* Do not show fab when accessibility service is enabled
*
* This binding is coupled with [AccessibleNewTabButtonBinding].
@ -56,6 +58,7 @@ class FloatingActionButtonBinding(
actionButton.apply {
shrink()
show()
contentDescription = context.getString(R.string.add_tab)
setIconResource(R.drawable.ic_new)
setOnClickListener {
browserTrayInteractor.onFabClicked(false)
@ -67,6 +70,7 @@ class FloatingActionButtonBinding(
setText(R.string.tab_drawer_fab_content)
extend()
show()
contentDescription = context.getString(R.string.add_private_tab)
setIconResource(R.drawable.ic_new)
setOnClickListener {
browserTrayInteractor.onFabClicked(true)

@ -226,7 +226,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
feature = AccessibleNewTabButtonBinding(
store = tabsTrayStore,
settings = requireComponents.settings,
newTabButton = tab_tray_new_tab,
actionButton = tab_tray_new_tab,
browserTrayInteractor = browserTrayInteractor
),
owner = this,

@ -4,17 +4,20 @@
package org.mozilla.fenix.tabstray
import android.content.Context
import android.view.View
import android.widget.ImageButton
import androidx.appcompat.content.res.AppCompatResources
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -29,110 +32,120 @@ class AccessibleNewTabButtonBindingTest {
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private val settings: Settings = mockk(relaxed = true)
private val newTabButton: ImageButton = mockk(relaxed = true)
private val actionButton: ImageButton = mockk(relaxed = true)
private val browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true)
private val context: Context = mockk(relaxed = true)
@Before
fun setup() {
mockkStatic(AppCompatResources::class)
every { AppCompatResources.getDrawable(any(), any()) } returns mockk(relaxed = true)
every { actionButton.context } returns context
}
@After
fun teardown() {
unmockkStatic(AppCompatResources::class)
}
@Test
fun `WHEN tab selected page is normal tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.VISIBLE }
verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
}
@Test
fun `WHEN tab selected page is private tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.VISIBLE }
verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
}
@Test
fun `WHEN tab selected page is sync tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.VISIBLE }
verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
}
@Test
fun `WHEN accessibility is disabled THEN new tab button is not visible`() {
var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
var newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns false
newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.GONE }
verify(exactly = 1) { actionButton.visibility = View.GONE }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
newTabButtonBinding.start()
verify(exactly = 2) { newTabButton.visibility = View.GONE }
verify(exactly = 2) { actionButton.visibility = View.GONE }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
newTabButtonBinding.start()
verify(exactly = 3) { newTabButton.visibility = View.GONE }
verify(exactly = 3) { actionButton.visibility = View.GONE }
}
@Test
fun `WHEN selected page is updated THEN button is updated`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.setImageResource(R.drawable.ic_new) }
verify(exactly = 1) { actionButton.setImageResource(R.drawable.ic_new) }
verify(exactly = 1) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.PrivateTabs.ordinal)))
tabsTrayStore.waitUntilIdle()
verify(exactly = 2) { newTabButton.setImageResource(R.drawable.ic_new) }
verify(exactly = 2) { actionButton.setImageResource(R.drawable.ic_new) }
verify(exactly = 2) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.SyncedTabs.ordinal)))
tabsTrayStore.waitUntilIdle()
verify(exactly = 1) { newTabButton.setImageResource(R.drawable.ic_fab_sync) }
verify(exactly = 1) { actionButton.setImageResource(R.drawable.ic_fab_sync) }
verify(exactly = 3) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.SyncNow)
tabsTrayStore.waitUntilIdle()
verify(exactly = 1) { newTabButton.visibility = View.GONE }
verify(exactly = 1) { actionButton.visibility = View.GONE }
}
}

@ -9,11 +9,13 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -37,6 +39,11 @@ class FloatingActionButtonBindingTest {
every { AppCompatResources.getDrawable(any(), any()) } returns mockk(relaxed = true)
}
@After
fun teardown() {
unmockkStatic(AppCompatResources::class)
}
@Test
fun `WHEN tab selected page is normal tab THEN shrink and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
@ -134,6 +141,7 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.extend() }
verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setIconResource(R.drawable.ic_new) }
verify(exactly = 1) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.PrivateTabs.ordinal)))
tabsTrayStore.waitUntilIdle()
@ -144,6 +152,7 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setText(R.string.tab_drawer_fab_content) }
verify(exactly = 2) { actionButton.setIconResource(R.drawable.ic_new) }
verify(exactly = 2) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.SyncedTabs.ordinal)))
tabsTrayStore.waitUntilIdle()
@ -154,6 +163,7 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setText(R.string.tab_drawer_fab_sync) }
verify(exactly = 1) { actionButton.setIconResource(R.drawable.ic_fab_sync) }
verify(exactly = 2) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.SyncNow)
tabsTrayStore.waitUntilIdle()
@ -164,5 +174,6 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setText(R.string.sync_syncing_in_progress) }
verify(exactly = 2) { actionButton.setIconResource(R.drawable.ic_fab_sync) }
verify(exactly = 2) { actionButton.contentDescription = any() }
}
}

Loading…
Cancel
Save