diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 3ca1fa820..db1b8e351 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -294,7 +294,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit thumbnailsFeature.get()?.requestScreenshot() findNavController().nav( R.id.browserFragment, - BrowserFragmentDirections.actionGlobalTabTrayDialogFragment() + getTrayDirection(context) ) }, onCloseTab = { closedSession -> @@ -1279,6 +1279,17 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit .show() } + /** + * Retrieves the correct tray direction while using a feature flag. + * + * Remove this when [FeatureFlags.tabsTrayRewrite] is removed. + */ + private fun getTrayDirection(context: Context) = if (context.settings().tabsTrayRewrite) { + BrowserFragmentDirections.actionGlobalTabsTrayFragment() + } else { + BrowserFragmentDirections.actionGlobalTabTrayDialogFragment() + } + companion object { private const val KEY_CUSTOM_TAB_SESSION_ID = "custom_tab_session_id" private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1 diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 3cd9a2989..924297d65 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -1013,9 +1013,14 @@ class HomeFragment : Fragment() { } private fun openTabTray() { + val direction = if (requireContext().settings().tabsTrayRewrite) { + HomeFragmentDirections.actionGlobalTabsTrayFragment() + } else { + HomeFragmentDirections.actionGlobalTabTrayDialogFragment() + } findNavController().nav( R.id.homeFragment, - HomeFragmentDirections.actionGlobalTabTrayDialogFragment() + direction ) } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt new file mode 100644 index 000000000..b0a0fc12a --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabstray + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.navigation.fragment.findNavController +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.tabs.TabLayout +import kotlinx.android.synthetic.main.component_tabstray2.* +import kotlinx.android.synthetic.main.component_tabstray2.view.* +import org.mozilla.fenix.R + +class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor { + + lateinit var behavior: BottomSheetBehavior + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.TabTrayDialogStyle) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val containerView = inflater.inflate(R.layout.fragment_tab_tray_dialog, container, false) + val view: View = LayoutInflater.from(containerView.context) + .inflate(R.layout.component_tabstray2, containerView as ViewGroup, true) + + behavior = BottomSheetBehavior.from(view.tab_wrapper) + + return containerView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupPager(view.context, this) + } + + override fun setCurrentTrayPosition(position: Int) { + tabsTray.currentItem = position + } + + override fun navigateToBrowser() { + dismissAllowingStateLoss() + + val navController = findNavController() + + if (navController.currentDestination?.id == R.id.browserFragment) { + return + } + + if (!navController.popBackStack(R.id.browserFragment, false)) { + navController.navigate(R.id.browserFragment) + } + } + + override fun tabRemoved(sessionId: String) { + // TODO re-implement these methods + // showUndoSnackbarForTab(sessionId) + // removeIfNotLastTab(sessionId) + } + + private fun setupPager(context: Context, interactor: TabsTrayInteractor) { + tabsTray.apply { + adapter = TrayPagerAdapter(context, interactor) + isUserInputEnabled = false + } + + tab_layout.addOnTabSelectedListener(TabLayoutObserver(interactor)) + } +} + +/** + * An observer for the [TabLayout] used for the Tabs Tray. + */ +internal class TabLayoutObserver( + private val interactor: TabsTrayInteractor +) : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + interactor.setCurrentTrayPosition(tab.position) + } + + override fun onTabUnselected(tab: TabLayout.Tab) = Unit + override fun onTabReselected(tab: TabLayout.Tab) = Unit +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt new file mode 100644 index 000000000..6c479aad3 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt @@ -0,0 +1,22 @@ +/* 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 + +interface TabsTrayInteractor { + /** + * Set the current tray item to the clamped [position]. + */ + fun setCurrentTrayPosition(position: Int) + + /** + * Dismisses the tabs tray and navigates to the browser. + */ + fun navigateToBrowser() + + /** + * Invoked when a tab is removed from the tabs tray with the given [sessionId]. + */ + fun tabRemoved(sessionId: String) +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TrayItem.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TrayItem.kt new file mode 100644 index 000000000..032a63492 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TrayItem.kt @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabstray + +import android.view.View +import android.view.ViewGroup + +/** + * A [View] or [ViewGroup] that can be add in the Tabs Tray. + */ +interface TrayItem diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt new file mode 100644 index 000000000..c4ea545ae --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabstray + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.mozilla.fenix.tabstray.BrowserTabViewHolder.Companion.LAYOUT_ID_NORMAL_TAB +import org.mozilla.fenix.tabstray.BrowserTabViewHolder.Companion.LAYOUT_ID_PRIVATE_TAB +import org.mozilla.fenix.tabtray.FenixTabsAdapter + +class TrayPagerAdapter( + context: Context, + val interactor: TabsTrayInteractor +) : RecyclerView.Adapter() { + + private val normalAdapter by lazy { FenixTabsAdapter(context) } + private val privateAdapter by lazy { FenixTabsAdapter(context) } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrayViewHolder { + val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + + return when (viewType) { + LAYOUT_ID_NORMAL_TAB -> BrowserTabViewHolder(itemView, interactor) + LAYOUT_ID_PRIVATE_TAB -> BrowserTabViewHolder(itemView, interactor) + else -> throw IllegalStateException("Unknown viewType.") + } + } + + override fun onBindViewHolder(viewHolder: TrayViewHolder, position: Int) { + val adapter = when (position) { + POSITION_NORMAL_TABS -> normalAdapter + POSITION_PRIVATE_TABS -> privateAdapter + else -> throw IllegalStateException("View type does not exist.") + } + + viewHolder.bind(adapter) + } + + override fun getItemViewType(position: Int): Int { + return when (position) { + POSITION_NORMAL_TABS -> LAYOUT_ID_NORMAL_TAB + POSITION_PRIVATE_TABS -> LAYOUT_ID_PRIVATE_TAB + else -> throw IllegalStateException("Unknown position.") + } + } + + override fun getItemCount(): Int = TRAY_TABS_COUNT + + companion object { + const val TRAY_TABS_COUNT = 2 + + const val POSITION_NORMAL_TABS = 0 + const val POSITION_PRIVATE_TABS = 1 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TrayViewHolders.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TrayViewHolders.kt new file mode 100644 index 000000000..e94226c8b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TrayViewHolders.kt @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabstray + +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.extensions.LayoutContainer +import org.mozilla.fenix.R +import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList + +sealed class TrayViewHolder constructor( + override val containerView: View +) : RecyclerView.ViewHolder(containerView), LayoutContainer { + + abstract fun bind(adapter: RecyclerView.Adapter) +} + +class BrowserTabViewHolder( + containerView: View, + interactor: TabsTrayInteractor +) : TrayViewHolder(containerView) { + + private val trayList: BaseBrowserTrayList = itemView.findViewById(R.id.tray_list_item) + + init { + trayList.interactor = interactor + } + + override fun bind(adapter: RecyclerView.Adapter) { + trayList.layoutManager = LinearLayoutManager(itemView.context) + trayList.adapter = adapter + } + + companion object { + const val LAYOUT_ID_NORMAL_TAB = R.layout.normal_browser_tray_list + const val LAYOUT_ID_PRIVATE_TAB = R.layout.private_browser_tray_list + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/BaseBrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BaseBrowserTrayList.kt new file mode 100644 index 000000000..caaa51108 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BaseBrowserTrayList.kt @@ -0,0 +1,95 @@ +/* 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 android.content.Context +import android.util.AttributeSet +import androidx.recyclerview.widget.RecyclerView +import mozilla.components.browser.tabstray.TabsAdapter +import mozilla.components.feature.tabs.TabsUseCases +import mozilla.components.feature.tabs.tabstray.TabsFeature +import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.tabstray.TabsTrayInteractor +import org.mozilla.fenix.tabstray.TrayItem +import org.mozilla.fenix.tabstray.ext.filterFromConfig +import org.mozilla.fenix.utils.view.LifecycleViewProvider + +abstract class BaseBrowserTrayList @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : RecyclerView(context, attrs, defStyleAttr), TrayItem { + + enum class BrowserTabType { NORMAL, PRIVATE } + data class Configuration(val browserTabType: BrowserTabType) + + abstract val configuration: Configuration + + var interactor: TabsTrayInteractor? = null + + private val lifecycleProvider = LifecycleViewProvider(this) + + private val selectTabUseCase = SelectTabUseCaseWrapper( + context.components.analytics.metrics, + context.components.useCases.tabsUseCases.selectTab + ) { + interactor?.navigateToBrowser() + } + + private val removeTabUseCase = RemoveTabUseCaseWrapper( + context.components.analytics.metrics + ) { sessionId -> + interactor?.tabRemoved(sessionId) + } + + private val tabsFeature by lazy { + ViewBoundFeatureWrapper( + feature = TabsFeature( + adapter as TabsAdapter, + context.components.core.store, + selectTabUseCase, + removeTabUseCase, + { it.filterFromConfig(configuration) }, + { } + ), + owner = lifecycleProvider, + view = this + ) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + // This is weird, but I don't have a better solution right now: We need to keep a + // lazy reference to the feature/adapter so that we do not re-create + // it every time it's attached. This reference is our way to init. + tabsFeature + } +} + +internal class SelectTabUseCaseWrapper( + private val metrics: MetricController, + private val selectTab: TabsUseCases.SelectTabUseCase, + private val onSelect: (String) -> Unit +) : TabsUseCases.SelectTabUseCase { + override fun invoke(tabId: String) { + metrics.track(Event.OpenedExistingTab) + selectTab(tabId) + onSelect(tabId) + } +} + +internal class RemoveTabUseCaseWrapper( + private val metrics: MetricController, + private val onRemove: (String) -> Unit +) : TabsUseCases.RemoveTabUseCase { + override fun invoke(sessionId: String) { + metrics.track(Event.ClosedExistingTab) + onRemove(sessionId) + } +} 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 new file mode 100644 index 000000000..eed7f8586 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/NormalBrowserTrayList.kt @@ -0,0 +1,19 @@ +/* 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 android.content.Context +import android.util.AttributeSet + +/** + * A browser tabs list that displays normal tabs. + */ +class NormalBrowserTrayList @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : BaseBrowserTrayList(context, attrs, defStyleAttr) { + override val configuration: Configuration = Configuration(BrowserTabType.NORMAL) +} 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 new file mode 100644 index 000000000..0b8f73e72 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/PrivateBrowserTrayList.kt @@ -0,0 +1,19 @@ +/* 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 android.content.Context +import android.util.AttributeSet + +/** + * A browser tabs list that displays private tabs. + */ +class PrivateBrowserTrayList @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : BaseBrowserTrayList(context, attrs, defStyleAttr) { + override val configuration: Configuration = Configuration(BrowserTabType.PRIVATE) +} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt new file mode 100644 index 000000000..cf11c8c39 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt @@ -0,0 +1,15 @@ +/* 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.ext + +import mozilla.components.browser.state.state.TabSessionState +import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList.BrowserTabType.PRIVATE +import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList.Configuration + +fun TabSessionState.filterFromConfig(configuration: Configuration): Boolean { + val isPrivate = configuration.browserTabType == PRIVATE + + return content.private == isPrivate +} diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/FenixTabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabtray/FenixTabsAdapter.kt index eece24425..777770165 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/FenixTabsAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/FenixTabsAdapter.kt @@ -15,6 +15,7 @@ import kotlinx.android.synthetic.main.tab_tray_item.view.* import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.tabstray.TabViewHolder import mozilla.components.browser.tabstray.TabsAdapter +import mozilla.components.browser.thumbnails.loader.ThumbnailLoader import mozilla.components.concept.base.images.ImageLoader import mozilla.components.concept.tabstray.Tab import mozilla.components.concept.tabstray.Tabs @@ -27,7 +28,7 @@ import org.mozilla.fenix.ext.updateAccessibilityCollectionItemInfo class FenixTabsAdapter( private val context: Context, - imageLoader: ImageLoader + imageLoader: ImageLoader = ThumbnailLoader(context.components.core.thumbnailStorage) ) : TabsAdapter( viewHolderProvider = { parentView -> TabTrayViewHolder( diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 5b051fbbe..acb200a65 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -701,7 +701,6 @@ class TabTrayView( fun scrollToSelectedBrowserTab(selectedTabId: String? = null) { view.tabsTray.apply { val recyclerViewIndex = getSelectedBrowserTabViewIndex(selectedTabId) - layoutManager?.scrollToPosition(recyclerViewIndex) smoothScrollBy( 0, diff --git a/app/src/main/java/org/mozilla/fenix/utils/view/LifecycleViewProvider.kt b/app/src/main/java/org/mozilla/fenix/utils/view/LifecycleViewProvider.kt new file mode 100644 index 000000000..010f82b6f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/utils/view/LifecycleViewProvider.kt @@ -0,0 +1,45 @@ +/* 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.utils.view + +import android.view.View +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.Lifecycle.State +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry + +/** + * Provides a [LifecycleOwner] on a given [View] for features that function on lifecycle events. + * + * When the [View] is attached to the window, observers will receive the [Lifecycle.Event.ON_START] event. + * When the [View] is detached to the window, observers will receive the [Lifecycle.Event.ON_STOP] event. + * + * @param view The [View] that will be observed. + */ +class LifecycleViewProvider(view: View) : LifecycleOwner { + private val registry = LifecycleRegistry(this) + + init { + registry.currentState = State.INITIALIZED + + view.addOnAttachStateChangeListener(ViewBinding(registry)) + } + + override fun getLifecycle(): Lifecycle = registry +} + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal class ViewBinding( + private val registry: LifecycleRegistry +) : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View?) { + registry.currentState = State.STARTED + } + + override fun onViewDetachedFromWindow(v: View?) { + registry.currentState = State.DESTROYED + } +} diff --git a/app/src/main/res/layout/component_tabstray2.xml b/app/src/main/res/layout/component_tabstray2.xml new file mode 100644 index 000000000..6bcbe4e4f --- /dev/null +++ b/app/src/main/res/layout/component_tabstray2.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/normal_browser_tray_list.xml b/app/src/main/res/layout/normal_browser_tray_list.xml new file mode 100644 index 000000000..4e243c9d7 --- /dev/null +++ b/app/src/main/res/layout/normal_browser_tray_list.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/private_browser_tray_list.xml b/app/src/main/res/layout/private_browser_tray_list.xml new file mode 100644 index 000000000..bd746632a --- /dev/null +++ b/app/src/main/res/layout/private_browser_tray_list.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/synced_tabs_tray_list.xml b/app/src/main/res/layout/synced_tabs_tray_list.xml new file mode 100644 index 000000000..d7a224408 --- /dev/null +++ b/app/src/main/res/layout/synced_tabs_tray_list.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index d3a9c5efb..23f548ae9 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -118,6 +118,11 @@ app:destination="@id/tabTrayDialogFragment" app:popUpTo="@id/tabTrayDialogFragment" app:popUpToInclusive="true"/> + @@ -128,6 +133,15 @@ android:id="@+id/action_global_tabSettingsFragment" app:destination="@id/tabsSettingsFragment" /> + + + + (relaxed = true) + + @Test + fun `WHEN tab is selected THEN notify the interactor`() { + val observer = TabLayoutObserver(interactor) + val tab = mockk() + every { tab.position } returns 1 + + observer.onTabSelected(tab) + + verify { interactor.setCurrentTrayPosition(1) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/RemoveTabUseCaseWrapperTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/RemoveTabUseCaseWrapperTest.kt new file mode 100644 index 000000000..3e6914d83 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/RemoveTabUseCaseWrapperTest.kt @@ -0,0 +1,27 @@ +/* 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 org.junit.Test +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController + +class RemoveTabUseCaseWrapperTest { + + val metricController = mockk(relaxed = true) + + @Test + fun `WHEN invoked THEN metrics, use case and callback are triggered`() { + val onRemove: (String) -> Unit = mockk(relaxed = true) + val wrapper = RemoveTabUseCaseWrapper(metricController, onRemove) + + wrapper("123") + + verify { metricController.track(Event.ClosedExistingTab) } + verify { onRemove("123") } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/SelectTabUseCaseWrapperTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/SelectTabUseCaseWrapperTest.kt new file mode 100644 index 000000000..fd0f24574 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/SelectTabUseCaseWrapperTest.kt @@ -0,0 +1,30 @@ +/* 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.feature.tabs.TabsUseCases +import org.junit.Test +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController + +class SelectTabUseCaseWrapperTest { + + val metricController = mockk(relaxed = true) + val selectUseCase: TabsUseCases.SelectTabUseCase = mockk(relaxed = true) + + @Test + fun `WHEN invoked THEN metrics, use case and callback are triggered`() { + val onSelect: (String) -> Unit = mockk(relaxed = true) + val wrapper = SelectTabUseCaseWrapper(metricController, selectUseCase, onSelect) + + wrapper("123") + + verify { metricController.track(Event.OpenedExistingTab) } + verify { selectUseCase("123") } + verify { onSelect("123") } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/ext/TabSessionStateKtTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/ext/TabSessionStateKtTest.kt new file mode 100644 index 000000000..410c4eac7 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabstray/ext/TabSessionStateKtTest.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.ext + +import io.mockk.every +import io.mockk.mockk +import mozilla.components.browser.state.state.ContentState +import mozilla.components.browser.state.state.TabSessionState +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList.BrowserTabType.NORMAL +import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList.BrowserTabType.PRIVATE +import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList.Configuration + +class TabSessionStateKtTest { + + @Test + fun `WHEN configuration is private THEN return true`() { + val contentState = mockk() + val state = TabSessionState(content = contentState) + val config = Configuration(PRIVATE) + + every { contentState.private } returns true + + assertTrue(state.filterFromConfig(config)) + } + + @Test + fun `WHEN configuration is normal THEN return false`() { + val contentState = mockk() + val state = TabSessionState(content = contentState) + val config = Configuration(NORMAL) + + every { contentState.private } returns false + + assertTrue(state.filterFromConfig(config)) + } + + @Test + fun `WHEN configuration does not match THEN return false`() { + val contentState = mockk() + val state = TabSessionState(content = contentState) + val config = Configuration(NORMAL) + + every { contentState.private } returns true + + assertFalse(state.filterFromConfig(config)) + + val config2 = Configuration(PRIVATE) + + every { contentState.private } returns false + + assertFalse(state.filterFromConfig(config2)) + } +}