[fenix] Issue https://github.com/mozilla-mobile/fenix/issues/19792: Add content description for tab tray action button

pull/600/head
Jonathan Almeida 3 years ago committed by Jonathan Almeida
parent 125e70ae1b
commit f5a50057a5

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

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

@ -4,17 +4,20 @@
package org.mozilla.fenix.tabstray package org.mozilla.fenix.tabstray
import android.content.Context
import android.view.View import android.view.View
import android.widget.ImageButton import android.widget.ImageButton
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -29,110 +32,120 @@ class AccessibleNewTabButtonBindingTest {
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher()) val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private val settings: Settings = mockk(relaxed = true) 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 browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true)
private val context: Context = mockk(relaxed = true)
@Before @Before
fun setup() { fun setup() {
mockkStatic(AppCompatResources::class) mockkStatic(AppCompatResources::class)
every { AppCompatResources.getDrawable(any(), any()) } returns mockk(relaxed = true) every { AppCompatResources.getDrawable(any(), any()) } returns mockk(relaxed = true)
every { actionButton.context } returns context
}
@After
fun teardown() {
unmockkStatic(AppCompatResources::class)
} }
@Test @Test
fun `WHEN tab selected page is normal tab THEN new tab button is visible`() { fun `WHEN tab selected page is normal tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start() newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.VISIBLE } verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
} }
@Test @Test
fun `WHEN tab selected page is private tab THEN new tab button is visible`() { fun `WHEN tab selected page is private tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start() newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.VISIBLE } verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
} }
@Test @Test
fun `WHEN tab selected page is sync tab THEN new tab button is visible`() { fun `WHEN tab selected page is sync tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start() newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.VISIBLE } verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
} }
@Test @Test
fun `WHEN accessibility is disabled THEN new tab button is not visible`() { fun `WHEN accessibility is disabled THEN new tab button is not visible`() {
var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
var newTabButtonBinding = AccessibleNewTabButtonBinding( var newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false every { settings.accessibilityServicesEnabled } returns false
newTabButtonBinding.start() newTabButtonBinding.start()
verify(exactly = 1) { newTabButton.visibility = View.GONE } verify(exactly = 1) { actionButton.visibility = View.GONE }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs)) tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding( newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
newTabButtonBinding.start() newTabButtonBinding.start()
verify(exactly = 2) { newTabButton.visibility = View.GONE } verify(exactly = 2) { actionButton.visibility = View.GONE }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs)) tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding( newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
newTabButtonBinding.start() newTabButtonBinding.start()
verify(exactly = 3) { newTabButton.visibility = View.GONE } verify(exactly = 3) { actionButton.visibility = View.GONE }
} }
@Test @Test
fun `WHEN selected page is updated THEN button is updated`() { fun `WHEN selected page is updated THEN button is updated`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start() 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.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.PrivateTabs.ordinal)))
tabsTrayStore.waitUntilIdle() 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.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.SyncedTabs.ordinal)))
tabsTrayStore.waitUntilIdle() 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.dispatch(TabsTrayAction.SyncNow)
tabsTrayStore.waitUntilIdle() 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.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -37,6 +39,11 @@ class FloatingActionButtonBindingTest {
every { AppCompatResources.getDrawable(any(), any()) } returns mockk(relaxed = true) every { AppCompatResources.getDrawable(any(), any()) } returns mockk(relaxed = true)
} }
@After
fun teardown() {
unmockkStatic(AppCompatResources::class)
}
@Test @Test
fun `WHEN tab selected page is normal tab THEN shrink and show is called`() { fun `WHEN tab selected page is normal tab THEN shrink and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
@ -134,6 +141,7 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.extend() } verify(exactly = 0) { actionButton.extend() }
verify(exactly = 0) { actionButton.hide() } verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setIconResource(R.drawable.ic_new) } 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.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.PrivateTabs.ordinal)))
tabsTrayStore.waitUntilIdle() tabsTrayStore.waitUntilIdle()
@ -144,6 +152,7 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() } verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setText(R.string.tab_drawer_fab_content) } verify(exactly = 1) { actionButton.setText(R.string.tab_drawer_fab_content) }
verify(exactly = 2) { actionButton.setIconResource(R.drawable.ic_new) } 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.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.SyncedTabs.ordinal)))
tabsTrayStore.waitUntilIdle() tabsTrayStore.waitUntilIdle()
@ -154,6 +163,7 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() } verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setText(R.string.tab_drawer_fab_sync) } verify(exactly = 1) { actionButton.setText(R.string.tab_drawer_fab_sync) }
verify(exactly = 1) { actionButton.setIconResource(R.drawable.ic_fab_sync) } verify(exactly = 1) { actionButton.setIconResource(R.drawable.ic_fab_sync) }
verify(exactly = 2) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.SyncNow) tabsTrayStore.dispatch(TabsTrayAction.SyncNow)
tabsTrayStore.waitUntilIdle() tabsTrayStore.waitUntilIdle()
@ -164,5 +174,6 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() } verify(exactly = 0) { actionButton.hide() }
verify(exactly = 1) { actionButton.setText(R.string.sync_syncing_in_progress) } 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.setIconResource(R.drawable.ic_fab_sync) }
verify(exactly = 2) { actionButton.contentDescription = any() }
} }
} }

Loading…
Cancel
Save