[fenix] For https://github.com/mozilla-mobile/fenix/issues/19475 - Introduce a DefaultTabsTrayInteractor
Refactored the TabsTrayFragment to not implement the TabsTrayInteractor which among other advantages allowed for easier testing.pull/600/head
parent
2825595c5f
commit
693a65646c
@ -0,0 +1,420 @@
|
||||
/* 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.tabstray
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.spyk
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import io.mockk.verifyOrder
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.browser.state.selector.findTab
|
||||
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.base.profiler.Profiler
|
||||
import mozilla.components.concept.tabstray.Tab
|
||||
import mozilla.components.feature.tabs.TabsUseCases
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
|
||||
import org.mozilla.fenix.helpers.DisableNavGraphProviderAssertionRule
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.home.HomeFragment
|
||||
import org.mozilla.fenix.tabtray.TabTrayDialogFragmentDirections
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DefaultTabsTrayControllerTest {
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var trayStore: TabsTrayStore
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var browserStore: BrowserStore
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var browsingModeManager: BrowsingModeManager
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var navController: NavController
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var navigateToHomeAndDeleteSession: (String) -> Unit
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var profiler: Profiler
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var navigationInteractor: NavigationInteractor
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var metrics: MetricController
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var tabsUseCases: TabsUseCases
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var selectTabPosition: (Int, Boolean) -> Unit
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var dismissTray: () -> Unit
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var showUndoSnackbarForTab: (Boolean) -> Unit
|
||||
|
||||
private lateinit var controller: DefaultTabsTrayController
|
||||
|
||||
@get:Rule
|
||||
val disableNavGraphProviderAssertionRule = DisableNavGraphProviderAssertionRule()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
controller = DefaultTabsTrayController(
|
||||
trayStore,
|
||||
browserStore,
|
||||
browsingModeManager,
|
||||
navController,
|
||||
navigateToHomeAndDeleteSession,
|
||||
profiler,
|
||||
navigationInteractor,
|
||||
metrics,
|
||||
tabsUseCases,
|
||||
selectTabPosition,
|
||||
dismissTray,
|
||||
showUndoSnackbarForTab
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN private mode WHEN handleOpeningNewTab is called THEN a profile marker is added for the operations executed`() {
|
||||
profiler = spyk(profiler) {
|
||||
every { getProfilerTime() } returns Double.MAX_VALUE
|
||||
}
|
||||
controller = DefaultTabsTrayController(
|
||||
trayStore,
|
||||
browserStore,
|
||||
browsingModeManager,
|
||||
navController,
|
||||
navigateToHomeAndDeleteSession,
|
||||
profiler,
|
||||
navigationInteractor,
|
||||
metrics,
|
||||
tabsUseCases,
|
||||
selectTabPosition,
|
||||
dismissTray,
|
||||
showUndoSnackbarForTab
|
||||
)
|
||||
|
||||
controller.handleOpeningNewTab(true)
|
||||
|
||||
verifyOrder {
|
||||
profiler.getProfilerTime()
|
||||
navController.navigateBlockingForAsyncNavGraph(
|
||||
TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
|
||||
)
|
||||
navigationInteractor.onTabTrayDismissed()
|
||||
profiler.addMarker(
|
||||
"DefaultTabTrayController.onNewTabTapped",
|
||||
Double.MAX_VALUE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal mode WHEN handleOpeningNewTab is called THEN a profile marker is added for the operations executed`() {
|
||||
profiler = spyk(profiler) {
|
||||
every { getProfilerTime() } returns Double.MAX_VALUE
|
||||
}
|
||||
controller = DefaultTabsTrayController(
|
||||
trayStore,
|
||||
browserStore,
|
||||
browsingModeManager,
|
||||
navController,
|
||||
navigateToHomeAndDeleteSession,
|
||||
profiler,
|
||||
navigationInteractor,
|
||||
metrics,
|
||||
tabsUseCases,
|
||||
selectTabPosition,
|
||||
dismissTray,
|
||||
showUndoSnackbarForTab
|
||||
)
|
||||
|
||||
controller.handleOpeningNewTab(false)
|
||||
|
||||
verifyOrder {
|
||||
profiler.getProfilerTime()
|
||||
navController.navigateBlockingForAsyncNavGraph(
|
||||
TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
|
||||
)
|
||||
navigationInteractor.onTabTrayDismissed()
|
||||
profiler.addMarker(
|
||||
"DefaultTabTrayController.onNewTabTapped",
|
||||
Double.MAX_VALUE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN private mode WHEN handleOpeningNewTab is called THEN Event#NewPrivateTabTapped is added to telemetry`() {
|
||||
controller.handleOpeningNewTab(true)
|
||||
|
||||
verify { metrics.track(Event.NewPrivateTabTapped) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN private mode WHEN handleOpeningNewTab is called THEN Event#NewTabTapped is added to telemetry`() {
|
||||
controller.handleOpeningNewTab(false)
|
||||
|
||||
verify { metrics.track(Event.NewTabTapped) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleTrayScrollingToPosition is called with smoothScroll=true THEN it scrolls to that position with smoothScroll`() {
|
||||
controller.handleTrayScrollingToPosition(3, true)
|
||||
|
||||
verify { selectTabPosition(3, true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleTrayScrollingToPosition is called with smoothScroll=true THEN it emits an action for the tray page of that tab position`() {
|
||||
controller.handleTrayScrollingToPosition(33, true)
|
||||
|
||||
verify { trayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(33))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleTrayScrollingToPosition is called with smoothScroll=false THEN it scrolls to that position without smoothScroll`() {
|
||||
controller.handleTrayScrollingToPosition(4, false)
|
||||
|
||||
verify { selectTabPosition(4, false) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleTrayScrollingToPosition is called with smoothScroll=false THEN it emits an action for the tray page of that tab position`() {
|
||||
controller.handleTrayScrollingToPosition(44, true)
|
||||
|
||||
verify { trayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(44))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN already on browserFragment WHEN handleNavigateToBrowser is called THEN the tray is dismissed`() {
|
||||
every { navController.currentDestination?.id } returns R.id.browserFragment
|
||||
|
||||
controller.handleNavigateToBrowser()
|
||||
|
||||
verify { dismissTray() }
|
||||
verify(exactly = 0) { navController.popBackStack() }
|
||||
verify(exactly = 0) { navController.popBackStack(any(), any()) }
|
||||
verify(exactly = 0) { navController.navigateBlockingForAsyncNavGraph(any<Int>()) }
|
||||
verify(exactly = 0) { navController.navigateBlockingForAsyncNavGraph(any<NavDirections>()) }
|
||||
verify(exactly = 0) { navController.navigateBlockingForAsyncNavGraph(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN not already on browserFragment WHEN handleNavigateToBrowser is called THEN the tray is dismissed and popBackStack is executed`() {
|
||||
every { navController.currentDestination?.id } returns R.id.browserFragment + 1
|
||||
every { navController.popBackStack(R.id.browserFragment, false) } returns true
|
||||
|
||||
controller.handleNavigateToBrowser()
|
||||
|
||||
verify { dismissTray() }
|
||||
verify { navController.popBackStack(R.id.browserFragment, false) }
|
||||
verify(exactly = 0) { navController.navigateBlockingForAsyncNavGraph(any<Int>()) }
|
||||
verify(exactly = 0) { navController.navigateBlockingForAsyncNavGraph(any<NavDirections>()) }
|
||||
verify(exactly = 0) { navController.navigateBlockingForAsyncNavGraph(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN not already on browserFragment WHEN handleNavigateToBrowser is called and popBackStack fails THEN it navigates to browserFragment`() {
|
||||
every { navController.currentDestination?.id } returns R.id.browserFragment + 1
|
||||
every { navController.popBackStack(R.id.browserFragment, false) } returns false
|
||||
|
||||
controller.handleNavigateToBrowser()
|
||||
|
||||
verify { dismissTray() }
|
||||
verify { navController.popBackStack(R.id.browserFragment, false) }
|
||||
verify { navController.navigateBlockingForAsyncNavGraph(R.id.browserFragment) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN not already on browserFragment WHEN handleNavigateToBrowser is called and popBackStack succeeds THEN the method finishes`() {
|
||||
every { navController.popBackStack(R.id.browserFragment, false) } returns true
|
||||
|
||||
controller.handleNavigateToBrowser()
|
||||
|
||||
verify { dismissTray() }
|
||||
verify(exactly = 1) { navController.popBackStack(R.id.browserFragment, false) }
|
||||
verify(exactly = 0) { navController.navigateBlockingForAsyncNavGraph(R.id.browserFragment) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN more tabs opened WHEN handleTabDeletion is called THEN that tab is removed and an undo snackbar is shown`() {
|
||||
val tab: TabSessionState = mockk {
|
||||
every { content } returns mockk()
|
||||
every { content.private } returns true
|
||||
}
|
||||
every { browserStore.state } returns mockk()
|
||||
try {
|
||||
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
every { browserStore.state.findTab(any()) } returns tab
|
||||
every { browserStore.state.getNormalOrPrivateTabs(any()) } returns listOf(tab, mockk())
|
||||
|
||||
controller.handleTabDeletion("22")
|
||||
|
||||
verify { tabsUseCases.removeTab("22") }
|
||||
verify { showUndoSnackbarForTab(true) }
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN only one tab opened WHEN handleTabDeletion is called THEN that it navigates to home where the tab will be removed`() {
|
||||
controller = spyk(controller)
|
||||
val tab: TabSessionState = mockk {
|
||||
every { content } returns mockk()
|
||||
every { content.private } returns true
|
||||
}
|
||||
every { browserStore.state } returns mockk()
|
||||
try {
|
||||
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
every { browserStore.state.findTab(any()) } returns tab
|
||||
every { browserStore.state.getNormalOrPrivateTabs(any()) } returns listOf(tab)
|
||||
|
||||
controller.handleTabDeletion("33")
|
||||
|
||||
verify { controller.dismissTabsTrayAndNavigateHome("33") }
|
||||
verify(exactly = 0) { tabsUseCases.removeTab(any()) }
|
||||
verify(exactly = 0) { showUndoSnackbarForTab(any()) }
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun `WHEN handleMultipleTabsDeletion is called to close all private tabs THEN that it navigates to home where that tabs will be removed and shows undo snackbar`() {
|
||||
controller = spyk(controller)
|
||||
val privateTab: Tab = mockk {
|
||||
every { private } returns true
|
||||
}
|
||||
every { browserStore.state } returns mockk()
|
||||
try {
|
||||
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
every { browserStore.state.getNormalOrPrivateTabs(any()) } returns listOf(mockk(), mockk())
|
||||
|
||||
controller.handleMultipleTabsDeletion(listOf(privateTab, mockk()))
|
||||
|
||||
verify { controller.dismissTabsTrayAndNavigateHome(HomeFragment.ALL_PRIVATE_TABS) }
|
||||
verify { showUndoSnackbarForTab(true) }
|
||||
verify(exactly = 0) { tabsUseCases.removeTabs(any()) }
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun `WHEN handleMultipleTabsDeletion is called to close all normal tabs THEN that it navigates to home where that tabs will be removed and shows undo snackbar`() {
|
||||
controller = spyk(controller)
|
||||
val normalTab: Tab = mockk {
|
||||
every { private } returns false
|
||||
}
|
||||
every { browserStore.state } returns mockk()
|
||||
try {
|
||||
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
every { browserStore.state.getNormalOrPrivateTabs(any()) } returns listOf(mockk(), mockk())
|
||||
|
||||
controller.handleMultipleTabsDeletion(listOf(normalTab, normalTab))
|
||||
|
||||
verify { controller.dismissTabsTrayAndNavigateHome(HomeFragment.ALL_NORMAL_TABS) }
|
||||
verify { showUndoSnackbarForTab(false) }
|
||||
verify(exactly = 0) { tabsUseCases.removeTabs(any()) }
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun `WHEN handleMultipleTabsDeletion is called to close some private tabs THEN that it uses tabsUseCases#removeTabs and shows an undo snackbar`() {
|
||||
controller = spyk(controller)
|
||||
val privateTab: Tab = mockk {
|
||||
every { private } returns true
|
||||
every { id } returns "42"
|
||||
}
|
||||
every { browserStore.state } returns mockk()
|
||||
try {
|
||||
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
every { browserStore.state.getNormalOrPrivateTabs(any()) } returns listOf(mockk(), mockk())
|
||||
|
||||
controller.handleMultipleTabsDeletion(listOf(privateTab))
|
||||
|
||||
verify { tabsUseCases.removeTabs(listOf("42")) }
|
||||
verify { showUndoSnackbarForTab(true) }
|
||||
verify(exactly = 0) { controller.dismissTabsTrayAndNavigateHome(any()) }
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun `WHEN handleMultipleTabsDeletion is called to close some normal tabs THEN that it uses tabsUseCases#removeTabs and shows an undo snackbar`() {
|
||||
controller = spyk(controller)
|
||||
val privateTab: Tab = mockk {
|
||||
every { private } returns false
|
||||
every { id } returns "24"
|
||||
}
|
||||
every { browserStore.state } returns mockk()
|
||||
try {
|
||||
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
every { browserStore.state.getNormalOrPrivateTabs(any()) } returns listOf(mockk(), mockk())
|
||||
|
||||
controller.handleMultipleTabsDeletion(listOf(privateTab))
|
||||
|
||||
verify { tabsUseCases.removeTabs(listOf("24")) }
|
||||
verify { showUndoSnackbarForTab(false) }
|
||||
verify(exactly = 0) { controller.dismissTabsTrayAndNavigateHome(any()) }
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN private mode selected WHEN sendNewTabEvent is called THEN NewPrivateTabTapped is tracked in telemetry`() {
|
||||
controller.sendNewTabEvent(true)
|
||||
|
||||
verify { metrics.track(Event.NewPrivateTabTapped) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal mode selected WHEN sendNewTabEvent is called THEN NewTabTapped is tracked in telemetry`() {
|
||||
controller.sendNewTabEvent(false)
|
||||
|
||||
verify { metrics.track(Event.NewTabTapped) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN dismissTabsTrayAndNavigateHome is called with a spefic tab id THEN tray is dismissed and navigates home is opened to delete that tab`() {
|
||||
controller.dismissTabsTrayAndNavigateHome("randomId")
|
||||
|
||||
verify { dismissTray() }
|
||||
verify { navigateToHomeAndDeleteSession("randomId") }
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* 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.tabstray
|
||||
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verifySequence
|
||||
import org.junit.Test
|
||||
import mozilla.components.concept.tabstray.Tab
|
||||
|
||||
class DefaultTabsTrayInteractorTest {
|
||||
val controller: TabsTrayController = mockk(relaxed = true)
|
||||
val trayInteractor = DefaultTabsTrayInteractor(controller)
|
||||
|
||||
@Test
|
||||
fun `GIVEN user selecting a new tray page WHEN onTrayPositionSelected is called THEN the Interactor delegates the controller`() {
|
||||
trayInteractor.onTrayPositionSelected(14, true)
|
||||
|
||||
verifySequence { controller.handleTrayScrollingToPosition(14, true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user selecting a new browser tab WHEN onBrowserTabSelected is called THEN the Interactor delegates the controller`() {
|
||||
trayInteractor.onBrowserTabSelected()
|
||||
|
||||
verifySequence { controller.handleNavigateToBrowser() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user deleted one browser tab page WHEN onDeleteTab is called THEN the Interactor delegates the controller`() {
|
||||
trayInteractor.onDeleteTab("testTabId")
|
||||
|
||||
verifySequence { controller.handleTabDeletion("testTabId") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user deleted multiple browser tabs WHEN onDeleteTabs is called THEN the Interactor delegates the controller`() {
|
||||
val tabsToDelete = listOf<Tab>(mockk(), mockk())
|
||||
|
||||
trayInteractor.onDeleteTabs(tabsToDelete)
|
||||
|
||||
verifySequence { controller.handleMultipleTabsDeletion(tabsToDelete) }
|
||||
}
|
||||
}
|
@ -0,0 +1,348 @@
|
||||
/* 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.tabstray
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.android.synthetic.main.component_tabstray2.*
|
||||
import kotlinx.android.synthetic.main.component_tabstray2.view.*
|
||||
import kotlinx.android.synthetic.main.component_tabstray_fab.*
|
||||
import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import mozilla.components.browser.menu.BrowserMenu
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.NavGraphDirections
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.home.HomeScreenViewModel
|
||||
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
|
||||
import org.mozilla.fenix.tabstray.ext.showWithTheme
|
||||
import org.mozilla.fenix.utils.allowUndo
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class TabsTrayFragmentTest {
|
||||
private lateinit var context: Context
|
||||
private lateinit var view: View
|
||||
private lateinit var fragment: TabsTrayFragment
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
context = mockk(relaxed = true)
|
||||
view = mockk(relaxed = true)
|
||||
|
||||
fragment = spyk(TabsTrayFragment())
|
||||
every { fragment.context } returns context
|
||||
every { fragment.view } returns view
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN showUndoSnackbarForTab is called for a private tab with new tab button visible THEN an appropriate snackbar is shown`() {
|
||||
try {
|
||||
mockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
|
||||
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
|
||||
val newTabButton: ExtendedFloatingActionButton = mockk {
|
||||
every { visibility } returns View.VISIBLE
|
||||
}
|
||||
every { fragment.new_tab_button } returns newTabButton
|
||||
every { fragment.context } returns testContext // needed for getString()
|
||||
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
fragment.showUndoSnackbarForTab(true)
|
||||
|
||||
verify { lifecycleScope.allowUndo(
|
||||
fragment.view!!,
|
||||
testContext.getString(R.string.snackbar_private_tab_closed),
|
||||
testContext.getString(R.string.snackbar_deleted_undo),
|
||||
any(),
|
||||
any(),
|
||||
newTabButton,
|
||||
TabsTrayFragment.ELEVATION,
|
||||
false
|
||||
) }
|
||||
} finally {
|
||||
unmockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
unmockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN showUndoSnackbarForTab is called for a private tab with new tab button not visible THEN an appropriate snackbar is shown`() {
|
||||
try {
|
||||
mockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
|
||||
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
|
||||
val newTabButton: ExtendedFloatingActionButton = mockk {
|
||||
every { visibility } returns View.GONE
|
||||
}
|
||||
every { fragment.new_tab_button } returns newTabButton
|
||||
every { fragment.context } returns testContext // needed for getString()
|
||||
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
fragment.showUndoSnackbarForTab(true)
|
||||
|
||||
verify { lifecycleScope.allowUndo(
|
||||
fragment.view!!,
|
||||
testContext.getString(R.string.snackbar_private_tab_closed),
|
||||
testContext.getString(R.string.snackbar_deleted_undo),
|
||||
any(),
|
||||
any(),
|
||||
null,
|
||||
TabsTrayFragment.ELEVATION,
|
||||
false
|
||||
) }
|
||||
} finally {
|
||||
unmockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
unmockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN showUndoSnackbarForTab is called for a normal tab with new tab button visible THEN an appropriate snackbar is shown`() {
|
||||
try {
|
||||
mockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
|
||||
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
|
||||
val newTabButton: ExtendedFloatingActionButton = mockk {
|
||||
every { visibility } returns View.VISIBLE
|
||||
}
|
||||
every { fragment.new_tab_button } returns newTabButton
|
||||
every { fragment.context } returns testContext // needed for getString()
|
||||
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
fragment.showUndoSnackbarForTab(false)
|
||||
|
||||
verify { lifecycleScope.allowUndo(
|
||||
fragment.view!!,
|
||||
testContext.getString(R.string.snackbar_tab_closed),
|
||||
testContext.getString(R.string.snackbar_deleted_undo),
|
||||
any(),
|
||||
any(),
|
||||
newTabButton,
|
||||
TabsTrayFragment.ELEVATION,
|
||||
false
|
||||
) }
|
||||
} finally {
|
||||
unmockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
unmockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN showUndoSnackbarForTab is called for a normal tab with new tab button not visible THEN an appropriate snackbar is shown`() {
|
||||
try {
|
||||
mockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
mockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
val lifecycleScope: LifecycleCoroutineScope = mockk(relaxed = true)
|
||||
every { any<LifecycleOwner>().lifecycleScope } returns lifecycleScope
|
||||
val newTabButton: ExtendedFloatingActionButton = mockk {
|
||||
every { visibility } returns View.GONE
|
||||
}
|
||||
every { fragment.new_tab_button } returns newTabButton
|
||||
every { fragment.context } returns testContext // needed for getString()
|
||||
every { any<CoroutineScope>().allowUndo(any(), any(), any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
fragment.showUndoSnackbarForTab(false)
|
||||
|
||||
verify { lifecycleScope.allowUndo(
|
||||
fragment.view!!,
|
||||
testContext.getString(R.string.snackbar_tab_closed),
|
||||
testContext.getString(R.string.snackbar_deleted_undo),
|
||||
any(),
|
||||
any(),
|
||||
null,
|
||||
TabsTrayFragment.ELEVATION,
|
||||
false
|
||||
) }
|
||||
} finally {
|
||||
unmockkStatic("org.mozilla.fenix.utils.UndoKt")
|
||||
unmockkStatic("androidx.lifecycle.LifecycleOwnerKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN setupPager is called THEN it sets the tray adapter and disables user initiated scrolling`() {
|
||||
val tray: ViewPager2 = mockk(relaxed = true)
|
||||
val store: TabsTrayStore = mockk()
|
||||
val trayInteractor: TabsTrayInteractor = mockk()
|
||||
val browserInteractor: BrowserTrayInteractor = mockk()
|
||||
val navigationInteractor: NavigationInteractor = mockk()
|
||||
val browserStore: BrowserStore = mockk()
|
||||
every { fragment.tabsTray } returns tray
|
||||
every { context.components.core.store } returns browserStore
|
||||
val adapterSlot = slot<TrayPagerAdapter>()
|
||||
|
||||
fragment.setupPager(
|
||||
context, store, trayInteractor, browserInteractor, navigationInteractor
|
||||
)
|
||||
|
||||
verify { tray.adapter = capture(adapterSlot) }
|
||||
assertSame(context, adapterSlot.captured.context)
|
||||
assertSame(store, adapterSlot.captured.store)
|
||||
assertSame(trayInteractor, adapterSlot.captured.interactor)
|
||||
assertSame(browserInteractor, adapterSlot.captured.browserInteractor)
|
||||
assertSame(navigationInteractor, adapterSlot.captured.navInteractor)
|
||||
assertSame(browserStore, adapterSlot.captured.browserStore)
|
||||
verify { tray.isUserInputEnabled = false }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN setupMenu is called THEN it sets a 3 dot menu click listener to open the tray menu`() {
|
||||
try {
|
||||
mockkStatic("org.mozilla.fenix.tabstray.ext.BrowserMenuKt")
|
||||
val navigationInteractor: NavigationInteractor = mockk()
|
||||
val threeDotMenu = ImageButton(testContext)
|
||||
every { view.tab_tray_overflow } returns threeDotMenu
|
||||
val metrics: MetricController = mockk(relaxed = true)
|
||||
every { context.components.analytics.metrics } returns metrics
|
||||
every { context.components.core.store } returns mockk()
|
||||
every { fragment.tabsTrayStore } returns mockk()
|
||||
every { fragment.tab_layout } returns mockk<TabLayout>()
|
||||
val menu: BrowserMenu = mockk {
|
||||
every { showWithTheme(any()) } just Runs
|
||||
}
|
||||
val menuBuilder: MenuIntegration = mockk(relaxed = true) {
|
||||
every { build() } returns menu
|
||||
}
|
||||
every { fragment.getTrayMenu(any(), any(), any(), any(), any()) } returns menuBuilder
|
||||
|
||||
fragment.setupMenu(view, navigationInteractor)
|
||||
threeDotMenu.performClick()
|
||||
|
||||
verify { metrics.track(Event.TabsTrayMenuOpened) }
|
||||
verify { menuBuilder.build() }
|
||||
verify { menu.showWithTheme(threeDotMenu) }
|
||||
} finally {
|
||||
unmockkStatic("org.mozilla.fenix.tabstray.ext.BrowserMenuKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN getTrayMenu is called THEN it returns a MenuIntegration initialized with the passed in parameters`() {
|
||||
val browserStore: BrowserStore = mockk()
|
||||
val tabsTrayStore: TabsTrayStore = mockk()
|
||||
val tabLayout: TabLayout = mockk()
|
||||
val navigationInteractor: NavigationInteractor = mockk()
|
||||
|
||||
val result = fragment.getTrayMenu(context, browserStore, tabsTrayStore, tabLayout, navigationInteractor)
|
||||
|
||||
assertSame(context, result.context)
|
||||
assertSame(browserStore, result.browserStore)
|
||||
assertSame(tabsTrayStore, result.tabsTrayStore)
|
||||
assertSame(tabLayout, result.tabLayout)
|
||||
assertSame(navigationInteractor, result.navigationInteractor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN setupBackgroundDismissalListener is called THEN it sets a click listener for tray's tabLayout and handle`() {
|
||||
var clickCount = 0
|
||||
val callback: (View) -> Unit = { clickCount++ }
|
||||
val tabLayout = CoordinatorLayout(testContext)
|
||||
val handle = Button(testContext)
|
||||
every { fragment.tabLayout } returns tabLayout
|
||||
every { fragment.handle } returns handle
|
||||
|
||||
fragment.setupBackgroundDismissalListener(callback)
|
||||
|
||||
tabLayout.performClick()
|
||||
assertEquals(1, clickCount)
|
||||
handle.performClick()
|
||||
assertEquals(2, clickCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN dismissTabsTrayAndNavigateHome is called with a sessionId THEN it navigates to home to delete that sessions and dismisses the tray`() {
|
||||
every { fragment.navigateToHomeAndDeleteSession(any()) } just Runs
|
||||
every { fragment.dismissTabsTray() } just Runs
|
||||
|
||||
fragment.dismissTabsTrayAndNavigateHome("test")
|
||||
|
||||
verify { fragment.navigateToHomeAndDeleteSession("test") }
|
||||
verify { fragment.dismissTabsTray() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigateToHomeAndDeleteSession is called with a sessionId THEN it navigates to home and transmits there the sessionId`() {
|
||||
try {
|
||||
mockkStatic("androidx.fragment.app.FragmentViewModelLazyKt")
|
||||
mockkStatic("androidx.navigation.fragment.FragmentKt")
|
||||
mockkStatic("org.mozilla.fenix.ext.NavControllerKt")
|
||||
val viewModel: HomeScreenViewModel = mockk(relaxed = true)
|
||||
every { fragment.homeViewModel } returns viewModel
|
||||
val navController: NavController = mockk(relaxed = true)
|
||||
every { fragment.findNavController() } returns navController
|
||||
|
||||
fragment.navigateToHomeAndDeleteSession("test")
|
||||
|
||||
verify { viewModel.sessionToDelete = "test" }
|
||||
verify { navController.navigateBlockingForAsyncNavGraph(NavGraphDirections.actionGlobalHome()) }
|
||||
} finally {
|
||||
unmockkStatic("org.mozilla.fenix.ext.NavControllerKt")
|
||||
unmockkStatic("androidx.navigation.fragment.FragmentKt")
|
||||
unmockkStatic("androidx.fragment.app.FragmentViewModelLazyKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN selectTabPosition is called with a position and smooth scroll indication THEN it scrolls to that tab and selects it`() {
|
||||
val tabsTray: ViewPager2 = mockk(relaxed = true)
|
||||
val tab: TabLayout.Tab = mockk(relaxed = true)
|
||||
val tabLayout: TabLayout = mockk {
|
||||
every { getTabAt(any()) } returns tab
|
||||
}
|
||||
every { fragment.tab_layout } returns tabLayout
|
||||
every { fragment.tabsTray } returns tabsTray
|
||||
|
||||
fragment.selectTabPosition(2, true)
|
||||
|
||||
verify { tabsTray.setCurrentItem(2, true) }
|
||||
verify { tab.select() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN dismissTabsTray is called THEN it dismisses the tray and record this event`() {
|
||||
every { fragment.dismissAllowingStateLoss() } just Runs
|
||||
val metrics: MetricController = mockk(relaxed = true)
|
||||
every { context.components.analytics.metrics } returns metrics
|
||||
|
||||
fragment.dismissTabsTray()
|
||||
|
||||
verify { fragment.dismissAllowingStateLoss() }
|
||||
verify { metrics.track(Event.TabsTrayClosed) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue