diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt index 67bd16500c..a3a6abb34a 100644 --- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedAdapter.kt @@ -9,28 +9,42 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import mozilla.components.browser.state.state.recover.RecoverableTab +import org.mozilla.fenix.selection.SelectionHolder class RecentlyClosedAdapter( private val interactor: RecentlyClosedFragmentInteractor -) : ListAdapter(DiffCallback) { +) : ListAdapter(DiffCallback), + SelectionHolder { + + private var selectedTabs: Set = emptySet() + override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): RecentlyClosedItemViewHolder { val view = LayoutInflater.from(parent.context) .inflate(RecentlyClosedItemViewHolder.LAYOUT_ID, parent, false) - return RecentlyClosedItemViewHolder(view, interactor) + return RecentlyClosedItemViewHolder(view, interactor, this) } override fun onBindViewHolder(holder: RecentlyClosedItemViewHolder, position: Int) { holder.bind(getItem(position)) } + override val selectedItems: Set + get() = selectedTabs + + fun updateData(tabs: List, selectedTabs: Set) { + this.selectedTabs = selectedTabs + notifyItemRangeChanged(0, tabs.size) + submitList(tabs) + } + private object DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: RecoverableTab, newItem: RecoverableTab) = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: RecoverableTab, newItem: RecoverableTab) = - oldItem.id == newItem.id + oldItem == newItem } } diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt index 47f1b29359..d1b3c18805 100644 --- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt @@ -21,18 +21,27 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph +@Suppress("TooManyFunctions") interface RecentlyClosedController { - fun handleOpen(item: RecoverableTab, mode: BrowsingMode? = null) - fun handleDeleteOne(tab: RecoverableTab) + fun handleOpen(tab: RecoverableTab, mode: BrowsingMode? = null) + fun handleOpen(tabs: Set, mode: BrowsingMode? = null) + fun handleDelete(tab: RecoverableTab) + fun handleDelete(tabs: Set) fun handleCopyUrl(item: RecoverableTab) - fun handleShare(item: RecoverableTab) + fun handleShare(tab: RecoverableTab) + fun handleShare(tabs: Set) fun handleNavigateToHistory() fun handleRestore(item: RecoverableTab) + fun handleSelect(tab: RecoverableTab) + fun handleDeselect(tab: RecoverableTab) + fun handleBackPressed(): Boolean } +@Suppress("TooManyFunctions") class DefaultRecentlyClosedController( private val navController: NavController, - private val store: BrowserStore, + private val browserStore: BrowserStore, + private val recentlyClosedStore: RecentlyClosedFragmentStore, private val tabsUseCases: TabsUseCases, private val resources: Resources, private val snackbar: FenixSnackbar, @@ -40,12 +49,30 @@ class DefaultRecentlyClosedController( private val activity: HomeActivity, private val openToBrowser: (item: RecoverableTab, mode: BrowsingMode?) -> Unit ) : RecentlyClosedController { - override fun handleOpen(item: RecoverableTab, mode: BrowsingMode?) { - openToBrowser(item, mode) + override fun handleOpen(tab: RecoverableTab, mode: BrowsingMode?) { + openToBrowser(tab, mode) } - override fun handleDeleteOne(tab: RecoverableTab) { - store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tab)) + override fun handleOpen(tabs: Set, mode: BrowsingMode?) { + recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll) + tabs.forEach { tab -> handleOpen(tab, mode) } + } + + override fun handleSelect(tab: RecoverableTab) { + recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Select(tab)) + } + + override fun handleDeselect(tab: RecoverableTab) { + recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Deselect(tab)) + } + + override fun handleDelete(tab: RecoverableTab) { + browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tab)) + } + + override fun handleDelete(tabs: Set) { + recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll) + tabs.forEach { tab -> handleDelete(tab) } } override fun handleNavigateToHistory() { @@ -64,10 +91,13 @@ class DefaultRecentlyClosedController( } } - override fun handleShare(item: RecoverableTab) { + override fun handleShare(tab: RecoverableTab) = handleShare(setOf(tab)) + + override fun handleShare(tabs: Set) { + val shareData = tabs.map { ShareData(url = it.url, title = it.title) } navController.navigateBlockingForAsyncNavGraph( RecentlyClosedFragmentDirections.actionGlobalShareFragment( - data = arrayOf(ShareData(url = item.url, title = item.title)) + data = shareData.toTypedArray() ) ) } @@ -75,7 +105,7 @@ class DefaultRecentlyClosedController( override fun handleRestore(item: RecoverableTab) { tabsUseCases.restore(item) - store.dispatch( + browserStore.dispatch( RecentlyClosedAction.RemoveClosedTabAction(item) ) @@ -83,4 +113,13 @@ class DefaultRecentlyClosedController( from = BrowserDirection.FromRecentlyClosed ) } + + override fun handleBackPressed(): Boolean { + return if (recentlyClosedStore.state.selectedTabs.isNotEmpty()) { + recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll) + true + } else { + false + } + } } diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt index 01e2188f63..7505df7e95 100644 --- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.library.recentlyclosed import android.content.ClipboardManager import android.content.Context import android.os.Bundle +import android.text.SpannableString import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -21,6 +22,7 @@ import kotlinx.coroutines.flow.map import mozilla.components.browser.state.state.recover.RecoverableTab import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.flowScoped +import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity @@ -30,17 +32,19 @@ import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.ext.getRootView import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.setTextColor import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.library.LibraryPageFragment @Suppress("TooManyFunctions") -class RecentlyClosedFragment : LibraryPageFragment() { +class RecentlyClosedFragment : LibraryPageFragment(), UserInteractionHandler { private lateinit var recentlyClosedFragmentStore: RecentlyClosedFragmentStore private var _recentlyClosedFragmentView: RecentlyClosedFragmentView? = null protected val recentlyClosedFragmentView: RecentlyClosedFragmentView get() = _recentlyClosedFragmentView!! private lateinit var recentlyClosedInteractor: RecentlyClosedFragmentInteractor + private lateinit var recentlyClosedController: RecentlyClosedController override fun onResume() { super.onResume() @@ -48,15 +52,43 @@ class RecentlyClosedFragment : LibraryPageFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.library_menu, menu) + if (recentlyClosedFragmentStore.state.selectedTabs.isNotEmpty()) { + inflater.inflate(R.menu.history_select_multi, menu) + menu.findItem(R.id.delete_history_multi_select)?.let { deleteItem -> + deleteItem.title = SpannableString(deleteItem.title) + .apply { setTextColor(requireContext(), R.attr.destructive) } + } + } else { + inflater.inflate(R.menu.library_menu, menu) + } } - override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.close_history -> { - close() - true + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val selectedTabs = recentlyClosedFragmentStore.state.selectedTabs + + return when (item.itemId) { + R.id.close_history -> { + close() + true + } + R.id.share_history_multi_select -> { + recentlyClosedController.handleShare(selectedTabs) + true + } + R.id.delete_history_multi_select -> { + recentlyClosedController.handleDelete(selectedTabs) + true + } + R.id.open_history_in_new_tabs_multi_select -> { + recentlyClosedController.handleOpen(selectedTabs, BrowsingMode.Normal) + true + } + R.id.open_history_in_private_tabs_multi_select -> { + recentlyClosedController.handleOpen(selectedTabs, BrowsingMode.Private) + true + } + else -> super.onOptionsItemSelected(item) } - else -> super.onOptionsItemSelected(item) } override fun onCreate(savedInstanceState: Bundle?) { @@ -73,25 +105,26 @@ class RecentlyClosedFragment : LibraryPageFragment() { recentlyClosedFragmentStore = StoreProvider.get(this) { RecentlyClosedFragmentStore( RecentlyClosedFragmentState( - items = listOf() + items = listOf(), + selectedTabs = emptySet() ) ) } - recentlyClosedInteractor = RecentlyClosedFragmentInteractor( - recentlyClosedController = DefaultRecentlyClosedController( - navController = findNavController(), - store = requireComponents.core.store, - activity = activity as HomeActivity, - tabsUseCases = requireComponents.useCases.tabsUseCases, - resources = requireContext().resources, - snackbar = FenixSnackbar.make( - view = requireActivity().getRootView()!!, - isDisplayedWithBrowserToolbar = true - ), - clipboardManager = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager, - openToBrowser = ::openItem - ) + recentlyClosedController = DefaultRecentlyClosedController( + navController = findNavController(), + browserStore = requireComponents.core.store, + recentlyClosedStore = recentlyClosedFragmentStore, + activity = activity as HomeActivity, + tabsUseCases = requireComponents.useCases.tabsUseCases, + resources = requireContext().resources, + snackbar = FenixSnackbar.make( + view = requireActivity().getRootView()!!, + isDisplayedWithBrowserToolbar = true + ), + clipboardManager = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager, + openToBrowser = ::openItem ) + recentlyClosedInteractor = RecentlyClosedFragmentInteractor(recentlyClosedController) _recentlyClosedFragmentView = RecentlyClosedFragmentView( view.recentlyClosedLayout, recentlyClosedInteractor @@ -116,8 +149,9 @@ class RecentlyClosedFragment : LibraryPageFragment() { @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - consumeFrom(recentlyClosedFragmentStore) { - recentlyClosedFragmentView.update(it.items) + consumeFrom(recentlyClosedFragmentStore) { state -> + recentlyClosedFragmentView.update(state) + activity?.invalidateOptionsMenu() } requireComponents.core.store.flowScoped(viewLifecycleOwner) { flow -> @@ -132,4 +166,8 @@ class RecentlyClosedFragment : LibraryPageFragment() { } override val selectedItems: Set = setOf() + + override fun onBackPressed(): Boolean { + return recentlyClosedController.handleBackPressed() + } } diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt index a4ffed2dc2..8c1469adea 100644 --- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractor.kt @@ -34,11 +34,23 @@ class RecentlyClosedFragmentInteractor( recentlyClosedController.handleOpen(item, BrowsingMode.Private) } - override fun onDeleteOne(tab: RecoverableTab) { - recentlyClosedController.handleDeleteOne(tab) + override fun onDelete(tab: RecoverableTab) { + recentlyClosedController.handleDelete(tab) } override fun onNavigateToHistory() { recentlyClosedController.handleNavigateToHistory() } + + override fun open(item: RecoverableTab) { + recentlyClosedController.handleRestore(item) + } + + override fun select(item: RecoverableTab) { + recentlyClosedController.handleSelect(item) + } + + override fun deselect(item: RecoverableTab) { + recentlyClosedController.handleDeselect(item) + } } diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt index 37b4d14c09..849f6bcd03 100644 --- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentStore.kt @@ -24,13 +24,19 @@ class RecentlyClosedFragmentStore(initialState: RecentlyClosedFragmentState) : */ sealed class RecentlyClosedFragmentAction : Action { data class Change(val list: List) : RecentlyClosedFragmentAction() + data class Select(val tab: RecoverableTab) : RecentlyClosedFragmentAction() + data class Deselect(val tab: RecoverableTab) : RecentlyClosedFragmentAction() + object DeselectAll : RecentlyClosedFragmentAction() } /** * The state for the Recently Closed Screen * @property items List of recently closed tabs to display */ -data class RecentlyClosedFragmentState(val items: List = emptyList()) : State +data class RecentlyClosedFragmentState( + val items: List = emptyList(), + val selectedTabs: Set +) : State /** * The RecentlyClosedFragmentState Reducer. @@ -41,5 +47,12 @@ private fun recentlyClosedStateReducer( ): RecentlyClosedFragmentState { return when (action) { is RecentlyClosedFragmentAction.Change -> state.copy(items = action.list) + is RecentlyClosedFragmentAction.Select -> { + state.copy(selectedTabs = state.selectedTabs + action.tab) + } + is RecentlyClosedFragmentAction.Deselect -> { + state.copy(selectedTabs = state.selectedTabs - action.tab) + } + RecentlyClosedFragmentAction.DeselectAll -> state.copy(selectedTabs = emptySet()) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt index 740e82fa00..0b9054ee4c 100644 --- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentView.kt @@ -5,17 +5,19 @@ package org.mozilla.fenix.library.recentlyclosed import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources -import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.extensions.LayoutContainer +import androidx.recyclerview.widget.SimpleItemAnimator import kotlinx.android.synthetic.main.component_recently_closed.* import mozilla.components.browser.state.state.recover.RecoverableTab import org.mozilla.fenix.R +import org.mozilla.fenix.library.LibraryPageView +import org.mozilla.fenix.selection.SelectionInteractor -interface RecentlyClosedInteractor { +interface RecentlyClosedInteractor : SelectionInteractor { /** * Called when an item is tapped to restore it. * @@ -57,11 +59,11 @@ interface RecentlyClosedInteractor { fun onOpenInPrivateTab(item: RecoverableTab) /** - * Deletes one recently closed tab item. + * Called when recently closed tab is selected for deletion. * - * @param tab the recently closed tab item to delete. + * @param tab the recently closed tab to delete. */ - fun onDeleteOne(tab: RecoverableTab) + fun onDelete(tab: RecoverableTab) } /** @@ -70,11 +72,10 @@ interface RecentlyClosedInteractor { class RecentlyClosedFragmentView( container: ViewGroup, private val interactor: RecentlyClosedFragmentInteractor -) : LayoutContainer { +) : LibraryPageView(container) { - override val containerView: ConstraintLayout = LayoutInflater.from(container.context) + val view: View = LayoutInflater.from(container.context) .inflate(R.layout.component_recently_closed, container, true) - .findViewById(R.id.recently_closed_wrapper) private val recentlyClosedAdapter: RecentlyClosedAdapter = RecentlyClosedAdapter(interactor) @@ -82,6 +83,7 @@ class RecentlyClosedFragmentView( recently_closed_list.apply { layoutManager = LinearLayoutManager(containerView.context) adapter = recentlyClosedAdapter + (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false } view_more_history.apply { @@ -102,9 +104,20 @@ class RecentlyClosedFragmentView( } } - fun update(items: List) { - recently_closed_empty_view.isVisible = items.isEmpty() - recently_closed_list.isVisible = items.isNotEmpty() - recentlyClosedAdapter.submitList(items) + fun update(state: RecentlyClosedFragmentState) { + state.apply { + recently_closed_empty_view.isVisible = items.isEmpty() + recently_closed_list.isVisible = items.isNotEmpty() + + recentlyClosedAdapter.updateData(items, selectedTabs) + + if (selectedTabs.isEmpty()) { + setUiForNormalMode(context.getString(R.string.library_recently_closed_tabs)) + } else { + setUiForSelectingMode( + context.getString(R.string.history_multi_select_title, selectedTabs.size) + ) + } + } } } diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt index 7d8e1f3466..03373a94de 100644 --- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedItemViewHolder.kt @@ -7,14 +7,19 @@ package org.mozilla.fenix.library.recentlyclosed import android.view.View import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.history_list_item.view.* +import kotlinx.android.synthetic.main.library_site_item.view.* import mozilla.components.browser.state.state.recover.RecoverableTab import org.mozilla.fenix.R +import org.mozilla.fenix.ext.hideAndDisable +import org.mozilla.fenix.ext.showAndEnable +import org.mozilla.fenix.selection.SelectionHolder import org.mozilla.fenix.library.history.HistoryItemMenu import org.mozilla.fenix.utils.Do class RecentlyClosedItemViewHolder( view: View, - private val recentlyClosedFragmentInteractor: RecentlyClosedFragmentInteractor + private val recentlyClosedFragmentInteractor: RecentlyClosedFragmentInteractor, + private val selectionHolder: SelectionHolder ) : RecyclerView.ViewHolder(view) { private var item: RecoverableTab? = null @@ -30,12 +35,17 @@ class RecentlyClosedItemViewHolder( if (item.title.isNotEmpty()) item.title else item.url itemView.history_layout.urlView.text = item.url + itemView.history_layout.setSelectionInteractor(item, selectionHolder, recentlyClosedFragmentInteractor) + itemView.history_layout.changeSelected(item in selectionHolder.selectedItems) + if (this.item?.url != item.url) { itemView.history_layout.loadFavicon(item.url) } - itemView.setOnClickListener { - recentlyClosedFragmentInteractor.restore(item) + if (selectionHolder.selectedItems.isEmpty()) { + itemView.overflow_menu.showAndEnable() + } else { + itemView.overflow_menu.hideAndDisable() } this.item = item @@ -53,9 +63,7 @@ class RecentlyClosedItemViewHolder( HistoryItemMenu.Item.OpenInPrivateTab -> recentlyClosedFragmentInteractor.onOpenInPrivateTab( item ) - HistoryItemMenu.Item.Delete -> recentlyClosedFragmentInteractor.onDeleteOne( - item - ) + HistoryItemMenu.Item.Delete -> recentlyClosedFragmentInteractor.onDelete(item) } } diff --git a/app/src/main/res/layout/component_recently_closed.xml b/app/src/main/res/layout/component_recently_closed.xml index e0b70db2c4..feaf9e690e 100644 --- a/app/src/main/res/layout/component_recently_closed.xml +++ b/app/src/main/res/layout/component_recently_closed.xml @@ -18,7 +18,8 @@ diff --git a/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt index 98825ea993..07b2355c57 100644 --- a/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/DefaultRecentlyClosedControllerTest.kt @@ -32,6 +32,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.ext.directionsEq +import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph import org.mozilla.fenix.ext.optionsEq import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @@ -46,13 +47,15 @@ class DefaultRecentlyClosedControllerTest { private val clipboardManager: ClipboardManager = mockk(relaxed = true) private val openToBrowser: (RecoverableTab, BrowsingMode?) -> Unit = mockk(relaxed = true) private val activity: HomeActivity = mockk(relaxed = true) - private val store: BrowserStore = mockk(relaxed = true) + private val browserStore: BrowserStore = mockk(relaxed = true) + private val recentlyClosedStore: RecentlyClosedFragmentStore = mockk(relaxed = true) private val tabsUseCases: TabsUseCases = mockk(relaxed = true) val mockedTab: RecoverableTab = mockk(relaxed = true) private val controller = DefaultRecentlyClosedController( navController, - store, + browserStore, + recentlyClosedStore, tabsUseCases, resources, snackbar, @@ -89,13 +92,62 @@ class DefaultRecentlyClosedControllerTest { } @Test - fun handleDeleteOne() { + fun `open multiple tabs`() { + val tabs = createFakeTabList(2) + + controller.handleOpen(tabs.toSet(), BrowsingMode.Normal) + + verify { + openToBrowser(tabs[0], BrowsingMode.Normal) + openToBrowser(tabs[1], BrowsingMode.Normal) + } + + controller.handleOpen(tabs.toSet(), BrowsingMode.Private) + + verify { + openToBrowser(tabs[0], BrowsingMode.Private) + openToBrowser(tabs[1], BrowsingMode.Private) + } + } + + @Test + fun `handle select tab`() { + val selectedTab = createFakeTab() + + controller.handleSelect(selectedTab) + + verify { recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Select(selectedTab)) } + } + + @Test + fun `handle deselect tab`() { + val deselectedTab = createFakeTab() + + controller.handleDeselect(deselectedTab) + + verify { recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.Deselect(deselectedTab)) } + } + + @Test + fun handleDelete() { val item: RecoverableTab = mockk(relaxed = true) - controller.handleDeleteOne(item) + controller.handleDelete(item) verify { - store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(item)) + browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(item)) + } + } + + @Test + fun `delete multiple tabs`() { + val tabs = createFakeTabList(2) + + controller.handleDelete(tabs.toSet()) + + verify { + browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tabs[0])) + browserStore.dispatch(RecentlyClosedAction.RemoveClosedTabAction(tabs[1])) } } @@ -104,7 +156,7 @@ class DefaultRecentlyClosedControllerTest { controller.handleNavigateToHistory() verify { - navController.navigate( + navController.navigateBlockingForAsyncNavGraph( directionsEq( RecentlyClosedFragmentDirections.actionGlobalHistoryFragment() ), @@ -139,7 +191,7 @@ class DefaultRecentlyClosedControllerTest { controller.handleShare(item) verify { - navController.navigate( + navController.navigateBlockingForAsyncNavGraph( directionsEq( RecentlyClosedFragmentDirections.actionGlobalShareFragment( data = arrayOf(ShareData(url = item.url, title = item.title)) @@ -149,6 +201,23 @@ class DefaultRecentlyClosedControllerTest { } } + @Test + fun `share multiple tabs`() { + val tabs = createFakeTabList(2) + + controller.handleShare(tabs.toSet()) + + verify { + val data = arrayOf( + ShareData(title = tabs[0].title, url = tabs[0].url), + ShareData(title = tabs[1].title, url = tabs[1].url) + ) + navController.navigateBlockingForAsyncNavGraph( + directionsEq(RecentlyClosedFragmentDirections.actionGlobalShareFragment(data)) + ) + } + } + @Test fun handleRestore() { controller.handleRestore(mockedTab) @@ -157,4 +226,25 @@ class DefaultRecentlyClosedControllerTest { verify { tabsUseCases.restore.invoke(mockedTab, true) } } + + @Test + fun `exist multi-select mode when back is pressed`() { + every { recentlyClosedStore.state.selectedTabs } returns createFakeTabList(3).toSet() + + controller.handleBackPressed() + + verify { recentlyClosedStore.dispatch(RecentlyClosedFragmentAction.DeselectAll) } + } + + private fun createFakeTab(id: String = "FakeId", url: String = "www.fake.com"): RecoverableTab = + RecoverableTab(id, url) + + private fun createFakeTabList(size: Int): List { + val fakeTabs = mutableListOf() + for (i in 0 until size) { + fakeTabs.add(createFakeTab(id = "FakeId$i")) + } + + return fakeTabs + } } diff --git a/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt index 934ce58eaa..9ba2a9ee1e 100644 --- a/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragmentInteractorTest.kt @@ -76,12 +76,12 @@ class RecentlyClosedFragmentInteractorTest { } @Test - fun onDeleteOne() { + fun onDelete() { val tab = RecoverableTab(id = "tab-id", title = "Mozilla", url = "mozilla.org", lastAccess = 1L) - interactor.onDeleteOne(tab) + interactor.onDelete(tab) verify { - defaultRecentlyClosedController.handleDeleteOne(tab) + defaultRecentlyClosedController.handleDelete(tab) } }