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.
415 lines
16 KiB
Kotlin
415 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 androidx.navigation.NavOptions
|
|
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.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.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
|
|
|
|
@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.navigate(
|
|
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.navigate(
|
|
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.navigate(any<Int>()) }
|
|
verify(exactly = 0) { navController.navigate(any<NavDirections>()) }
|
|
verify(exactly = 0) { navController.navigate(any<NavDirections>(), any<NavOptions>()) }
|
|
}
|
|
|
|
@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.navigate(any<Int>()) }
|
|
verify(exactly = 0) { navController.navigate(any<NavDirections>()) }
|
|
verify(exactly = 0) { navController.navigate(any<NavDirections>(), any<NavOptions>()) }
|
|
}
|
|
|
|
@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.navigate(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.navigate(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") }
|
|
}
|
|
}
|