For #26400 - Add long-press option to remove tab pickup on homescreen

pull/543/head
Alexandru2909 2 years ago committed by mergify[bot]
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
@ -53,27 +53,37 @@ class BlocklistHandler(private val settings: Settings) {
}
/**
* Filter a list of recent history items by the blocklist. Requires this class to be contextually
* in a scope.
* 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("filterRecentHistory")
fun List<RecentlyVisitedItem>.filteredByBlocklist(): List<RecentlyVisitedItem> =
settings.homescreenBlocklist.let { blocklist ->
filterNot {
it is RecentlyVisitedItem.RecentHistoryHighlight &&
@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 recently synced tabs by the blocklist. Requires this class to be contextually
* Filter a list of recent history items by the blocklist. Requires this class to be contextually
* in a scope.
*/
@JvmName("filterRecentSyncedTab")
fun List<RecentSyncedTab>.filteredByBlocklist(): List<RecentSyncedTab> =
@JvmName("filterRecentHistory")
fun List<RecentlyVisitedItem>.filteredByBlocklist(): List<RecentlyVisitedItem> =
settings.homescreenBlocklist.let { blocklist ->
filterNot {
blocklistContainsUrl(blocklist, it.url)
it is RecentlyVisitedItem.RecentHistoryHighlight &&
blocklistContainsUrl(blocklist, it.url)
}
}

@ -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…
Cancel
Save