You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/test/java/org/mozilla/fenix/tabstray/DefaultTabsTrayControllerTe...

420 lines
16 KiB
Kotlin

/* 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
@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(
TabsTrayFragmentDirections.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(
TabsTrayFragmentDirections.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") }
}
}