diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 9c7f3cfab..254898460 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -6,11 +6,14 @@ package org.mozilla.fenix import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle import android.os.StrictMode import android.text.format.DateUtils import android.util.AttributeSet +import android.view.KeyEvent import android.view.View +import android.view.ViewConfiguration import android.view.WindowManager import androidx.annotation.CallSuper import androidx.annotation.IdRes @@ -30,6 +33,8 @@ import kotlinx.android.synthetic.main.activity_home.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.SessionManager @@ -139,6 +144,9 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { ) } + // See onKeyDown for why this is necessary + private var backLongPressJob: Job? = null + private lateinit var navigationToolbar: Toolbar final override fun onCreate(savedInstanceState: Bundle?) { @@ -349,6 +357,50 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { super.onBackPressed() } + private fun isAndroidN(): Boolean = + Build.VERSION.SDK_INT == Build.VERSION_CODES.N || Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1 + + private fun handleBackLongPress(): Boolean { + supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { + if (it is OnBackLongPressedListener && it.onBackLongPressed()) { + return true + } + } + return false + } + + final override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + // Inspired by https://searchfox.org/mozilla-esr68/source/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java#584-613 + // Android N has broken passing onKeyLongPress events for the back button, so we + // instead implement the long press behavior ourselves + // - For short presses, we cancel the callback in onKeyUp + // - For long presses, the normal keypress is marked as cancelled, hence won't be handled elsewhere + // (but Android still provides the haptic feedback), and the long press action is run + if (isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) { + backLongPressJob = lifecycleScope.launch { + delay(ViewConfiguration.getLongPressTimeout().toLong()) + handleBackLongPress() + } + } + return super.onKeyDown(keyCode, event) + } + + final override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + if (isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) { + backLongPressJob?.cancel() + } + return super.onKeyUp(keyCode, event) + } + + final override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean { + // onKeyLongPress is broken in Android N so we don't handle back button long presses here + // for N. The version check ensures we don't handle back button long presses twice. + if (!isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) { + return handleBackLongPress() + } + return super.onKeyLongPress(keyCode, event) + } + final override fun onUserLeaveHint() { supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { if (it is UserInteractionHandler && it.onHomePressed()) { diff --git a/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt b/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt new file mode 100644 index 000000000..e47a0b71b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt @@ -0,0 +1,21 @@ +/* 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 + +/** + * Interface for features and fragments that want to handle long presses of the system back button + */ +interface OnBackLongPressedListener { + + /** + * Called when the system back button is long pressed. + * + * Note: This cannot be called when gesture navigation is enabled on Android 10+ due to system + * limitations. + * + * @return true if the event was handled + */ + fun onBackLongPressed(): Boolean +} 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 25be5f7b5..55288f861 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -77,6 +77,7 @@ import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.NavGraphDirections +import org.mozilla.fenix.OnBackLongPressedListener import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.readermode.DefaultReaderModeController @@ -116,7 +117,8 @@ import java.lang.ref.WeakReference */ @ExperimentalCoroutinesApi @Suppress("TooManyFunctions", "LargeClass") -abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer { +abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer, + OnBackLongPressedListener { private lateinit var browserFragmentStore: BrowserFragmentStore private lateinit var browserAnimator: BrowserAnimator @@ -757,6 +759,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session removeSessionIfNeeded() } + override fun onBackLongPressed(): Boolean { + findNavController().navigate(R.id.action_global_tabHistoryDialogFragment) + return true + } + /** * Saves the external app session ID to be restored later in [onViewStateRestored]. */ diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index 41795a446..565224752 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -170,7 +170,13 @@ class DefaultBrowserToolbarController( Do exhaustive when (item) { ToolbarMenu.Item.Back -> sessionUseCases.goBack.invoke(currentSession) - ToolbarMenu.Item.Forward -> sessionUseCases.goForward.invoke(currentSession) + is ToolbarMenu.Item.Forward -> { + if (item.viewHistory) { + navController.navigate(R.id.action_global_tabHistoryDialogFragment) + } else { + sessionUseCases.goForward.invoke(currentSession) + } + } ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke(currentSession) ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession) ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically { @@ -333,7 +339,7 @@ class DefaultBrowserToolbarController( private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) { val eventItem = when (item) { ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK - ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD + is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index a27691bac..caf922208 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -82,9 +82,10 @@ class DefaultToolbarMenu( session?.canGoForward ?: true }, secondaryImageTintResource = ThemeManager.resolveAttribute(R.attr.disabled, context), - disableInSecondaryState = true + disableInSecondaryState = true, + longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = true)) } ) { - onItemTapped.invoke(ToolbarMenu.Item.Forward) + onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = false)) } val refresh = BrowserMenuItemToolbar.TwoStateButton( diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt index db8dbbe14..aeb33dd15 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt @@ -14,7 +14,7 @@ interface ToolbarMenu { object FindInPage : Item() object Share : Item() object Back : Item() - object Forward : Item() + data class Forward(val viewHistory: Boolean) : Item() object Reload : Item() object Stop : Item() object OpenInFenix : Item() diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt index 32a33b8d4..a5f0a9017 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt @@ -75,9 +75,10 @@ class CustomTabToolbarMenu( R.attr.disabled, context ), - disableInSecondaryState = true + disableInSecondaryState = true, + longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = true)) } ) { - onItemTapped.invoke(ToolbarMenu.Item.Forward) + onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = false)) } val refresh = BrowserMenuItemToolbar.TwoStateButton( diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryAdapter.kt new file mode 100644 index 000000000..433c620e1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryAdapter.kt @@ -0,0 +1,40 @@ +/* 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.tabhistory + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.mozilla.fenix.R + +data class TabHistoryItem( + val title: String, + val url: String, + val index: Int, + val isSelected: Boolean +) + +class TabHistoryAdapter( + private val interactor: TabHistoryViewInteractor +) : RecyclerView.Adapter() { + + var historyList: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabHistoryViewHolder { + val view = + LayoutInflater.from(parent.context).inflate(R.layout.history_list_item, parent, false) + return TabHistoryViewHolder(view, interactor) + } + + override fun onBindViewHolder(holder: TabHistoryViewHolder, position: Int) { + holder.bind(historyList[position]) + } + + override fun getItemCount(): Int = historyList.size +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryController.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryController.kt new file mode 100644 index 000000000..2d6ecc784 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryController.kt @@ -0,0 +1,24 @@ +/* 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.tabhistory + +import androidx.navigation.NavController +import mozilla.components.feature.session.SessionUseCases +import org.mozilla.fenix.R + +interface TabHistoryController { + fun handleGoToHistoryItem(item: TabHistoryItem) +} + +class DefaultTabHistoryController( + private val navController: NavController, + private val goToHistoryIndexUseCase: SessionUseCases.GoToHistoryIndexUseCase +) : TabHistoryController { + + override fun handleGoToHistoryItem(item: TabHistoryItem) { + navController.popBackStack(R.id.browserFragment, false) + goToHistoryIndexUseCase.invoke(item.index) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryDialogFragment.kt new file mode 100644 index 000000000..5ae534b73 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryDialogFragment.kt @@ -0,0 +1,56 @@ +/* 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.tabhistory + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.fragment_tab_history_dialog.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.lib.state.ext.consumeFrom +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.requireComponents + +class TabHistoryDialogFragment : BottomSheetDialogFragment() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.BottomSheet) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.fragment_tab_history_dialog, container, false) + + @ExperimentalCoroutinesApi + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val controller = DefaultTabHistoryController( + navController = findNavController(), + goToHistoryIndexUseCase = requireComponents.useCases.sessionUseCases.goToHistoryIndex + ) + val tabHistoryView = TabHistoryView( + container = tabHistoryLayout, + expandDialog = ::expand, + interactor = TabHistoryInteractor(controller) + ) + + consumeFrom(requireComponents.core.store) { + tabHistoryView.updateState(it) + } + } + + private fun expand() { + (dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryInteractor.kt new file mode 100644 index 000000000..1c659acf0 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryInteractor.kt @@ -0,0 +1,14 @@ +/* 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.tabhistory + +class TabHistoryInteractor( + private val controller: TabHistoryController +) : TabHistoryViewInteractor { + + override fun goToHistoryItem(item: TabHistoryItem) { + controller.handleGoToHistoryItem(item) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt new file mode 100644 index 000000000..c02ba038d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt @@ -0,0 +1,79 @@ +/* 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.tabhistory + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.component_tabhistory.* +import mozilla.components.browser.state.selector.selectedTab +import mozilla.components.browser.state.state.BrowserState +import org.mozilla.fenix.R + +interface TabHistoryViewInteractor { + + /** + * Jump to a specific index in the tab's history. + */ + fun goToHistoryItem(item: TabHistoryItem) +} + +class TabHistoryView( + private val container: ViewGroup, + private val expandDialog: () -> Unit, + interactor: TabHistoryViewInteractor +) : LayoutContainer { + + override val containerView: View? + get() = container + + val view: View = LayoutInflater.from(container.context) + .inflate(R.layout.component_tabhistory, container, true) + + private val adapter = TabHistoryAdapter(interactor) + private val layoutManager = object : LinearLayoutManager(view.context) { + override fun onLayoutCompleted(state: RecyclerView.State?) { + super.onLayoutCompleted(state) + currentIndex?.let { index -> + // Force expansion of the dialog, otherwise scrolling to the current history item + // won't work when its position is near the bottom of the recyclerview. + expandDialog.invoke() + // Also, attempt to center the current history item. + val itemView = tabHistoryRecyclerView.findViewHolderForLayoutPosition( + findFirstCompletelyVisibleItemPosition() + )?.itemView + val offset = tabHistoryRecyclerView.height / 2 - (itemView?.height ?: 0) / 2 + scrollToPositionWithOffset(index, offset) + } + } + }.apply { + reverseLayout = true + } + + private var currentIndex: Int? = null + + init { + tabHistoryRecyclerView.adapter = adapter + tabHistoryRecyclerView.layoutManager = layoutManager + } + + fun updateState(state: BrowserState) { + state.selectedTab?.content?.history?.let { historyState -> + currentIndex = historyState.currentIndex + val items = historyState.items.mapIndexed { index, historyItem -> + TabHistoryItem( + title = historyItem.title, + url = historyItem.uri, + index = index, + isSelected = index == historyState.currentIndex + ) + } + adapter.historyList = items + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryViewHolder.kt new file mode 100644 index 000000000..88bd26d55 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryViewHolder.kt @@ -0,0 +1,34 @@ +/* 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.tabhistory + +import android.view.View +import androidx.core.text.bold +import androidx.core.text.buildSpannedString +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.history_list_item.view.* + +class TabHistoryViewHolder( + private val view: View, + private val interactor: TabHistoryViewInteractor +) : RecyclerView.ViewHolder(view) { + + fun bind(item: TabHistoryItem) { + view.history_layout.overflowView.isVisible = false + view.history_layout.urlView.text = item.url + view.history_layout.loadFavicon(item.url) + + view.history_layout.titleView.text = if (item.isSelected) { + buildSpannedString { + bold { append(item.title) } + } + } else { + item.title + } + + view.setOnClickListener { interactor.goToHistoryItem(item) } + } +} diff --git a/app/src/main/res/layout/component_tabhistory.xml b/app/src/main/res/layout/component_tabhistory.xml new file mode 100644 index 000000000..3095029a2 --- /dev/null +++ b/app/src/main/res/layout/component_tabhistory.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_tab_history_dialog.xml b/app/src/main/res/layout/fragment_tab_history_dialog.xml new file mode 100644 index 000000000..3be7d859a --- /dev/null +++ b/app/src/main/res/layout/fragment_tab_history_dialog.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 9daebae5a..d543ac084 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -102,6 +102,9 @@ + + diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt index 20fb905e1..a9d530bbf 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -220,7 +220,7 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarForwardPress() = runBlockingTest { - val item = ToolbarMenu.Item.Forward + val item = ToolbarMenu.Item.Forward(false) val controller = createController(scope = this) controller.handleToolbarItemInteraction(item) @@ -229,6 +229,17 @@ class DefaultBrowserToolbarControllerTest { verify { sessionUseCases.goForward(currentSession) } } + @Test + fun handleToolbarForwardLongPress() = runBlockingTest { + val item = ToolbarMenu.Item.Forward(true) + + val controller = createController(scope = this) + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) } + verify { navController.navigate(R.id.action_global_tabHistoryDialogFragment) } + } + @Test fun handleToolbarReloadPress() = runBlockingTest { val item = ToolbarMenu.Item.Reload diff --git a/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryControllerTest.kt b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryControllerTest.kt new file mode 100644 index 000000000..8b32cc10f --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryControllerTest.kt @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabhistory + +import androidx.navigation.NavController +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.browser.session.SessionManager +import mozilla.components.feature.session.SessionUseCases +import org.junit.Test + +class TabHistoryControllerTest { + + val sessionManager: SessionManager = mockk(relaxed = true) + val navController: NavController = mockk(relaxed = true) + val sessionUseCases = SessionUseCases(sessionManager) + val goToHistoryIndexUseCase = sessionUseCases.goToHistoryIndex + val controller = DefaultTabHistoryController( + navController = navController, + goToHistoryIndexUseCase = goToHistoryIndexUseCase + ) + + val currentItem = TabHistoryItem( + index = 0, + title = "", + url = "", + isSelected = true + ) + + @Test + fun handleGoToHistoryIndex() { + controller.handleGoToHistoryItem(currentItem) + + verify { goToHistoryIndexUseCase.invoke(currentItem.index) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryInteractorTest.kt new file mode 100644 index 000000000..d9ffc5ca2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabhistory/TabHistoryInteractorTest.kt @@ -0,0 +1,24 @@ +/* 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.tabhistory + +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +class TabHistoryInteractorTest { + + val controller: TabHistoryController = mockk(relaxed = true) + val interactor = TabHistoryInteractor(controller) + + @Test + fun onGoToHistoryItem() { + val item: TabHistoryItem = mockk() + + interactor.goToHistoryItem(item) + + verify { controller.handleGoToHistoryItem(item) } + } +}