For #26400 - Add long-press option to remove tab pickup on homescreen
This commit is contained in:
parent
9fe10e8a2b
commit
b29b5049aa
|
@ -21,6 +21,7 @@ import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
|
|||
import org.mozilla.fenix.library.history.PendingDeletionHistory
|
||||
import org.mozilla.fenix.gleanplumb.Message
|
||||
import org.mozilla.fenix.gleanplumb.MessagingState
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
|
||||
import org.mozilla.fenix.wallpapers.Wallpaper
|
||||
|
||||
/**
|
||||
|
@ -44,7 +45,8 @@ sealed class AppAction : Action {
|
|||
val showCollectionPlaceholder: Boolean,
|
||||
val recentTabs: List<RecentTab>,
|
||||
val recentBookmarks: List<RecentBookmark>,
|
||||
val recentHistory: List<RecentlyVisitedItem>
|
||||
val recentHistory: List<RecentlyVisitedItem>,
|
||||
val recentSyncedTabState: RecentSyncedTabState,
|
||||
) :
|
||||
AppAction()
|
||||
|
||||
|
@ -108,6 +110,12 @@ sealed class AppAction : Action {
|
|||
*/
|
||||
data class RecentSyncedTabStateChange(val state: RecentSyncedTabState) : AppAction()
|
||||
|
||||
/**
|
||||
* Add a [RecentSyncedTab] url to the homescreen blocklist and remove it
|
||||
* from the recent synced tabs list.
|
||||
*/
|
||||
data class RemoveRecentSyncedTab(val syncedTab: RecentSyncedTab) : AppAction()
|
||||
|
||||
/**
|
||||
* [Action]s related to interactions with the Messaging Framework.
|
||||
*/
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.mozilla.fenix.ext.filterOutTab
|
|||
import org.mozilla.fenix.ext.getFilteredStories
|
||||
import org.mozilla.fenix.gleanplumb.state.MessagingReducer
|
||||
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesSelectedCategory
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryGroup
|
||||
|
||||
|
@ -43,6 +44,7 @@ internal object AppStoreReducer {
|
|||
recentBookmarks = action.recentBookmarks,
|
||||
recentTabs = action.recentTabs,
|
||||
recentHistory = action.recentHistory,
|
||||
recentSyncedTabState = action.recentSyncedTabState
|
||||
)
|
||||
is AppAction.CollectionExpanded -> {
|
||||
val newExpandedCollection = state.expandedCollections.toMutableSet()
|
||||
|
@ -89,6 +91,14 @@ internal object AppStoreReducer {
|
|||
it is RecentlyVisitedItem.RecentHistoryHighlight && it.url == action.highlightUrl
|
||||
}
|
||||
)
|
||||
is AppAction.RemoveRecentSyncedTab -> state.copy(
|
||||
recentSyncedTabState = when (state.recentSyncedTabState) {
|
||||
is RecentSyncedTabState.Success -> RecentSyncedTabState.Success(
|
||||
state.recentSyncedTabState.tabs - action.syncedTab
|
||||
)
|
||||
else -> state.recentSyncedTabState
|
||||
}
|
||||
)
|
||||
is AppAction.DisbandSearchGroupAction -> state.copy(
|
||||
recentHistory = state.recentHistory.filterNot {
|
||||
it is RecentHistoryGroup && it.title.equals(action.searchTerm, true)
|
||||
|
|
|
@ -171,7 +171,8 @@ fun AppState.filterState(blocklistHandler: BlocklistHandler): AppState =
|
|||
copy(
|
||||
recentBookmarks = recentBookmarks.filteredByBlocklist(),
|
||||
recentTabs = recentTabs.filteredByBlocklist(),
|
||||
recentHistory = recentHistory.filteredByBlocklist()
|
||||
recentHistory = recentHistory.filteredByBlocklist(),
|
||||
recentSyncedTabState = recentSyncedTabState.filteredByBlocklist()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
|
|||
import androidx.constraintlayout.widget.ConstraintSet.TOP
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -45,7 +45,6 @@ import kotlinx.coroutines.Dispatchers.IO
|
|||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -354,6 +353,7 @@ class HomeFragment : Fragment() {
|
|||
tabsUseCase = requireComponents.useCases.tabsUseCases,
|
||||
navController = findNavController(),
|
||||
accessPoint = TabsTrayAccessPoint.HomeRecentSyncedTab,
|
||||
appStore = components.appStore,
|
||||
),
|
||||
recentBookmarksController = DefaultRecentBookmarksController(
|
||||
activity = activity,
|
||||
|
|
|
@ -7,7 +7,7 @@ package org.mozilla.fenix.home.blocklist
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import mozilla.components.support.ktx.kotlin.sha1
|
||||
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
@ -52,6 +52,28 @@ class BlocklistHandler(private val settings: Settings) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the state is set to [RecentSyncedTabState.Success], filter the list of recently synced
|
||||
* tabs by the blocklist. If the filtered list of tabs is empty, change the state to
|
||||
* [RecentSyncedTabState.None]
|
||||
*/
|
||||
@JvmName("filterRecentSyncedTabState")
|
||||
fun RecentSyncedTabState.filteredByBlocklist() =
|
||||
if (this is RecentSyncedTabState.Success) {
|
||||
val filteredTabs = settings.homescreenBlocklist.let { blocklist ->
|
||||
this.tabs.filterNot {
|
||||
blocklistContainsUrl(blocklist, it.url)
|
||||
}
|
||||
}
|
||||
if (filteredTabs.isEmpty()) {
|
||||
RecentSyncedTabState.None
|
||||
} else {
|
||||
RecentSyncedTabState.Success(filteredTabs)
|
||||
}
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of recent history items by the blocklist. Requires this class to be contextually
|
||||
* in a scope.
|
||||
|
@ -65,18 +87,6 @@ class BlocklistHandler(private val settings: Settings) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of recently synced tabs by the blocklist. Requires this class to be contextually
|
||||
* in a scope.
|
||||
*/
|
||||
@JvmName("filterRecentSyncedTab")
|
||||
fun List<RecentSyncedTab>.filteredByBlocklist(): List<RecentSyncedTab> =
|
||||
settings.homescreenBlocklist.let { blocklist ->
|
||||
filterNot {
|
||||
blocklistContainsUrl(blocklist, it.url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun blocklistContainsUrl(blocklist: Set<String>, url: String): Boolean =
|
||||
blocklist.any { it == url.stripAndHash() }
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import mozilla.components.lib.state.Middleware
|
|||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import org.mozilla.fenix.components.appstate.AppAction
|
||||
import org.mozilla.fenix.components.appstate.AppState
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
|
||||
/**
|
||||
|
@ -33,6 +32,7 @@ class BlocklistMiddleware(
|
|||
next(getUpdatedAction(context.state, action))
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun getUpdatedAction(
|
||||
state: AppState,
|
||||
action: AppAction
|
||||
|
@ -42,7 +42,8 @@ class BlocklistMiddleware(
|
|||
action.copy(
|
||||
recentBookmarks = action.recentBookmarks.filteredByBlocklist(),
|
||||
recentTabs = action.recentTabs.filteredByBlocklist(),
|
||||
recentHistory = action.recentHistory.filteredByBlocklist()
|
||||
recentHistory = action.recentHistory.filteredByBlocklist(),
|
||||
recentSyncedTabState = action.recentSyncedTabState.filteredByBlocklist()
|
||||
)
|
||||
}
|
||||
is AppAction.RecentTabsChange -> {
|
||||
|
@ -59,13 +60,9 @@ class BlocklistMiddleware(
|
|||
action.copy(recentHistory = action.recentHistory.filteredByBlocklist())
|
||||
}
|
||||
is AppAction.RecentSyncedTabStateChange -> {
|
||||
if (action.state is RecentSyncedTabState.Success) {
|
||||
action.copy(
|
||||
state = RecentSyncedTabState.Success(action.state.tabs.filteredByBlocklist())
|
||||
)
|
||||
} else {
|
||||
action
|
||||
}
|
||||
action.copy(
|
||||
state = action.state.filteredByBlocklist()
|
||||
)
|
||||
}
|
||||
is AppAction.RemoveRecentTab -> {
|
||||
if (action.recentTab is RecentTab.Tab) {
|
||||
|
@ -85,6 +82,10 @@ class BlocklistMiddleware(
|
|||
addUrlToBlocklist(action.highlightUrl)
|
||||
state.toActionFilteringAllState(this)
|
||||
}
|
||||
is AppAction.RemoveRecentSyncedTab -> {
|
||||
addUrlToBlocklist(action.syncedTab.url)
|
||||
state.toActionFilteringAllState(this)
|
||||
}
|
||||
else -> action
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +103,8 @@ class BlocklistMiddleware(
|
|||
topSites = topSites,
|
||||
mode = mode,
|
||||
collections = collections,
|
||||
showCollectionPlaceholder = showCollectionPlaceholder
|
||||
showCollectionPlaceholder = showCollectionPlaceholder,
|
||||
recentSyncedTabState = recentSyncedTabState.filteredByBlocklist()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import android.content.Context
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import mozilla.components.concept.storage.HistoryStorage
|
||||
import mozilla.components.browser.storage.sync.Tab
|
||||
import mozilla.components.concept.storage.HistoryStorage
|
||||
import mozilla.components.concept.sync.Device
|
||||
import mozilla.components.concept.sync.DeviceType
|
||||
import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage
|
||||
|
|
|
@ -8,6 +8,8 @@ import androidx.navigation.NavController
|
|||
import mozilla.components.feature.tabs.TabsUseCases
|
||||
import org.mozilla.fenix.GleanMetrics.RecentSyncedTabs
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.AppStore
|
||||
import org.mozilla.fenix.components.appstate.AppAction
|
||||
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.interactor.RecentSyncedTabInteractor
|
||||
|
@ -27,6 +29,13 @@ interface RecentSyncedTabController {
|
|||
* @see [RecentSyncedTabInteractor.onRecentSyncedTabClicked]
|
||||
*/
|
||||
fun handleSyncedTabShowAllClicked()
|
||||
|
||||
/**
|
||||
* Handle removing the synced tab from the homescreen.
|
||||
*
|
||||
* @param tab The recent synced tab to be removed.
|
||||
*/
|
||||
fun handleRecentSyncedTabRemoved(tab: RecentSyncedTab)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,6 +48,7 @@ class DefaultRecentSyncedTabController(
|
|||
private val tabsUseCase: TabsUseCases,
|
||||
private val navController: NavController,
|
||||
private val accessPoint: TabsTrayAccessPoint,
|
||||
private val appStore: AppStore,
|
||||
) : RecentSyncedTabController {
|
||||
override fun handleRecentSyncedTabClick(tab: RecentSyncedTab) {
|
||||
RecentSyncedTabs.recentSyncedTabOpened[tab.deviceType.name.lowercase()].add()
|
||||
|
@ -55,4 +65,8 @@ class DefaultRecentSyncedTabController(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleRecentSyncedTabRemoved(tab: RecentSyncedTab) {
|
||||
appStore.dispatch(AppAction.RemoveRecentSyncedTab(tab))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,12 @@ interface RecentSyncedTabInteractor {
|
|||
* tabs" button.
|
||||
*/
|
||||
fun onSyncedTabShowAllClicked()
|
||||
|
||||
/**
|
||||
* Adds the url of the synced tab to the homescreen blocklist and removes the tab
|
||||
* from the recent synced tabs.
|
||||
*
|
||||
* @param tab The recent synced tab to be removed.
|
||||
*/
|
||||
fun onRemovedRecentSyncedTab(tab: RecentSyncedTab)
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
|
||||
package org.mozilla.fenix.home.recentsyncedtabs.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -21,12 +22,20 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
@ -39,6 +48,7 @@ import org.mozilla.fenix.compose.Image
|
|||
import org.mozilla.fenix.compose.ThumbnailCard
|
||||
import org.mozilla.fenix.compose.button.Button
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.theme.Theme
|
||||
|
||||
|
@ -48,19 +58,32 @@ import org.mozilla.fenix.theme.Theme
|
|||
* @param tab The [RecentSyncedTab] to display.
|
||||
* @param onRecentSyncedTabClick Invoked when the user clicks on the recent synced tab.
|
||||
* @param onSeeAllSyncedTabsButtonClick Invoked when user clicks on the "See all" button in the synced tab card.
|
||||
* @param onRemoveSyncedTab Invoked when user clicks on the "Remove" dropdown menu option.
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun RecentSyncedTab(
|
||||
tab: RecentSyncedTab?,
|
||||
onRecentSyncedTabClick: (RecentSyncedTab) -> Unit,
|
||||
onSeeAllSyncedTabsButtonClick: () -> Unit,
|
||||
onRemoveSyncedTab: (RecentSyncedTab) -> Unit,
|
||||
) {
|
||||
var isDropdownExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
fun removeSyncedTab(recentSyncedTab: RecentSyncedTab) {
|
||||
isDropdownExpanded = false
|
||||
onRemoveSyncedTab(recentSyncedTab)
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(180.dp)
|
||||
.clickable { tab?.let { onRecentSyncedTabClick(tab) } },
|
||||
.combinedClickable(
|
||||
onClick = { tab?.let { onRecentSyncedTabClick(tab) } },
|
||||
onLongClick = { isDropdownExpanded = true }
|
||||
),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
backgroundColor = FirefoxTheme.colors.layer2,
|
||||
elevation = 6.dp
|
||||
|
@ -160,6 +183,8 @@ fun RecentSyncedTab(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
SyncedTabDropdown(isDropdownExpanded, tab, ::removeSyncedTab) { isDropdownExpanded = false }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,6 +227,48 @@ private fun TextLinePlaceHolder() {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Long click dropdown menu shown for a [RecentSyncedTab].
|
||||
*
|
||||
* @param showMenu Whether this is currently open and visible to the user.
|
||||
* @param tab The [RecentTab.Tab] for which this menu is shown.
|
||||
* @param onRemove Called when the user interacts with the `Remove` option.
|
||||
* @param onDismiss Called when the user chooses a menu option or requests to dismiss the menu.
|
||||
*/
|
||||
@Composable
|
||||
private fun SyncedTabDropdown(
|
||||
showMenu: Boolean,
|
||||
tab: RecentSyncedTab?,
|
||||
onRemove: (RecentSyncedTab) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
DisposableEffect(LocalConfiguration.current.orientation) {
|
||||
onDispose { onDismiss() }
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = showMenu && tab != null,
|
||||
onDismissRequest = { onDismiss() },
|
||||
modifier = Modifier
|
||||
.background(color = FirefoxTheme.colors.layer2)
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
tab?.let { onRemove(it) }
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.recent_synced_tab_menu_item_remove),
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun LoadedRecentSyncedTab() {
|
||||
|
@ -217,6 +284,7 @@ private fun LoadedRecentSyncedTab() {
|
|||
tab = tab,
|
||||
onRecentSyncedTabClick = {},
|
||||
onSeeAllSyncedTabsButtonClick = {},
|
||||
onRemoveSyncedTab = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -229,6 +297,7 @@ private fun LoadingRecentSyncedTab() {
|
|||
tab = null,
|
||||
onRecentSyncedTabClick = {},
|
||||
onSeeAllSyncedTabsButtonClick = {},
|
||||
onRemoveSyncedTab = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ class RecentSyncedTabViewHolder(
|
|||
tab = syncedTab,
|
||||
onRecentSyncedTabClick = recentSyncedTabInteractor::onRecentSyncedTabClicked,
|
||||
onSeeAllSyncedTabsButtonClick = recentSyncedTabInteractor::onSyncedTabShowAllClicked,
|
||||
onRemoveSyncedTab = recentSyncedTabInteractor::onRemovedRecentSyncedTab
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -388,6 +388,10 @@ class SessionControlInteractor(
|
|||
recentSyncedTabController.handleSyncedTabShowAllClicked()
|
||||
}
|
||||
|
||||
override fun onRemovedRecentSyncedTab(tab: RecentSyncedTab) {
|
||||
recentSyncedTabController.handleRecentSyncedTabRemoved(tab)
|
||||
}
|
||||
|
||||
override fun onRecentBookmarkClicked(bookmark: RecentBookmark) {
|
||||
recentBookmarksController.handleBookmarkClicked(bookmark)
|
||||
}
|
||||
|
|
|
@ -128,6 +128,8 @@
|
|||
<string name="recent_tabs_see_all_synced_tabs_button_text">See all synced tabs</string>
|
||||
<!-- Accessibility description for device icon used for recent synced tab -->
|
||||
<string name="recent_tabs_synced_device_icon_content_description">Synced device</string>
|
||||
<!-- Text for the dropdown menu to remove a recent synced tab from the homescreen -->
|
||||
<string name="recent_synced_tab_menu_item_remove">Remove</string>
|
||||
<!-- Text for the menu button to remove a grouped highlight from the user's browsing history
|
||||
in the Recently visited section -->
|
||||
<string name="recent_tab_menu_item_remove">Remove</string>
|
||||
|
|
|
@ -55,6 +55,7 @@ class AppStoreTest {
|
|||
private lateinit var currentMode: CurrentMode
|
||||
private lateinit var appState: AppState
|
||||
private lateinit var appStore: AppStore
|
||||
private lateinit var recentSyncedTabsList: List<RecentSyncedTab>
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
|
@ -62,6 +63,15 @@ class AppStoreTest {
|
|||
accountManager = mockk(relaxed = true)
|
||||
onboarding = mockk(relaxed = true)
|
||||
browsingModeManager = mockk(relaxed = true)
|
||||
recentSyncedTabsList = listOf(
|
||||
RecentSyncedTab(
|
||||
deviceDisplayName = "",
|
||||
deviceType = mockk(relaxed = true),
|
||||
title = "",
|
||||
url = "",
|
||||
previewImageUrl = null
|
||||
)
|
||||
)
|
||||
|
||||
every { context.components.backgroundServices.accountManager } returns accountManager
|
||||
every { onboarding.userHasBeenOnboarded() } returns true
|
||||
|
@ -79,7 +89,8 @@ class AppStoreTest {
|
|||
mode = currentMode.getCurrentMode(),
|
||||
topSites = emptyList(),
|
||||
showCollectionPlaceholder = true,
|
||||
recentTabs = emptyList()
|
||||
recentTabs = emptyList(),
|
||||
recentSyncedTabState = RecentSyncedTabState.Success(recentSyncedTabsList)
|
||||
)
|
||||
|
||||
appStore = AppStore(appState)
|
||||
|
@ -244,7 +255,7 @@ class AppStoreTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `Test changing the collections, mode, recent tabs and bookmarks, history metadata and top sites in the AppStore`() =
|
||||
fun `Test changing the collections, mode, recent tabs and bookmarks, history metadata, top sites and recent synced tabs in the AppStore`() =
|
||||
runTest {
|
||||
// Verify that the default state of the HomeFragment is correct.
|
||||
assertEquals(0, appStore.state.collections.size)
|
||||
|
@ -253,6 +264,10 @@ class AppStoreTest {
|
|||
assertEquals(0, appStore.state.recentBookmarks.size)
|
||||
assertEquals(0, appStore.state.recentHistory.size)
|
||||
assertEquals(Mode.Normal, appStore.state.mode)
|
||||
assertEquals(
|
||||
RecentSyncedTabState.Success(recentSyncedTabsList),
|
||||
appStore.state.recentSyncedTabState
|
||||
)
|
||||
|
||||
val collections: List<TabCollection> = listOf(mockk())
|
||||
val topSites: List<TopSite> = listOf(mockk(), mockk())
|
||||
|
@ -263,6 +278,15 @@ class AppStoreTest {
|
|||
val group3 = RecentHistoryGroup(title = "test two")
|
||||
val highlight = RecentHistoryHighlight(group2.title, "")
|
||||
val recentHistory: List<RecentlyVisitedItem> = listOf(group1, group2, group3, highlight)
|
||||
val recentSyncedTab = RecentSyncedTab(
|
||||
deviceDisplayName = "device1",
|
||||
deviceType = mockk(relaxed = true),
|
||||
title = "1",
|
||||
url = "",
|
||||
previewImageUrl = null
|
||||
)
|
||||
val recentSyncedTabState: RecentSyncedTabState =
|
||||
RecentSyncedTabState.Success(recentSyncedTabsList + recentSyncedTab)
|
||||
|
||||
appStore.dispatch(
|
||||
AppAction.Change(
|
||||
|
@ -272,7 +296,8 @@ class AppStoreTest {
|
|||
showCollectionPlaceholder = true,
|
||||
recentTabs = recentTabs,
|
||||
recentBookmarks = recentBookmarks,
|
||||
recentHistory = recentHistory
|
||||
recentHistory = recentHistory,
|
||||
recentSyncedTabState = recentSyncedTabState
|
||||
)
|
||||
).join()
|
||||
|
||||
|
@ -282,6 +307,10 @@ class AppStoreTest {
|
|||
assertEquals(recentBookmarks, appStore.state.recentBookmarks)
|
||||
assertEquals(listOf(group1, group2, group3, highlight), appStore.state.recentHistory)
|
||||
assertEquals(Mode.Private, appStore.state.mode)
|
||||
assertEquals(
|
||||
recentSyncedTabState,
|
||||
appStore.state.recentSyncedTabState
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -47,6 +47,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState,
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -73,6 +74,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState,
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -99,6 +101,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState,
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -125,6 +128,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -153,6 +157,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = updatedRecentTabs,
|
||||
recentBookmarks = updatedBookmarks,
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -188,6 +193,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = updatedRecentTabs,
|
||||
recentBookmarks = updatedBookmarks,
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -240,6 +246,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -267,6 +274,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -294,6 +302,7 @@ class BlocklistMiddlewareTest {
|
|||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory,
|
||||
recentSyncedTabState = store.state.recentSyncedTabState
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
|
@ -308,14 +317,14 @@ class BlocklistMiddlewareTest {
|
|||
deviceType = mock(),
|
||||
title = "",
|
||||
url = "https://www.mozilla.org",
|
||||
previewImageUrl = ""
|
||||
previewImageUrl = null
|
||||
)
|
||||
val allowedTab = RecentSyncedTab(
|
||||
deviceDisplayName = "",
|
||||
deviceType = mock(),
|
||||
title = "",
|
||||
url = "https://github.com",
|
||||
previewImageUrl = ""
|
||||
previewImageUrl = null
|
||||
)
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedHost.stripAndHash())
|
||||
|
@ -341,4 +350,105 @@ class BlocklistMiddlewareTest {
|
|||
(store.state.recentSyncedTabState as RecentSyncedTabState.Success).tabs.single()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the recent synced tab state is changed to None or Loading THEN the middleware does not change the state`() {
|
||||
val blockedHost = "https://www.mozilla.org"
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedHost.stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = AppStore(
|
||||
AppState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
AppAction.RecentSyncedTabStateChange(
|
||||
RecentSyncedTabState.None
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(RecentSyncedTabState.None, store.state.recentSyncedTabState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN all recently synced submitted tabs are blocked THEN the recent synced tab state should be set to None`() {
|
||||
val blockedHost = "https://www.mozilla.org"
|
||||
val blockedTab = RecentSyncedTab(
|
||||
deviceDisplayName = "",
|
||||
deviceType = mock(),
|
||||
title = "",
|
||||
url = "https://www.mozilla.org",
|
||||
previewImageUrl = null
|
||||
)
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedHost.stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = AppStore(
|
||||
AppState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
AppAction.RecentSyncedTabStateChange(
|
||||
RecentSyncedTabState.Success(
|
||||
listOf(blockedTab)
|
||||
)
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(
|
||||
RecentSyncedTabState.None,
|
||||
store.state.recentSyncedTabState
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the most recent used synced tab is blocked THEN the following recent synced tabs remain ordered`() {
|
||||
val tabUrls = listOf("link1", "link2", "link3")
|
||||
val currentTabs = listOf(
|
||||
RecentSyncedTab(
|
||||
deviceDisplayName = "device1",
|
||||
deviceType = mock(),
|
||||
title = "",
|
||||
url = tabUrls[0],
|
||||
previewImageUrl = null
|
||||
),
|
||||
RecentSyncedTab(
|
||||
deviceDisplayName = "",
|
||||
deviceType = mock(),
|
||||
title = "",
|
||||
url = tabUrls[1],
|
||||
previewImageUrl = null
|
||||
),
|
||||
RecentSyncedTab(
|
||||
deviceDisplayName = "",
|
||||
deviceType = mock(),
|
||||
title = "",
|
||||
url = tabUrls[2],
|
||||
previewImageUrl = null
|
||||
)
|
||||
)
|
||||
val store = AppStore(
|
||||
AppState(recentSyncedTabState = RecentSyncedTabState.Success(currentTabs)),
|
||||
middlewares = listOf(BlocklistMiddleware(blocklistHandler))
|
||||
)
|
||||
val updateSlot = slot<Set<String>>()
|
||||
every { mockSettings.homescreenBlocklist = capture(updateSlot) } returns Unit
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(tabUrls[0].stripAndHash())
|
||||
|
||||
store.dispatch(
|
||||
AppAction.RemoveRecentSyncedTab(
|
||||
currentTabs.first()
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(
|
||||
2, (store.state.recentSyncedTabState as RecentSyncedTabState.Success).tabs.size
|
||||
)
|
||||
assertEquals(setOf(tabUrls[0].stripAndHash()), updateSlot.captured)
|
||||
assertEquals(
|
||||
currentTabs[1],
|
||||
(store.state.recentSyncedTabState as RecentSyncedTabState.Success).tabs.firstOrNull()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.GleanMetrics.RecentSyncedTabs
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.AppStore
|
||||
import org.mozilla.fenix.components.appstate.AppAction
|
||||
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
|
||||
import org.mozilla.fenix.tabstray.Page
|
||||
|
@ -42,6 +44,7 @@ class DefaultRecentSyncedTabControllerTest {
|
|||
|
||||
private val tabsUseCases: TabsUseCases = mockk()
|
||||
private val navController: NavController = mockk()
|
||||
private val appStore: AppStore = mockk(relaxed = true)
|
||||
private val accessPoint = TabsTrayAccessPoint.HomeRecentSyncedTab
|
||||
|
||||
private lateinit var controller: RecentSyncedTabController
|
||||
|
@ -52,6 +55,7 @@ class DefaultRecentSyncedTabControllerTest {
|
|||
tabsUseCase = tabsUseCases,
|
||||
navController = navController,
|
||||
accessPoint = accessPoint,
|
||||
appStore = appStore
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -174,4 +178,19 @@ class DefaultRecentSyncedTabControllerTest {
|
|||
|
||||
assertEquals(1, RecentSyncedTabs.showAllSyncedTabsClicked.testGetValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN synced tab is removed from homescreen THEN RemoveRecentSyncedTab action is dispatched`() {
|
||||
val tab = RecentSyncedTab(
|
||||
deviceDisplayName = "display",
|
||||
deviceType = DeviceType.DESKTOP,
|
||||
title = "title",
|
||||
url = "https://mozilla.org",
|
||||
previewImageUrl = null
|
||||
)
|
||||
|
||||
controller.handleRecentSyncedTabRemoved(tab)
|
||||
|
||||
verify { appStore.dispatch(AppAction.RemoveRecentSyncedTab(tab)) }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user