@@ -16,6 +16,7 @@ import androidx.lifecycle.LifecycleOwner
1616import androidx.navigation.NavController
1717import io.mockk.every
1818import io.mockk.mockk
19+ import io.mockk.verify
1920import kotlinx.coroutines.CompletableDeferred
2021import kotlinx.coroutines.test.runTest
2122import mozilla.components.browser.state.action.ContentAction.UpdateProgressAction
@@ -26,10 +27,13 @@ import mozilla.components.browser.state.state.BrowserState
2627import mozilla.components.browser.state.state.CustomTabSessionState
2728import mozilla.components.browser.state.state.SecurityInfoState
2829import mozilla.components.browser.state.state.createCustomTab
30+ import mozilla.components.browser.state.state.createTab
2931import mozilla.components.browser.state.store.BrowserStore
3032import mozilla.components.compose.browser.toolbar.concept.Action.ActionButton
3133import mozilla.components.compose.browser.toolbar.concept.Action.ActionButtonRes
3234import mozilla.components.compose.browser.toolbar.concept.PageOrigin
35+ import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.ContextualMenuOption
36+ import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.PageOriginContextualMenuInteractions.CopyToClipboardClicked
3337import mozilla.components.compose.browser.toolbar.store.BrowserToolbarStore
3438import mozilla.components.compose.browser.toolbar.store.EnvironmentCleared
3539import mozilla.components.compose.browser.toolbar.store.EnvironmentRehydrated
@@ -43,6 +47,7 @@ import mozilla.components.support.ktx.kotlin.getRegistrableDomainIndexRange
4347import mozilla.components.support.test.ext.joinBlocking
4448import mozilla.components.support.test.robolectric.testContext
4549import mozilla.components.support.test.rule.MainLooperTestRule
50+ import mozilla.components.support.utils.ClipboardHandler
4651import org.junit.Assert.assertEquals
4752import org.junit.Assert.assertFalse
4853import org.junit.Assert.assertNotNull
@@ -51,12 +56,16 @@ import org.junit.Assert.assertTrue
5156import org.junit.Rule
5257import org.junit.Test
5358import org.junit.runner.RunWith
59+ import org.mozilla.fenix.GleanMetrics.Events
5460import org.mozilla.fenix.R
61+ import org.mozilla.fenix.components.AppStore
62+ import org.mozilla.fenix.components.appstate.AppAction.URLCopiedToClipboard
5563import org.mozilla.fenix.components.toolbar.CustomTabBrowserToolbarMiddleware.Companion.DisplayActions.MenuClicked
5664import org.mozilla.fenix.components.toolbar.CustomTabBrowserToolbarMiddleware.Companion.DisplayActions.ShareClicked
5765import org.mozilla.fenix.components.toolbar.CustomTabBrowserToolbarMiddleware.Companion.EndPageActions.CustomButtonClicked
5866import org.mozilla.fenix.components.toolbar.CustomTabBrowserToolbarMiddleware.Companion.StartBrowserActions.CloseClicked
5967import org.mozilla.fenix.components.toolbar.CustomTabBrowserToolbarMiddleware.Companion.StartPageActions.SiteInfoClicked
68+ import org.mozilla.fenix.helpers.FenixGleanTestRule
6069import org.mozilla.fenix.helpers.lifecycle.TestLifecycleOwner
6170import org.mozilla.fenix.utils.Settings
6271import org.robolectric.RobolectricTestRunner
@@ -73,25 +82,33 @@ class CustomTabBrowserToolbarMiddlewareTest {
7382 @get:Rule
7483 val mainLooperRule = MainLooperTestRule ()
7584
85+ @get:Rule
86+ val gleanRule = FenixGleanTestRule (testContext)
87+
7688 private val customTabId = " test"
7789 private val customTab: CustomTabSessionState = mockk(relaxed = true ) {
7890 every { id } returns customTabId
7991 }
92+ private val selectedTab = createTab(" test.com" )
8093 private val browserStore = BrowserStore (
8194 BrowserState (
95+ tabs = listOf (selectedTab),
8296 customTabs = listOf (customTab),
97+ selectedTabId = selectedTab.id,
8398 ),
8499 )
100+ private val appStore: AppStore = mockk()
85101 private val permissionsStorage: SitePermissionsStorage = mockk()
86102 private val cookieBannersStorage: CookieBannersStorage = mockk()
87103 private val useCases: CustomTabsUseCases = mockk()
88104 private val trackingProtectionUseCases: TrackingProtectionUseCases = mockk()
89105 private val publicSuffixList: PublicSuffixList = mockk {
90106 every { getPublicSuffixPlusOne(any()) } returns CompletableDeferred (null )
91107 }
108+ private val clipboard: ClipboardHandler = mockk()
92109 private val lifecycleOwner = TestLifecycleOwner (Lifecycle .State .RESUMED )
93110 private val navController: NavController = mockk()
94- private val closeTabDelegate: () -> Unit = mockk()
111+ private val closeTabDelegate: () -> Unit = {}
95112 private val settings: Settings = mockk {
96113 every { shouldUseBottomToolbar } returns true
97114 }
@@ -318,6 +335,46 @@ class CustomTabBrowserToolbarMiddlewareTest {
318335 assertEquals(expectedSecureIndicator, securityIndicator)
319336 }
320337
338+ @Test
339+ @Config(sdk = [31 ])
340+ fun `GIVEN on Android 12 WHEN choosing to copy the current URL to clipboard THEN copy to clipboard and show a snackbar` () {
341+ val appStore: AppStore = mockk(relaxed = true )
342+ val navController: NavController = mockk(relaxed = true )
343+ every { customTab.content.url } returns " https://mozilla.test"
344+ val clipboard = ClipboardHandler (testContext)
345+ val middleware = buildMiddleware(appStore = appStore, clipboard = clipboard)
346+ val toolbarStore = buildStore(
347+ middleware = middleware,
348+ navController = navController,
349+ )
350+
351+ toolbarStore.dispatch(CopyToClipboardClicked )
352+
353+ assertEquals(customTab.content.url, clipboard.text)
354+ verify { appStore.dispatch(URLCopiedToClipboard ) }
355+ assertNotNull(Events .copyUrlTapped.testGetValue())
356+ }
357+
358+ @Test
359+ @Config(sdk = [33 ])
360+ fun `GIVEN on Android 13 WHEN choosing to copy the current URL to clipboard THEN copy to clipboard and don't show a snackbar` () {
361+ val appStore: AppStore = mockk(relaxed = true )
362+ val navController: NavController = mockk(relaxed = true )
363+ every { customTab.content.url } returns " https://mozilla.test"
364+ val clipboard = ClipboardHandler (testContext)
365+ val middleware = buildMiddleware(appStore = appStore, clipboard = clipboard)
366+ val toolbarStore = buildStore(
367+ middleware = middleware,
368+ navController = navController,
369+ )
370+
371+ toolbarStore.dispatch(CopyToClipboardClicked )
372+
373+ assertEquals(customTab.content.url, clipboard.text)
374+ verify(exactly = 0 ) { appStore.dispatch(URLCopiedToClipboard ) }
375+ assertNotNull(Events .copyUrlTapped.testGetValue())
376+ }
377+
321378 @Test
322379 fun `WHEN the website title changes THEN update the shown page origin` () = runTest {
323380 val customTab = createCustomTab(title = " Title" , url = " URL" , id = customTabId)
@@ -329,6 +386,7 @@ class CustomTabBrowserToolbarMiddlewareTest {
329386 hint = R .string.search_hint,
330387 title = " Title" ,
331388 url = " URL" ,
389+ contextualMenuOptions = listOf (ContextualMenuOption .CopyURLToClipboard ),
332390 onClick = null ,
333391 )
334392
@@ -354,6 +412,7 @@ class CustomTabBrowserToolbarMiddlewareTest {
354412 hint = R .string.search_hint,
355413 title = null ,
356414 url = " URL" ,
415+ contextualMenuOptions = listOf (ContextualMenuOption .CopyURLToClipboard ),
357416 onClick = null ,
358417 )
359418
@@ -379,6 +438,7 @@ class CustomTabBrowserToolbarMiddlewareTest {
379438 hint = R .string.search_hint,
380439 title = " Title" ,
381440 url = " URL" ,
441+ contextualMenuOptions = listOf (ContextualMenuOption .CopyURLToClipboard ),
382442 onClick = null ,
383443 )
384444
@@ -411,6 +471,7 @@ class CustomTabBrowserToolbarMiddlewareTest {
411471 hint = R .string.search_hint,
412472 title = " Title" ,
413473 url = " 127.0.0.1" ,
474+ contextualMenuOptions = listOf (ContextualMenuOption .CopyURLToClipboard ),
414475 onClick = null ,
415476 )
416477
@@ -558,20 +619,24 @@ class CustomTabBrowserToolbarMiddlewareTest {
558619
559620 private fun buildMiddleware (
560621 browserStore : BrowserStore = this.browserStore,
622+ appStore : AppStore = this.appStore,
561623 permissionsStorage : SitePermissionsStorage = this.permissionsStorage,
562624 cookieBannersStorage : CookieBannersStorage = this.cookieBannersStorage,
563625 useCases : CustomTabsUseCases = this.useCases,
564626 trackingProtectionUseCases : TrackingProtectionUseCases = this.trackingProtectionUseCases,
565627 publicSuffixList : PublicSuffixList = this.publicSuffixList,
628+ clipboard : ClipboardHandler = this.clipboard,
566629 settings : Settings = this.settings,
567630 ) = CustomTabBrowserToolbarMiddleware (
568631 customTabId = this .customTabId,
569632 browserStore = browserStore,
633+ appStore = appStore,
570634 permissionsStorage = permissionsStorage,
571635 cookieBannersStorage = cookieBannersStorage,
572636 useCases = useCases,
573637 trackingProtectionUseCases = trackingProtectionUseCases,
574638 publicSuffixList = publicSuffixList,
639+ clipboard = clipboard,
575640 settings = settings,
576641 )
577642
0 commit comments