diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBinding.kt index 9c1c3f4e09..5f759b462a 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBinding.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBinding.kt @@ -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(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 diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/FloatingActionButtonBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/FloatingActionButtonBinding.kt index e72e022c59..bc7f95a6e4 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/FloatingActionButtonBinding.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/FloatingActionButtonBinding.kt @@ -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) diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index 0a22c4d76b..a8e4fb9ca2 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -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, diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBindingTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBindingTest.kt index b6431aa5af..859b0f8874 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBindingTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/AccessibleNewTabButtonBindingTest.kt @@ -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 } } } diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/FloatingActionButtonBindingTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/FloatingActionButtonBindingTest.kt index 61533ded89..4382004316 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/FloatingActionButtonBindingTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/FloatingActionButtonBindingTest.kt @@ -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() } } }