commit 802d3f18298937d64882e23b721fb7b5e1c45ab2
parent d314389fc89c949297261180264c1c7ec56aaf08
Author: Cathy Lu <calu@mozilla.com>
Date: Thu, 8 Jan 2026 20:07:28 +0000
Bug 2008814 - Tab search will only show one about:home result r=android-reviewers,007
Differential Revision: https://phabricator.services.mozilla.com/D278205
Diffstat:
2 files changed, 107 insertions(+), 1 deletion(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/redux/middleware/TabSearchMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/redux/middleware/TabSearchMiddleware.kt
@@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.concept.engine.utils.ABOUT_HOME_URL
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.Store
import org.mozilla.fenix.tabstray.Page
@@ -47,7 +48,15 @@ class TabSearchMiddleware(
val filteredTabs = if (query.isBlank()) {
emptyList()
} else {
- tabs.filter { it.contains(text = query) }
+ val (matchingHomepage, matchingNonHomepage) =
+ tabs.filter { it.contains(text = query) }
+ .sortedByDescending { it.lastAccess }
+ .partition { it.isHomepage() }
+
+ // If the results contain homepages, only display one homepage result
+ val homeTab = matchingHomepage.take(1)
+
+ homeTab + matchingNonHomepage
}
mainScope.launch {
@@ -63,4 +72,8 @@ class TabSearchMiddleware(
private fun TabSessionState.contains(text: String): Boolean {
return content.url.contains(text, ignoreCase = true) || content.title.contains(text, ignoreCase = true)
}
+
+ private fun TabSessionState.isHomepage(): Boolean {
+ return content.url.equals(ABOUT_HOME_URL, ignoreCase = true)
+ }
}
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/tabstray/redux/middleware/TabSearchMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/tabstray/redux/middleware/TabSearchMiddlewareTest.kt
@@ -10,6 +10,7 @@ import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
+import mozilla.components.concept.engine.utils.ABOUT_HOME_URL
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -371,4 +372,96 @@ class TabSearchMiddlewareTest {
assertTrue(store.state.tabSearchState.searchResults.isEmpty())
}
+
+ @Test
+ fun `WHEN SearchQueryChanged AND multiple about home tabs match THEN only the most recently accessed about home tab is included`() = runTest {
+ val aboutHomeOld = createTab(url = ABOUT_HOME_URL, title = "Homepage", lastAccess = 10L)
+ val aboutHomeNew = createTab(url = ABOUT_HOME_URL, title = "Homepage", lastAccess = 20L)
+ val aboutHomeNewest = createTab(url = ABOUT_HOME_URL, title = "Homepage", lastAccess = 30L)
+
+ val matchingNonHomepage = listOf(
+ createTab(url = "mozilla.org/home"),
+ createTab(url = "mozilla.org", title = "Homepage"),
+ )
+ val nonMatching = listOf(
+ createTab(url = "example.com"),
+ )
+
+ val store = TabsTrayStore(
+ middlewares = listOf(
+ TabSearchMiddleware(
+ scope = this,
+ mainScope = this,
+ ),
+ ),
+ initialState = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ normalTabs = listOf(aboutHomeOld, aboutHomeNew, aboutHomeNewest) + matchingNonHomepage + nonMatching,
+ inactiveTabs = emptyList(),
+ ),
+ )
+
+ store.dispatch(TabSearchAction.SearchQueryChanged("home"))
+ advanceUntilIdle()
+
+ val expectedSearchResults = listOf(aboutHomeNewest) + matchingNonHomepage
+ assertEquals(expectedSearchResults, store.state.tabSearchState.searchResults)
+ }
+
+ @Test
+ fun `WHEN SearchQueryChanged AND about home tabs do not match query THEN no about home tabs are included`() = runTest {
+ val aboutHomeOld = createTab(url = ABOUT_HOME_URL, title = "Home", lastAccess = 10L)
+ val aboutHomeNew = createTab(url = ABOUT_HOME_URL, title = "Home", lastAccess = 20L)
+
+ val matchingNonHomepage = listOf(
+ createTab(url = "mozilla.org"),
+ )
+
+ val store = TabsTrayStore(
+ middlewares = listOf(
+ TabSearchMiddleware(
+ scope = this,
+ mainScope = this,
+ ),
+ ),
+ initialState = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ normalTabs = listOf(aboutHomeOld, aboutHomeNew) + matchingNonHomepage,
+ inactiveTabs = emptyList(),
+ ),
+ )
+
+ store.dispatch(TabSearchAction.SearchQueryChanged("mozilla"))
+ advanceUntilIdle()
+
+ assertEquals(matchingNonHomepage, store.state.tabSearchState.searchResults)
+ }
+
+ @Test
+ fun `WHEN SearchQueryChanged AND single about home tab matches THEN it is included`() = runTest {
+ val aboutHome = createTab(url = ABOUT_HOME_URL, title = "Homepage", lastAccess = 42L)
+ val matchingNonHomepage = listOf(
+ createTab(url = "mozilla.org/home"),
+ )
+
+ val store = TabsTrayStore(
+ middlewares = listOf(
+ TabSearchMiddleware(
+ scope = this,
+ mainScope = this,
+ ),
+ ),
+ initialState = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ normalTabs = listOf(aboutHome) + matchingNonHomepage,
+ inactiveTabs = emptyList(),
+ ),
+ )
+
+ store.dispatch(TabSearchAction.SearchQueryChanged("home"))
+ advanceUntilIdle()
+
+ val expectedSearchResults = listOf(aboutHome) + matchingNonHomepage
+ assertEquals(expectedSearchResults, store.state.tabSearchState.searchResults)
+ }
}