No issue: uplift three dot menu redesign for beta. (#19312)

* For #19005: new tab three-dot menu sync sign in  (#19037)

* Show synced tabs or sync account in new tab menu

* Sync sign in item navigates to account settings

* Check account auth and get sync item title

* Look for sync sign in item on home menu for UI test

* Sync sign in menu item UI test

* For #19114: check state of sync account when navigating from sync sign in menu item (#19118)

* For #18591 - Make the navbar sticky in the new menu

* Turn on feature flag for three-dot menu redesign for beta. (#19111)

* For #18591 - Update tests

Some tests trying to "Add to top sites" may fail because of the sticky navbar
now covering that menu item -> scroll the menu up a little.

Some tests that previously were trying to scroll to the bottom of the menu to
interact with navbar options now don't have to. Them trying to interact with
navbar option with the menu opened as collapsed is a good test for the navbar
stickyness.

Co-authored-by: Mugurell <Mugurell@users.noreply.github.com>
pull/420/head v89.0.0-beta.4
Elise Richards 3 years ago committed by GitHub
parent fc1fa6ce78
commit 9fc6a87697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,6 +17,7 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
@ -226,12 +227,21 @@ class SmokeTest {
}
@Test
// Verifies the Synced tabs menu opens from a tab's 3 dot menu
fun openMainMenuSyncedTabsItemTest() {
homeScreen {
}.openThreeDotMenu {
}.openSyncedTabs {
verifySyncedTabsMenuHeader()
// Verifies the Synced tabs menu or Sync Sign In menu opens from a tab's 3 dot menu.
// The test is assuming we are NOT signed in.
fun openMainMenuSyncItemTest() {
if (FeatureFlags.tabsTrayRewrite) {
homeScreen {
}.openThreeDotMenu {
}.openSyncSignIn {
verifySyncSignInMenuHeader()
}
} else {
homeScreen {
}.openThreeDotMenu {
}.openSyncedTabs {
verifySyncedTabsMenuHeader()
}
}
}
@ -311,7 +321,6 @@ class SmokeTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
}.openSaveToCollection {
verifyCollectionNameTextField()
}

@ -9,6 +9,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.ui.robots.homeScreen
@ -48,7 +49,11 @@ class ThreeDotMenuMainTest {
verifyHistoryButton()
verifyDownloadsButton()
verifyAddOnsButton()
verifySyncedTabsButton()
if (FeatureFlags.tabsTrayRewrite) {
verifySyncSignInButton()
} else {
verifySyncedTabsButton()
}
verifyDesktopSite()
verifyWhatsNewButton()
verifyHelpButton()

@ -54,6 +54,7 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
@ -73,6 +74,7 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
@ -104,6 +106,7 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
@ -128,6 +131,7 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
@ -152,6 +156,7 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")

@ -0,0 +1,52 @@
/* 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.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.click
/**
* Implementation of Robot Pattern for Sync Sign In sub menu.
*/
class SyncSignInRobot {
fun verifyAccountSettingsMenuHeader() = assertAccountSettingsMenuHeader()
fun verifySyncSignInMenuHeader() = assertSyncSignInMenuHeader()
class Transition {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())!!
fun goBack(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
goBackButton().click()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
}
}
private fun goBackButton() =
onView(allOf(withContentDescription("Navigate up")))
private fun assertAccountSettingsMenuHeader() {
// Replaced with the new string here, the test is assuming we are NOT signed in
// Sync tests in SettingsSyncTest are still TO-DO, so I'm not sure that we have a test for signing into Sync
onView(withText(R.string.preferences_account_settings))
.check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
}
private fun assertSyncSignInMenuHeader() {
onView(withText(R.string.sign_in_with_camera))
.check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
}

@ -51,6 +51,7 @@ class ThreeDotMenuMainRobot {
fun verifyHistoryButton() = assertHistoryButton()
fun verifyBookmarksButton() = assertBookmarksButton()
fun verifySyncedTabsButton() = assertSyncedTabsButton()
fun verifySyncSignInButton() = assertSignInToSyncButton()
fun verifyHelpButton() = assertHelpButton()
fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists()
fun verifyForwardButton() = assertForwardButton()
@ -164,6 +165,15 @@ class ThreeDotMenuMainRobot {
return SyncedTabsRobot.Transition()
}
fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Sign in to sync")), waitingTime)
signInToSyncButton().click()
SyncSignInRobot().interact()
return SyncSignInRobot.Transition()
}
fun openBookmarks(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition {
onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
@ -193,11 +203,6 @@ class ThreeDotMenuMainRobot {
}
fun sharePage(interact: LibrarySubMenusMultipleSelectionToolbarRobot.() -> Unit): LibrarySubMenusMultipleSelectionToolbarRobot.Transition {
var maxSwipes = 3
while (!shareButton().exists() && maxSwipes != 0) {
threeDotMenuRecyclerView().perform(swipeUp())
maxSwipes--
}
shareButton().click()
LibrarySubMenusMultipleSelectionToolbarRobot().interact()
return LibrarySubMenusMultipleSelectionToolbarRobot.Transition()
@ -212,11 +217,6 @@ class ThreeDotMenuMainRobot {
}
fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
var maxSwipes = 3
while (!forwardButton().exists() && maxSwipes != 0) {
threeDotMenuRecyclerView().perform(swipeUp())
maxSwipes--
}
forwardButton().click()
BrowserRobot().interact()
@ -250,11 +250,6 @@ class ThreeDotMenuMainRobot {
}
fun refreshPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
var maxSwipes = 3
while (!refreshButton().exists() && maxSwipes != 0) {
threeDotMenuRecyclerView().perform(swipeUp())
maxSwipes--
}
assertRefreshButton()
refreshButton().click()
@ -369,6 +364,11 @@ class ThreeDotMenuMainRobot {
}
fun openSaveToCollection(interact: ThreeDotMenuMainRobot.() -> Unit): ThreeDotMenuMainRobot.Transition {
// Ensure the menu is expanded and fully scrolled to the bottom.
for (i in 0..3) {
threeDotMenuRecyclerView().perform(swipeUp())
}
mDevice.waitNotNull(Until.findObject(By.text("Save to collection")), waitingTime)
saveCollectionButton().click()
ThreeDotMenuMainRobot().interact()

@ -37,7 +37,7 @@ object FeatureFlags {
/**
* Shows new three-dot toolbar menu design.
*/
val toolbarMenuFeature = Config.channel.isNightlyOrDebug
const val toolbarMenuFeature = true
/**
* Enables the tabs tray re-write with Synced Tabs.

@ -0,0 +1,33 @@
/* 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.components.accounts
import android.content.Context
import mozilla.components.service.fxa.manager.FxaAccountManager
import org.mozilla.fenix.ext.components
/**
* Component which holds a reference to [FxaAccountManager]. Manages account authentication,
* profiles, and profile state observers.
*/
open class FenixAccountManager(context: Context) {
val accountManager = context.components.backgroundServices.accountManager
val authenticatedAccount
get() = accountManager.authenticatedAccount() != null
val accountProfileEmail
get() = accountManager.accountProfile()?.email
/**
* Check if the current account is signed in and authenticated.
*/
fun signedInToFxa(): Boolean {
val account = accountManager.authenticatedAccount()
val needsReauth = accountManager.accountNeedsReauth()
return account != null && !needsReauth
}
}

@ -226,10 +226,15 @@ class DefaultBrowserToolbarMenuController(
)
}
is ToolbarMenu.Item.SyncAccount -> {
val directions = if (item.signedIn) {
BrowserFragmentDirections.actionGlobalAccountSettingsFragment()
} else {
BrowserFragmentDirections.actionGlobalTurnOnSync()
}
browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalAccountSettingsFragment()
directions
)
}
}

@ -39,6 +39,7 @@ import org.mozilla.fenix.FeatureFlags.tabsTrayRewrite
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.accounts.FenixAccountManager
import org.mozilla.fenix.experiments.ExperimentBranch
import org.mozilla.fenix.experiments.Experiments
import org.mozilla.fenix.ext.asActivity
@ -74,12 +75,11 @@ open class DefaultToolbarMenu(
private val shouldDeleteDataOnQuit = context.settings().shouldDeleteBrowsingDataOnQuit
private val shouldUseBottomToolbar = context.settings().shouldUseBottomToolbar
private val accountManager = FenixAccountManager(context)
private val selectedSession: TabSessionState?
get() = store.state.selectedTab
private val accountManager = context.components.backgroundServices.accountManager
override val menuBuilder by lazy {
WebExtensionBrowserMenuBuilder(
items =
@ -161,7 +161,7 @@ open class DefaultToolbarMenu(
registerForIsBookmarkedUpdates()
if (FeatureFlags.toolbarMenuFeature) {
BrowserMenuItemToolbar(listOf(back, forward, share, refresh))
BrowserMenuItemToolbar(listOf(back, forward, share, refresh), isSticky = true)
} else {
val bookmark = BrowserMenuItemToolbar.TwoStateButton(
primaryImageResource = R.drawable.ic_bookmark_filled,
@ -534,10 +534,10 @@ open class DefaultToolbarMenu(
}
private fun getSyncItemTitle(): String {
val authenticatedAccount = accountManager.authenticatedAccount() != null
val email = accountManager.accountProfile()?.email
val authenticatedAccount = accountManager.authenticatedAccount
val email = accountManager.accountProfileEmail
return if (authenticatedAccount && email != null) {
return if (authenticatedAccount && !email.isNullOrEmpty()) {
email
} else {
context.getString(R.string.sync_menu_sign_in)
@ -549,7 +549,7 @@ open class DefaultToolbarMenu(
R.drawable.ic_synced_tabs,
primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.SyncAccount)
onItemTapped.invoke(ToolbarMenu.Item.SyncAccount(accountManager.signedInToFxa()))
}
@VisibleForTesting(otherwise = PRIVATE)

@ -23,7 +23,7 @@ interface ToolbarMenu {
object InstallPwaToHomeScreen : Item()
object AddToHomeScreen : Item()
object SyncedTabs : Item()
object SyncAccount : Item()
data class SyncAccount(val signedIn: Boolean) : Item()
object AddonsManager : Item()
object Quit : Item()
object OpenInApp : Item()

@ -89,6 +89,7 @@ import org.mozilla.fenix.GleanMetrics.PerfStartup
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PrivateShortcutCreateManager
@ -792,6 +793,18 @@ class HomeFragment : Fragment() {
HomeFragmentDirections.actionGlobalSyncedTabsFragment()
)
}
is HomeMenu.Item.SyncAccount -> {
hideOnboardingIfNeeded()
val directions = if (it.signedIn) {
BrowserFragmentDirections.actionGlobalAccountSettingsFragment()
} else {
BrowserFragmentDirections.actionGlobalTurnOnSync()
}
nav(
R.id.homeFragment,
directions
)
}
HomeMenu.Item.Bookmarks -> {
hideOnboardingIfNeeded()
nav(

@ -24,7 +24,9 @@ import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.FeatureFlags.tabsTrayRewrite
import org.mozilla.fenix.R
import org.mozilla.fenix.components.accounts.FenixAccountManager
import org.mozilla.fenix.experiments.ExperimentBranch
import org.mozilla.fenix.experiments.Experiments
import org.mozilla.fenix.ext.components
@ -47,6 +49,7 @@ class HomeMenu(
object Downloads : Item()
object Extensions : Item()
object SyncTabs : Item()
data class SyncAccount(val signedIn: Boolean) : Item()
object WhatsNew : Item()
object Help : Item()
object Settings : Item()
@ -55,15 +58,14 @@ class HomeMenu(
data class DesktopMode(val checked: Boolean) : Item()
}
private val primaryTextColor =
ThemeManager.resolveAttribute(R.attr.primaryText, context)
private val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context)
private val syncDisconnectedColor =
ThemeManager.resolveAttribute(R.attr.syncDisconnected, context)
private val syncDisconnectedBackgroundColor =
context.getColorFromAttr(R.attr.syncDisconnectedBackground)
private val shouldUseBottomToolbar = context.settings().shouldUseBottomToolbar
private val accountManager = context.components.backgroundServices.accountManager
private val accountManager = FenixAccountManager(context)
// 'Reconnect' and 'Quit' items aren't needed most of the time, so we'll only create the if necessary.
private val reconnectToSyncItem by lazy {
@ -92,17 +94,33 @@ class HomeMenu(
}
}
val syncedTabsItem = BrowserMenuImageText(
context.getString(R.string.synced_tabs),
R.drawable.ic_synced_tabs,
primaryTextColor
) {
onItemTapped.invoke(Item.SyncTabs)
}
private fun getSyncItemTitle(): String {
val authenticatedAccount = accountManager.authenticatedAccount() != null
val email = accountManager.accountProfile()?.email
val authenticatedAccount = accountManager.authenticatedAccount
val email = accountManager.accountProfileEmail
return if (authenticatedAccount && email != null) {
return if (authenticatedAccount && !email.isNullOrEmpty()) {
email
} else {
context.getString(R.string.sync_menu_sign_in)
}
}
val syncSignInMenuItem = BrowserMenuImageText(
getSyncItemTitle(),
R.drawable.ic_synced_tabs,
primaryTextColor
) {
onItemTapped.invoke(Item.SyncAccount(accountManager.signedInToFxa()))
}
private val oldCoreMenuItems by lazy {
val whatsNewItem = BrowserMenuHighlightableItem(
context.getString(R.string.browser_menu_whats_new),
@ -169,14 +187,6 @@ class HomeMenu(
onItemTapped.invoke(Item.Settings)
}
val syncedTabsItem = BrowserMenuImageText(
getSyncItemTitle(),
R.drawable.ic_synced_tabs,
primaryTextColor
) {
onItemTapped.invoke(Item.SyncTabs)
}
val helpItem = BrowserMenuImageText(
context.getString(R.string.browser_menu_help),
R.drawable.ic_help,
@ -293,14 +303,6 @@ class HomeMenu(
onItemTapped.invoke(Item.Extensions)
}
val syncSignInItem = BrowserMenuImageText(
context.getString(R.string.library_synced_tabs),
R.drawable.ic_synced_tabs,
primaryTextColor
) {
onItemTapped.invoke(Item.SyncTabs)
}
val whatsNewItem = BrowserMenuHighlightableItem(
context.getString(R.string.browser_menu_whats_new),
R.drawable.ic_whats_new,
@ -344,7 +346,7 @@ class HomeMenu(
historyItem,
downloadsItem,
extensionsItem,
syncSignInItem,
if (tabsTrayRewrite) syncSignInMenuItem else syncedTabsItem,
accountAuthItem,
BrowserMenuDivider(),
desktopItem,

@ -0,0 +1,101 @@
/* 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.components.accounts
import android.content.Context
import io.mockk.every
import io.mockk.mockk
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import mozilla.components.service.fxa.manager.FxaAccountManager
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class FenixAccountManagerTest {
private lateinit var fenixFxaManager: FenixAccountManager
private lateinit var accountManagerComponent: FxaAccountManager
private lateinit var context: Context
private lateinit var account: OAuthAccount
private lateinit var profile: Profile
@Before
fun setUp() {
context = mockk(relaxed = true)
account = mockk(relaxed = true)
profile = mockk(relaxed = true)
accountManagerComponent = mockk(relaxed = true)
}
@Test
fun `GIVEN an account exists THEN fetch the associated email address`() {
every { accountManagerComponent.authenticatedAccount() } returns account
every { accountManagerComponent.accountProfile() } returns profile
every { context.components.backgroundServices.accountManager } returns accountManagerComponent
fenixFxaManager = FenixAccountManager(context)
val emailAddress = "firefoxIsFun@test.com"
every { accountManagerComponent.accountProfile()?.email } returns emailAddress
val result = fenixFxaManager.accountProfileEmail
assertEquals(emailAddress, result)
}
@Test
fun `GIVEN an account does not exist THEN return null when fetching the associated email address`() {
every { accountManagerComponent.authenticatedAccount() } returns null
every { accountManagerComponent.accountProfile() } returns null
every { context.components.backgroundServices.accountManager } returns accountManagerComponent
fenixFxaManager = FenixAccountManager(context)
val result = fenixFxaManager.accountProfileEmail
assertEquals(null, result)
}
@Test
fun `GIVEN an account is signed in and authenticated THEN check returns true`() {
every { accountManagerComponent.authenticatedAccount() } returns account
every { accountManagerComponent.accountNeedsReauth() } returns false
every { context.components.backgroundServices.accountManager } returns accountManagerComponent
fenixFxaManager = FenixAccountManager(context)
val signedIn = fenixFxaManager.signedInToFxa()
assertTrue(signedIn)
}
@Test
fun `GIVEN an account is signed in and NOT authenticated THEN check returns false`() {
every { accountManagerComponent.authenticatedAccount() } returns account
every { accountManagerComponent.accountNeedsReauth() } returns true
every { context.components.backgroundServices.accountManager } returns accountManagerComponent
fenixFxaManager = FenixAccountManager(context)
val signedIn = fenixFxaManager.signedInToFxa()
assertFalse(signedIn)
}
@Test
fun `GIVEN an account is not signed in THEN check returns false`() {
every { accountManagerComponent.authenticatedAccount() } returns null
every { accountManagerComponent.accountNeedsReauth() } returns true
every { context.components.backgroundServices.accountManager } returns accountManagerComponent
fenixFxaManager = FenixAccountManager(context)
val signedIn = fenixFxaManager.signedInToFxa()
assertFalse(signedIn)
}
}

@ -572,6 +572,28 @@ class DefaultBrowserToolbarMenuControllerTest {
}
}
@Test
fun `WHEN sync sign in menu item is pressed AND account is signed out THEN navigate to sync sign in`() = runBlockingTest {
val item = ToolbarMenu.Item.SyncAccount(false)
val directions = BrowserFragmentDirections.actionGlobalTurnOnSync()
val controller = createController(scope = this, store = browserStore)
controller.handleToolbarItemInteraction(item)
verify { navController.navigate(directions, null) }
}
@Test
fun `WHEN sync sign in menu item is pressed AND account is signed in THEN navigate to sync sign in`() = runBlockingTest {
val item = ToolbarMenu.Item.SyncAccount(true)
val directions = BrowserFragmentDirections.actionGlobalAccountSettingsFragment()
val controller = createController(scope = this, store = browserStore)
controller.handleToolbarItemInteraction(item)
verify { navController.navigate(directions, null) }
}
private fun createController(
scope: CoroutineScope,
store: BrowserStore,

Loading…
Cancel
Save