Bug 1841143 - Convert `CollectionTest` to support Tabs Tray rewrite

fenix/117.0
Noah Bond 11 months ago committed by mergify[bot]
parent 41b5b60608
commit 14d036e6d7

@ -0,0 +1,501 @@
/* 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.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.collectionRobot
import org.mozilla.fenix.ui.robots.composeTabDrawer
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying basic functionality of tab collections
*
*/
class ComposeCollectionTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val firstCollectionName = "testcollection_1"
private val secondCollectionName = "testcollection_2"
private val collectionName = "First Collection"
@get:Rule
val composeTestRule =
AndroidComposeTestRule(
HomeActivityIntentTestRule(
isHomeOnboardingDialogEnabled = false,
isJumpBackInCFREnabled = false,
isRecentTabsFeatureEnabled = false,
isRecentlyVisitedFeatureEnabled = false,
isPocketEnabled = false,
isWallpaperOnboardingEnabled = false,
isTCPCFREnabled = false,
tabsTrayRewriteEnabled = true,
),
) { it.activity }
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun createFirstCollectionTest() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
mDevice.waitForIdle()
}.openComposeTabDrawer(composeTestRule) {
}.openNewTab {
}.submitQuery(secondWebPage.url.toString()) {
mDevice.waitForIdle()
}.goToHomescreen {
}.clickSaveTabsToCollectionButton(composeTestRule) {
longClickTab(firstWebPage.title)
selectTab(secondWebPage.title)
verifyTabsMultiSelectionCounter(2)
}.clickSaveCollection {
typeCollectionNameAndSave(collectionName)
}
composeTabDrawer(composeTestRule) {
verifySnackBarText("Collection saved!")
clickSnackbarButton("VIEW")
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}
}
@SmokeTest
@Test
fun verifyExpandedCollectionItemsTest() {
val webPage = getGenericAsset(mockWebServer, 1)
val webPageUrl = webPage.url.host.toString()
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(webPage.title, collectionName = collectionName)
clickSnackbarButton("VIEW")
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title)
verifyCollectionTabUrl(true, webPageUrl)
verifyShareCollectionButtonIsVisible(true)
verifyCollectionMenuIsVisible(true, composeTestRule)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, true)
}.collapseCollection(collectionName) {}
collectionRobot {
verifyTabSavedInCollection(webPage.title, false)
verifyShareCollectionButtonIsVisible(false)
verifyCollectionMenuIsVisible(false, composeTestRule)
verifyCollectionTabUrl(false, webPageUrl)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, false)
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title)
verifyCollectionTabUrl(true, webPageUrl)
verifyShareCollectionButtonIsVisible(true)
verifyCollectionMenuIsVisible(true, composeTestRule)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, true)
}.collapseCollection(collectionName) {}
collectionRobot {
verifyTabSavedInCollection(webPage.title, false)
verifyShareCollectionButtonIsVisible(false)
verifyCollectionMenuIsVisible(false, composeTestRule)
verifyCollectionTabUrl(false, webPageUrl)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, false)
}
}
@SmokeTest
@Test
fun openAllTabsInCollectionTest() {
val firstTestPage = getGenericAsset(mockWebServer, 1)
val secondTestPage = getGenericAsset(mockWebServer, 2)
navigationToolbar {
}.enterURLAndEnterToBrowser(firstTestPage.url) {
waitForPageToLoad()
}.openComposeTabDrawer(composeTestRule) {
}.openNewTab {
}.submitQuery(secondTestPage.url.toString()) {
waitForPageToLoad()
}.openComposeTabDrawer(composeTestRule) {
createCollection(
firstTestPage.title,
secondTestPage.title,
collectionName = collectionName,
)
}.openThreeDotMenu {
}.closeAllTabs {
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectOpenTabs(composeTestRule)
}
composeTabDrawer(composeTestRule) {
verifyExistingOpenTabs(firstTestPage.title, secondTestPage.title)
}
}
@SmokeTest
@Test
fun shareCollectionTest() {
val firstWebsite = getGenericAsset(mockWebServer, 1)
val secondWebsite = getGenericAsset(mockWebServer, 2)
val sharingApp = "Gmail"
val urlString = "${secondWebsite.url}\n\n${firstWebsite.url}"
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebsite.url) {
}.openComposeTabDrawer(composeTestRule) {
}.openNewTab {
}.submitQuery(secondWebsite.url.toString()) {
waitForPageToLoad()
}.openComposeTabDrawer(composeTestRule) {
createCollection(firstWebsite.title, secondWebsite.title, collectionName = collectionName)
verifySnackBarText("Collection saved!")
}.openThreeDotMenu {
}.closeAllTabs {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
}.clickShareCollectionButton {
verifyShareTabsOverlay(firstWebsite.title, secondWebsite.title)
verifySharingWithSelectedApp(sharingApp, urlString, collectionName)
}
}
// Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds
@SmokeTest
@Test
fun deleteCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(webPage.title, collectionName = collectionName)
clickSnackbarButton("VIEW")
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectDeleteCollection(composeTestRule)
}
homeScreen {
verifySnackBarText("Collection deleted")
verifyNoCollectionsText()
}
}
// open a webpage, and add currently opened tab to existing collection
@Test
fun mainMenuSaveToExistingCollection() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(firstWebPage.title, collectionName = collectionName)
verifySnackBarText("Collection saved!")
}.closeTabDrawer {}
navigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
verifyPageContent(secondWebPage.content)
}.openThreeDotMenu {
}.openSaveToCollection {
}.selectExistingCollection(collectionName) {
verifySnackBarText("Tab saved!")
}.goToHomescreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
verifyTabSavedInCollection(firstWebPage.title)
verifyTabSavedInCollection(secondWebPage.title)
}
}
@Test
fun verifyAddTabButtonOfCollectionMenu() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(firstWebPage.title, collectionName = collectionName)
verifySnackBarText("Collection saved!")
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
}.goToHomescreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectAddTabToCollection(composeTestRule)
verifyTabsSelectedCounterText(1)
saveTabsSelectedForCollection()
verifySnackBarText("Tab saved!")
verifyTabSavedInCollection(secondWebPage.title)
}
}
@Test
fun renameCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(webPage.title, collectionName = firstCollectionName)
verifySnackBarText("Collection saved!")
}.closeTabDrawer {
}.goToHomescreen {
verifyCollectionIsDisplayed(firstCollectionName)
}.expandCollection(firstCollectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectRenameCollection(composeTestRule)
}.typeCollectionNameAndSave(secondCollectionName) {}
homeScreen {
verifyCollectionIsDisplayed(secondCollectionName)
}
}
@Test
fun createSecondCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(webPage.title, collectionName = firstCollectionName)
verifySnackBarText("Collection saved!")
createCollection(
webPage.title,
collectionName = secondCollectionName,
firstCollection = false,
)
verifySnackBarText("Collection saved!")
}.closeTabDrawer {
}.goToHomescreen {
verifyCollectionIsDisplayed(firstCollectionName)
verifyCollectionIsDisplayed(secondCollectionName)
}
}
@Test
fun removeTabFromCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(webPage.title, collectionName = collectionName)
closeTab()
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title, true)
removeTabFromCollection(webPage.title)
verifyTabSavedInCollection(webPage.title, false)
}
homeScreen {
verifyCollectionIsDisplayed(collectionName, false)
}
}
@Test
fun swipeLeftToRemoveTabFromCollectionTest() {
val testPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(testPage.url) {
waitForPageToLoad()
}.openComposeTabDrawer(composeTestRule) {
createCollection(
testPage.title,
collectionName = collectionName,
)
closeTab()
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
swipeTabLeft(testPage.title, composeTestRule)
verifyTabSavedInCollection(testPage.title, false)
}
homeScreen {
verifyCollectionIsDisplayed(collectionName, false)
}
}
@Test
fun swipeRightToRemoveTabFromCollectionTest() {
val testPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(testPage.url) {
waitForPageToLoad()
}.openComposeTabDrawer(composeTestRule) {
createCollection(
testPage.title,
collectionName = collectionName,
)
closeTab()
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
swipeTabRight(testPage.title, composeTestRule)
verifyTabSavedInCollection(testPage.title, false)
}
homeScreen {
verifyCollectionIsDisplayed(collectionName, false)
}
}
@Test
fun selectTabOnLongTapTest() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
waitForPageToLoad()
}.openComposeTabDrawer(composeTestRule) {
}.openNewTab {
}.submitQuery(secondWebPage.url.toString()) {
waitForPageToLoad()
}.openComposeTabDrawer(composeTestRule) {
verifyExistingOpenTabs(firstWebPage.title, secondWebPage.title)
longClickTab(firstWebPage.title)
verifyTabsMultiSelectionCounter(1)
selectTab(secondWebPage.title)
verifyTabsMultiSelectionCounter(2)
}.clickSaveCollection {
typeCollectionNameAndSave(collectionName)
verifySnackBarText("Tabs saved!")
}
composeTabDrawer(composeTestRule) {
}.closeTabDrawer {
}.goToHomescreen {
}.expandCollection(collectionName) {
verifyTabSavedInCollection(firstWebPage.title)
verifyTabSavedInCollection(secondWebPage.title)
}
}
@Test
fun navigateBackInCollectionFlowTest() {
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(webPage.title, collectionName = collectionName)
verifySnackBarText("Collection saved!")
}.closeTabDrawer {
}.openThreeDotMenu {
}.openSaveToCollection {
verifySelectCollectionScreen()
goBackInCollectionFlow()
}
browserScreen {
}.openThreeDotMenu {
}.openSaveToCollection {
verifySelectCollectionScreen()
clickAddNewCollection()
verifyCollectionNameTextField()
goBackInCollectionFlow()
verifySelectCollectionScreen()
goBackInCollectionFlow()
}
// verify the browser layout is visible
browserScreen {
verifyMenuButton()
}
}
@SmokeTest
@Test
fun undoDeleteCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openComposeTabDrawer(composeTestRule) {
createCollection(webPage.title, collectionName = collectionName)
clickSnackbarButton("VIEW")
}
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectDeleteCollection(composeTestRule)
}
homeScreen {
verifySnackBarText("Collection deleted")
clickSnackbarButton("UNDO")
verifyCollectionIsDisplayed(collectionName, true)
}
}
}

@ -336,7 +336,7 @@ class ComposeTabbedBrowsingTest {
verifyFab()
verifyTabThumbnail()
verifyExistingOpenTabs(defaultWebPage.title)
verifyTabCloseButton(defaultWebPage.title)
verifyTabCloseButton()
}.openTab(defaultWebPage.title) {
verifyUrl(defaultWebPage.url.toString())
verifyTabCounter("1")

@ -7,16 +7,22 @@
package org.mozilla.fenix.ui.robots
import android.view.View
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.filter
import androidx.compose.ui.test.hasAnyChild
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
@ -29,8 +35,10 @@ import androidx.test.espresso.matcher.ViewMatchers
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.hamcrest.Matcher
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.clickAtLocationInView
@ -139,14 +147,10 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
}
/**
* Verifies a tab with [title] has a close button.
* Verifies a tab's close button when there is only one tab open.
*/
fun verifyTabCloseButton(title: String) {
composeTestRule.tabItem(title).assert(
hasAnyChild(
hasTestTag(TabsTrayTestTag.tabItemClose),
),
)
fun verifyTabCloseButton() {
composeTestRule.closeTabButton().assertExists()
}
fun verifyTabsTrayBehaviorState(expectedState: Int) {
@ -182,6 +186,54 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
composeTestRule.tabItem(title).performTouchInput { swipeRight() }
}
/**
* Creates a collection from the provided [tabTitles].
*/
fun createCollection(
vararg tabTitles: String,
collectionName: String,
firstCollection: Boolean = true,
) {
composeTestRule.threeDotButton().performClick()
composeTestRule.dropdownMenuItemSelectTabs().performClick()
for (tab in tabTitles) {
selectTab(tab)
}
clickCollectionsButton(composeTestRule) {
if (!firstCollection) {
clickAddNewCollection()
}
typeCollectionNameAndSave(collectionName)
}
}
/**
* Selects a tab with [title].
*/
@OptIn(ExperimentalTestApi::class)
fun selectTab(title: String) {
composeTestRule.waitUntilExactlyOneExists(hasText(title), TestAssetHelper.waitingTime)
composeTestRule.tabItem(title).performClick()
}
/**
* Performs a long click on a tab with [title].
*/
fun longClickTab(title: String) {
composeTestRule.tabItem(title)
.performTouchInput { longClick(durationMillis = Constants.LONG_CLICK_DURATION) }
}
/**
* Verifies the multi selection counter displays [numOfTabs].
*/
fun verifyTabsMultiSelectionCounter(numOfTabs: Int) {
composeTestRule.multiSelectionCounter()
.assert(hasText("$numOfTabs selected"))
}
class Transition(private val composeTestRule: HomeActivityComposeTestRule) {
fun openNewTab(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
@ -283,9 +335,41 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
ComposeTabDrawerRobot(composeTestRule).interact()
return Transition(composeTestRule)
}
fun closeTabDrawer(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
composeTestRule.bannerHandle().performSemanticsAction(SemanticsActions.OnClick)
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickSaveCollection(interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
composeTestRule.collectionsButton().performClick()
CollectionRobot().interact()
return CollectionRobot.Transition()
}
}
}
/**
* Opens a transition in the [ComposeTabDrawerRobot].
*/
fun composeTabDrawer(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
ComposeTabDrawerRobot(composeTestRule).interact()
return ComposeTabDrawerRobot.Transition(composeTestRule)
}
/**
* Clicks on the Collections button in the Tabs Tray banner and opens a transition in the [CollectionRobot].
*/
private fun clickCollectionsButton(composeTestRule: HomeActivityComposeTestRule, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
composeTestRule.collectionsButton().performClick()
CollectionRobot().interact()
return CollectionRobot.Transition()
}
/**
* Obtains the root [View] that wraps the Tabs Tray.
*/
@ -344,7 +428,9 @@ private fun ComposeTestRule.emptyPrivateTabsList() = onNodeWithTag(TabsTrayTestT
/**
* Obtains the tab with the provided [title]
*/
private fun ComposeTestRule.tabItem(title: String) = onNodeWithText(title)
private fun ComposeTestRule.tabItem(title: String) = onAllNodesWithTag(TabsTrayTestTag.tabItemRoot)
.filter(hasAnyChild(hasText(title)))
.onFirst()
/**
* Obtains an open tab's close button when there's only one tab open.
@ -395,3 +481,18 @@ private fun ComposeTestRule.dropdownMenuItemTabSettings() = onNodeWithTag(TabsTr
* Obtains the normal tabs counter.
*/
private fun ComposeTestRule.normalTabsCounter() = onNodeWithTag(TabsTrayTestTag.normalTabsCounter)
/**
* Obtains the Tabs Tray banner collections button.
*/
private fun ComposeTestRule.collectionsButton() = onNodeWithTag(TabsTrayTestTag.collectionsButton)
/**
* Obtains the Tabs Tray banner multi selection counter.
*/
private fun ComposeTestRule.multiSelectionCounter() = onNodeWithTag(TabsTrayTestTag.selectionCounter)
/**
* Obtains the Tabs Tray banner handle.
*/
private fun ComposeTestRule.bannerHandle() = onNodeWithTag(TabsTrayTestTag.bannerHandle)

@ -784,6 +784,14 @@ class HomeScreenRobot {
return TabDrawerRobot.Transition()
}
fun clickSaveTabsToCollectionButton(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
scrollToElementByText(getStringResource(R.string.no_collections_description2))
saveTabsToCollectionButton().click()
ComposeTabDrawerRobot(composeTestRule).interact()
return ComposeTabDrawerRobot.Transition(composeTestRule)
}
fun expandCollection(title: String, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
assertItemContainingTextExists(itemContainingText(title))
itemContainingText(title).clickAndWaitForNewWindow(waitingTimeShort)

@ -133,7 +133,11 @@ fun TabGridItem(
},
),
) {
Box(modifier = Modifier.wrapContentSize()) {
Box(
modifier = Modifier
.wrapContentSize()
.testTag(TabsTrayTestTag.tabItemRoot),
) {
Card(
modifier = Modifier
.fillMaxWidth()

@ -124,7 +124,8 @@ fun TabListItem(
onLongClick = { onLongClick(tab) },
onClick = { onClick(tab) },
)
.padding(start = 16.dp, top = 8.dp, bottom = 8.dp),
.padding(start = 16.dp, top = 8.dp, bottom = 8.dp)
.testTag(TabsTrayTestTag.tabItemRoot),
verticalAlignment = Alignment.CenterVertically,
) {
Thumbnail(

@ -84,6 +84,7 @@ import mozilla.components.browser.storage.sync.Tab as SyncTab
* @param onBookmarkSelectedTabsClick Invoked when the user clicks on the bookmark banner menu item.
* @param onDeleteSelectedTabsClick Invoked when the user clicks on the close selected tabs banner menu item.
* @param onForceSelectedTabsAsInactiveClick Invoked when the user clicks on the make inactive banner menu item.
* @param onTabsTrayDismiss Invoked when accessibility services or UI automation requests dismissal.
*/
@Suppress("LongMethod", "LongParameterList", "ComplexMethod")
@Composable
@ -117,6 +118,7 @@ fun TabsTray(
onBookmarkSelectedTabsClick: () -> Unit,
onDeleteSelectedTabsClick: () -> Unit,
onForceSelectedTabsAsInactiveClick: () -> Unit,
onTabsTrayDismiss: () -> Unit,
) {
val multiselectMode = tabsTrayStore
.observeAsComposableState { state -> state.mode }.value ?: TabsTrayState.Mode.Normal
@ -157,6 +159,7 @@ fun TabsTray(
onBookmarkSelectedTabsClick = onBookmarkSelectedTabsClick,
onDeleteSelectedTabsClick = onDeleteSelectedTabsClick,
onForceSelectedTabsAsInactiveClick = onForceSelectedTabsAsInactiveClick,
onDismissClick = onTabsTrayDismiss,
)
}
@ -534,6 +537,7 @@ private fun TabsTrayPreviewRoot(
onDeleteSelectedTabsClick = {},
onBookmarkSelectedTabsClick = {},
onForceSelectedTabsAsInactiveClick = {},
onTabsTrayDismiss = {},
)
}
}

@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Tab
@ -49,6 +48,7 @@ import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.lib.state.ext.observeAsComposableState
import mozilla.components.ui.tabcounter.TabCounter
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.BottomSheetHandle
import org.mozilla.fenix.compose.ContextualMenu
import org.mozilla.fenix.compose.MenuItem
import org.mozilla.fenix.compose.annotation.LightDarkPreview
@ -56,6 +56,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
private val ICON_SIZE = 24.dp
private const val MAX_WIDTH_TAB_ROW_PERCENT = 0.5f
private const val BOTTOM_SHEET_HANDLE_WIDTH_PERCENT = 0.1f
/**
* Top-level UI for displaying the banner in [TabsTray].
@ -74,6 +75,7 @@ private const val MAX_WIDTH_TAB_ROW_PERCENT = 0.5f
* @param onDeleteSelectedTabsClick Invoked when user interacts with the close menu item.
* @param onBookmarkSelectedTabsClick Invoked when user interacts with the bookmark menu item.
* @param onForceSelectedTabsAsInactiveClick Invoked when user interacts with the make inactive menu item.
* @param onDismissClick Invoked when accessibility services or UI automation requests dismissal.
*/
@Suppress("LongParameterList")
@Composable
@ -91,6 +93,7 @@ fun TabsTrayBanner(
onDeleteSelectedTabsClick: () -> Unit,
onBookmarkSelectedTabsClick: () -> Unit,
onForceSelectedTabsAsInactiveClick: () -> Unit,
onDismissClick: () -> Unit,
) {
val normalTabCount = tabsTrayStore.observeAsComposableState { state ->
state.normalTabs.size + state.inactiveTabs.size
@ -125,6 +128,7 @@ fun TabsTrayBanner(
onRecentlyClosedClick = onRecentlyClosedClick,
onAccountSettingsClick = onAccountSettingsClick,
onDeleteAllTabsClick = onDeleteAllTabsClick,
onDismissClick = onDismissClick,
)
}
}
@ -142,6 +146,7 @@ private fun SingleSelectBanner(
onRecentlyClosedClick: () -> Unit,
onAccountSettingsClick: () -> Unit,
onDeleteAllTabsClick: () -> Unit,
onDismissClick: () -> Unit,
) {
val selectedColor = FirefoxTheme.colors.iconActive
val inactiveColor = FirefoxTheme.colors.iconPrimaryInactive
@ -150,12 +155,13 @@ private fun SingleSelectBanner(
Column(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.bottom_sheet_handle_top_margin)))
Divider(
BottomSheetHandle(
onRequestDismiss = onDismissClick,
contentDescription = stringResource(R.string.a11y_action_label_collapse),
modifier = Modifier
.fillMaxWidth(DRAG_INDICATOR_WIDTH_PERCENT)
.align(Alignment.CenterHorizontally),
color = FirefoxTheme.colors.textSecondary,
thickness = dimensionResource(id = R.dimen.bottom_sheet_handle_height),
.fillMaxWidth(BOTTOM_SHEET_HANDLE_WIDTH_PERCENT)
.align(Alignment.CenterHorizontally)
.testTag(TabsTrayTestTag.bannerHandle),
)
Row(
@ -472,13 +478,17 @@ private fun MultiSelectBanner(
Text(
text = stringResource(R.string.tab_tray_multi_select_title, selectedTabCount),
modifier = Modifier.testTag(TabsTrayTestTag.selectionCounter),
style = FirefoxTheme.typography.headline6,
color = FirefoxTheme.colors.textOnColorPrimary,
)
Spacer(modifier = Modifier.weight(1.0f))
IconButton(onClick = onSaveToCollectionsClick) {
IconButton(
onClick = onSaveToCollectionsClick,
modifier = Modifier.testTag(TabsTrayTestTag.collectionsButton),
) {
Icon(
painter = painterResource(id = R.drawable.ic_tab_collection),
contentDescription = stringResource(
@ -592,13 +602,12 @@ private fun TabsTrayBannerPreviewRoot(
onBookmarkSelectedTabsClick = {},
onDeleteSelectedTabsClick = {},
onForceSelectedTabsAsInactiveClick = {},
onDismissClick = {},
)
}
}
}
private const val DRAG_INDICATOR_WIDTH_PERCENT = 0.1f
private object DisabledRippleTheme : RippleTheme {
@Composable
override fun defaultColor() = Color.Unspecified

@ -281,6 +281,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
onDeleteSelectedTabsClick = tabsTrayInteractor::onDeleteSelectedTabsClicked,
onBookmarkSelectedTabsClick = tabsTrayInteractor::onBookmarkSelectedTabsClicked,
onForceSelectedTabsAsInactiveClick = tabsTrayInteractor::onForceSelectedTabsAsInactiveClicked,
onTabsTrayDismiss = ::onTabsTrayDismissed,
)
}
}
@ -372,8 +373,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
)
setupBackgroundDismissalListener {
TabsTray.closed.record(NoExtras())
dismissAllowingStateLoss()
onTabsTrayDismissed()
}
if (!requireContext().settings().enableTabsTrayToCompose) {
@ -749,6 +749,11 @@ class TabsTrayFragment : AppCompatDialogFragment() {
snackbar.show()
}
private fun onTabsTrayDismissed() {
TabsTray.closed.record(NoExtras())
dismissAllowingStateLoss()
}
companion object {
private const val DOWNLOAD_CANCEL_DIALOG_FRAGMENT_TAG = "DOWNLOAD_CANCEL_DIALOG_FRAGMENT_TAG"

@ -9,11 +9,15 @@ internal object TabsTrayTestTag {
// Tabs Tray Banner
private const val bannerTestTagRoot = "$tabsTray.banner"
const val bannerHandle = "$bannerTestTagRoot.handle"
const val normalTabsPageButton = "$bannerTestTagRoot.normalTabsPageButton"
const val normalTabsCounter = "$normalTabsPageButton.counter"
const val privateTabsPageButton = "$bannerTestTagRoot.privateTabsPageButton"
const val syncedTabsPageButton = "$bannerTestTagRoot.syncedTabsPageButton"
const val selectionCounter = "$bannerTestTagRoot.selectionCounter"
const val collectionsButton = "$bannerTestTagRoot.collections"
// Tabs Tray Banner three dot menu
const val threeDotButton = "$bannerTestTagRoot.threeDotButton"
@ -37,7 +41,7 @@ internal object TabsTrayTestTag {
const val emptyPrivateTabsList = "$privateTabsList.empty"
// Tab items
private const val tabItemRoot = "$tabsTray.tabItem"
const val tabItemRoot = "$tabsTray.tabItem"
const val tabItemClose = "$tabItemRoot.close"
const val tabItemThumbnail = "$tabItemRoot.thumbnail"
}

Loading…
Cancel
Save