[fenix] for https://github.com/mozilla-mobile/fenix/issues/23069: add blocklist middleware for home
parent
6bf6f7f4ab
commit
ce058f1abf
@ -0,0 +1,18 @@
|
||||
/* 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.ext
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
|
||||
/**
|
||||
* Removes a [RecentTab.Tab] from a list of [RecentTab]. [RecentTab.SearchGroup]s will not be filtered.
|
||||
*
|
||||
* @param tab [RecentTab] to remove from the list
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal fun List<RecentTab>.filterOutTab(tab: RecentTab): List<RecentTab> = filterNot {
|
||||
it is RecentTab.Tab && tab is RecentTab.Tab && it.state.id == tab.state.id
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/* 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.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.recenttabs.RecentTab
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
/**
|
||||
* Class for interacting with the a blocklist stored in [settings].
|
||||
* The blocklist is a set of SHA1 hashed URLs, which are stripped
|
||||
* of protocols and common subdomains like "www" or "mobile".
|
||||
*/
|
||||
class BlocklistHandler(private val settings: Settings) {
|
||||
|
||||
/**
|
||||
* Add an URL to the blocklist. The URL will be stripped and hashed,
|
||||
* so no pre-formatted is required.
|
||||
*/
|
||||
fun addUrlToBlocklist(url: String) {
|
||||
val updatedBlocklist = settings.homescreenBlocklist + url.stripAndHash()
|
||||
settings.homescreenBlocklist = updatedBlocklist
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of recent bookmarks by the blocklist. Requires this class to be contextually
|
||||
* in a scope.
|
||||
*/
|
||||
@JvmName("filterRecentBookmark")
|
||||
fun List<RecentBookmark>.filteredByBlocklist(): List<RecentBookmark> =
|
||||
settings.homescreenBlocklist.let { blocklist ->
|
||||
filterNot {
|
||||
it.url?.let { url -> blocklistContainsUrl(blocklist, url) } ?: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of recent tabs by the blocklist. Requires this class to be contextually
|
||||
* in a scope.
|
||||
*/
|
||||
@JvmName("filterRecentTab")
|
||||
fun List<RecentTab>.filteredByBlocklist(): List<RecentTab> =
|
||||
settings.homescreenBlocklist.let { blocklist ->
|
||||
filterNot {
|
||||
it is RecentTab.Tab && blocklistContainsUrl(blocklist, it.state.content.url)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of recent history items by the blocklist. Requires this class to be contextually
|
||||
* in a scope.
|
||||
*/
|
||||
@JvmName("filterRecentHistory")
|
||||
fun List<RecentlyVisitedItem>.filteredByBlocklist(): List<RecentlyVisitedItem> =
|
||||
settings.homescreenBlocklist.let { blocklist ->
|
||||
filterNot {
|
||||
it is RecentlyVisitedItem.RecentHistoryHighlight &&
|
||||
blocklistContainsUrl(blocklist, it.url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun blocklistContainsUrl(blocklist: Set<String>, url: String): Boolean =
|
||||
blocklist.any { it == url.stripAndHash() }
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun String.stripAndHash(): String =
|
||||
this.stripProtocolAndCommonSubdomains().sha1()
|
||||
|
||||
// Eventually, this should be standardize in A-C and this can then be removed
|
||||
// https://github.com/mozilla-mobile/android-components/issues/11743
|
||||
private fun String.stripProtocolAndCommonSubdomains(): String {
|
||||
val stripped = this.substringAfter("://").dropLastWhile { it == '/' }
|
||||
// This kind of stripping allows us to match "twitter" to "mobile.twitter.com".
|
||||
// Borrowed from DomainMatcher in A-C
|
||||
val domainsToStrip = listOf("www", "mobile", "m")
|
||||
|
||||
domainsToStrip.forEach { domain ->
|
||||
if (stripped.startsWith("$domain.")) {
|
||||
return stripped.substring(domain.length + 1)
|
||||
}
|
||||
}
|
||||
return stripped
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/* 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.home.blocklist
|
||||
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import org.mozilla.fenix.home.HomeFragmentAction
|
||||
import org.mozilla.fenix.home.HomeFragmentState
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
|
||||
/**
|
||||
* This [Middleware] reacts to item removals from the home screen, adding them to a blocklist.
|
||||
* Additionally, it reacts to state changes in order to filter them by the blocklist.
|
||||
*
|
||||
* @param settings Blocklist is stored here as a string set
|
||||
*/
|
||||
class BlocklistMiddleware(
|
||||
private val blocklistHandler: BlocklistHandler
|
||||
) : Middleware<HomeFragmentState, HomeFragmentAction> {
|
||||
|
||||
/**
|
||||
* Will filter "Change" actions using the blocklist and use "Remove" actions to update
|
||||
* the blocklist.
|
||||
*/
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<HomeFragmentState, HomeFragmentAction>,
|
||||
next: (HomeFragmentAction) -> Unit,
|
||||
action: HomeFragmentAction
|
||||
) {
|
||||
next(getUpdatedAction(context.state, action))
|
||||
}
|
||||
|
||||
private fun getUpdatedAction(
|
||||
state: HomeFragmentState,
|
||||
action: HomeFragmentAction
|
||||
) = with(blocklistHandler) {
|
||||
when (action) {
|
||||
is HomeFragmentAction.Change -> {
|
||||
action.copy(
|
||||
recentBookmarks = action.recentBookmarks.filteredByBlocklist(),
|
||||
recentTabs = action.recentTabs.filteredByBlocklist(),
|
||||
recentHistory = action.recentHistory.filteredByBlocklist()
|
||||
)
|
||||
}
|
||||
is HomeFragmentAction.RecentTabsChange -> {
|
||||
action.copy(
|
||||
recentTabs = action.recentTabs.filteredByBlocklist()
|
||||
)
|
||||
}
|
||||
is HomeFragmentAction.RecentBookmarksChange -> {
|
||||
action.copy(
|
||||
recentBookmarks = action.recentBookmarks.filteredByBlocklist()
|
||||
)
|
||||
}
|
||||
is HomeFragmentAction.RecentHistoryChange -> {
|
||||
action.copy(recentHistory = action.recentHistory.filteredByBlocklist())
|
||||
}
|
||||
is HomeFragmentAction.RemoveRecentTab -> {
|
||||
if (action.recentTab is RecentTab.Tab) {
|
||||
addUrlToBlocklist(action.recentTab.state.content.url)
|
||||
state.toActionFilteringAllState(this)
|
||||
} else {
|
||||
action
|
||||
}
|
||||
}
|
||||
is HomeFragmentAction.RemoveRecentBookmark -> {
|
||||
action.recentBookmark.url?.let { url ->
|
||||
addUrlToBlocklist(url)
|
||||
state.toActionFilteringAllState(this)
|
||||
} ?: action
|
||||
}
|
||||
is HomeFragmentAction.RemoveRecentHistoryHighlight -> {
|
||||
addUrlToBlocklist(action.highlightUrl)
|
||||
state.toActionFilteringAllState(this)
|
||||
}
|
||||
else -> action
|
||||
}
|
||||
}
|
||||
|
||||
// When an item is removed from any part of the state, it should also be removed from any other
|
||||
// relevant parts that contain it.
|
||||
// This is a candidate for refactoring once context receivers lands in Kotlin 1.6.20
|
||||
// https://blog.jetbrains.com/kotlin/2022/02/kotlin-1-6-20-m1-released/#prototype-of-context-receivers-for-kotlin-jvm
|
||||
private fun HomeFragmentState.toActionFilteringAllState(blocklistHandler: BlocklistHandler) =
|
||||
with(blocklistHandler) {
|
||||
HomeFragmentAction.Change(
|
||||
recentTabs = recentTabs.filteredByBlocklist(),
|
||||
recentBookmarks = recentBookmarks.filteredByBlocklist(),
|
||||
recentHistory = recentHistory.filteredByBlocklist(),
|
||||
topSites = topSites,
|
||||
mode = mode,
|
||||
collections = collections,
|
||||
tip = tip,
|
||||
showCollectionPlaceholder = showCollectionPlaceholder
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/* 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.home.recentbookmarks.view
|
||||
|
||||
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
|
||||
|
||||
/**
|
||||
* A menu item in the recent bookmarks dropdown menu.
|
||||
*
|
||||
* @property title The menu item title.
|
||||
* @property onClick Invoked when the user clicks on the menu item.
|
||||
*/
|
||||
data class RecentBookmarksMenuItem(
|
||||
val title: String,
|
||||
val onClick: (RecentBookmark) -> Unit
|
||||
)
|
@ -0,0 +1,18 @@
|
||||
/* 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.home.recenttabs.view
|
||||
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
|
||||
/**
|
||||
* A menu item in the recent tab dropdown menu.
|
||||
*
|
||||
* @property title The menu item title.
|
||||
* @property onClick Invoked when the user clicks on the menu item.
|
||||
*/
|
||||
class RecentTabMenuItem(
|
||||
val title: String,
|
||||
val onClick: (RecentTab.Tab) -> Unit
|
||||
)
|
@ -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.ext
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
|
||||
class RecentTabsTest {
|
||||
@Test
|
||||
fun `Test filtering out tab`() {
|
||||
val filteredId = "id"
|
||||
val mockSessionState: TabSessionState = mockk()
|
||||
every { mockSessionState.id } returns filteredId
|
||||
val tab = RecentTab.Tab(mockSessionState)
|
||||
val searchGroup = RecentTab.SearchGroup(
|
||||
tabId = filteredId,
|
||||
searchTerm = "",
|
||||
url = "",
|
||||
thumbnail = null,
|
||||
count = 0
|
||||
)
|
||||
|
||||
val recentTabs = listOf(tab, searchGroup)
|
||||
val result = recentTabs.filterOutTab(tab)
|
||||
|
||||
assertEquals(listOf(searchGroup), result)
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package org.mozilla.fenix.home.blocklist
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import mozilla.components.browser.state.state.ContentState
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class BlocklistHandlerTest {
|
||||
private val mockSettings: Settings = mockk()
|
||||
|
||||
private lateinit var blocklistHandler: BlocklistHandler
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
blocklistHandler = BlocklistHandler(mockSettings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN url added to blocklist THEN settings updated with hash`() {
|
||||
val addedUrl = "url"
|
||||
val updateSlot = slot<Set<String>>()
|
||||
every { mockSettings.homescreenBlocklist } returns setOf()
|
||||
every { mockSettings.homescreenBlocklist = capture(updateSlot) } returns Unit
|
||||
|
||||
blocklistHandler.addUrlToBlocklist(addedUrl)
|
||||
|
||||
assertEquals(setOf(addedUrl.stripAndHash()), updateSlot.captured)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN bookmark is not in blocklist THEN will not be filtered`() {
|
||||
val bookmarks = listOf(RecentBookmark(url = "test"))
|
||||
every { mockSettings.homescreenBlocklist } returns setOf()
|
||||
|
||||
val filtered = with(blocklistHandler) {
|
||||
bookmarks.filteredByBlocklist()
|
||||
}
|
||||
|
||||
assertEquals(bookmarks, filtered)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN bookmark is in blocklist THEN will be filtered`() {
|
||||
val blockedUrl = "test"
|
||||
val bookmarks = listOf(RecentBookmark(url = blockedUrl))
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedUrl.stripAndHash())
|
||||
|
||||
val filtered = with(blocklistHandler) {
|
||||
bookmarks.filteredByBlocklist()
|
||||
}
|
||||
|
||||
assertEquals(listOf<String>(), filtered)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN recent history is not in blocklist THEN will not be filtered`() {
|
||||
val recentHistory = listOf(RecentlyVisitedItem.RecentHistoryHighlight(url = "test", title = ""))
|
||||
every { mockSettings.homescreenBlocklist } returns setOf()
|
||||
|
||||
val filtered = with(blocklistHandler) {
|
||||
recentHistory.filteredByBlocklist()
|
||||
}
|
||||
|
||||
assertEquals(recentHistory, filtered)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN recent history is in blocklist THEN will be filtered`() {
|
||||
val blockedUrl = "test"
|
||||
val recentHistory = listOf(RecentlyVisitedItem.RecentHistoryHighlight(url = blockedUrl, title = ""))
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedUrl.stripAndHash())
|
||||
|
||||
val filtered = with(blocklistHandler) {
|
||||
recentHistory.filteredByBlocklist()
|
||||
}
|
||||
|
||||
assertEquals(listOf<String>(), filtered)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN recent tab is not in blocklist THEN will not be filtered`() {
|
||||
val mockSessionState: TabSessionState = mockk()
|
||||
val mockContent: ContentState = mockk()
|
||||
val tabs = listOf(RecentTab.Tab(mockSessionState))
|
||||
every { mockSessionState.content } returns mockContent
|
||||
every { mockContent.url } returns "test"
|
||||
every { mockSettings.homescreenBlocklist } returns setOf()
|
||||
|
||||
val filtered = with(blocklistHandler) {
|
||||
tabs.filteredByBlocklist()
|
||||
}
|
||||
|
||||
assertEquals(tabs, filtered)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN recent tab is in blocklist THEN will be filtered`() {
|
||||
val blockedUrl = "test"
|
||||
val mockSessionState: TabSessionState = mockk()
|
||||
val mockContent: ContentState = mockk()
|
||||
val tabs = listOf(RecentTab.Tab(mockSessionState))
|
||||
every { mockSessionState.content } returns mockContent
|
||||
every { mockContent.url } returns blockedUrl
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedUrl.stripAndHash())
|
||||
|
||||
val filtered = with(blocklistHandler) {
|
||||
tabs.filteredByBlocklist()
|
||||
}
|
||||
|
||||
assertEquals(listOf<String>(), filtered)
|
||||
}
|
||||
}
|
@ -0,0 +1,308 @@
|
||||
/* 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.home.blocklist
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.middleware.CaptureActionsMiddleware
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.home.HomeFragmentAction
|
||||
import org.mozilla.fenix.home.HomeFragmentState
|
||||
import org.mozilla.fenix.home.HomeFragmentStore
|
||||
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
|
||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class BlocklistMiddlewareTest {
|
||||
private val mockSettings: Settings = mockk()
|
||||
private val blocklistHandler = BlocklistHandler(mockSettings)
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty blocklist WHEN action intercepted THEN unchanged by middleware`() {
|
||||
val updatedBookmark = RecentBookmark(url = "https://www.mozilla.org/")
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf()
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(updatedBookmark, store.state.recentBookmarks[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-empty blocklist WHEN action intercepted with no matching elements THEN unchanged by middleware`() {
|
||||
val updatedBookmark = RecentBookmark(url = "https://www.mozilla.org/")
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf("https://www.github.org/".stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(updatedBookmark, store.state.recentBookmarks[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-empty blocklist with specific pages WHEN action intercepted with matching host THEN unchanged by middleware`() {
|
||||
val updatedBookmark = RecentBookmark(url = "https://github.com/")
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf("https://github.com/mozilla-mobile/fenix".stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(updatedBookmark, store.state.recentBookmarks[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-empty blocklist WHEN action intercepted with matching elements THEN filtered by middleware`() {
|
||||
val updatedBookmark = RecentBookmark(url = "https://www.mozilla.org/")
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf("https://www.mozilla.org/".stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertTrue(store.state.recentBookmarks.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-empty blocklist WHEN action intercepted with matching elements THEN all relevant sections filtered by middleware`() {
|
||||
val blockedUrl = "https://www.mozilla.org/"
|
||||
val updatedBookmarks = listOf(RecentBookmark(url = blockedUrl))
|
||||
val updatedRecentTabs = listOf(RecentTab.Tab(createTab(url = blockedUrl)))
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedUrl.stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = updatedRecentTabs,
|
||||
recentBookmarks = updatedBookmarks,
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertTrue(store.state.recentBookmarks.isEmpty())
|
||||
assertTrue(store.state.recentTabs.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-empty blocklist WHEN action intercepted with matching elements THEN only matching urls removed`() {
|
||||
val blockedUrl = "https://www.mozilla.org/"
|
||||
val unblockedUrl = "https://www.github.org/"
|
||||
val unblockedBookmark = RecentBookmark(unblockedUrl)
|
||||
val updatedBookmarks = listOf(
|
||||
RecentBookmark(url = blockedUrl), unblockedBookmark
|
||||
)
|
||||
val unblockedRecentTab = RecentTab.Tab(createTab(url = unblockedUrl))
|
||||
val updatedRecentTabs =
|
||||
listOf(RecentTab.Tab(createTab(url = blockedUrl)), unblockedRecentTab)
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(blockedUrl.stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = updatedRecentTabs,
|
||||
recentBookmarks = updatedBookmarks,
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(unblockedBookmark, store.state.recentBookmarks[0])
|
||||
assertEquals(unblockedRecentTab, store.state.recentTabs[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN remove action intercepted THEN hashed url added to blocklist and Change action dispatched`() {
|
||||
val captureMiddleware = CaptureActionsMiddleware<HomeFragmentState, HomeFragmentAction>()
|
||||
val removedUrl = "https://www.mozilla.org/"
|
||||
val removedBookmark = RecentBookmark(url = removedUrl)
|
||||
|
||||
val updateSlot = slot<Set<String>>()
|
||||
every { mockSettings.homescreenBlocklist } returns setOf() andThen setOf(removedUrl.stripAndHash())
|
||||
every { mockSettings.homescreenBlocklist = capture(updateSlot) } returns Unit
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(recentBookmarks = listOf(removedBookmark)),
|
||||
middlewares = listOf(middleware, captureMiddleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.RemoveRecentBookmark(removedBookmark)
|
||||
).joinBlocking()
|
||||
|
||||
val capturedAction = captureMiddleware.findFirstAction(HomeFragmentAction.Change::class)
|
||||
assertEquals(emptyList<RecentBookmark>(), capturedAction.recentBookmarks)
|
||||
assertEquals(setOf(removedUrl.stripAndHash()), updateSlot.captured)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN urls are compared to blocklist THEN protocols are stripped`() {
|
||||
val host = "www.mozilla.org/"
|
||||
val updatedBookmark = RecentBookmark(url = "http://$host")
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf("https://$host".stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertTrue(store.state.recentBookmarks.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN urls are compared to blocklist THEN common subdomains are stripped`() {
|
||||
val host = "mozilla.org/"
|
||||
val updatedBookmark = RecentBookmark(url = host)
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf(host.stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertTrue(store.state.recentBookmarks.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN urls are compared to blocklist THEN trailing slashes are stripped`() {
|
||||
val host = "www.mozilla.org"
|
||||
val updatedBookmark = RecentBookmark(url = "http://$host/")
|
||||
|
||||
every { mockSettings.homescreenBlocklist } returns setOf("https://$host".stripAndHash())
|
||||
val middleware = BlocklistMiddleware(blocklistHandler)
|
||||
val store = HomeFragmentStore(
|
||||
HomeFragmentState(),
|
||||
middlewares = listOf(middleware)
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
HomeFragmentAction.Change(
|
||||
topSites = store.state.topSites,
|
||||
mode = store.state.mode,
|
||||
collections = store.state.collections,
|
||||
tip = store.state.tip,
|
||||
showCollectionPlaceholder = store.state.showCollectionPlaceholder,
|
||||
recentTabs = store.state.recentTabs,
|
||||
recentBookmarks = listOf(updatedBookmark),
|
||||
recentHistory = store.state.recentHistory
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertTrue(store.state.recentBookmarks.isEmpty())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue