[fenix] For https://github.com/mozilla-mobile/fenix/issues/28265 - Refactor InactiveTabs interactor/controller into TabsTray

pull/600/head
Noah Bond 1 year ago committed by mergify[bot]
parent 638d8e03b5
commit 71e79fa494

@ -29,19 +29,24 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.ext.DEFAULT_ACTIVE_DAYS
import org.mozilla.fenix.ext.potentialInactiveTabs
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.tabstray.browser.InactiveTabsController
import org.mozilla.fenix.tabstray.browser.SelectTabUseCaseWrapper
import org.mozilla.fenix.tabstray.ext.isActiveDownload
import org.mozilla.fenix.tabstray.ext.isSelect
import org.mozilla.fenix.utils.Settings
import java.util.concurrent.TimeUnit
import org.mozilla.fenix.GleanMetrics.Tab as GleanTab
/**
* Controller for handling any actions in the tabs tray.
*/
interface TabsTrayController : SyncedTabsController {
interface TabsTrayController : SyncedTabsController, InactiveTabsController {
/**
* Called to open a new tab.
@ -170,7 +175,9 @@ interface TabsTrayController : SyncedTabsController {
* Default implementation of [TabsTrayController].
*
* @property activity [HomeActivity] used to perform top-level app actions.
* @property appStore [AppStore] used to dispatch any [AppAction].
* @property tabsTrayStore [TabsTrayStore] used to read/update the [TabsTrayState].
* @property settings [Settings] used to update any user preferences.
* @property browserStore [BrowserStore] used to read/update the current [BrowserState].
* @property browsingModeManager [BrowsingModeManager] used to read/update the current [BrowsingMode].
* @property navController [NavController] used to navigate away from the tabs tray.
@ -185,8 +192,10 @@ interface TabsTrayController : SyncedTabsController {
@Suppress("TooManyFunctions", "LongParameterList")
class DefaultTabsTrayController(
private val activity: HomeActivity,
private val appStore: AppStore,
private val tabsTrayStore: TabsTrayStore,
private val browserStore: BrowserStore,
private val settings: Settings,
private val browsingModeManager: BrowsingModeManager,
private val navController: NavController,
private val navigateToHomeAndDeleteSession: (String) -> Unit,
@ -424,4 +433,52 @@ class DefaultTabsTrayController(
}
return false
}
override fun handleInactiveTabClicked(tab: TabSessionState) {
TabsTray.openInactiveTab.add()
handleTabSelected(tab, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME)
}
override fun handleCloseInactiveTabClicked(tab: TabSessionState) {
TabsTray.closeInactiveTab.add()
handleTabDeletion(tab.id, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME)
}
override fun handleInactiveTabsHeaderClicked(expanded: Boolean) {
appStore.dispatch(AppAction.UpdateInactiveExpanded(expanded))
when (expanded) {
true -> TabsTray.inactiveTabsExpanded.record(NoExtras())
false -> TabsTray.inactiveTabsCollapsed.record(NoExtras())
}
}
override fun handleInactiveTabsAutoCloseDialogDismiss() {
markDialogAsShown()
TabsTray.autoCloseDimissed.record(NoExtras())
}
override fun handleEnableInactiveTabsAutoCloseClicked() {
markDialogAsShown()
settings.closeTabsAfterOneMonth = true
settings.closeTabsAfterOneWeek = false
settings.closeTabsAfterOneDay = false
settings.manuallyCloseTabs = false
TabsTray.autoCloseTurnOnClicked.record(NoExtras())
}
override fun handleDeleteAllInactiveTabsClicked() {
TabsTray.closeAllInactiveTabs.record(NoExtras())
browserStore.state.potentialInactiveTabs.map { it.id }.let {
tabsUseCases.removeTabs(it)
}
showUndoSnackbarForTab(false)
}
/**
* Marks the inactive tabs auto close dialog as shown and to not be displayed again.
*/
private fun markDialogAsShown() {
settings.hasInactiveTabsAutoCloseDialogBeenDismissed = true
}
}

@ -49,9 +49,6 @@ import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeScreenViewModel
import org.mozilla.fenix.share.ShareFragment
import org.mozilla.fenix.tabstray.browser.DefaultInactiveTabsController
import org.mozilla.fenix.tabstray.browser.DefaultInactiveTabsInteractor
import org.mozilla.fenix.tabstray.browser.InactiveTabsInteractor
import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding
import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModifier
import org.mozilla.fenix.tabstray.browser.SelectionHandleBinding
@ -80,7 +77,6 @@ class TabsTrayFragment : AppCompatDialogFragment() {
@VisibleForTesting internal lateinit var tabsTrayStore: TabsTrayStore
private lateinit var tabsTrayInteractor: TabsTrayInteractor
private lateinit var tabsTrayController: DefaultTabsTrayController
private lateinit var inactiveTabsInteractor: DefaultInactiveTabsInteractor
private lateinit var navigationInteractor: DefaultNavigationInteractor
@VisibleForTesting internal lateinit var trayBehaviorManager: TabSheetBehaviorManager
@ -210,8 +206,10 @@ class TabsTrayFragment : AppCompatDialogFragment() {
tabsTrayController = DefaultTabsTrayController(
activity = activity,
appStore = requireComponents.appStore,
tabsTrayStore = tabsTrayStore,
browserStore = requireComponents.core.store,
settings = requireContext().settings(),
browsingModeManager = activity.browsingModeManager,
navController = findNavController(),
navigateToHomeAndDeleteSession = ::navigateToHomeAndDeleteSession,
@ -228,24 +226,12 @@ class TabsTrayFragment : AppCompatDialogFragment() {
controller = tabsTrayController,
)
inactiveTabsInteractor = DefaultInactiveTabsInteractor(
controller = DefaultInactiveTabsController(
appStore = requireComponents.appStore,
settings = requireContext().settings(),
browserStore = requireComponents.core.store,
tabsUseCases = requireComponents.useCases.tabsUseCases,
showUndoSnackbar = ::showUndoSnackbarForTab,
),
tabsTrayInteractor = tabsTrayInteractor,
)
setupMenu(navigationInteractor)
setupPager(
context = view.context,
lifecycleOwner = viewLifecycleOwner,
store = tabsTrayStore,
trayInteractor = tabsTrayInteractor,
inactiveTabsInteractor = inactiveTabsInteractor,
)
setupBackgroundDismissalListener {
@ -485,17 +471,15 @@ class TabsTrayFragment : AppCompatDialogFragment() {
lifecycleOwner: LifecycleOwner,
store: TabsTrayStore,
trayInteractor: TabsTrayInteractor,
inactiveTabsInteractor: InactiveTabsInteractor,
) {
tabsTrayBinding.tabsTray.apply {
adapter = TrayPagerAdapter(
context = context,
lifecycleOwner = lifecycleOwner,
tabsTrayStore = store,
tabsTrayInteractor = trayInteractor,
interactor = trayInteractor,
browserStore = requireComponents.core.store,
appStore = requireComponents.appStore,
inactiveTabsInteractor = inactiveTabsInteractor,
)
isUserInputEnabled = false
}

@ -8,11 +8,12 @@ import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.storage.sync.Tab
import mozilla.components.browser.tabstray.TabsTray
import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.tabstray.browser.InactiveTabsInteractor
/**
* Interactor for responding to all user actions in the tabs tray.
*/
interface TabsTrayInteractor : SyncedTabsInteractor, TabsTray.Delegate {
interface TabsTrayInteractor : SyncedTabsInteractor, TabsTray.Delegate, InactiveTabsInteractor {
/**
* Invoked when a page in the tabs tray is selected.
*
@ -187,4 +188,46 @@ class DefaultTabsTrayInteractor(
override fun onTabUnselected(tab: TabSessionState) {
controller.handleTabUnselected(tab)
}
/**
* See [InactiveTabsInteractor.onInactiveTabsHeaderClicked].
*/
override fun onInactiveTabsHeaderClicked(expanded: Boolean) {
controller.handleInactiveTabsHeaderClicked(expanded)
}
/**
* See [InactiveTabsInteractor.onAutoCloseDialogCloseButtonClicked].
*/
override fun onAutoCloseDialogCloseButtonClicked() {
controller.handleInactiveTabsAutoCloseDialogDismiss()
}
/**
* See [InactiveTabsInteractor.onEnableAutoCloseClicked].
*/
override fun onEnableAutoCloseClicked() {
controller.handleEnableInactiveTabsAutoCloseClicked()
}
/**
* See [InactiveTabsInteractor.onInactiveTabClicked].
*/
override fun onInactiveTabClicked(tab: TabSessionState) {
controller.handleInactiveTabClicked(tab)
}
/**
* See [InactiveTabsInteractor.onInactiveTabClosed].
*/
override fun onInactiveTabClosed(tab: TabSessionState) {
controller.handleCloseInactiveTabClicked(tab)
}
/**
* See [InactiveTabsInteractor.onDeleteAllInactiveTabsClicked].
*/
override fun onDeleteAllInactiveTabsClicked() {
controller.handleDeleteAllInactiveTabsClicked()
}
}

@ -15,7 +15,6 @@ import mozilla.components.browser.state.store.BrowserStore
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
import org.mozilla.fenix.tabstray.browser.InactiveTabsAdapter
import org.mozilla.fenix.tabstray.browser.InactiveTabsInteractor
import org.mozilla.fenix.tabstray.viewholders.AbstractPageViewHolder
import org.mozilla.fenix.tabstray.viewholders.NormalBrowserPageViewHolder
import org.mozilla.fenix.tabstray.viewholders.PrivateBrowserPageViewHolder
@ -26,10 +25,9 @@ class TrayPagerAdapter(
internal val context: Context,
internal val lifecycleOwner: LifecycleOwner,
internal val tabsTrayStore: TabsTrayStore,
internal val tabsTrayInteractor: TabsTrayInteractor,
internal val interactor: TabsTrayInteractor,
internal val browserStore: BrowserStore,
internal val appStore: AppStore,
internal val inactiveTabsInteractor: InactiveTabsInteractor,
) : RecyclerView.Adapter<AbstractPageViewHolder>() {
/**
@ -42,17 +40,17 @@ class TrayPagerAdapter(
InactiveTabsAdapter(
lifecycleOwner = lifecycleOwner,
tabsTrayStore = tabsTrayStore,
inactiveTabsInteractor = inactiveTabsInteractor,
interactor = interactor,
featureName = INACTIVE_TABS_FEATURE_NAME,
),
BrowserTabsAdapter(context, tabsTrayInteractor, tabsTrayStore, TABS_TRAY_FEATURE_NAME, lifecycleOwner),
BrowserTabsAdapter(context, interactor, tabsTrayStore, TABS_TRAY_FEATURE_NAME, lifecycleOwner),
)
}
private val privateAdapter by lazy {
BrowserTabsAdapter(
context,
tabsTrayInteractor,
interactor,
tabsTrayStore,
TABS_TRAY_FEATURE_NAME,
lifecycleOwner,
@ -68,7 +66,7 @@ class TrayPagerAdapter(
tabsTrayStore,
browserStore,
appStore,
tabsTrayInteractor,
interactor,
)
}
PrivateBrowserPageViewHolder.LAYOUT_ID -> {
@ -76,7 +74,7 @@ class TrayPagerAdapter(
LayoutInflater.from(parent.context).inflate(viewType, parent, false),
tabsTrayStore,
browserStore,
tabsTrayInteractor,
interactor,
)
}
SyncedTabsPageViewHolder.LAYOUT_ID -> {
@ -88,7 +86,7 @@ class TrayPagerAdapter(
)
},
tabsTrayStore = tabsTrayStore,
interactor = tabsTrayInteractor,
interactor = interactor,
)
}
else -> throw IllegalStateException("Unknown viewType.")

@ -14,17 +14,17 @@ import org.mozilla.fenix.tabstray.TabsTrayStore
/**
* The adapter for displaying the section of inactive tabs.
*
* @param lifecycleOwner [LifecycleOwner] to which the Composable will be tied to.
* @param tabsTrayStore [TabsTrayStore] used to listen for changes to [TabsTrayState.inactiveTabs].
* @param inactiveTabsInteractor [InactiveTabsInteractor] used to respond to interactions with the inactive tabs header
* @property lifecycleOwner [LifecycleOwner] to which the Composable will be tied to.
* @property tabsTrayStore [TabsTrayStore] used to listen for changes to [TabsTrayState.inactiveTabs].
* @property interactor [InactiveTabsInteractor] used to respond to interactions with the inactive tabs header
* and the auto close dialog.
* @param featureName [String] representing the name of the inactive tabs feature for telemetry reporting.
* @property featureName [String] representing the name of the inactive tabs feature for telemetry reporting.
*/
@Suppress("LongParameterList")
class InactiveTabsAdapter(
private val lifecycleOwner: LifecycleOwner,
private val tabsTrayStore: TabsTrayStore,
private val inactiveTabsInteractor: InactiveTabsInteractor,
private val interactor: InactiveTabsInteractor,
override val featureName: String,
) : RecyclerView.Adapter<InactiveTabViewHolder>(), FeatureNameHolder {
@ -35,7 +35,7 @@ class InactiveTabsAdapter(
composeView = ComposeView(parent.context),
lifecycleOwner = lifecycleOwner,
tabsTrayStore = tabsTrayStore,
interactor = inactiveTabsInteractor,
interactor = interactor,
)
}

@ -5,15 +5,6 @@
package org.mozilla.fenix.tabstray.browser
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.telemetry.glean.private.NoExtras
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppAction.UpdateInactiveExpanded
import org.mozilla.fenix.ext.potentialInactiveTabs
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.GleanMetrics.TabsTray as TabsTrayMetrics
/**
* Contract for how all user interactions with the Inactive Tabs feature are to be handled.
@ -56,67 +47,3 @@ interface InactiveTabsController {
*/
fun handleDeleteAllInactiveTabsClicked()
}
/**
* Default behavior for handling all user interactions with the Inactive Tabs feature.
*
* @param appStore [AppStore] used to dispatch any [AppAction].
* @param settings [Settings] used to update any user preferences.
* @param browserStore [BrowserStore] used to obtain all inactive tabs.
* @param tabsUseCases [TabsUseCases] used to perform the deletion of all inactive tabs.
* @param showUndoSnackbar Invoked when deleting all inactive tabs.
*/
class DefaultInactiveTabsController(
private val appStore: AppStore,
private val settings: Settings,
private val browserStore: BrowserStore,
private val tabsUseCases: TabsUseCases,
private val showUndoSnackbar: (Boolean) -> Unit,
) : InactiveTabsController {
override fun handleInactiveTabClicked(tab: TabSessionState) {
TabsTrayMetrics.openInactiveTab.add()
}
override fun handleCloseInactiveTabClicked(tab: TabSessionState) {
TabsTrayMetrics.closeInactiveTab.add()
}
override fun handleInactiveTabsHeaderClicked(expanded: Boolean) {
appStore.dispatch(UpdateInactiveExpanded(expanded))
when (expanded) {
true -> TabsTrayMetrics.inactiveTabsExpanded.record(NoExtras())
false -> TabsTrayMetrics.inactiveTabsCollapsed.record(NoExtras())
}
}
override fun handleInactiveTabsAutoCloseDialogDismiss() {
markDialogAsShown()
TabsTrayMetrics.autoCloseDimissed.record(NoExtras())
}
override fun handleEnableInactiveTabsAutoCloseClicked() {
markDialogAsShown()
settings.closeTabsAfterOneMonth = true
settings.closeTabsAfterOneWeek = false
settings.closeTabsAfterOneDay = false
settings.manuallyCloseTabs = false
TabsTrayMetrics.autoCloseTurnOnClicked.record(NoExtras())
}
override fun handleDeleteAllInactiveTabsClicked() {
TabsTrayMetrics.closeAllInactiveTabs.record(NoExtras())
browserStore.state.potentialInactiveTabs.map { it.id }.let {
tabsUseCases.removeTabs(it)
}
showUndoSnackbar(false)
}
/**
* Marks the dialog as shown and to not be displayed again.
*/
private fun markDialogAsShown() {
settings.hasInactiveTabsAutoCloseDialogBeenDismissed = true
}
}

@ -5,8 +5,6 @@
package org.mozilla.fenix.tabstray.browser
import mozilla.components.browser.state.state.TabSessionState
import org.mozilla.fenix.tabstray.TabsTrayInteractor
import org.mozilla.fenix.tabstray.TrayPagerAdapter
/**
* Interactor for all things related to inactive tabs in the tabs tray.
@ -48,61 +46,3 @@ interface InactiveTabsInteractor {
*/
fun onEnableAutoCloseClicked()
}
/**
* Interactor to be called for any user interactions with the Inactive Tabs feature.
*
* @param controller An instance of [InactiveTabsController] which will be delegated for all
* user interactions.
* @param tabsTrayInteractor [TabsTrayInteractor] used to respond to interactions with specific
* inactive tabs.
*/
class DefaultInactiveTabsInteractor(
private val controller: InactiveTabsController,
private val tabsTrayInteractor: TabsTrayInteractor,
) : InactiveTabsInteractor {
/**
* See [InactiveTabsInteractor.onInactiveTabsHeaderClicked].
*/
override fun onInactiveTabsHeaderClicked(expanded: Boolean) {
controller.handleInactiveTabsHeaderClicked(expanded)
}
/**
* See [InactiveTabsInteractor.onAutoCloseDialogCloseButtonClicked].
*/
override fun onAutoCloseDialogCloseButtonClicked() {
controller.handleInactiveTabsAutoCloseDialogDismiss()
}
/**
* See [InactiveTabsInteractor.onEnableAutoCloseClicked].
*/
override fun onEnableAutoCloseClicked() {
controller.handleEnableInactiveTabsAutoCloseClicked()
}
/**
* See [InactiveTabsInteractor.onInactiveTabClicked].
*/
override fun onInactiveTabClicked(tab: TabSessionState) {
controller.handleInactiveTabClicked(tab)
tabsTrayInteractor.onTabSelected(tab, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME)
}
/**
* See [InactiveTabsInteractor.onInactiveTabClosed].
*/
override fun onInactiveTabClosed(tab: TabSessionState) {
controller.handleCloseInactiveTabClicked(tab)
tabsTrayInteractor.onTabClosed(tab, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME)
}
/**
* See [InactiveTabsInteractor.onDeleteAllInactiveTabsClicked].
*/
override fun onDeleteAllInactiveTabsClicked() {
controller.handleDeleteAllInactiveTabsClicked()
}
}

@ -10,8 +10,10 @@ import androidx.navigation.NavOptions
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.spyk
import io.mockk.unmockkStatic
import io.mockk.verify
@ -19,6 +21,7 @@ import io.mockk.verifyOrder
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.state.createTab
@ -46,8 +49,12 @@ import org.mozilla.fenix.GleanMetrics.TabsTray
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.ext.maxActiveTime
import org.mozilla.fenix.ext.potentialInactiveTabs
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.utils.Settings
import java.util.concurrent.TimeUnit
@RunWith(FenixRobolectricTestRunner::class) // for gleanTestRule
@ -76,6 +83,9 @@ class DefaultTabsTrayControllerTest {
@MockK(relaxed = true)
private lateinit var activity: HomeActivity
private val appStore: AppStore = mockk(relaxed = true)
private val settings: Settings = mockk(relaxed = true)
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
@ -572,6 +582,135 @@ class DefaultTabsTrayControllerTest {
}
}
fun `WHEN the inactive tabs section is expanded THEN the expanded telemetry event should be reported`() {
val controller = createController()
assertNull(TabsTray.inactiveTabsExpanded.testGetValue())
assertNull(TabsTray.inactiveTabsCollapsed.testGetValue())
controller.handleInactiveTabsHeaderClicked(expanded = true)
assertNotNull(TabsTray.inactiveTabsExpanded.testGetValue())
assertNull(TabsTray.inactiveTabsCollapsed.testGetValue())
}
@Test
fun `WHEN the inactive tabs section is collapsed THEN the collapsed telemetry event should be reported`() {
val controller = createController()
assertNull(TabsTray.inactiveTabsExpanded.testGetValue())
assertNull(TabsTray.inactiveTabsCollapsed.testGetValue())
controller.handleInactiveTabsHeaderClicked(expanded = false)
assertNull(TabsTray.inactiveTabsExpanded.testGetValue())
assertNotNull(TabsTray.inactiveTabsCollapsed.testGetValue())
}
@Test
fun `WHEN the inactive tabs auto-close feature prompt is dismissed THEN update settings and report the telemetry event`() {
val controller = spyk(createController())
assertNull(TabsTray.autoCloseDimissed.testGetValue())
controller.handleInactiveTabsAutoCloseDialogDismiss()
assertNotNull(TabsTray.autoCloseDimissed.testGetValue())
verify { settings.hasInactiveTabsAutoCloseDialogBeenDismissed = true }
}
@Test
fun `WHEN the inactive tabs auto-close feature prompt is accepted THEN update settings and report the telemetry event`() {
val controller = spyk(createController())
assertNull(TabsTray.autoCloseTurnOnClicked.testGetValue())
controller.handleEnableInactiveTabsAutoCloseClicked()
assertNotNull(TabsTray.autoCloseTurnOnClicked.testGetValue())
verify { settings.closeTabsAfterOneMonth = true }
verify { settings.closeTabsAfterOneWeek = false }
verify { settings.closeTabsAfterOneDay = false }
verify { settings.manuallyCloseTabs = false }
verify { settings.hasInactiveTabsAutoCloseDialogBeenDismissed = true }
}
@Test
fun `WHEN an inactive tab is selected THEN report the telemetry event and open the tab`() {
val controller = spyk(createController())
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
every { controller.handleTabSelected(any(), any()) } just runs
assertNull(TabsTray.openInactiveTab.testGetValue())
controller.handleInactiveTabClicked(tab)
assertNotNull(TabsTray.openInactiveTab.testGetValue())
verify { controller.handleTabSelected(tab, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME) }
}
@Test
fun `WHEN an inactive tab is closed THEN report the telemetry event and delete the tab`() {
val controller = spyk(createController())
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
every { controller.handleTabDeletion(any(), any()) } just runs
assertNull(TabsTray.closeInactiveTab.testGetValue())
controller.handleCloseInactiveTabClicked(tab)
assertNotNull(TabsTray.closeInactiveTab.testGetValue())
verify { controller.handleTabDeletion(tab.id, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME) }
}
@Test
fun `WHEN all inactive tabs are closed THEN perform the deletion and report the telemetry event and show a Snackbar`() {
var showSnackbarInvoked = false
val controller = createController(
showUndoSnackbarForTab = {
showSnackbarInvoked = true
},
)
val inactiveTab: TabSessionState = mockk {
every { lastAccess } returns maxActiveTime
every { createdAt } returns 0
every { id } returns "24"
every { content } returns mockk {
every { private } returns false
}
}
try {
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
every { browserStore.state } returns mockk()
every { browserStore.state.potentialInactiveTabs } returns listOf(inactiveTab)
assertNull(TabsTray.closeAllInactiveTabs.testGetValue())
controller.handleDeleteAllInactiveTabsClicked()
verify { tabsUseCases.removeTabs(listOf("24")) }
assertNotNull(TabsTray.closeAllInactiveTabs.testGetValue())
assertTrue(showSnackbarInvoked)
} finally {
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
}
}
private fun createController(
navigateToHomeAndDeleteSession: (String) -> Unit = { },
selectTabPosition: (Int, Boolean) -> Unit = { _, _ -> },
@ -580,18 +719,20 @@ class DefaultTabsTrayControllerTest {
showCancelledDownloadWarning: (Int, String?, String?) -> Unit = { _, _, _ -> },
): DefaultTabsTrayController {
return DefaultTabsTrayController(
activity,
trayStore,
browserStore,
browsingModeManager,
navController,
navigateToHomeAndDeleteSession,
profiler,
navigationInteractor,
tabsUseCases,
selectTabPosition,
dismissTray,
showUndoSnackbarForTab,
activity = activity,
appStore = appStore,
tabsTrayStore = trayStore,
browserStore = browserStore,
settings = settings,
browsingModeManager = browsingModeManager,
navController = navController,
navigateToHomeAndDeleteSession = navigateToHomeAndDeleteSession,
profiler = profiler,
navigationInteractor = navigationInteractor,
tabsUseCases = tabsUseCases,
selectTabPosition = selectTabPosition,
dismissTray = dismissTray,
showUndoSnackbarForTab = showUndoSnackbarForTab,
showCancelledDownloadWarning = showCancelledDownloadWarning,
)
}

@ -6,14 +6,16 @@ package org.mozilla.fenix.tabstray
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.mockk.verifySequence
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import org.junit.Test
class DefaultTabsTrayInteractorTest {
val controller: TabsTrayController = mockk(relaxed = true)
val interactor = DefaultTabsTrayInteractor(controller)
private val controller: TabsTrayController = mockk(relaxed = true)
private val interactor = DefaultTabsTrayInteractor(controller)
@Test
fun `WHEN user selects a new tray page THEN the Interactor delegates to the controller`() {
@ -55,4 +57,60 @@ class DefaultTabsTrayInteractorTest {
verifySequence { controller.handleMultipleTabsDeletion(tabsToDelete) }
}
@Test
fun `WHEN the inactive tabs header is clicked THEN update the expansion state of the inactive tabs card`() {
interactor.onInactiveTabsHeaderClicked(true)
verify { controller.handleInactiveTabsHeaderClicked(true) }
}
@Test
fun `WHEN the inactive tabs auto close dialog's close button is clicked THEN dismiss the dialog`() {
interactor.onAutoCloseDialogCloseButtonClicked()
verify { controller.handleInactiveTabsAutoCloseDialogDismiss() }
}
@Test
fun `WHEN the enable inactive tabs auto close button is clicked THEN turn on the auto close feature`() {
interactor.onEnableAutoCloseClicked()
verify { controller.handleEnableInactiveTabsAutoCloseClicked() }
}
@Test
fun `WHEN an inactive tab is clicked THEN open the tab`() {
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
interactor.onInactiveTabClicked(tab)
verify { controller.handleInactiveTabClicked(tab) }
}
@Test
fun `WHEN an inactive tab is clicked to be closed THEN close the tab`() {
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
interactor.onInactiveTabClosed(tab)
verify { controller.handleCloseInactiveTabClicked(tab) }
}
@Test
fun `WHEN the close all inactive tabs button is clicked THEN delete all inactive tabs`() {
interactor.onDeleteAllInactiveTabsClicked()
verify { controller.handleDeleteAllInactiveTabsClicked() }
}
}

@ -53,7 +53,6 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.helpers.MockkRetryTestRule
import org.mozilla.fenix.home.HomeScreenViewModel
import org.mozilla.fenix.tabstray.browser.InactiveTabsInteractor
import org.mozilla.fenix.tabstray.ext.showWithTheme
import org.mozilla.fenix.utils.allowUndo
@ -221,7 +220,6 @@ class TabsTrayFragmentTest {
val store: TabsTrayStore = mockk()
val lifecycleOwner = mockk<LifecycleOwner>(relaxed = true)
val trayInteractor: TabsTrayInteractor = mockk()
val inactiveTabsInteractor: InactiveTabsInteractor = mockk()
val browserStore: BrowserStore = mockk()
every { context.components.core.store } returns browserStore
@ -230,14 +228,13 @@ class TabsTrayFragmentTest {
lifecycleOwner = lifecycleOwner,
store = store,
trayInteractor = trayInteractor,
inactiveTabsInteractor = inactiveTabsInteractor,
)
val adapter = (tabsTrayBinding.tabsTray.adapter as TrayPagerAdapter)
assertSame(context, adapter.context)
assertSame(lifecycleOwner, adapter.lifecycleOwner)
assertSame(store, adapter.tabsTrayStore)
assertSame(trayInteractor, adapter.tabsTrayInteractor)
assertSame(trayInteractor, adapter.interactor)
assertSame(browserStore, adapter.browserStore)
assertFalse(tabsTrayBinding.tabsTray.isUserInputEnabled)
}

@ -1,176 +0,0 @@
/* 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.browser
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.unmockkStatic
import io.mockk.verify
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.ext.maxActiveTime
import org.mozilla.fenix.ext.potentialInactiveTabs
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.GleanMetrics.TabsTray as TabsTrayMetrics
@RunWith(FenixRobolectricTestRunner::class)
class DefaultInactiveTabsControllerTest {
private val appStore: AppStore = mockk(relaxed = true)
private val settings: Settings = mockk(relaxed = true)
private val browserStore: BrowserStore = mockk(relaxed = true)
private val tabsUseCases: TabsUseCases = mockk(relaxed = true)
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
@Test
fun `WHEN the inactive tabs section is expanded THEN the expanded telemetry event should be reported`() {
val controller = createController()
assertNull(TabsTrayMetrics.inactiveTabsExpanded.testGetValue())
assertNull(TabsTrayMetrics.inactiveTabsCollapsed.testGetValue())
controller.handleInactiveTabsHeaderClicked(expanded = true)
assertNotNull(TabsTrayMetrics.inactiveTabsExpanded.testGetValue())
assertNull(TabsTrayMetrics.inactiveTabsCollapsed.testGetValue())
}
@Test
fun `WHEN the inactive tabs section is collapsed THEN the collapsed telemetry event should be reported`() {
val controller = createController()
assertNull(TabsTrayMetrics.inactiveTabsExpanded.testGetValue())
assertNull(TabsTrayMetrics.inactiveTabsCollapsed.testGetValue())
controller.handleInactiveTabsHeaderClicked(expanded = false)
assertNull(TabsTrayMetrics.inactiveTabsExpanded.testGetValue())
assertNotNull(TabsTrayMetrics.inactiveTabsCollapsed.testGetValue())
}
@Test
fun `WHEN the inactive tabs auto-close feature prompt is dismissed THEN update settings and report the telemetry event`() {
val controller = spyk(createController())
assertNull(TabsTrayMetrics.autoCloseDimissed.testGetValue())
controller.handleInactiveTabsAutoCloseDialogDismiss()
assertNotNull(TabsTrayMetrics.autoCloseDimissed.testGetValue())
verify { settings.hasInactiveTabsAutoCloseDialogBeenDismissed = true }
}
@Test
fun `WHEN the inactive tabs auto-close feature prompt is accepted THEN update settings and report the telemetry event`() {
val controller = spyk(createController())
assertNull(TabsTrayMetrics.autoCloseTurnOnClicked.testGetValue())
controller.handleEnableInactiveTabsAutoCloseClicked()
assertNotNull(TabsTrayMetrics.autoCloseTurnOnClicked.testGetValue())
verify { settings.closeTabsAfterOneMonth = true }
verify { settings.closeTabsAfterOneWeek = false }
verify { settings.closeTabsAfterOneDay = false }
verify { settings.manuallyCloseTabs = false }
verify { settings.hasInactiveTabsAutoCloseDialogBeenDismissed = true }
}
@Test
fun `WHEN an inactive tab is selected THEN report the telemetry event`() {
val controller = createController()
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
assertNull(TabsTrayMetrics.openInactiveTab.testGetValue())
controller.handleInactiveTabClicked(tab)
assertNotNull(TabsTrayMetrics.openInactiveTab.testGetValue())
}
@Test
fun `WHEN an inactive tab is closed THEN report the telemetry event`() {
val controller = createController()
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
assertNull(TabsTrayMetrics.closeInactiveTab.testGetValue())
controller.handleCloseInactiveTabClicked(tab)
assertNotNull(TabsTrayMetrics.closeInactiveTab.testGetValue())
}
@Test
fun `WHEN all inactive tabs are closed THEN perform the deletion and report the telemetry event and show a Snackbar`() {
var showSnackbarInvoked = false
val controller = createController(
showUndoSnackbar = {
showSnackbarInvoked = true
},
)
val inactiveTab: TabSessionState = mockk {
every { lastAccess } returns maxActiveTime
every { createdAt } returns 0
every { id } returns "24"
every { content } returns mockk {
every { private } returns false
}
}
try {
mockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
every { browserStore.state } returns mockk()
every { browserStore.state.potentialInactiveTabs } returns listOf(inactiveTab)
assertNull(TabsTrayMetrics.closeAllInactiveTabs.testGetValue())
controller.handleDeleteAllInactiveTabsClicked()
verify { tabsUseCases.removeTabs(listOf("24")) }
assertNotNull(TabsTrayMetrics.closeAllInactiveTabs.testGetValue())
assertTrue(showSnackbarInvoked)
} finally {
unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt")
}
}
private fun createController(
showUndoSnackbar: (Boolean) -> Unit = { _ -> },
): DefaultInactiveTabsController {
return DefaultInactiveTabsController(
appStore = appStore,
settings = settings,
browserStore = browserStore,
tabsUseCases = tabsUseCases,
showUndoSnackbar = showUndoSnackbar,
)
}
}

@ -1,84 +0,0 @@
/* 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.browser
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import org.junit.Test
import org.mozilla.fenix.tabstray.TabsTrayInteractor
import org.mozilla.fenix.tabstray.TrayPagerAdapter
class DefaultInactiveTabsInteractorTest {
private val controller: InactiveTabsController = mockk(relaxed = true)
private val interactor: TabsTrayInteractor = mockk(relaxed = true)
@Test
fun `WHEN the inactive tabs header is clicked THEN update the expansion state of the inactive tabs card`() {
createInteractor().onInactiveTabsHeaderClicked(true)
verify { controller.handleInactiveTabsHeaderClicked(true) }
}
@Test
fun `WHEN the inactive tabs auto close dialog's close button is clicked THEN dismiss the dialog`() {
createInteractor().onAutoCloseDialogCloseButtonClicked()
verify { controller.handleInactiveTabsAutoCloseDialogDismiss() }
}
@Test
fun `WHEN the enable inactive tabs auto close button is clicked THEN turn on the auto close feature`() {
createInteractor().onEnableAutoCloseClicked()
verify { controller.handleEnableInactiveTabsAutoCloseClicked() }
}
@Test
fun `WHEN an inactive tab is clicked THEN open the tab`() {
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
createInteractor().onInactiveTabClicked(tab)
verify { controller.handleInactiveTabClicked(tab) }
verify { interactor.onTabSelected(tab, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME) }
}
@Test
fun `WHEN an inactive tab is clicked to be closed THEN close the tab`() {
val tab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
createInteractor().onInactiveTabClosed(tab)
verify { controller.handleCloseInactiveTabClicked(tab) }
verify { interactor.onTabClosed(tab, TrayPagerAdapter.INACTIVE_TABS_FEATURE_NAME) }
}
@Test
fun `WHEN the close all inactive tabs button is clicked THEN delete all inactive tabs`() {
createInteractor().onDeleteAllInactiveTabsClicked()
verify { controller.handleDeleteAllInactiveTabsClicked() }
}
private fun createInteractor(): DefaultInactiveTabsInteractor {
return DefaultInactiveTabsInteractor(
controller = controller,
tabsTrayInteractor = interactor,
)
}
}
Loading…
Cancel
Save