From 199547f94f06c9b9013397c65aa17ffd7ccd2a14 Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Wed, 3 Nov 2021 22:38:04 -0400 Subject: [PATCH] [fenix] Close https://github.com/mozilla-mobile/fenix/issues/22305: Use TabsTrayStore for populating adapters --- .../fenix/tabstray/TabsTrayFragment.kt | 16 ++ .../browser/AbstractBrowserTrayList.kt | 23 --- .../tabstray/browser/InactiveTabsAdapter.kt | 2 +- .../tabstray/browser/InactiveTabsBinding.kt | 31 ++++ .../browser/InactiveTabsController.kt | 20 +-- .../tabstray/browser/NormalBrowserTrayList.kt | 51 +++--- .../tabstray/browser/NormalTabsBinding.kt | 33 ++++ .../tabstray/browser/OtherHeaderBinding.kt | 31 ++++ .../browser/PrivateBrowserTrayList.kt | 15 +- .../tabstray/browser/PrivateTabsBinding.kt | 33 ++++ .../fenix/tabstray/browser/TabGroupBinding.kt | 29 +++ .../fenix/tabstray/browser/TabSorter.kt | 28 +-- .../browser/InactiveTabsBindingTest.kt | 58 ++++++ .../browser/InactiveTabsControllerTest.kt | 27 ++- .../tabstray/browser/NormalTabsBindingTest.kt | 68 +++++++ .../browser/OtherHeaderBindingTest.kt | 73 ++++++++ .../browser/PrivateTabsBindingTest.kt | 72 ++++++++ .../tabstray/browser/TabGroupBindingTest.kt | 58 ++++++ .../fenix/tabstray/browser/TabSorterTest.kt | 169 ++++++------------ 19 files changed, 618 insertions(+), 219 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBinding.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalTabsBinding.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBinding.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBinding.kt create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupBinding.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBindingTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/browser/NormalTabsBindingTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBindingTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBindingTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/tabstray/browser/TabGroupBindingTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index 0874967cf2..adf9d79da9 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -26,6 +26,7 @@ import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.feature.tabs.tabstray.TabsFeature import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.NavGraphDirections @@ -48,6 +49,7 @@ import org.mozilla.fenix.tabstray.browser.DefaultBrowserTrayInteractor import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModifier import org.mozilla.fenix.tabstray.browser.SelectionHandleBinding +import org.mozilla.fenix.tabstray.browser.TabSorter import org.mozilla.fenix.tabstray.ext.anchorWithAction import org.mozilla.fenix.tabstray.ext.bookmarkMessage import org.mozilla.fenix.tabstray.ext.collectionMessage @@ -72,6 +74,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { private val selectionHandleBinding = ViewBoundFeatureWrapper() private val tabsTrayCtaBinding = ViewBoundFeatureWrapper() private val secureTabsTrayBinding = ViewBoundFeatureWrapper() + private val tabsFeature = ViewBoundFeatureWrapper() private val tabsTrayInactiveTabsOnboardingBinding = ViewBoundFeatureWrapper() @VisibleForTesting @Suppress("VariableNaming") @@ -223,6 +226,19 @@ class TabsTrayFragment : AppCompatDialogFragment() { displayMetrics = requireContext().resources.displayMetrics ) + tabsFeature.set( + feature = TabsFeature( + tabsTray = TabSorter( + requireContext().settings(), + requireContext().components.analytics.metrics, + tabsTrayStore + ), + store = requireContext().components.core.store, + ), + owner = this, + view = view + ) + tabsTrayCtaBinding.set( feature = TabsTrayInfoBannerBinding( context = view.context, diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/AbstractBrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/AbstractBrowserTrayList.kt index 6b40df5265..1ad3518188 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/AbstractBrowserTrayList.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/AbstractBrowserTrayList.kt @@ -7,8 +7,6 @@ package org.mozilla.fenix.tabstray.browser import android.content.Context import android.util.AttributeSet import androidx.recyclerview.widget.RecyclerView -import mozilla.components.feature.tabs.tabstray.TabsFeature -import org.mozilla.fenix.ext.components import org.mozilla.fenix.tabstray.TabsTrayInteractor import org.mozilla.fenix.tabstray.TabsTrayStore @@ -24,27 +22,6 @@ abstract class AbstractBrowserTrayList @JvmOverloads constructor( lateinit var interactor: TabsTrayInteractor lateinit var tabsTrayStore: TabsTrayStore - /** - * A [TabsFeature] is required for each browser list to ensure one always exists for displaying - * tabs. - */ - abstract val tabsFeature: TabsFeature - - // NB: The use cases here are duplicated because there isn't a nicer - // way to share them without a better dependency injection solution. - protected val selectTabUseCase = SelectTabUseCaseWrapper( - context.components.analytics.metrics, - context.components.useCases.tabsUseCases.selectTab - ) { - interactor.onBrowserTabSelected() - } - - protected val removeTabUseCase = RemoveTabUseCaseWrapper( - context.components.analytics.metrics - ) { sessionId -> - interactor.onDeleteTab(sessionId) - } - protected val swipeToDelete by lazy { SwipeToDeleteBinding(tabsTrayStore) } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt index 4d4f8b4b19..9b5002add6 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsAdapter.kt @@ -41,7 +41,7 @@ class InactiveTabsAdapter( ) : Adapter(DiffCallback), TabsTray, FeatureNameHolder { internal lateinit var inactiveTabsInteractor: InactiveTabsInteractor - internal var inActiveTabsCount: Int = 0 + private var inActiveTabsCount: Int = 0 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InactiveTabViewHolder { val view = LayoutInflater.from(parent.context) diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBinding.kt new file mode 100644 index 0000000000..d7939e6908 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBinding.kt @@ -0,0 +1,31 @@ +/* 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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import mozilla.components.browser.tabstray.TabsTray +import mozilla.components.lib.state.helpers.AbstractBinding +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged +import org.mozilla.fenix.tabstray.TabsTrayState +import org.mozilla.fenix.tabstray.TabsTrayStore + +/** + * An inactive tabs observer that updates the provided [TabsTray]. + */ +class InactiveTabsBinding( + store: TabsTrayStore, + private val tray: TabsTray +) : AbstractBinding(store) { + override suspend fun onState(flow: Flow) { + flow.map { it.inactiveTabs } + .ifChanged() + .collect { + // We pass null for the selected tab id here, because inactive tabs doesn't care. + tray.updateTabs(it, null) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsController.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsController.kt index d79c274270..5f8bbb083d 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsController.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabsController.kt @@ -5,19 +5,17 @@ package org.mozilla.fenix.tabstray.browser import androidx.annotation.VisibleForTesting -import mozilla.components.browser.state.state.TabSessionState -import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.tabstray.TabsTray -import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.appstate.AppAction.UpdateInactiveExpanded import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.tabstray.TabsTrayStore import org.mozilla.fenix.utils.Settings class InactiveTabsController( - private val browserStore: BrowserStore, + private val tabsTrayStore: TabsTrayStore, private val appStore: AppStore, - private val tabFilter: (TabSessionState) -> Boolean, private val tray: TabsTray, private val metrics: MetricController, private val settings: Settings @@ -27,7 +25,11 @@ class InactiveTabsController( * the title showing. */ fun updateCardExpansion(isExpanded: Boolean) { - appStore.dispatch(AppAction.UpdateInactiveExpanded(isExpanded)) + appStore.dispatch(UpdateInactiveExpanded(isExpanded)).invokeOnCompletion { + // To avoid racing, we read the list of inactive tabs only after we have updated + // the expanded state. + refreshInactiveTabsSection() + } metrics.track( when (isExpanded) { @@ -35,8 +37,6 @@ class InactiveTabsController( false -> Event.TabsTrayInactiveTabsCollapsed } ) - - refreshInactiveTabsSection() } /** @@ -70,7 +70,7 @@ class InactiveTabsController( @VisibleForTesting internal fun refreshInactiveTabsSection() { - val tabs = browserStore.state.tabs.filter(tabFilter) - tray.updateTabs(tabs, browserStore.state.selectedTabId) + val tabs = tabsTrayStore.state.inactiveTabs + tray.updateTabs(tabs, null) } } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt index 18fb960848..87b328af59 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt @@ -7,15 +7,13 @@ package org.mozilla.fenix.tabstray.browser import android.content.Context import android.util.AttributeSet import androidx.recyclerview.widget.ConcatAdapter -import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.tabstray.TabViewHolder -import mozilla.components.feature.tabs.tabstray.TabsFeature import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.maxActiveTime import org.mozilla.fenix.ext.settings import org.mozilla.fenix.tabstray.ext.browserAdapter import org.mozilla.fenix.tabstray.ext.inactiveTabsAdapter -import org.mozilla.fenix.tabstray.ext.isNormalTabInactive +import org.mozilla.fenix.tabstray.ext.tabGroupAdapter +import org.mozilla.fenix.tabstray.ext.titleHeaderAdapter class NormalBrowserTrayList @JvmOverloads constructor( context: Context, @@ -24,26 +22,28 @@ class NormalBrowserTrayList @JvmOverloads constructor( ) : AbstractBrowserTrayList(context, attrs, defStyleAttr) { private val concatAdapter by lazy { adapter as ConcatAdapter } - private val tabSorter by lazy { - TabSorter( - context.settings(), - context.components.analytics.metrics, - concatAdapter - ) + + private val inactiveTabsBinding by lazy { + InactiveTabsBinding(tabsTrayStore, concatAdapter.inactiveTabsAdapter) } - private val inactiveTabsFilter: (TabSessionState) -> Boolean = filter@{ - if (!context.settings().inactiveTabsAreEnabled) { - return@filter false - } - it.isNormalTabInactive(maxActiveTime) + + private val normalTabsBinding by lazy { + NormalTabsBinding(tabsTrayStore, context.components.core.store, concatAdapter.browserAdapter) + } + + private val titleHeaderBinding by lazy { + OtherHeaderBinding(tabsTrayStore) { concatAdapter.titleHeaderAdapter.handleListChanges(it) } + } + + private val tabGroupBinding by lazy { + TabGroupBinding(tabsTrayStore) { concatAdapter.tabGroupAdapter.submitList(it) } } private val inactiveTabsInteractor by lazy { DefaultInactiveTabsInteractor( InactiveTabsController( - context.components.core.store, + tabsTrayStore, context.components.appStore, - inactiveTabsFilter, concatAdapter.inactiveTabsAdapter, context.components.analytics.metrics, context.settings() @@ -51,13 +51,6 @@ class NormalBrowserTrayList @JvmOverloads constructor( ) } - override val tabsFeature by lazy { - TabsFeature( - tabSorter, - context.components.core.store, - ) { !it.content.private } - } - private val touchHelper by lazy { TabsTouchHelper( interactionDelegate = concatAdapter.browserAdapter.interactor, @@ -74,7 +67,10 @@ class NormalBrowserTrayList @JvmOverloads constructor( concatAdapter.inactiveTabsAdapter.inactiveTabsInteractor = inactiveTabsInteractor - tabsFeature.start() + inactiveTabsBinding.start() + normalTabsBinding.start() + titleHeaderBinding.start() + tabGroupBinding.start() touchHelper.attachToRecyclerView(this) } @@ -82,7 +78,10 @@ class NormalBrowserTrayList @JvmOverloads constructor( override fun onDetachedFromWindow() { super.onDetachedFromWindow() - tabsFeature.stop() + inactiveTabsBinding.stop() + normalTabsBinding.stop() + titleHeaderBinding.stop() + tabGroupBinding.stop() touchHelper.attachToRecyclerView(null) } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalTabsBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalTabsBinding.kt new file mode 100644 index 0000000000..cbe2a5f256 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalTabsBinding.kt @@ -0,0 +1,33 @@ +/* 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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.browser.tabstray.TabsTray +import mozilla.components.lib.state.helpers.AbstractBinding +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged +import org.mozilla.fenix.tabstray.TabsTrayState +import org.mozilla.fenix.tabstray.TabsTrayStore + +/** + * A normal tabs observer that updates the provided [TabsTray]. + */ +class NormalTabsBinding( + store: TabsTrayStore, + private val browserStore: BrowserStore, + private val tabsTray: TabsTray +) : AbstractBinding(store) { + override suspend fun onState(flow: Flow) { + flow.map { it.normalTabs } + .ifChanged() + .collect { + // Getting the selectedTabId from the BrowserStore at a different time might lead to a race. + tabsTray.updateTabs(it, browserStore.state.selectedTabId) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBinding.kt new file mode 100644 index 0000000000..43f511b34f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBinding.kt @@ -0,0 +1,31 @@ +/* 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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import mozilla.components.lib.state.helpers.AbstractBinding +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged +import org.mozilla.fenix.tabstray.TabsTrayState +import org.mozilla.fenix.tabstray.TabsTrayStore + +/** + * A tabs observer that informs [showHeader] if an "Other tabs" title should be displayed in the tray. + */ +class OtherHeaderBinding( + store: TabsTrayStore, + private val showHeader: (Boolean) -> Unit +) : AbstractBinding(store) { + override suspend fun onState(flow: Flow) { + flow.ifAnyChanged { arrayOf(it.normalTabs, it.searchTermGroups) } + .collect { + if (it.searchTermGroups.isNotEmpty() && it.normalTabs.isNotEmpty()) { + showHeader(true) + } else { + showHeader(false) + } + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt index 851e54fd77..cbfc2f2586 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt @@ -8,7 +8,6 @@ import android.content.Context import android.util.AttributeSet import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE -import mozilla.components.feature.tabs.tabstray.TabsFeature import org.mozilla.fenix.ext.components class PrivateBrowserTrayList @JvmOverloads constructor( @@ -17,14 +16,10 @@ class PrivateBrowserTrayList @JvmOverloads constructor( defStyleAttr: Int = 0 ) : AbstractBrowserTrayList(context, attrs, defStyleAttr) { - override val tabsFeature by lazy { - // NB: The use cases here are duplicated because there isn't a nicer - // way to share them without a better dependency injection solution. - TabsFeature( - adapter as BrowserTabsAdapter, - context.components.core.store, - ) { it.content.private } + private val privateTabsBinding by lazy { + PrivateTabsBinding(tabsTrayStore, context.components.core.store, adapter as BrowserTabsAdapter) } + private val touchHelper by lazy { TabsTouchHelper( interactionDelegate = (adapter as BrowserTabsAdapter).delegate, @@ -37,7 +32,7 @@ class PrivateBrowserTrayList @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - tabsFeature.start() + privateTabsBinding.start() swipeToDelete.start() adapter?.onAttachedToRecyclerView(this) @@ -49,7 +44,7 @@ class PrivateBrowserTrayList @JvmOverloads constructor( public override fun onDetachedFromWindow() { super.onDetachedFromWindow() - tabsFeature.stop() + privateTabsBinding.stop() swipeToDelete.stop() // Notify the adapter that it is released from the view preemptively. diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBinding.kt new file mode 100644 index 0000000000..34c681e288 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBinding.kt @@ -0,0 +1,33 @@ +/* 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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.browser.tabstray.TabsTray +import mozilla.components.lib.state.helpers.AbstractBinding +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged +import org.mozilla.fenix.tabstray.TabsTrayState +import org.mozilla.fenix.tabstray.TabsTrayStore + +/** + * A private tabs observer that updates the provided [TabsTray]. + */ +class PrivateTabsBinding( + store: TabsTrayStore, + private val browserStore: BrowserStore, + private val tray: TabsTray +) : AbstractBinding(store) { + override suspend fun onState(flow: Flow) { + flow.map { it.privateTabs } + .ifChanged() + .collect { + // Getting the selectedTabId from the BrowserStore at a different time might lead to a race. + tray.updateTabs(it, browserStore.state.selectedTabId) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupBinding.kt new file mode 100644 index 0000000000..0a5d330ead --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupBinding.kt @@ -0,0 +1,29 @@ +/* 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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import mozilla.components.lib.state.helpers.AbstractBinding +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged +import org.mozilla.fenix.tabstray.TabsTrayState +import org.mozilla.fenix.tabstray.TabsTrayStore + +/** + * A search-term tab group observer that updates the provided [tray]. + */ +class TabGroupBinding( + store: TabsTrayStore, + private val tray: (List) -> Unit +) : AbstractBinding(store) { + override suspend fun onState(flow: Flow) { + flow.map { it.searchTermGroups } + .ifChanged() + .collect { + tray.invoke(it) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabSorter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabSorter.kt index f85acb80d1..7f3a11ca6c 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabSorter.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabSorter.kt @@ -4,7 +4,6 @@ package org.mozilla.fenix.tabstray.browser -import androidx.recyclerview.widget.ConcatAdapter import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.tabstray.TabsTray import mozilla.components.feature.tabs.tabstray.TabsFeature @@ -14,13 +13,9 @@ import org.mozilla.fenix.ext.maxActiveTime import org.mozilla.fenix.ext.toSearchGroup import org.mozilla.fenix.tabstray.TabsTrayAction import org.mozilla.fenix.tabstray.TabsTrayStore -import org.mozilla.fenix.tabstray.ext.browserAdapter import org.mozilla.fenix.tabstray.ext.hasSearchTerm -import org.mozilla.fenix.tabstray.ext.inactiveTabsAdapter import org.mozilla.fenix.tabstray.ext.isActive import org.mozilla.fenix.tabstray.ext.isNormalTabActiveWithSearchTerm -import org.mozilla.fenix.tabstray.ext.tabGroupAdapter -import org.mozilla.fenix.tabstray.ext.titleHeaderAdapter import org.mozilla.fenix.utils.Settings /** @@ -29,7 +24,6 @@ import org.mozilla.fenix.utils.Settings class TabSorter( private val settings: Settings, private val metrics: MetricController, - private val concatAdapter: ConcatAdapter? = null, private val tabsTrayStore: TabsTrayStore? = null ) : TabsTray { private var shouldReportMetrics: Boolean = true @@ -37,35 +31,29 @@ class TabSorter( override fun updateTabs(tabs: List, selectedTabId: String?) { val privateTabs = tabs.filter { it.content.private } - tabsTrayStore?.dispatch(TabsTrayAction.UpdatePrivateTabs(privateTabs)) + val allNormalTabs = tabs - privateTabs + val inactiveTabs = allNormalTabs.getInactiveTabs(settings) + val searchTermTabs = allNormalTabs.getSearchGroupTabs(settings) + val normalTabs = allNormalTabs - inactiveTabs - searchTermTabs - val normalTabs = tabs - privateTabs - val inactiveTabs = normalTabs.getInactiveTabs(settings) - val searchTermTabs = normalTabs.getSearchGroupTabs(settings) - val regularTabs = normalTabs - inactiveTabs - searchTermTabs + // Private tabs + tabsTrayStore?.dispatch(TabsTrayAction.UpdatePrivateTabs(privateTabs)) // Inactive tabs tabsTrayStore?.dispatch(TabsTrayAction.UpdateInactiveTabs(inactiveTabs)) // Tab groups - // We don't need to provide a selectedId, because the [TabGroupAdapter] has that built-in with support from - // NormalBrowserPageViewHolder.scrollToTab. val (groups, remainderTabs) = searchTermTabs.toSearchGroup(groupsSet) groupsSet.clear() groupsSet.addAll(groups.map { it.searchTerm }) - concatAdapter?.tabGroupAdapter?.submitList(groups) tabsTrayStore?.dispatch(TabsTrayAction.UpdateSearchGroupTabs(groups)) // Normal tabs. - val totalNormalTabs = (regularTabs + remainderTabs) - concatAdapter?.browserAdapter?.updateTabs(totalNormalTabs, selectedTabId) + val totalNormalTabs = (normalTabs + remainderTabs) tabsTrayStore?.dispatch(TabsTrayAction.UpdateNormalTabs(totalNormalTabs)) - // Normal tab title header. - concatAdapter?.titleHeaderAdapter - ?.handleListChanges(totalNormalTabs.isNotEmpty() && groups.isNotEmpty()) - + // TODO move this to a middleware in the TabsTrayStore. if (shouldReportMetrics) { shouldReportMetrics = false diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBindingTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBindingTest.kt new file mode 100644 index 0000000000..1a4fdd508a --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsBindingTest.kt @@ -0,0 +1,58 @@ +/* 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.createTab +import mozilla.components.browser.tabstray.TabsTray +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.mozilla.fenix.tabstray.TabsTrayAction +import org.mozilla.fenix.tabstray.TabsTrayStore + +class InactiveTabsBindingTest { + val store = TabsTrayStore() + val tray: TabsTray = mockk(relaxed = true) + val binding = InactiveTabsBinding(store, tray) + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + @After + fun teardown() { + binding.stop() + } + + @Test + fun `WHEN the store is updated THEN notify the tabs tray`() { + assertTrue(store.state.inactiveTabs.isEmpty()) + + store.dispatch(TabsTrayAction.UpdateInactiveTabs(listOf(createTab("https://mozilla.org")))).joinBlocking() + + binding.start() + + assertTrue(store.state.inactiveTabs.isNotEmpty()) + + verify { tray.updateTabs(any(), any()) } + } + + @Test + fun `WHEN non-inactive tabs are updated THEN do not notify the tabs tray`() { + assertTrue(store.state.inactiveTabs.isEmpty()) + + store.dispatch(TabsTrayAction.UpdatePrivateTabs(listOf(createTab("https://mozilla.org")))).joinBlocking() + + binding.start() + + assertTrue(store.state.inactiveTabs.isEmpty()) + + verify { tray.updateTabs(emptyList(), null) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsControllerTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsControllerTest.kt index 11a17ccc54..7f5e043762 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/InactiveTabsControllerTest.kt @@ -11,7 +11,6 @@ import io.mockk.mockk import io.mockk.slot import io.mockk.spyk import io.mockk.verify -import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.tabstray.TabsTray @@ -21,6 +20,8 @@ import org.junit.Test import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.tabstray.TabsTrayState +import org.mozilla.fenix.tabstray.TabsTrayStore import org.mozilla.fenix.utils.Settings class InactiveTabsControllerTest { @@ -31,20 +32,18 @@ class InactiveTabsControllerTest { @Test fun `WHEN expanded THEN notify filtered card`() { - val filter: (TabSessionState) -> Boolean = { !it.content.private } - val store = BrowserStore( - BrowserState( - tabs = listOf( + val store = TabsTrayStore( + TabsTrayState( + inactiveTabs = listOf( createTabState("https://mozilla.org", id = "1"), - createTabState("https://firefox.com", id = "2"), - createTabState("https://getpocket.com", id = "3", private = true) + createTabState("https://firefox.com", id = "2") ) ) ) val tray: TabsTray = mockk(relaxed = true) val tabsSlot = slot>() val controller = - InactiveTabsController(store, appStore, filter, tray, mockk(relaxed = true), settings) + InactiveTabsController(store, appStore, tray, mockk(relaxed = true), settings) controller.updateCardExpansion(true) @@ -56,9 +55,9 @@ class InactiveTabsControllerTest { @Test fun `WHEN expanded THEN track telemetry event`() { - val store = BrowserStore(BrowserState()) + val store = TabsTrayStore() val controller = InactiveTabsController( - store, appStore, mockk(relaxed = true), mockk(relaxed = true), metrics, settings + store, appStore, mockk(relaxed = true), metrics, settings ) controller.updateCardExpansion(true) @@ -68,9 +67,9 @@ class InactiveTabsControllerTest { @Test fun `WHEN collapsed THEN track telemetry event`() { - val store = BrowserStore(BrowserState()) + val store = TabsTrayStore() val controller = InactiveTabsController( - store, appStore, mockk(relaxed = true), mockk(relaxed = true), metrics, settings + store, appStore, mockk(relaxed = true), metrics, settings ) controller.updateCardExpansion(false) @@ -80,10 +79,10 @@ class InactiveTabsControllerTest { @Test fun `WHEN close THEN update settings and refresh`() { - val store = BrowserStore() + val store = TabsTrayStore() val controller = spyk( InactiveTabsController( - store, appStore, mockk(relaxed = true), mockk(relaxed = true), metrics, settings + store, appStore, mockk(relaxed = true), metrics, settings ) ) diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/NormalTabsBindingTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/NormalTabsBindingTest.kt new file mode 100644 index 0000000000..d623b171b5 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/NormalTabsBindingTest.kt @@ -0,0 +1,68 @@ +/* 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.slot +import io.mockk.verify +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.browser.tabstray.TabsTray +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.mozilla.fenix.tabstray.TabsTrayAction +import org.mozilla.fenix.tabstray.TabsTrayStore + +class NormalTabsBindingTest { + val store = TabsTrayStore() + val browserStore = BrowserStore(BrowserState(tabs = listOf(createTab("", id = "1")), selectedTabId = "1")) + val tray: TabsTray = mockk(relaxed = true) + val binding = NormalTabsBinding(store, browserStore, tray) + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + @After + fun teardown() { + binding.stop() + } + + @Test + fun `WHEN the store is updated THEN notify the tabs tray`() { + val slotTabs = slot>() + val expectedTabs = listOf(createTab("https://mozilla.org")) + + assertTrue(store.state.normalTabs.isEmpty()) + + store.dispatch(TabsTrayAction.UpdateNormalTabs(expectedTabs)).joinBlocking() + + binding.start() + + assertTrue(store.state.normalTabs.isNotEmpty()) + + verify { tray.updateTabs(capture(slotTabs), "1") } + assertEquals(expectedTabs, slotTabs.captured) + } + + @Test + fun `WHEN non-inactive tabs are updated THEN do not notify the tabs tray`() { + assertTrue(store.state.normalTabs.isEmpty()) + + store.dispatch(TabsTrayAction.UpdatePrivateTabs(listOf(createTab("https://mozilla.org")))).joinBlocking() + + binding.start() + + assertTrue(store.state.normalTabs.isEmpty()) + + verify { tray.updateTabs(emptyList(), "1") } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBindingTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBindingTest.kt new file mode 100644 index 0000000000..3c18aad7c5 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/OtherHeaderBindingTest.kt @@ -0,0 +1,73 @@ +/* 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 mozilla.components.support.test.libstate.ext.waitUntilIdle +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.mozilla.fenix.tabstray.TabsTrayState +import org.mozilla.fenix.tabstray.TabsTrayStore + +class OtherHeaderBindingTest { + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + @Test + fun `WHEN there are no tabs THEN show no header`() { + val store = TabsTrayStore() + var result: Boolean? = null + val binding = OtherHeaderBinding(store) { result = it } + + binding.start() + + store.waitUntilIdle() + + assertFalse(result!!) + } + + @Test + fun `WHEN tabs for only groups THEN show no header`() { + val store = TabsTrayStore(TabsTrayState(searchTermGroups = listOf(mockk()))) + var result: Boolean? = null + val binding = OtherHeaderBinding(store) { result = it } + + binding.start() + + store.waitUntilIdle() + + assertFalse(result!!) + } + + @Test + fun `WHEN tabs for only normal tabs THEN show no header`() { + val store = TabsTrayStore(TabsTrayState(normalTabs = listOf(mockk()))) + var result: Boolean? = null + val binding = OtherHeaderBinding(store) { result = it } + + binding.start() + + store.waitUntilIdle() + + assertFalse(result!!) + } + + @Test + fun `WHEN normal tabs and groups exist THEN show header`() { + val store = TabsTrayStore(TabsTrayState(normalTabs = listOf(mockk()), searchTermGroups = listOf(mockk()))) + var result: Boolean? = null + val binding = OtherHeaderBinding(store) { result = it } + + binding.start() + + store.waitUntilIdle() + + assertTrue(result!!) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBindingTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBindingTest.kt new file mode 100644 index 0000000000..88a12d532d --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/PrivateTabsBindingTest.kt @@ -0,0 +1,72 @@ +/* + * 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.slot +import io.mockk.verify +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.browser.tabstray.TabsTray +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.mozilla.fenix.tabstray.TabsTrayAction +import org.mozilla.fenix.tabstray.TabsTrayStore + +class PrivateTabsBindingTest { + val store = TabsTrayStore() + val browserStore = BrowserStore(BrowserState(tabs = listOf(createTab("", id = "1")), selectedTabId = "1")) + val tray: TabsTray = mockk(relaxed = true) + val binding = PrivateTabsBinding(store, browserStore, tray) + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + @After + fun teardown() { + binding.stop() + } + + @Test + fun `WHEN the store is updated THEN notify the tabs tray`() { + val slotTabs = slot>() + val expectedTabs = listOf(createTab("https://mozilla.org", private = true)) + + assertTrue(store.state.privateTabs.isEmpty()) + + store.dispatch(TabsTrayAction.UpdatePrivateTabs(expectedTabs)).joinBlocking() + + binding.start() + + assertTrue(store.state.privateTabs.isNotEmpty()) + + verify { tray.updateTabs(capture(slotTabs), "1") } + assertEquals(expectedTabs, slotTabs.captured) + } + + @Test + fun `WHEN non-inactive tabs are updated THEN do not notify the tabs tray`() { + assertTrue(store.state.privateTabs.isEmpty()) + + store.dispatch(TabsTrayAction.UpdateInactiveTabs(listOf(createTab("https://mozilla.org", private = true)))) + .joinBlocking() + + binding.start() + + assertTrue(store.state.privateTabs.isEmpty()) + + verify { tray.updateTabs(emptyList(), "1") } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabGroupBindingTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabGroupBindingTest.kt new file mode 100644 index 0000000000..18b18ca942 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabGroupBindingTest.kt @@ -0,0 +1,58 @@ +/* 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 mozilla.components.browser.state.state.createTab +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.mozilla.fenix.tabstray.TabsTrayAction +import org.mozilla.fenix.tabstray.TabsTrayStore + +class TabGroupBindingTest { + val store = TabsTrayStore() + var captured: List? = null + val binding = TabGroupBinding(store) { captured = it } + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + @After + fun teardown() { + binding.stop() + } + + @Test + fun `WHEN the store is updated THEN notify the adapter`() { + val expectedGroups = listOf(TabGroup("cats", emptyList(), 0)) + + assertTrue(store.state.searchTermGroups.isEmpty()) + + store.dispatch(TabsTrayAction.UpdateSearchGroupTabs(expectedGroups)).joinBlocking() + + binding.start() + + assertTrue(store.state.searchTermGroups.isNotEmpty()) + + assertEquals(expectedGroups, captured) + } + + @Test + fun `WHEN non-group tabs are updated THEN do not notify the adapter`() { + assertTrue(store.state.searchTermGroups.isEmpty()) + + store.dispatch(TabsTrayAction.UpdatePrivateTabs(listOf(createTab("https://mozilla.org")))).joinBlocking() + + binding.start() + + assertTrue(store.state.searchTermGroups.isEmpty()) + + assertEquals(emptyList(), captured) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabSorterTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabSorterTest.kt index d26baabed4..133cb80739 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabSorterTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabSorterTest.kt @@ -4,31 +4,18 @@ package org.mozilla.fenix.tabstray.browser -import androidx.recyclerview.widget.ConcatAdapter import io.mockk.every import io.mockk.mockk import mozilla.components.browser.state.state.createTab -import mozilla.components.support.test.mock -import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.libstate.ext.waitUntilIdle import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.tabstray.TabsTrayStore -import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.INACTIVE_TABS_FEATURE_NAME -import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.TABS_TRAY_FEATURE_NAME -import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.TAB_GROUP_FEATURE_NAME -import org.mozilla.fenix.tabstray.ext.browserAdapter -import org.mozilla.fenix.tabstray.ext.inactiveTabsAdapter -import org.mozilla.fenix.tabstray.ext.tabGroupAdapter -import org.mozilla.fenix.tabstray.ext.titleHeaderAdapter import org.mozilla.fenix.utils.Settings -@RunWith(FenixRobolectricTestRunner::class) class TabSorterTest { - private val context = testContext private val settings: Settings = mockk() private val metrics: MetricController = mockk() private var inactiveTimestamp = 0L @@ -43,13 +30,7 @@ class TabSorterTest { @Test fun `WHEN updated with one normal tab THEN adapter have only one normal tab and no header`() { - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -58,22 +39,16 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 1) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 0) - assertEquals(adapter.tabGroupAdapter.itemCount, 0) - assertEquals(adapter.titleHeaderAdapter.itemCount, 0) - assertEquals(adapter.browserAdapter.itemCount, 1) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 0) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 0) + assertEquals(tabsTrayStore.state.normalTabs.size, 1) } @Test fun `WHEN updated with one normal tab and two search term tab THEN adapter have normal tab and a search group`() { - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -94,22 +69,16 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 3) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 0) - assertEquals(adapter.tabGroupAdapter.itemCount, 1) - assertEquals(adapter.titleHeaderAdapter.itemCount, 1) - assertEquals(adapter.browserAdapter.itemCount, 1) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 0) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 1) + assertEquals(tabsTrayStore.state.normalTabs.size, 1) } @Test fun `WHEN updated with one normal tab, one inactive tab and two search term tab THEN adapter have normal tab, inactive tab and a search group`() { - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -136,23 +105,17 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 4) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 1) - assertEquals(adapter.tabGroupAdapter.itemCount, 1) - assertEquals(adapter.titleHeaderAdapter.itemCount, 1) - assertEquals(adapter.browserAdapter.itemCount, 1) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 1) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 1) + assertEquals(tabsTrayStore.state.normalTabs.size, 1) } @Test fun `WHEN inactive tabs is off THEN adapter have no inactive tab`() { every { settings.inactiveTabsAreEnabled }.answers { false } - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -179,23 +142,17 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 4) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 0) - assertEquals(adapter.tabGroupAdapter.itemCount, 1) - assertEquals(adapter.titleHeaderAdapter.itemCount, 1) - assertEquals(adapter.browserAdapter.itemCount, 2) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 0) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 1) + assertEquals(tabsTrayStore.state.normalTabs.size, 2) } @Test fun `WHEN search term tabs is off THEN adapter have no search term group`() { every { settings.searchTermTabGroupsAreEnabled }.answers { false } - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -222,24 +179,18 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 4) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 1) - assertEquals(adapter.tabGroupAdapter.itemCount, 0) - assertEquals(adapter.titleHeaderAdapter.itemCount, 0) - assertEquals(adapter.browserAdapter.itemCount, 3) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 1) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 0) + assertEquals(tabsTrayStore.state.normalTabs.size, 3) } @Test fun `WHEN both inactive tabs and search term tabs are off THEN adapter have only normal tabs`() { every { settings.inactiveTabsAreEnabled }.answers { false } every { settings.searchTermTabGroupsAreEnabled }.answers { false } - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -265,22 +216,16 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 4) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 0) - assertEquals(adapter.tabGroupAdapter.itemCount, 0) - assertEquals(adapter.titleHeaderAdapter.itemCount, 0) - assertEquals(adapter.browserAdapter.itemCount, 4) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 0) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 0) + assertEquals(tabsTrayStore.state.normalTabs.size, 4) } @Test fun `WHEN only one search term tab THEN there is no search group`() { - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -292,22 +237,16 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 1) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 0) - assertEquals(adapter.tabGroupAdapter.itemCount, 0) - assertEquals(adapter.titleHeaderAdapter.itemCount, 0) - assertEquals(adapter.browserAdapter.itemCount, 1) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 0) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 0) + assertEquals(tabsTrayStore.state.normalTabs.size, 1) } @Test fun `WHEN remove second last one search term tab THEN search group is kept even if there's only one tab`() { - val adapter = ConcatAdapter( - InactiveTabsAdapter(context, tabsTrayStore, mock(), mock(), INACTIVE_TABS_FEATURE_NAME, settings), - TabGroupAdapter(context, mock(), mock(), TAB_GROUP_FEATURE_NAME), - TitleHeaderAdapter(), - BrowserTabsAdapter(context, mock(), mock(), TABS_TRAY_FEATURE_NAME) - ) - val tabSorter = TabSorter(settings, metrics, adapter) + val tabSorter = TabSorter(settings, metrics, tabsTrayStore) tabSorter.updateTabs( listOf( @@ -323,11 +262,11 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 1) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 0) - assertEquals(adapter.tabGroupAdapter.itemCount, 1) - assertEquals(adapter.titleHeaderAdapter.itemCount, 0) - assertEquals(adapter.browserAdapter.itemCount, 0) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 0) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 1) + assertEquals(tabsTrayStore.state.normalTabs.size, 0) tabSorter.updateTabs( listOf( @@ -339,10 +278,10 @@ class TabSorterTest { selectedTabId = "tab1" ) - assertEquals(adapter.itemCount, 1) - assertEquals(adapter.inactiveTabsAdapter.inActiveTabsCount, 0) - assertEquals(adapter.tabGroupAdapter.itemCount, 1) - assertEquals(adapter.titleHeaderAdapter.itemCount, 0) - assertEquals(adapter.browserAdapter.itemCount, 0) + tabsTrayStore.waitUntilIdle() + + assertEquals(tabsTrayStore.state.inactiveTabs.size, 0) + assertEquals(tabsTrayStore.state.searchTermGroups.size, 1) + assertEquals(tabsTrayStore.state.normalTabs.size, 0) } }