* Issue #18862: Add new addBookmark BookmarksUseCase * Issue #18862: Add class for state binding features * Issue #18862: Add delete multiple tabs to tray interactor * Issue #18862: Add new actions to navigation interactor * Issue #18862: Enable select mode from main tray menu * Issue #18862: Add menu when in select mode * Close #18862: Add multi-select banner to tabs tray * Close #18862: Add select support for handle UI We apply various layout changes to the "handle" UI in the tabs tray when switching modes. It isn't quite clear to my, why we do this, if it's really needed to meet the end result, and if there is a better way. For now, we're simplying moving over that logic that we can re-evaluate at a later time.upstream-sync
parent
52209673fb
commit
f3df2c73d9
@ -0,0 +1,42 @@
|
||||
/* 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.components.bookmarks
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import mozilla.appservices.places.BookmarkRoot
|
||||
import mozilla.components.concept.storage.BookmarksStorage
|
||||
|
||||
/**
|
||||
* Use cases that allow for modifying bookmarks.
|
||||
*/
|
||||
class BookmarksUseCase(storage: BookmarksStorage) {
|
||||
|
||||
class AddBookmarksUseCase internal constructor(private val storage: BookmarksStorage) {
|
||||
|
||||
/**
|
||||
* Adds a new bookmark with the provided [url] and [title].
|
||||
*
|
||||
* @return The result if the operation was executed or not. A bookmark may not be added if
|
||||
* one with the identical [url] already exists.
|
||||
*/
|
||||
@WorkerThread
|
||||
suspend operator fun invoke(url: String, title: String, position: Int? = null): Boolean {
|
||||
val canAdd = storage.getBookmarksWithUrl(url).firstOrNull { it.url == it.url } == null
|
||||
|
||||
if (canAdd) {
|
||||
storage.addItem(
|
||||
BookmarkRoot.Mobile.id,
|
||||
url = url,
|
||||
title = title,
|
||||
position = position
|
||||
)
|
||||
}
|
||||
|
||||
return canAdd
|
||||
}
|
||||
}
|
||||
|
||||
val addBookmark by lazy { AddBookmarksUseCase(storage) }
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/* 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.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import kotlinx.android.synthetic.main.component_tabstray2.view.exit_multi_select
|
||||
import kotlinx.android.synthetic.main.component_tabstray2.view.multiselect_title
|
||||
import kotlinx.android.synthetic.main.tabstray_multiselect_items.view.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.map
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.AbstractBinding
|
||||
import org.mozilla.fenix.tabstray.NavigationInteractor
|
||||
import org.mozilla.fenix.tabstray.TabsTrayInteractor
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
import org.mozilla.fenix.tabstray.TabsTrayAction.ExitSelectMode
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState.Mode
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState.Mode.Select
|
||||
import org.mozilla.fenix.tabstray.ext.showWithTheme
|
||||
|
||||
/**
|
||||
* A binding that shows/hides the multi-select banner of the selected count of tabs.
|
||||
*
|
||||
* @property context An Android context.
|
||||
* @property store The TabsTrayStore instance.
|
||||
* @property navInteractor An instance of [NavigationInteractor] for navigating on menu clicks.
|
||||
* @property tabsTrayInteractor An instance of [TabsTrayInteractor] for handling deletion.
|
||||
* @property containerView The view in the layout that contains all the implicit multi-select
|
||||
* views. NB: This parameter is a bit opaque and requires a larger layout refactor to correct.
|
||||
* @property backgroundView The background view that we want to alter when changing [Mode].
|
||||
* @property showOnSelectViews A variable list of views that will be made visible when in select mode.
|
||||
* @property showOnNormalViews A variable list of views that will be made visible when in normal mode.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class SelectionBannerBinding(
|
||||
private val context: Context,
|
||||
private val store: TabsTrayStore,
|
||||
private val navInteractor: NavigationInteractor,
|
||||
private val tabsTrayInteractor: TabsTrayInteractor,
|
||||
private val containerView: View,
|
||||
private val backgroundView: View,
|
||||
private val showOnSelectViews: VisibilityModifier,
|
||||
private val showOnNormalViews: VisibilityModifier
|
||||
) : AbstractBinding<TabsTrayState>(store) {
|
||||
|
||||
/**
|
||||
* A holder of views that will be used by having their [View.setVisibility] modified.
|
||||
*/
|
||||
class VisibilityModifier(vararg val views: View)
|
||||
|
||||
private var isPreviousModeSelect = false
|
||||
|
||||
override fun start() {
|
||||
super.start()
|
||||
|
||||
initListeners(containerView)
|
||||
}
|
||||
|
||||
override suspend fun onState(flow: Flow<TabsTrayState>) {
|
||||
flow.map { it.mode }
|
||||
// ignore initial mode update; we never start in select mode.
|
||||
.drop(1)
|
||||
.ifChanged()
|
||||
.collect { mode ->
|
||||
val isSelectMode = mode is Select
|
||||
|
||||
showOnSelectViews.views.forEach {
|
||||
it.isVisible = isSelectMode
|
||||
}
|
||||
|
||||
showOnNormalViews.views.forEach {
|
||||
it.isVisible = isSelectMode.not()
|
||||
}
|
||||
|
||||
updateBackgroundColor(isSelectMode)
|
||||
|
||||
updateSelectTitle(isSelectMode, mode.selectedTabs.size)
|
||||
|
||||
isPreviousModeSelect = isSelectMode
|
||||
}
|
||||
}
|
||||
|
||||
private fun initListeners(containerView: View) {
|
||||
containerView.share_multi_select.setOnClickListener {
|
||||
navInteractor.onShareTabs(store.state.mode.selectedTabs)
|
||||
}
|
||||
|
||||
containerView.collect_multi_select.setOnClickListener {
|
||||
navInteractor.onSaveToCollections(store.state.mode.selectedTabs)
|
||||
}
|
||||
|
||||
containerView.exit_multi_select.setOnClickListener {
|
||||
store.dispatch(ExitSelectMode)
|
||||
}
|
||||
|
||||
containerView.menu_multi_select.setOnClickListener { anchor ->
|
||||
val menu = SelectionMenuIntegration(
|
||||
context,
|
||||
store,
|
||||
navInteractor,
|
||||
tabsTrayInteractor
|
||||
).build()
|
||||
|
||||
menu.showWithTheme(anchor)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
private fun updateBackgroundColor(isSelectMode: Boolean) {
|
||||
// memoize to avoid setting the background unnecessarily.
|
||||
if (isPreviousModeSelect != isSelectMode) {
|
||||
val colorResource = if (isSelectMode) {
|
||||
R.color.accent_normal_theme
|
||||
} else {
|
||||
R.color.foundation_normal_theme
|
||||
}
|
||||
|
||||
val color = ContextCompat.getColor(backgroundView.context, colorResource)
|
||||
|
||||
backgroundView.setBackgroundColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
private fun updateSelectTitle(selectedMode: Boolean, tabCount: Int) {
|
||||
if (selectedMode) {
|
||||
containerView.multiselect_title.text =
|
||||
context.getString(R.string.tab_tray_multi_select_title, tabCount)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/* 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.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.map
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.AbstractBinding
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState
|
||||
import org.mozilla.fenix.tabstray.TabsTrayState.Mode
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
|
||||
private const val NORMAL_HANDLE_PERCENT_WIDTH = 0.1F
|
||||
|
||||
/**
|
||||
* Various layout updates that need to be applied to the "handle" view when switching
|
||||
* between [Mode].
|
||||
*
|
||||
* @param store The TabsTrayStore instance.
|
||||
* @property handle The "handle" of the Tabs Tray that is used to drag the tray open/close.
|
||||
* @property containerLayout The [ConstraintLayout] that contains the "handle".
|
||||
*/
|
||||
class SelectionHandleBinding(
|
||||
store: TabsTrayStore,
|
||||
private val handle: View,
|
||||
private val containerLayout: ConstraintLayout
|
||||
) : AbstractBinding<TabsTrayState>(store) {
|
||||
|
||||
private var isPreviousModeSelect = false
|
||||
|
||||
override suspend fun onState(flow: Flow<TabsTrayState>) {
|
||||
flow.map { it.mode }
|
||||
// ignore initial mode update; we never start in select mode.
|
||||
.drop(1)
|
||||
.ifChanged()
|
||||
.collect { mode ->
|
||||
val isSelectMode = mode is Mode.Select
|
||||
|
||||
// memoize to avoid unnecessary layout updates.
|
||||
if (isPreviousModeSelect != isSelectMode) {
|
||||
updateLayoutParams(handle, isSelectMode)
|
||||
|
||||
updateBackgroundColor(handle, isSelectMode)
|
||||
|
||||
updateWidthPercent(containerLayout, handle, isSelectMode)
|
||||
}
|
||||
|
||||
isPreviousModeSelect = isSelectMode
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLayoutParams(handle: View, multiselect: Boolean) {
|
||||
handle.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
height = handle.resources.getDimensionPixelSize(
|
||||
if (multiselect) {
|
||||
R.dimen.tab_tray_multiselect_handle_height
|
||||
} else {
|
||||
R.dimen.bottom_sheet_handle_height
|
||||
}
|
||||
)
|
||||
topMargin = handle.resources.getDimensionPixelSize(
|
||||
if (multiselect) {
|
||||
R.dimen.tab_tray_multiselect_handle_top_margin
|
||||
} else {
|
||||
R.dimen.bottom_sheet_handle_top_margin
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBackgroundColor(handle: View, multiselect: Boolean) {
|
||||
val colorResource = if (multiselect) {
|
||||
R.color.accent_normal_theme
|
||||
} else {
|
||||
R.color.secondary_text_normal_theme
|
||||
}
|
||||
|
||||
val color = ContextCompat.getColor(handle.context, colorResource)
|
||||
|
||||
handle.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
private fun updateWidthPercent(
|
||||
container: ConstraintLayout,
|
||||
handle: View,
|
||||
multiselect: Boolean
|
||||
) {
|
||||
val widthPercent = if (multiselect) 1F else NORMAL_HANDLE_PERCENT_WIDTH
|
||||
container.run {
|
||||
ConstraintSet().apply {
|
||||
clone(this@run)
|
||||
constrainPercentWidth(handle.id, widthPercent)
|
||||
applyTo(this@run)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.tabstray.browser
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
class SelectionMenu(
|
||||
private val context: Context,
|
||||
private val onItemTapped: (Item) -> Unit = {}
|
||||
) {
|
||||
sealed class Item {
|
||||
object BookmarkTabs : Item()
|
||||
object DeleteTabs : Item()
|
||||
}
|
||||
|
||||
val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
|
||||
|
||||
private val menuItems by lazy {
|
||||
listOf(
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.tab_tray_multiselect_menu_item_bookmark),
|
||||
textColorResource = R.color.primary_text_normal_theme
|
||||
) {
|
||||
onItemTapped.invoke(Item.BookmarkTabs)
|
||||
},
|
||||
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.tab_tray_multiselect_menu_item_close),
|
||||
textColorResource = R.color.primary_text_normal_theme
|
||||
) {
|
||||
onItemTapped.invoke(Item.DeleteTabs)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -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.browser
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||
import org.mozilla.fenix.tabstray.NavigationInteractor
|
||||
import org.mozilla.fenix.tabstray.TabsTrayInteractor
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
import org.mozilla.fenix.utils.Do
|
||||
|
||||
class SelectionMenuIntegration(
|
||||
private val context: Context,
|
||||
private val store: TabsTrayStore,
|
||||
private val navInteractor: NavigationInteractor,
|
||||
private val trayInteractor: TabsTrayInteractor
|
||||
) {
|
||||
private val menu by lazy {
|
||||
SelectionMenu(context, ::handleMenuClicked)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the internal menu items list. See [BrowserMenuBuilder.build].
|
||||
*/
|
||||
fun build() = menu.menuBuilder.build(context)
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun handleMenuClicked(item: SelectionMenu.Item) {
|
||||
Do exhaustive when (item) {
|
||||
is SelectionMenu.Item.BookmarkTabs -> navInteractor.onSaveToBookmarks(
|
||||
store.state.mode.selectedTabs
|
||||
)
|
||||
is SelectionMenu.Item.DeleteTabs -> trayInteractor.onDeleteTabs(
|
||||
store.state.mode.selectedTabs
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/* 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 android.view.View
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import mozilla.components.browser.menu.BrowserMenu
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
/**
|
||||
* Invokes [BrowserMenu.show] and applies the default theme color background.
|
||||
*/
|
||||
fun BrowserMenu.showWithTheme(view: View) {
|
||||
show(view).also { popupMenu ->
|
||||
(popupMenu.contentView as? CardView)?.setCardBackgroundColor(
|
||||
ContextCompat.getColor(
|
||||
view.context,
|
||||
R.color.foundation_normal_theme
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/* 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.components
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AbstractBindingTest {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
|
||||
|
||||
@Test
|
||||
fun `WHEN started THEN onState flow is invoked`() {
|
||||
val store = BrowserStore()
|
||||
var invoked = false
|
||||
val binding = TestBinding(store) {
|
||||
invoked = true
|
||||
}
|
||||
|
||||
binding.start()
|
||||
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertTrue(invoked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN actions are dispatched THEN onState flow is invoked`() {
|
||||
val store = BrowserStore()
|
||||
var invoked = false
|
||||
val binding = TestBinding(store) {
|
||||
if (store.state.tabs.isNotEmpty()) {
|
||||
invoked = true
|
||||
}
|
||||
}
|
||||
|
||||
binding.start()
|
||||
|
||||
store.dispatch(TabListAction.AddTabAction(createTab("https://mozilla.org")))
|
||||
|
||||
store.waitUntilIdle()
|
||||
|
||||
assertTrue(invoked)
|
||||
}
|
||||
|
||||
class TestBinding(
|
||||
store: BrowserStore,
|
||||
private val invoked: (BrowserState) -> Unit
|
||||
) : AbstractBinding<BrowserState>(store) {
|
||||
override suspend fun onState(flow: Flow<BrowserState>) {
|
||||
flow.collect {
|
||||
invoked(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* 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.components.bookmarks
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import mozilla.appservices.places.BookmarkRoot
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.concept.storage.BookmarksStorage
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class BookmarksUseCaseTest {
|
||||
|
||||
@Test
|
||||
fun `WHEN adding existing bookmark THEN no new item is stored`() = runBlockingTest {
|
||||
val storage = mockk<BookmarksStorage>()
|
||||
val bookmarkNode = mockk<BookmarkNode>()
|
||||
val useCase = BookmarksUseCase(storage)
|
||||
|
||||
every { bookmarkNode.url }.answers { "https://mozilla.org" }
|
||||
coEvery { storage.getBookmarksWithUrl(any()) }.coAnswers { listOf(bookmarkNode) }
|
||||
|
||||
val result = useCase.addBookmark("https://mozilla.org", "Mozilla")
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN adding bookmark THEN new item is stored`() = runBlockingTest {
|
||||
val storage = mockk<BookmarksStorage>(relaxed = true)
|
||||
val useCase = BookmarksUseCase(storage)
|
||||
|
||||
coEvery { storage.getBookmarksWithUrl(any()) }.coAnswers { emptyList() }
|
||||
|
||||
val result = useCase.addBookmark("https://mozilla.org", "Mozilla")
|
||||
|
||||
assertTrue(result)
|
||||
|
||||
coVerify { storage.addItem(BookmarkRoot.Mobile.id, "https://mozilla.org", "Mozilla", null) }
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/* 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.tabstray.NavigationInteractor
|
||||
import org.mozilla.fenix.tabstray.TabsTrayInteractor
|
||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
||||
|
||||
class SelectionMenuIntegrationTest {
|
||||
|
||||
private val navInteractor = mockk<NavigationInteractor>(relaxed = true)
|
||||
private val trayInteractor = mockk<TabsTrayInteractor>(relaxed = true)
|
||||
private val store = TabsTrayStore()
|
||||
|
||||
@Test
|
||||
fun `WHEN bookmark item is clicked THEN invoke interactor`() {
|
||||
val menu = SelectionMenuIntegration(mockk(), store, navInteractor, trayInteractor)
|
||||
|
||||
menu.handleMenuClicked(SelectionMenu.Item.BookmarkTabs)
|
||||
|
||||
verify { navInteractor.onSaveToBookmarks(store.state.mode.selectedTabs) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN delete tabs item is clicked THEN invoke interactor`() {
|
||||
val menu = SelectionMenuIntegration(mockk(), store, navInteractor, trayInteractor)
|
||||
|
||||
menu.handleMenuClicked(SelectionMenu.Item.DeleteTabs)
|
||||
|
||||
verify { trayInteractor.onDeleteTabs(store.state.mode.selectedTabs) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue