[fenix] No issue: refactor tabs tray to use interactor/controller, add tests
parent
3eda4d3fc3
commit
96c09a6a4d
@ -0,0 +1,124 @@
|
|||||||
|
/* 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.tabtray
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import mozilla.components.browser.session.Session
|
||||||
|
import mozilla.components.browser.session.SessionManager
|
||||||
|
import mozilla.components.concept.engine.prompt.ShareData
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||||
|
import org.mozilla.fenix.collections.SaveCollectionStep
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.ext.sessionsOfType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [TabTrayDialogFragment] controller.
|
||||||
|
*
|
||||||
|
* Delegated by View Interactors, handles container business logic and operates changes on it.
|
||||||
|
*/
|
||||||
|
interface TabTrayController {
|
||||||
|
fun onNewTabTapped(private: Boolean)
|
||||||
|
fun onTabTrayDismissed()
|
||||||
|
fun onShareTabsClicked(private: Boolean)
|
||||||
|
fun onSaveToCollectionClicked()
|
||||||
|
fun onCloseAllTabsClicked(private: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
|
class DefaultTabTrayController(
|
||||||
|
private val activity: HomeActivity,
|
||||||
|
private val navController: NavController,
|
||||||
|
private val dismissTabTray: () -> Unit,
|
||||||
|
private val showUndoSnackbar: (String, SessionManager.Snapshot) -> Unit,
|
||||||
|
private val registerCollectionStorageObserver: () -> Unit
|
||||||
|
) : TabTrayController {
|
||||||
|
override fun onNewTabTapped(private: Boolean) {
|
||||||
|
activity.browsingModeManager.mode = BrowsingMode.fromBoolean(private)
|
||||||
|
navController.navigate(TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true))
|
||||||
|
dismissTabTray()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabTrayDismissed() {
|
||||||
|
dismissTabTray()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveToCollectionClicked() {
|
||||||
|
val tabs = getListOfSessions(false)
|
||||||
|
val tabIds = tabs.map { it.id }.toList().toTypedArray()
|
||||||
|
val tabCollectionStorage = activity.components.core.tabCollectionStorage
|
||||||
|
|
||||||
|
val step = when {
|
||||||
|
// Show the SelectTabs fragment if there are multiple opened tabs to select which tabs
|
||||||
|
// you want to save to a collection.
|
||||||
|
tabs.size > 1 -> SaveCollectionStep.SelectTabs
|
||||||
|
// If there is an existing tab collection, show the SelectCollection fragment to save
|
||||||
|
// the selected tab to a collection of your choice.
|
||||||
|
tabCollectionStorage.cachedTabCollections.isNotEmpty() -> SaveCollectionStep.SelectCollection
|
||||||
|
// Show the NameCollection fragment to create a new collection for the selected tab.
|
||||||
|
else -> SaveCollectionStep.NameCollection
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navController.currentDestination?.id == R.id.collectionCreationFragment) return
|
||||||
|
|
||||||
|
// Only register the observer right before moving to collection creation
|
||||||
|
registerCollectionStorageObserver()
|
||||||
|
|
||||||
|
val directions = TabTrayDialogFragmentDirections.actionGlobalCollectionCreationFragment(
|
||||||
|
tabIds = tabIds,
|
||||||
|
saveCollectionStep = step,
|
||||||
|
selectedTabIds = tabIds
|
||||||
|
)
|
||||||
|
navController.navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareTabsClicked(private: Boolean) {
|
||||||
|
val tabs = getListOfSessions(private)
|
||||||
|
val data = tabs.map {
|
||||||
|
ShareData(url = it.url, title = it.title)
|
||||||
|
}
|
||||||
|
val directions = TabTrayDialogFragmentDirections.actionGlobalShareFragment(
|
||||||
|
data = data.toTypedArray()
|
||||||
|
)
|
||||||
|
navController.navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCloseAllTabsClicked(private: Boolean) {
|
||||||
|
val sessionManager = activity.components.core.sessionManager
|
||||||
|
val tabs = getListOfSessions(private)
|
||||||
|
|
||||||
|
val selectedIndex = sessionManager
|
||||||
|
.selectedSession?.let { sessionManager.sessions.indexOf(it) } ?: 0
|
||||||
|
|
||||||
|
val snapshot = tabs
|
||||||
|
.map(sessionManager::createSessionSnapshot)
|
||||||
|
.map {
|
||||||
|
it.copy(
|
||||||
|
engineSession = null,
|
||||||
|
engineSessionState = it.engineSession?.saveState()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.let { SessionManager.Snapshot(it, selectedIndex) }
|
||||||
|
|
||||||
|
tabs.forEach {
|
||||||
|
sessionManager.remove(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val snackbarMessage = if (private) {
|
||||||
|
activity.getString(R.string.snackbar_private_tabs_closed)
|
||||||
|
} else {
|
||||||
|
activity.getString(R.string.snackbar_tabs_closed)
|
||||||
|
}
|
||||||
|
|
||||||
|
showUndoSnackbar(snackbarMessage, snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
|
private fun getListOfSessions(private: Boolean): List<Session> {
|
||||||
|
return activity.components.core.sessionManager.sessionsOfType(private = private).toList()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/* 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.tabtray
|
||||||
|
|
||||||
|
interface TabTrayInteractor {
|
||||||
|
fun onNewTabTapped(private: Boolean)
|
||||||
|
fun onTabTrayDismissed()
|
||||||
|
fun onShareTabsClicked(private: Boolean)
|
||||||
|
fun onSaveToCollectionClicked()
|
||||||
|
fun onCloseAllTabsClicked(private: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactor for the tab tray fragment.
|
||||||
|
*/
|
||||||
|
class TabTrayFragmentInteractor(private val controller: TabTrayController) : TabTrayInteractor {
|
||||||
|
override fun onNewTabTapped(private: Boolean) {
|
||||||
|
controller.onNewTabTapped(private)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabTrayDismissed() {
|
||||||
|
controller.onTabTrayDismissed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareTabsClicked(private: Boolean) {
|
||||||
|
controller.onShareTabsClicked(private)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveToCollectionClicked() {
|
||||||
|
controller.onSaveToCollectionClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCloseAllTabsClicked(private: Boolean) {
|
||||||
|
controller.onCloseAllTabsClicked(private)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
/* 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.tabtray
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
|
import androidx.navigation.NavDirections
|
||||||
|
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.verify
|
||||||
|
import io.mockk.verifyOrder
|
||||||
|
import mozilla.components.browser.session.Session
|
||||||
|
import mozilla.components.browser.session.SessionManager
|
||||||
|
import mozilla.components.feature.tab.collections.TabCollection
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||||
|
import org.mozilla.fenix.components.TabCollectionStorage
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.ext.sessionsOfType
|
||||||
|
|
||||||
|
class DefaultTabTrayControllerTest {
|
||||||
|
|
||||||
|
private val activity: HomeActivity = mockk(relaxed = true)
|
||||||
|
private val navController: NavController = mockk()
|
||||||
|
private val sessionManager: SessionManager = mockk(relaxed = true)
|
||||||
|
private val dismissTabTray: (() -> Unit) = mockk(relaxed = true)
|
||||||
|
private val showUndoSnackbar: ((String, SessionManager.Snapshot) -> Unit) =
|
||||||
|
mockk(relaxed = true)
|
||||||
|
private val registerCollectionStorageObserver: (() -> Unit) = mockk(relaxed = true)
|
||||||
|
private val tabCollectionStorage: TabCollectionStorage = mockk(relaxed = true)
|
||||||
|
private val tabCollection: TabCollection = mockk()
|
||||||
|
private val cachedTabCollections: List<TabCollection> = listOf(tabCollection)
|
||||||
|
private val currentDestination: NavDestination = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private lateinit var controller: DefaultTabTrayController
|
||||||
|
|
||||||
|
private val session = Session(
|
||||||
|
"mozilla.org",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
private val nonPrivateSession = Session(
|
||||||
|
"mozilla.org",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkStatic("org.mozilla.fenix.ext.SessionManagerKt")
|
||||||
|
|
||||||
|
every { activity.components.core.sessionManager } returns sessionManager
|
||||||
|
every { activity.components.core.tabCollectionStorage } returns tabCollectionStorage
|
||||||
|
every { sessionManager.sessionsOfType(private = true) } returns listOf(session).asSequence()
|
||||||
|
every { sessionManager.sessionsOfType(private = false) } returns listOf(nonPrivateSession).asSequence()
|
||||||
|
every { sessionManager.createSessionSnapshot(any()) } returns SessionManager.Snapshot.Item(
|
||||||
|
session
|
||||||
|
)
|
||||||
|
every { sessionManager.remove(any()) } just Runs
|
||||||
|
every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollections
|
||||||
|
every { sessionManager.selectedSession } returns nonPrivateSession
|
||||||
|
every { navController.navigate(any<NavDirections>()) } just Runs
|
||||||
|
every { navController.currentDestination } returns currentDestination
|
||||||
|
every { currentDestination.id } returns R.id.browserFragment
|
||||||
|
|
||||||
|
controller = DefaultTabTrayController(
|
||||||
|
activity = activity,
|
||||||
|
navController = navController,
|
||||||
|
dismissTabTray = dismissTabTray,
|
||||||
|
showUndoSnackbar = showUndoSnackbar,
|
||||||
|
registerCollectionStorageObserver = registerCollectionStorageObserver
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onNewTabTapped() {
|
||||||
|
controller.onNewTabTapped(private = false)
|
||||||
|
|
||||||
|
verifyOrder {
|
||||||
|
activity.browsingModeManager.mode = BrowsingMode.fromBoolean(false)
|
||||||
|
navController.navigate(
|
||||||
|
TabTrayDialogFragmentDirections.actionGlobalHome(
|
||||||
|
focusOnAddressBar = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dismissTabTray()
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.onNewTabTapped(private = true)
|
||||||
|
|
||||||
|
verifyOrder {
|
||||||
|
activity.browsingModeManager.mode = BrowsingMode.fromBoolean(true)
|
||||||
|
navController.navigate(
|
||||||
|
TabTrayDialogFragmentDirections.actionGlobalHome(
|
||||||
|
focusOnAddressBar = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dismissTabTray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onTabTrayDismissed() {
|
||||||
|
controller.onTabTrayDismissed()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
dismissTabTray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onSaveToCollectionClicked() {
|
||||||
|
val navDirectionsSlot = slot<NavDirections>()
|
||||||
|
every { navController.navigate(capture(navDirectionsSlot)) } just Runs
|
||||||
|
|
||||||
|
controller.onSaveToCollectionClicked()
|
||||||
|
verify {
|
||||||
|
registerCollectionStorageObserver()
|
||||||
|
navController.navigate(capture(navDirectionsSlot))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(navDirectionsSlot.isCaptured)
|
||||||
|
assertEquals(
|
||||||
|
R.id.action_global_collectionCreationFragment,
|
||||||
|
navDirectionsSlot.captured.actionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onShareTabsClicked() {
|
||||||
|
val navDirectionsSlot = slot<NavDirections>()
|
||||||
|
every { navController.navigate(capture(navDirectionsSlot)) } just Runs
|
||||||
|
|
||||||
|
controller.onShareTabsClicked(private = false)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
navController.navigate(capture(navDirectionsSlot))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(navDirectionsSlot.isCaptured)
|
||||||
|
assertEquals(R.id.action_global_shareFragment, navDirectionsSlot.captured.actionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onCloseAllTabsClicked() {
|
||||||
|
controller.onCloseAllTabsClicked(private = false)
|
||||||
|
val snackbarMessage = activity.getString(R.string.snackbar_tabs_closed)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
sessionManager.createSessionSnapshot(nonPrivateSession)
|
||||||
|
sessionManager.remove(nonPrivateSession)
|
||||||
|
showUndoSnackbar(snackbarMessage, any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/* 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.tabtray
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class TabTrayFragmentInteractorTest {
|
||||||
|
private val controller = mockk<TabTrayController>(relaxed = true)
|
||||||
|
private val interactor = TabTrayFragmentInteractor(controller)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onNewTabTapped() {
|
||||||
|
interactor.onNewTabTapped(private = true)
|
||||||
|
verify { controller.onNewTabTapped(true) }
|
||||||
|
|
||||||
|
interactor.onNewTabTapped(private = false)
|
||||||
|
verify { controller.onNewTabTapped(false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onTabTrayDismissed() {
|
||||||
|
interactor.onTabTrayDismissed()
|
||||||
|
verify { controller.onTabTrayDismissed() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onShareTabsClicked() {
|
||||||
|
interactor.onShareTabsClicked(private = true)
|
||||||
|
verify { controller.onShareTabsClicked(true) }
|
||||||
|
|
||||||
|
interactor.onShareTabsClicked(private = false)
|
||||||
|
verify { controller.onShareTabsClicked(false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onSaveToCollectionClicked() {
|
||||||
|
interactor.onSaveToCollectionClicked()
|
||||||
|
verify { controller.onSaveToCollectionClicked() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onCloseAllTabsClicked() {
|
||||||
|
interactor.onCloseAllTabsClicked(private = false)
|
||||||
|
verify { controller.onCloseAllTabsClicked(false) }
|
||||||
|
|
||||||
|
interactor.onCloseAllTabsClicked(private = true)
|
||||||
|
verify { controller.onCloseAllTabsClicked(true) }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue