1
0
Fork 0

Copione merged onto master
continuous-integration/drone/push Build is passing Details

master
blallo 2020-07-17 00:00:47 +02:00
commit 78b14e2919
109 changed files with 1974 additions and 762 deletions

View File

@ -32,3 +32,9 @@ jobs:
treeherder-symbol: bump-ac
target-tasks-method: bump_android_components
when: [{hour: 14, minute: 0}]
- name: screenshots
job:
type: decision-task
treeherder-symbol: screenshots-D
target-tasks-method: screenshots
when: [{weekday: 'Monday', hour: 10, minute: 0}]

View File

@ -5,7 +5,6 @@ plugins {
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco'
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
apply plugin: 'androidx.navigation.safeargs.kotlin'

View File

@ -23,10 +23,11 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
hasUserGesture: Boolean,
isSameDomain: Boolean,
isRedirect: Boolean,
isDirectNavigation: Boolean
isDirectNavigation: Boolean,
isSubframeRequest: Boolean
): RequestInterceptor.InterceptionResponse? {
return appContext.components.services.accountsAuthFeature.interceptor.onLoadRequest(
engineSession, uri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation
engineSession, uri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation, isSubframeRequest
)
}
}

View File

@ -92,9 +92,10 @@ class MenuScreenShotTest : ScreenshotTest() {
Screengrab.screenshot("SettingsSubMenuDefaultBrowserRobot_settings-default-browser")
mDevice.pressBack()
settingsTP()
Screengrab.screenshot("settings-enhanced-tp")
mDevice.pressBack()
// Disabled for Pixel 2
// settingsTP()
// Screengrab.screenshot("settings-enhanced-tp")
// mDevice.pressBack()
loginsAndPassword()
Screengrab.screenshot("SettingsSubMenuLoginsAndPasswords-settings-logins-passwords")

View File

@ -234,7 +234,7 @@ class BookmarksTest {
IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!)
}.openThreeDotMenu(defaultWebPage.url) {
}.clickOpenInNewTab {
// verifyPageContent(defaultWebPage.content)
verifyUrl(defaultWebPage.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
}
@ -253,7 +253,7 @@ class BookmarksTest {
IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!)
}.openThreeDotMenu(defaultWebPage.url) {
}.clickOpenInPrivateTab {
// verifyPageContent(defaultWebPage.content)
verifyUrl(defaultWebPage.url.toString())
}.openTabDrawer {
verifyPrivateModeSelected()
}

View File

@ -61,7 +61,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("Link 1")
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInNewTab()
@ -83,7 +83,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("Link 2")
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInPrivateTab()
@ -105,7 +105,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("Link 3")
verifyLinkContextMenuItems(genericURL.url)
clickContextCopyLink()
@ -125,12 +125,13 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("Link 1")
verifyLinkContextMenuItems(genericURL.url)
clickContextShareLink(genericURL.url) // verify share intent is matched with associated URL
}
}
@Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12367")
@Test
fun verifyContextOpenImageNewTab() {
@ -141,7 +142,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("test_link_image")
verifyLinkImageContextMenuItems(imageResource.url)
clickContextOpenImageNewTab()
@ -161,7 +162,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("test_link_image")
verifyLinkImageContextMenuItems(imageResource.url)
clickContextCopyImageLocation()
@ -182,7 +183,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("test_link_image")
verifyLinkImageContextMenuItems(imageResource.url)
clickContextSaveImage()
@ -209,7 +210,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
// verifyPageContent(pageLinks.content)
mDevice.waitForIdle()
longClickMatchingText("Link 1")
verifyLinkContextMenuItems(genericURL.url)
dismissContentContextMenu(genericURL.url)

View File

@ -0,0 +1,147 @@
/* 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.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.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.DeepLinkRobot
/**
* Tests for verifying basic functionality of deep links
* - fenix://home
* - fenix://open
* - fenix://settings_notifications — take the user to the notification settings page
* - fenix://settings_privacy — take the user to the privacy settings page.
* - fenix://settings_search_engine — take the user to the search engine page, to set the default search engine.
* - fenix://home_collections — take the user to the home screen to see the list of collections.
* - fenix://urls_history — take the user to the history list.
* - fenix://urls_bookmarks — take the user to the bookmarks list
* - fenix://settings_logins — take the user to the settings page to do with logins (not the saved logins).
**/
class DeepLinkTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
private val robot = DeepLinkRobot()
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
setDispatcher(AndroidAssetDispatcher())
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun openHomeScreen() {
robot.openHomeScreen {
verifyHomeComponent()
}
robot.openSettings { /* move away from the home screen */ }
robot.openHomeScreen {
verifyHomeComponent()
}
}
@Test
fun openURL() {
val genericURL =
TestAssetHelper.getGenericAsset(mockWebServer, 1)
robot.openURL(genericURL.url.toString()) {
verifyUrl(genericURL.url.toString())
}
}
@Test
fun openBookmarks() {
robot.openBookmarks {
// verify we can see headings.
verifyFolderTitle("Desktop Bookmarks")
}
}
@Test
fun openHistory() {
robot.openHistory {
verifyHistoryMenuView()
}
}
@Test
fun openCollections() {
robot.openHomeScreen { /* do nothing */ }.dismissOnboarding()
robot.openCollections {
verifyCollectionsHeader()
}
}
@Test
fun openSettings() {
robot.openSettings {
verifyBasicsHeading()
verifyAdvancedHeading()
}
}
@Test
fun openSettingsLogins() {
robot.openSettingsLogins {
verifyDefaultView()
verifyDefaultValueSyncLogins()
verifyDefaultValueAutofillLogins()
}
}
@Test
fun openSettingsPrivacy() {
robot.openSettingsPrivacy {
verifyPrivacyHeading()
}
}
@Test
fun openSettingsTrackingProtection() {
robot.openSettingsTrackingProtection {
verifyEnhancedTrackingProtectionHeader()
}
}
@Test
fun openSettingsSearchEngine() {
robot.openSettingsSearchEngine {
verifyDefaultSearchEngineHeader()
}
}
@Test
fun openSettingsNotifications() {
robot.openSettingsNotification {
verifyNotifications()
}
}
@Test
fun openMakeDefaultBrowser() {
robot.openMakeDefaultBrowser {
verifyMakeDefaultBrowser()
}
}
}

View File

@ -82,7 +82,7 @@ class DownloadTest {
navigationToolbar {
}.openNewTabAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
clickLinkMatchingText(defaultWebPage.content)
}
@ -100,7 +100,7 @@ class DownloadTest {
navigationToolbar {
}.openNewTabAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
clickLinkMatchingText(defaultWebPage.content)
}

View File

@ -19,6 +19,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.ui.robots.historyMenu
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.mDevice
import org.mozilla.fenix.ui.robots.multipleSelectionToolbar
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -70,7 +71,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
verifyHistoryMenuView()
@ -86,7 +87,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
}.openThreeDotMenu {
@ -101,7 +102,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
}.openThreeDotMenu {
@ -119,12 +120,12 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
}.openThreeDotMenu {
}.clickOpenInNormalTab {
// verifyPageContent(firstWebPage.content)
verifyUrl(firstWebPage.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
}
@ -136,12 +137,12 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
}.openThreeDotMenu {
}.clickOpenInPrivateTab {
// verifyPageContent(firstWebPage.content)
verifyUrl(firstWebPage.url.toString())
}.openTabDrawer {
verifyPrivateModeSelected()
}
@ -153,7 +154,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
}.openThreeDotMenu {
@ -168,7 +169,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
clickDeleteHistoryButton()
@ -184,7 +185,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
longTapSelectItem(firstWebPage.url)
@ -206,7 +207,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openTabDrawer {
closeTab()
}.openHomeScreen { }.openThreeDotMenu {
@ -228,7 +229,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
longTapSelectItem(firstWebPage.url)
@ -249,12 +250,11 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
}.openTabDrawer { }.openHomeScreen { }
navigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
// verifyPageContent("Page content: 2")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
longTapSelectItem(firstWebPage.url)
@ -277,7 +277,7 @@ class HistoryTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
// verifyPageContent("Page content: 1")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openHistory {
longTapSelectItem(firstWebPage.url)

View File

@ -58,10 +58,8 @@ class MediaNotificationTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(videoTestPage.url) {
// verifyPageContent(videoTestPage.content)
mDevice.waitForIdle()
clickMediaPlayerPlayButton()
waitForPlaybackToStart()
// verifyPageContent("Media file is playing")
}.openNotificationShade {
verifySystemNotificationExists(videoTestPage.title)
clickMediaSystemNotificationControlButton("Pause")
@ -92,7 +90,7 @@ class MediaNotificationTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(audioTestPage.url) {
// verifyPageContent(audioTestPage.content)
mDevice.waitForIdle()
clickMediaPlayerPlayButton()
waitForPlaybackToStart()
}.openNotificationShade {
@ -125,10 +123,9 @@ class MediaNotificationTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(audioTestPage.url) {
// verifyPageContent(audioTestPage.content)
mDevice.waitForIdle()
clickMediaPlayerPlayButton()
waitForPlaybackToStart()
// verifyPageContent("Media file is playing")
}.openTabDrawer {
verifyTabMediaControlButtonState("Pause")
clickTabMediaControlButton()
@ -146,10 +143,9 @@ class MediaNotificationTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(audioTestPage.url) {
// verifyPageContent(audioTestPage.content)
mDevice.waitForIdle()
clickMediaPlayerPlayButton()
waitForPlaybackToStart()
// verifyPageContent("Media file is playing")
}.openNotificationShade {
verifySystemNotificationExists("A site is playing media")
clickMediaSystemNotificationControlButton("Pause")

View File

@ -55,18 +55,12 @@ class NavigationToolbarTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(nextWebPage.url) {
// verifyPageContent(nextWebPage.content)
}
// Re-open the three-dot menu for verification
navigationToolbar {
}.openThreeDotMenu {
verifyThreeDotMenuExists()
}.goBack {
// verifyPageContent(defaultWebPage.content)
verifyUrl(nextWebPage.url.toString())
mDevice.pressBack()
verifyUrl(defaultWebPage.url.toString())
}
}
@ -77,12 +71,14 @@ class NavigationToolbarTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(nextWebPage.url) {
// verifyPageContent(nextWebPage.content)
mDevice.waitForIdle()
verifyUrl(nextWebPage.url.toString())
mDevice.pressBack()
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
verifyUrl(defaultWebPage.url.toString())
}
// Re-open the three-dot menu for verification
@ -91,7 +87,7 @@ class NavigationToolbarTest {
verifyThreeDotMenuExists()
verifyForwardButton()
}.goForward {
// verifyPageContent(nextWebPage.content)
verifyUrl(nextWebPage.url.toString())
}
}
@ -101,7 +97,7 @@ class NavigationToolbarTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(refreshWebPage.url) {
// verifyPageContent("DEFAULT")
mDevice.waitForIdle()
}
// Use refresh from the three-dot menu
@ -110,7 +106,7 @@ class NavigationToolbarTest {
verifyThreeDotMenuExists()
verifyRefreshButton()
}.refreshPage {
// verifyPageContent("REFRESHED")
verifyPageContent("REFRESHED")
}
}
@ -120,7 +116,7 @@ class NavigationToolbarTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
verifyUrl(defaultWebPage.url.toString())
}
}
@ -131,7 +127,7 @@ class NavigationToolbarTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(loremIpsumWebPage.url) {
// verifyPageContent(loremIpsumWebPage.content)
mDevice.waitForIdle()
}
navigationToolbar {

View File

@ -19,6 +19,7 @@ import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.mDevice
/**
* Tests for verifying basic functionality of content context menus
@ -70,7 +71,7 @@ class ReaderViewTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(readerViewPage.url) {
// verifyPageContent(readerViewPage.content)
mDevice.waitForIdle()
}
IdlingRegistry.getInstance().register(readerViewNotificationDot)
@ -99,7 +100,7 @@ class ReaderViewTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
// verifyPageContent(genericPage.content)
mDevice.waitForIdle()
}
readerViewRobot {
@ -120,7 +121,7 @@ class ReaderViewTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(readerViewPage.url) {
// verifyPageContent(readerViewPage.content)
mDevice.waitForIdle()
}
IdlingRegistry.getInstance().register(readerViewNotificationDot)
@ -152,7 +153,7 @@ class ReaderViewTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(readerViewPage.url) {
// verifyPageContent(readerViewPage.content)
mDevice.waitForIdle()
}
IdlingRegistry.getInstance().register(readerViewNotificationDot)
@ -187,7 +188,7 @@ class ReaderViewTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(readerViewPage.url) {
// verifyPageContent(readerViewPage.content)
mDevice.waitForIdle()
}
IdlingRegistry.getInstance().register(readerViewNotificationDot)
@ -222,7 +223,7 @@ class ReaderViewTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(readerViewPage.url) {
// verifyPageContent(readerViewPage.content)
mDevice.waitForIdle()
}
IdlingRegistry.getInstance().register(readerViewNotificationDot)
@ -263,7 +264,7 @@ class ReaderViewTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(readerViewPage.url) {
// verifyPageContent(readerViewPage.content)
mDevice.waitForIdle()
}
IdlingRegistry.getInstance().register(readerViewNotificationDot)

View File

@ -133,27 +133,24 @@ class SettingsBasicsTest {
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(page1.url) {
// verifyPageContent(page1.content)
}.openThreeDotMenu {
clickAddBookmarkButton()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(page2.url) {
// verifyPageContent(page2.content)
}.openThreeDotMenu {
clickAddBookmarkButton()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(page3.url) {
// verifyPageContent(page3.content)
mDevice.waitForIdle()
}
navigationToolbar {
verifyNoHistoryBookmarks()
}
}
@Test

View File

@ -50,7 +50,7 @@ class ShareButtonTest {
// - Visit a URL, wait until it's loaded
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
}
// From the 3-dot menu next to the Select share menu

View File

@ -50,12 +50,10 @@ class SmokeTest {
homeScreen {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
verifyNavURLBarItems()
}.openNavigationToolbar {
}.goBackToWebsite {
// Check disabled due to intermittent failures
// verifyPageContent(defaultWebPage.content)
}.openTabDrawer {
verifyExistingTabList()
}.openHomeScreen {

View File

@ -66,7 +66,7 @@ class TabbedBrowsingTest {
navigationToolbar {
}.openNewTabAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
verifyTabCounter("1")
}.openTabDrawer {
verifyExistingTabList()
@ -92,7 +92,7 @@ class TabbedBrowsingTest {
navigationToolbar {
}.openNewTabAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
mDevice.waitForIdle()
verifyTabCounter("1")
}.openTabDrawer {
verifyExistingTabList()
@ -110,7 +110,6 @@ class TabbedBrowsingTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
}.openTabDrawer {
verifyExistingTabList()
}.openTabsListThreeDotMenu {
@ -128,7 +127,6 @@ class TabbedBrowsingTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
}.openTabDrawer {
verifyPrivateModeSelected()
verifyExistingTabList()
@ -149,7 +147,6 @@ class TabbedBrowsingTest {
genericURLS.forEachIndexed { index, element ->
navigationToolbar {
}.openNewTabAndEnterToBrowser(element.url) {
// verifyPageContent(element.content)
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_${index + 1}")
verifyCloseTabsButton("Test_Page_${index + 1}")
@ -182,7 +179,6 @@ class TabbedBrowsingTest {
genericURLS.forEachIndexed { index, element ->
navigationToolbar {
}.openNewTabAndEnterToBrowser(element.url) {
// verifyPageContent(element.content)
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_${index + 1}")
verifyCloseTabsButton("Test_Page_${index + 1}")

View File

@ -53,7 +53,6 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
verifyAddFirefoxHome()
}.addToFirefoxHome {
@ -72,7 +71,6 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
verifyAddFirefoxHome()
}.addToFirefoxHome {
@ -82,7 +80,6 @@ class TopSitesTest {
verifyExistingTopSitesList()
verifyExistingTopSitesTabs(defaultWebPageTitle)
}.openTopSiteTabWithTitle(title = defaultWebPageTitle) {
// verifyPageContent(defaultWebPage.content)
verifyUrl(defaultWebPage.url.toString().replace("http://", ""))
}.openTabDrawer {
}.openHomeScreen {
@ -103,7 +100,6 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
verifyAddFirefoxHome()
}.addToFirefoxHome {
@ -126,7 +122,6 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
// verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
verifyAddFirefoxHome()
}.addToFirefoxHome {

View File

@ -39,7 +39,6 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
@ -81,13 +80,11 @@ class BrowserRobot {
/* Asserts that the text within DOM element with ID="testContent" has the given text, i.e.
* document.querySelector('#testContent').innerText == expectedText
*
* This function is not working at intended and needs a replacement.
*/
/* fun verifyPageContent(expectedText: String) {
// val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// mDevice.waitNotNull(Until.findObject(By.textContains(expectedText)), waitingTime)
}*/
fun verifyPageContent(expectedText: String) {
assertTrue(mDevice.findObject(UiSelector().text(expectedText)).waitForExists(waitingTime))
}
fun verifyTabCounter(expectedText: String) {
onView(withId(R.id.counter_text))
@ -336,21 +333,13 @@ class BrowserRobot {
}
fun waitForPlaybackToStart() {
mDevice.waitNotNull(
hasObject(
text("Media file is playing")
), waitingTimeShort
)
val playStateMessage = mDevice.findObject(UiSelector().text("Media file is playing"))
assertTrue(playStateMessage.waitForExists(waitingTime))
}
fun verifyMediaIsPaused() {
mDevice.waitNotNull(
hasObject(
text("Media file is paused")
), waitingTimeShort
)
mDevice.findObject(UiSelector().text("Media file is paused")).exists()
val pausedStateMessage = mDevice.findObject(UiSelector().text("Media file is paused"))
assertTrue(pausedStateMessage.waitForExists(waitingTime))
}
class Transition {

View File

@ -0,0 +1,80 @@
/* 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 android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import androidx.test.platform.app.InstrumentationRegistry
import org.mozilla.fenix.BuildConfig.DEEP_LINK_SCHEME
class DeepLinkRobot {
private fun openDeepLink(url: String) {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val intent = Intent().apply {
action = Intent.ACTION_VIEW
data = Uri.parse("$DEEP_LINK_SCHEME://$url")
flags = Intent.FLAG_ACTIVITY_NEW_TASK
addCategory(Intent.CATEGORY_BROWSABLE)
}
try {
context.startActivity(intent)
} catch (ex: ActivityNotFoundException) {
intent.setPackage(null)
context.startActivity(intent)
}
}
fun openURL(url: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
val deepLink = Uri.parse("open")
.buildUpon()
.appendQueryParameter("url", url)
.build()
.toString()
openDeepLink(deepLink)
return browserScreen(interact)
}
fun openHomeScreen(interact: HomeScreenRobot.() -> Unit) =
openDeepLink("home").run { homeScreen(interact) }
fun openBookmarks(interact: BookmarksRobot.() -> Unit) =
openDeepLink("urls_bookmarks").run { bookmarksMenu(interact) }
fun openHistory(interact: HistoryRobot.() -> Unit) =
openDeepLink("urls_history").run { historyMenu(interact) }
fun openCollections(interact: HomeScreenRobot.() -> Unit) =
openDeepLink("home_collections").run { homeScreen(interact) }
fun openSettings(interact: SettingsRobot.() -> Unit) =
openDeepLink("settings").run { settings(interact) }
fun openSettingsPrivacy(interact: SettingsRobot.() -> Unit) =
openDeepLink("settings_privacy").run { settings(interact) }
fun openSettingsLogins(interact: SettingsSubMenuLoginsAndPasswordRobot.() -> Unit) =
openDeepLink("settings_logins").run { settingsSubMenuLoginsAndPassword(interact) }
fun openSettingsTrackingProtection(interact: SettingsSubMenuEnhancedTrackingProtectionRobot.() -> Unit) =
openDeepLink("settings_tracking_protection").run {
settingsSubMenuEnhancedTrackingProtection(interact)
}
fun openSettingsSearchEngine(interact: SettingsSubMenuSearchRobot.() -> Unit) =
openDeepLink("settings_search_engine").run {
SettingsSubMenuSearchRobot().interact()
SettingsSubMenuSearchRobot.Transition()
}
fun openSettingsNotification(interact: SystemSettingsRobot.() -> Unit) =
openDeepLink("settings_notifications").run { systemSettings(interact) }
fun openMakeDefaultBrowser(interact: SystemSettingsRobot.() -> Unit) =
openDeepLink("make_default_browser").run { systemSettings(interact) }
}
private fun settings(interact: SettingsRobot.() -> Unit) =
SettingsRobot().interact().run { SettingsRobot.Transition() }

View File

@ -0,0 +1,27 @@
/* 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.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
class SystemSettingsRobot {
fun verifyNotifications() {
Intents.intended(hasAction("android.settings.APP_NOTIFICATION_SETTINGS"))
}
fun verifyMakeDefaultBrowser() {
Intents.intended(hasAction(SettingsRobot.DEFAULT_APPS_SETTINGS_ACTION))
}
class Transition {
// Difficult to know where this will go
}
}
fun systemSettings(interact: SystemSettingsRobot.() -> Unit): SystemSettingsRobot.Transition {
SystemSettingsRobot().interact()
return SystemSettingsRobot.Transition()
}

View File

@ -79,24 +79,42 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="${deepLinkScheme}"
android:host="enable_private_browsing"/>
<data android:scheme="${deepLinkScheme}"
android:host="home"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings"/>
android:host="home_collections"/>
<data android:scheme="${deepLinkScheme}"
android:host="turn_on_sync"/>
android:host="install_search_widget"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_search_engine"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_accessibility"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_delete_browsing_data"/>
<data android:scheme="${deepLinkScheme}"
android:host="enable_private_browsing"/>
android:host="make_default_browser"/>
<data android:scheme="${deepLinkScheme}"
android:host="open"/>
<data android:scheme="${deepLinkScheme}"
android:host="make_default_browser"/>
android:host="settings"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_accessibility"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_addon_manager"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_delete_browsing_data"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_logins"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_notifications"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_privacy"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_search_engine"/>
<data android:scheme="${deepLinkScheme}"
android:host="settings_tracking_protection"/>
<data android:scheme="${deepLinkScheme}"
android:host="turn_on_sync"/>
<data android:scheme="${deepLinkScheme}"
android:host="urls_bookmarks"/>
<data android:scheme="${deepLinkScheme}"
android:host="urls_history"/>
</intent-filter>
</activity>

View File

@ -22,10 +22,13 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
hasUserGesture: Boolean,
isSameDomain: Boolean,
isRedirect: Boolean,
isDirectNavigation: Boolean
isDirectNavigation: Boolean,
isSubframeRequest: Boolean
): RequestInterceptor.InterceptionResponse? {
return context.components.services.appLinksInterceptor
.onLoadRequest(engineSession, uri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation)
.onLoadRequest(
engineSession, uri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation, isSubframeRequest
)
}
override fun onErrorRequest(

View File

@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.getSystemService
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
@ -67,7 +66,6 @@ open class FenixApplication : LocaleAwareApplication() {
var visibilityLifecycleCallback: VisibilityLifecycleCallback? = null
private set
@ExperimentalCoroutinesApi
override fun onCreate() {
super.onCreate()
@ -116,7 +114,6 @@ open class FenixApplication : LocaleAwareApplication() {
Log.addSink(AndroidLogSink())
}
@ExperimentalCoroutinesApi
@CallSuper
open fun setupInMainProcessOnly() {
run {

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix
import androidx.navigation.NavDirections
import mozilla.appservices.places.BookmarkRoot
/**
* Used with [HomeActivity] global navigation to indicate which fragment is being opened.
@ -14,6 +15,14 @@ import androidx.navigation.NavDirections
*/
enum class GlobalDirections(val navDirections: NavDirections, val destinationId: Int) {
Home(NavGraphDirections.actionGlobalHome(), R.id.homeFragment),
Bookmarks(
NavGraphDirections.actionGlobalBookmarkFragment(BookmarkRoot.Root.id),
R.id.bookmarkFragment
),
History(
NavGraphDirections.actionGlobalHistoryFragment(),
R.id.historyFragment
),
Settings(
NavGraphDirections.actionGlobalSettingsFragment(),
R.id.settingsFragment
@ -37,5 +46,13 @@ enum class GlobalDirections(val navDirections: NavDirections, val destinationId:
SettingsAddonManager(
NavGraphDirections.actionGlobalSettingsAddonsManagementFragment(),
R.id.addonsManagementFragment
),
SettingsLogins(
NavGraphDirections.actionGlobalSavedLoginsAuthFragment(),
R.id.saveLoginSettingFragment
),
SettingsTrackingProtection(
NavGraphDirections.actionGlobalTrackingProtectionFragment(),
R.id.trackingProtectionFragment
)
}

View File

@ -29,6 +29,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session
@ -104,6 +105,7 @@ import org.mozilla.fenix.utils.RunWhenReadyQueue
* - home screen
* - browser screen
*/
@OptIn(ExperimentalCoroutinesApi::class)
@SuppressWarnings("TooManyFunctions", "LargeClass")
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {

View File

@ -158,7 +158,6 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management)
onPositiveButtonClicked = onPositiveButtonClicked
)
dialog.show(parentFragmentManager, PERMISSIONS_DIALOG_FRAGMENT_TAG)
requireContext().components.analytics.metrics.track(Event.AddonInstalled(addon.id))
}
}

View File

@ -4,9 +4,9 @@
package org.mozilla.fenix.components
import android.app.Application
import android.content.Context
import android.content.Intent
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import mozilla.components.feature.addons.AddonManager
import mozilla.components.feature.addons.amo.AddonCollectionProvider
@ -106,5 +106,5 @@ class Components(private val context: Context) {
val migrationStore by lazy { MigrationStore() }
val performance by lazy { PerformanceComponent() }
val push by lazy { Push(context, analytics.crashReporter) }
val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context as Application) }
val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context.getSystemService()!!) }
}

View File

@ -50,7 +50,6 @@ import org.mozilla.fenix.BuildConfig.DIGITAL_ASSET_LINKS_TOKEN
import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ReleaseChannel
import org.mozilla.fenix.downloads.DownloadService
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
@ -97,11 +96,7 @@ class Core(private val context: Context) {
* disabled in Fenix Release builds for now.
* This is consistent with both Fennec and Firefox Desktop.
*/
val shouldEnableWebcompatReporter = Config.channel !in setOf(
ReleaseChannel.FenixProduction,
ReleaseChannel.FennecProduction
)
if (shouldEnableWebcompatReporter) {
if (Config.channel.isNightlyOrDebug || Config.channel.isBeta) {
WebCompatReporterFeature.install(it)
}
}

View File

@ -102,6 +102,15 @@ class LeanplumMetricsService(private val application: Application) : MetricsServ
val installedApps = MozillaProductDetector.getInstalledMozillaProducts(application)
val trackingProtection = application.settings().run {
when {
!shouldUseTrackingProtection -> "none"
useStandardTrackingProtection -> "standard"
useStrictTrackingProtection -> "strict"
else -> "custom"
}
}
Leanplum.start(application, hashMapOf(
"default_browser" to MozillaProductDetector.getMozillaBrowserDefault(application).orEmpty(),
"fennec_installed" to installedApps.contains(MozillaProducts.FIREFOX.productName),
@ -110,6 +119,8 @@ class LeanplumMetricsService(private val application: Application) : MetricsServ
"fxa_signed_in" to application.settings().fxaSignedIn,
"fxa_has_synced_items" to application.settings().fxaHasSyncedItems,
"search_widget_installed" to application.settings().searchWidgetInstalled,
"tracking_protection_enabled" to application.settings().shouldUseTrackingProtection,
"tracking_protection_setting" to trackingProtection,
"fenix" to true
))

View File

@ -7,7 +7,6 @@ package org.mozilla.fenix.components.searchengine
import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
@ -33,14 +32,16 @@ import java.util.Locale
open class FenixSearchEngineProvider(
private val context: Context
) : SearchEngineProvider, CoroutineScope by CoroutineScope(Job() + Dispatchers.IO) {
private val locationService: LocationService = if (Config.channel.isDebug) {
LocationService.dummy()
} else {
MozillaLocationService(
context,
context.components.core.client,
BuildConfig.MLS_TOKEN
)
private val locationService = with(MozillaLocationService(
context,
context.components.core.client,
BuildConfig.MLS_TOKEN
)) {
if (Config.channel.isDebug || !this.hasRegionCached()) {
LocationService.dummy()
} else {
this
}
}
// We have two search engine types: one based on MLS reported region, one based only on Locale.
@ -93,17 +94,6 @@ open class FenixSearchEngineProvider(
private var loadedSearchEngines = refreshAsync()
// https://github.com/mozilla-mobile/fenix/issues/9935
// Create new getter that will return the fallback SearchEngineList if
// the main one hasn't completed yet
private val searchEngines: Deferred<SearchEngineList>
get() =
if (isRegionCachedByLocationService) {
loadedSearchEngines
} else {
fallbackEngines
}
fun getDefaultEngine(context: Context): SearchEngine {
val engines = installedSearchEngines(context)
val selectedName = context.settings().defaultSearchEngineName
@ -117,7 +107,7 @@ open class FenixSearchEngineProvider(
*/
fun installedSearchEngines(context: Context): SearchEngineList = runBlocking {
val installedIdentifiers = installedSearchEngineIdentifiers(context)
val engineList = searchEngines.await()
val engineList = loadedSearchEngines.await()
engineList.copy(
list = engineList.list.filter {
@ -188,7 +178,11 @@ open class FenixSearchEngineProvider(
}
private fun refreshAsync() = async {
val engineList = baseSearchEngines.await()
val engineList = if (isRegionCachedByLocationService) {
baseSearchEngines.await()
} else {
fallbackEngines.await()
}
val bundledList = bundledSearchEngines.await().list
val customList = customSearchEngines.await().list

View File

@ -126,7 +126,6 @@ suspend fun String.toRoundedDrawable(context: Context, client: Client) = bitmapF
}
suspend fun bitmapForUrl(url: String, client: Client): Bitmap? = withContext(Dispatchers.IO) {
// TODO cache this image, see https://github.com/mozilla-mobile/fenix/issues/9531
// Code below will cache it in Gecko's cache, which ensures that as long as we've fetched it once,
// we will be able to display this avatar as long as the cache isn't purged (e.g. via 'clear user data').
val body = try {

View File

@ -0,0 +1,19 @@
/* 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 android.text.style.UnderlineSpan
import android.widget.TextView
import androidx.core.text.toSpannable
/**
* Adds an underline effect to the text displayed in the TextView.
*/
fun TextView.addUnderline() {
val currentText = text
text = currentText.toSpannable().apply {
setSpan(UnderlineSpan(), 0, currentText.length, 0)
}
}

View File

@ -4,16 +4,19 @@
package org.mozilla.fenix.home.intent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS
import android.provider.Settings
import androidx.navigation.NavController
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.GlobalDirections
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.ext.alreadyOnDestination
/**
@ -24,7 +27,7 @@ class DeepLinkIntentProcessor(
) : HomeIntentProcessor {
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
val scheme = intent.scheme?.contains("fenix") ?: return false
val scheme = intent.scheme?.equals(BuildConfig.DEEP_LINK_SCHEME, ignoreCase = true) ?: return false
return if (scheme) {
intent.data?.let { handleDeepLink(it, navController) }
true
@ -39,12 +42,22 @@ class DeepLinkIntentProcessor(
val globalDirections = when (deepLink.host) {
"home", "enable_private_browsing" -> GlobalDirections.Home
"urls_bookmarks" -> GlobalDirections.Bookmarks
"urls_history" -> GlobalDirections.History
"settings" -> GlobalDirections.Settings
"turn_on_sync" -> GlobalDirections.Sync
"settings_search_engine" -> GlobalDirections.SearchEngine
"settings_accessibility" -> GlobalDirections.Accessibility
"settings_delete_browsing_data" -> GlobalDirections.DeleteData
"settings_addon_manager" -> GlobalDirections.SettingsAddonManager
"settings_logins" -> GlobalDirections.SettingsLogins
"settings_tracking_protection" -> GlobalDirections.SettingsTrackingProtection
// We'd like to highlight views within the fragment
// https://github.com/mozilla-mobile/fenix/issues/11856
// The current version of UI has these features in more complex screens.
"settings_privacy" -> GlobalDirections.Settings
"home_collections" -> GlobalDirections.Home
else -> return
}
@ -63,7 +76,7 @@ class DeepLinkIntentProcessor(
}
"make_default_browser" -> {
if (SDK_INT >= Build.VERSION_CODES.N) {
val settingsIntent = Intent(ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
val settingsIntent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
activity.startActivity(settingsIntent)
}
}
@ -76,6 +89,38 @@ class DeepLinkIntentProcessor(
)
}
}
"settings_notifications" -> {
val intent = notificationSettings(activity)
activity.startActivity(intent)
}
"install_search_widget" -> {
if (SDK_INT >= Build.VERSION_CODES.O) {
SearchWidgetCreator.createSearchWidget(activity)
}
}
}
}
private fun notificationSettings(context: Context, channel: String? = null) =
Intent().apply {
when {
SDK_INT >= Build.VERSION_CODES.O -> {
action = channel?.let {
putExtra(Settings.EXTRA_CHANNEL_ID, it)
Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
} ?: Settings.ACTION_APP_NOTIFICATION_SETTINGS
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
}
SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
action = "android.settings.APP_NOTIFICATION_SETTINGS"
putExtra("app_package", context.packageName)
putExtra("app_uid", context.applicationInfo.uid)
}
else -> {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
addCategory(Intent.CATEGORY_DEFAULT)
data = Uri.parse("package:" + context.packageName)
}
}
}
}

View File

@ -9,15 +9,14 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.R
import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.home.HomeFragmentState
import org.mozilla.fenix.home.HomeScreenViewModel
import org.mozilla.fenix.home.Mode
import org.mozilla.fenix.home.OnboardingState
import org.mozilla.fenix.components.tips.Tip
// This method got a little complex with the addition of the tab tray feature flag
// When we remove the tabs from the home screen this will get much simpler again.
@ -110,7 +109,6 @@ private fun collectionTabItems(collection: TabCollection) = collection.tabs.mapI
AdapterItem.TabInCollectionItem(collection, tab, index == collection.tabs.lastIndex)
}
@ExperimentalCoroutinesApi
class SessionControlView(
override val containerView: View?,
interactor: SessionControlInteractor,

View File

@ -4,13 +4,12 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.UnderlineSpan
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.private_browsing_description.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.home.sessioncontrol.TabSessionInteractor
class PrivateBrowsingDescriptionViewHolder(
@ -24,13 +23,9 @@ class PrivateBrowsingDescriptionViewHolder(
view.private_session_description.text = resources.getString(
R.string.private_browsing_placeholder_description_2, appName
)
val commonMythsText = view.private_session_common_myths.text.toString()
val textWithLink = SpannableString(commonMythsText).apply {
setSpan(UnderlineSpan(), 0, commonMythsText.length, 0)
}
with(view.private_session_common_myths) {
movementMethod = LinkMovementMethod.getInstance()
text = textWithLink
addUnderline()
setOnClickListener {
interactor.onPrivateBrowsingLearnMoreClicked()
}

View File

@ -6,8 +6,8 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.setBounds
@ -16,9 +16,9 @@ import org.mozilla.fenix.ext.setBounds
* Sets the drawableStart of a header in an onboarding card.
*/
fun TextView.setOnboardingIcon(@DrawableRes id: Int) {
val icon = AppCompatResources.getDrawable(context, id)
val size = context.resources.getDimensionPixelSize(R.dimen.onboarding_header_icon_height_width)
icon?.setBounds(size)
icon?.setTint(context.getColorFromAttr(R.attr.onboardingSelected))
val icon = context.getDrawableWithTint(id, context.getColorFromAttr(R.attr.onboardingSelected))?.apply {
val size = context.resources.getDimensionPixelSize(R.dimen.onboarding_header_icon_height_width)
setBounds(size)
}
putCompoundDrawablesRelative(start = icon)
}

View File

@ -5,11 +5,11 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_manual_signin.view.*
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
@ -34,8 +34,10 @@ class OnboardingManualSignInViewHolder(view: View) : RecyclerView.ViewHolder(vie
val appName = context.getString(R.string.app_name)
headerText.text = context.getString(R.string.onboarding_firefox_account_header, appName)
val icon = AppCompatResources.getDrawable(context, R.drawable.ic_onboarding_firefox_accounts)
icon?.setTint(ContextCompat.getColor(context, R.color.white_color))
val icon = context.getDrawableWithTint(
R.drawable.ic_onboarding_firefox_accounts,
ContextCompat.getColor(context, R.color.white_color)
)
headerText.putCompoundDrawablesRelativeWithIntrinsicBounds(start = icon)
}

View File

@ -4,13 +4,12 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.text.SpannableString
import android.text.style.UnderlineSpan
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_whats_new.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.home.sessioncontrol.OnboardingInteractor
@ -25,12 +24,7 @@ class OnboardingWhatsNewViewHolder(
val appName = view.context.getString(R.string.app_name)
view.description_text.text = view.context.getString(R.string.onboarding_whats_new_description, appName)
val getAnswersText = view.get_answers.text.toString()
val textWithLink = SpannableString(getAnswersText).apply {
setSpan(UnderlineSpan(), 0, getAnswersText.length, 0)
}
view.get_answers.text = textWithLink
view.get_answers.addUnderline()
view.get_answers.setOnClickListener {
interactor.onWhatsNewGetAnswersClicked()
view.context.components.analytics.metrics.track(Event.OnboardingWhatsNew)

View File

@ -1,7 +1,5 @@
package org.mozilla.fenix.home.tips
import android.text.SpannableString
import android.text.style.UnderlineSpan
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.button_tip_item.view.*
@ -11,6 +9,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.components.tips.TipType
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
@ -36,12 +35,7 @@ class ButtonTipViewHolder(
if (tip.learnMoreURL == null) {
tip_learn_more.visibility = View.GONE
} else {
val learnMoreText = context.getString(R.string.search_suggestions_onboarding_learn_more_link)
val textWithLink = SpannableString(learnMoreText).apply {
setSpan(UnderlineSpan(), 0, learnMoreText.length, 0)
}
tip_learn_more.text = textWithLink
tip_learn_more.addUnderline()
tip_learn_more.setOnClickListener {
(context as HomeActivity).openToBrowserAndLoad(

View File

@ -2,8 +2,9 @@
* 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
package org.mozilla.fenix.migration
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.base.log.logger.Logger
@ -16,6 +17,8 @@ class MigrationTelemetryListener(
private val metrics: MetricController,
private val store: MigrationStore
) {
@OptIn(ExperimentalCoroutinesApi::class)
fun start() {
// Observe for migration completed.
store.flowScoped { flow ->

View File

@ -7,8 +7,8 @@ package org.mozilla.fenix.perf
import android.app.Activity
import android.view.View
import androidx.core.view.doOnPreDraw
import kotlinx.android.synthetic.main.activity_home.*
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.reportFullyDrawnSafe
import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.TopSiteItemViewHolder
import org.mozilla.fenix.perf.StartupTimelineStateMachine.StartupDestination.APP_LINK
@ -36,7 +36,7 @@ class StartupReportFullyDrawn {
state is StartupState.Cold && state.destination == APP_LINK) {
// Instrumenting the first frame drawn should be good enough for app link for now.
isInstrumented = true
attachReportFullyDrawn(activity, activity.rootContainer)
attachReportFullyDrawn(activity, activity.findViewById(R.id.rootContainer))
}
}
@ -59,7 +59,7 @@ class StartupReportFullyDrawn {
}
}
private fun attachReportFullyDrawn(activity: HomeActivity, view: View) {
private fun attachReportFullyDrawn(activity: Activity, view: View) {
// For greater accuracy, we could add an onDrawListener instead of a preDrawListener but:
// - single use onDrawListeners are not built-in and it's non-trivial to write one
// - the difference in timing is minimal (< 7ms on Pixel 2)

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.search.telemetry
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
@ -85,6 +86,7 @@ abstract class BaseSearchTelemetry {
internal fun getProviderForUrl(url: String): SearchProviderModel? =
providerList.find { provider -> provider.regexp.containsMatchIn(url) }
@OptIn(ExperimentalCoroutinesApi::class)
internal fun installWebExtension(
engine: Engine,
store: BrowserStore,

View File

@ -15,10 +15,10 @@ import mozilla.components.concept.engine.Engine
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.android.view.hideKeyboard
import org.mozilla.fenix.R
import org.mozilla.fenix.search.SearchFragmentState
import org.mozilla.fenix.theme.ThemeManager
/**
* Interface for the Toolbar Interactor. This interface is implemented by objects that want
@ -71,10 +71,9 @@ class ToolbarView(
false
}
background =
AppCompatResources.getDrawable(
context, ThemeManager.resolveAttribute(R.attr.foundation, context)
)
background = AppCompatResources.getDrawable(
context, context.theme.resolveAttribute(R.attr.foundation)
)
edit.hint = context.getString(R.string.search_hint)

View File

@ -16,32 +16,44 @@ interface LocaleSettingsController {
}
class DefaultLocaleSettingsController(
private val context: Context,
private val activity: Activity,
private val localeSettingsStore: LocaleSettingsStore
) : LocaleSettingsController {
override fun handleLocaleSelected(locale: Locale) {
if (localeSettingsStore.state.selectedLocale == locale &&
!LocaleManager.isDefaultLocaleSelected(context)) {
!LocaleManager.isDefaultLocaleSelected(activity)) {
return
}
localeSettingsStore.dispatch(LocaleSettingsAction.Select(locale))
LocaleManager.setNewLocale(context, locale.toLanguageTag())
LocaleManager.updateBaseConfiguration(context, locale)
(context as Activity).recreate()
LocaleManager.setNewLocale(activity, locale.toLanguageTag())
LocaleManager.updateBaseConfiguration(activity, locale)
activity.recreate()
}
override fun handleDefaultLocaleSelected() {
if (LocaleManager.isDefaultLocaleSelected(context)) {
if (LocaleManager.isDefaultLocaleSelected(activity)) {
return
}
localeSettingsStore.dispatch(LocaleSettingsAction.Select(localeSettingsStore.state.localeList[0]))
LocaleManager.resetToSystemDefault(context)
LocaleManager.updateBaseConfiguration(context, localeSettingsStore.state.localeList[0])
(context as Activity).recreate()
LocaleManager.resetToSystemDefault(activity)
LocaleManager.updateBaseConfiguration(activity, localeSettingsStore.state.localeList[0])
activity.recreate()
}
override fun handleSearchQueryTyped(query: String) {
localeSettingsStore.dispatch(LocaleSettingsAction.Search(query))
}
/**
* Update the locale for the configuration of the app context's resources
*/
@Suppress("Deprecation")
fun LocaleManager.updateBaseConfiguration(context: Context, locale: Locale) {
val resources = context.applicationContext.resources
val config = resources.configuration
config.setLocale(locale)
config.setLayoutDirection(locale)
resources.updateConfiguration(config, resources.displayMetrics)
}
}

View File

@ -5,14 +5,9 @@
package org.mozilla.fenix.settings.advanced
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.locale_settings_item.view.*
import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.R
import java.util.Locale
@ -95,67 +90,3 @@ class LocaleAdapter(private val interactor: LocaleSettingsViewInteractor) :
DEFAULT, LOCALE;
}
}
class LocaleViewHolder(
view: View,
selectedLocale: Locale,
private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) {
private val icon = view.locale_selected_icon
private val title = view.locale_title_text
private val subtitle = view.locale_subtitle_text
override fun bind(locale: Locale) {
// capitalisation is done using the rules of the appropriate locale (endonym and exonym)
title.text = locale.getDisplayName(locale).capitalize(locale)
subtitle.text = locale.displayName.capitalize(Locale.getDefault())
icon.isVisible = isCurrentLocaleSelected(locale, isDefault = false)
itemView.setOnClickListener {
interactor.onLocaleSelected(locale)
}
}
}
class SystemLocaleViewHolder(
view: View,
selectedLocale: Locale,
private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) {
private val icon = view.locale_selected_icon
private val title = view.locale_title_text
private val subtitle = view.locale_subtitle_text
override fun bind(locale: Locale) {
title.text = itemView.context.getString(R.string.default_locale_text)
subtitle.visibility = View.GONE
icon.isVisible = isCurrentLocaleSelected(locale, isDefault = true)
itemView.setOnClickListener {
interactor.onDefaultLocaleSelected()
}
}
}
abstract class BaseLocaleViewHolder(
view: View,
private val selectedLocale: Locale
) : RecyclerView.ViewHolder(view) {
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
internal fun isCurrentLocaleSelected(locale: Locale, isDefault: Boolean): Boolean {
return if (isDefault) {
locale == selectedLocale && LocaleManager.isDefaultLocaleSelected(itemView.context)
} else {
locale == selectedLocale && !LocaleManager.isDefaultLocaleSelected(itemView.context)
}
}
abstract fun bind(locale: Locale)
}
/**
* Similar to Kotlin's capitalize with locale parameter, but that method is currently experimental
*/
private fun String.capitalize(locale: Locale): String {
return substring(0, 1).toUpperCase(locale) + substring(1)
}

View File

@ -51,15 +51,3 @@ fun LocaleManager.getSelectedLocale(
fun LocaleManager.isDefaultLocaleSelected(context: Context): Boolean {
return getCurrentLocale(context) == null
}
/**
* Update the locale for the configuration of the app context's resources
*/
@Suppress("DEPRECATION")
fun LocaleManager.updateBaseConfiguration(context: Context, locale: Locale) {
val resources = context.applicationContext.resources
val config = resources.configuration
config.setLocale(locale)
config.setLayoutDirection(locale)
resources.updateConfiguration(config, resources.displayMetrics)
}

View File

@ -43,7 +43,7 @@ class LocaleSettingsFragment : Fragment() {
store = getStore()
interactor = LocaleSettingsInteractor(
controller = DefaultLocaleSettingsController(
context = requireContext(),
activity = requireActivity(),
localeSettingsStore = store
)
)

View File

@ -0,0 +1,72 @@
/* 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.settings.advanced
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.locale_settings_item.*
import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.R
import org.mozilla.fenix.utils.view.ViewHolder
import java.util.Locale
class LocaleViewHolder(
view: View,
selectedLocale: Locale,
private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) {
override fun bind(locale: Locale) {
// capitalisation is done using the rules of the appropriate locale (endonym and exonym)
locale_title_text.text = locale.getDisplayName(locale).capitalize(locale)
locale_subtitle_text.text = locale.displayName.capitalize(Locale.getDefault())
locale_selected_icon.isVisible = isCurrentLocaleSelected(locale, isDefault = false)
itemView.setOnClickListener {
interactor.onLocaleSelected(locale)
}
}
}
class SystemLocaleViewHolder(
view: View,
selectedLocale: Locale,
private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) {
override fun bind(locale: Locale) {
locale_title_text.text = itemView.context.getString(R.string.default_locale_text)
locale_subtitle_text.visibility = View.GONE
locale_selected_icon.isVisible = isCurrentLocaleSelected(locale, isDefault = true)
itemView.setOnClickListener {
interactor.onDefaultLocaleSelected()
}
}
}
abstract class BaseLocaleViewHolder(
view: View,
private val selectedLocale: Locale
) : ViewHolder(view) {
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
internal fun isCurrentLocaleSelected(locale: Locale, isDefault: Boolean): Boolean {
return if (isDefault) {
locale == selectedLocale && LocaleManager.isDefaultLocaleSelected(itemView.context)
} else {
locale == selectedLocale && !LocaleManager.isDefaultLocaleSelected(itemView.context)
}
}
abstract fun bind(locale: Locale)
}
/**
* Similar to Kotlin's capitalize with locale parameter, but that method is currently experimental
*/
private fun String.capitalize(locale: Locale): String {
return substring(0, 1).toUpperCase(locale) + substring(1)
}

View File

@ -54,6 +54,7 @@ sealed class LoginsAction : Action {
data class UpdateLoginsList(val list: List<SavedLogin>) : LoginsAction()
data class UpdateCurrentLogin(val item: SavedLogin) : LoginsAction()
data class SortLogins(val sortingStrategy: SortingStrategy) : LoginsAction()
data class LoginSelected(val item: SavedLogin) : LoginsAction()
}
/**
@ -110,6 +111,13 @@ private fun savedLoginsStateReducer(
state
)
}
is LoginsAction.LoginSelected -> {
state.copy(
isLoading = true,
loginList = emptyList(),
filteredItems = emptyList()
)
}
}
}

View File

@ -34,7 +34,7 @@ class LoginsListViewHolder(
updateFavIcon(item.origin)
view.setOnClickListener {
interactor.itemClicked(item)
interactor.onItemClicked(item)
}
}

View File

@ -37,12 +37,10 @@ import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.SupportUtils
@SuppressWarnings("TooManyFunctions")
class SavedLoginsFragment : Fragment() {
@ -88,9 +86,14 @@ class SavedLoginsFragment : Fragment() {
)
}
val savedLoginsController: SavedLoginsController =
SavedLoginsController(savedLoginsStore, requireContext().settings())
savedLoginsInteractor =
SavedLoginsInteractor(savedLoginsController, ::itemClicked, ::openLearnMore)
SavedLoginsController(
store = savedLoginsStore,
navController = findNavController(),
browserNavigator = ::openToBrowserAndLoad,
settings = requireContext().settings(),
metrics = requireContext().components.analytics.metrics
)
savedLoginsInteractor = SavedLoginsInteractor(savedLoginsController)
savedLoginsView = SavedLoginsView(view.savedLoginsLayout, savedLoginsInteractor)
loadAndMapLogins()
return view
@ -138,20 +141,8 @@ class SavedLoginsFragment : Fragment() {
super.onPause()
}
private fun itemClicked(item: SavedLogin) {
context?.components?.analytics?.metrics?.track(Event.OpenOneLogin)
val directions =
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToLoginDetailFragment(item.guid)
findNavController().navigate(directions)
}
private fun openLearnMore() {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
(SupportUtils.SumoTopic.SYNC_SETUP),
newTab = true,
from = BrowserDirection.FromSavedLoginsFragment
)
private fun openToBrowserAndLoad(searchTermOrURL: String, newTab: Boolean, from: BrowserDirection) {
(activity as HomeActivity).openToBrowserAndLoad(searchTermOrURL, newTab, from)
}
private fun loadAndMapLogins() {
@ -222,11 +213,15 @@ class SavedLoginsFragment : Fragment() {
sortingStrategyMenu = SavedLoginsSortingStrategyMenu(requireContext(), itemToHighlight) {
when (it) {
SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort -> {
savedLoginsInteractor.sort(SortingStrategy.Alphabetically(requireContext().applicationContext))
savedLoginsInteractor.onSortingStrategyChanged(
SortingStrategy.Alphabetically(requireContext().applicationContext)
)
}
SavedLoginsSortingStrategyMenu.Item.LastUsedSort -> {
savedLoginsInteractor.sort(SortingStrategy.LastUsed(requireContext().applicationContext))
savedLoginsInteractor.onSortingStrategyChanged(
SortingStrategy.LastUsed(requireContext().applicationContext)
)
}
}
}

View File

@ -4,18 +4,21 @@
package org.mozilla.fenix.settings.logins
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.UnderlineSpan
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import androidx.navigation.NavController
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_saved_logins.view.*
import kotlinx.android.synthetic.main.component_saved_logins.view.progress_bar
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
/**
@ -39,14 +42,10 @@ class SavedLoginsView(
itemAnimator = null
}
val learnMoreText = view.saved_passwords_empty_learn_more.text.toString()
val textWithLink = SpannableString(learnMoreText).apply {
setSpan(UnderlineSpan(), 0, learnMoreText.length, 0)
}
with(view.saved_passwords_empty_learn_more) {
movementMethod = LinkMovementMethod.getInstance()
text = textWithLink
setOnClickListener { interactor.onLearnMore() }
addUnderline()
setOnClickListener { interactor.onLearnMoreClicked() }
}
with(view.saved_passwords_empty_message) {
@ -60,6 +59,7 @@ class SavedLoginsView(
}
fun update(state: LoginsListState) {
// todo MVI views should not have logic. Needs refactoring.
if (state.isLoading) {
view.progress_bar.isVisible = true
} else {
@ -73,29 +73,63 @@ class SavedLoginsView(
/**
* Interactor for the saved logins screen
*
* @param savedLoginsController [SavedLoginsController] which will be delegated for all users interactions.
*/
class SavedLoginsInteractor(
private val savedLoginsController: SavedLoginsController,
private val itemClicked: (SavedLogin) -> Unit,
private val learnMore: () -> Unit
private val savedLoginsController: SavedLoginsController
) {
fun itemClicked(item: SavedLogin) {
itemClicked.invoke(item)
fun onItemClicked(item: SavedLogin) {
savedLoginsController.handleItemClicked(item)
}
fun onLearnMore() {
learnMore.invoke()
fun onLearnMoreClicked() {
savedLoginsController.handleLearnMoreClicked()
}
fun sort(sortingStrategy: SortingStrategy) {
fun onSortingStrategyChanged(sortingStrategy: SortingStrategy) {
savedLoginsController.handleSort(sortingStrategy)
}
}
/**
* Controller for the saved logins screen
*
* @param store Store used to hold in-memory collection state.
* @param navController NavController manages app navigation within a NavHost.
* @param browserNavigator Controller allowing browser navigation to any Uri.
* @param settings SharedPreferences wrapper for easier usage.
* @param metrics Controller that handles telemetry events.
*/
class SavedLoginsController(val store: LoginsFragmentStore, val settings: Settings) {
class SavedLoginsController(
private val store: LoginsFragmentStore,
private val navController: NavController,
private val browserNavigator: (
searchTermOrURL: String,
newTab: Boolean,
from: BrowserDirection
) -> Unit,
private val settings: Settings,
private val metrics: MetricController
) {
fun handleSort(sortingStrategy: SortingStrategy) {
store.dispatch(LoginsAction.SortLogins(sortingStrategy))
settings.savedLoginsSortingStrategy = sortingStrategy
}
fun handleItemClicked(item: SavedLogin) {
store.dispatch(LoginsAction.LoginSelected(item))
metrics.track(Event.OpenOneLogin)
navController.navigate(
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToLoginDetailFragment(item.guid)
)
}
fun handleLearnMoreClicked() {
browserNavigator.invoke(
SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.SYNC_SETUP),
true,
BrowserDirection.FromSavedLoginsFragment
)
}
}

View File

@ -12,6 +12,11 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.service.fxa.manager.FxaAccountManager
import org.mozilla.fenix.ext.components
/**
* Starts and stops SyncedTabsStorage based on the authentication state.
* @param context Used to get synced tabs storage, due to cyclic dependency.
* @param accountManager Used to check and observe account authentication state.
*/
class SyncedTabsIntegration(
private val context: Context,
private val accountManager: FxaAccountManager

View File

@ -10,6 +10,10 @@ import android.view.View
import android.widget.FrameLayout
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import org.mozilla.fenix.R
@ -23,6 +27,7 @@ class SyncedTabsLayout @JvmOverloads constructor(
override var listener: SyncedTabsView.Listener? = null
private val adapter = SyncedTabsAdapter { listener?.onTabClicked(it) }
private val coroutineScope = CoroutineScope(Dispatchers.Main)
init {
inflate(getContext(), R.layout.component_sync_tabs, this)
@ -34,42 +39,46 @@ class SyncedTabsLayout @JvmOverloads constructor(
}
override fun onError(error: SyncedTabsView.ErrorType) {
// We may still be displaying a "loading" spinner, hide it.
stopLoading()
coroutineScope.launch {
// We may still be displaying a "loading" spinner, hide it.
stopLoading()
val stringResId = when (error) {
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE -> R.string.synced_tabs_connect_another_device
SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing
SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_connect_to_sync_account
SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth
SyncedTabsView.ErrorType.NO_TABS_AVAILABLE -> R.string.synced_tabs_no_tabs
val stringResId = when (error) {
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE -> R.string.synced_tabs_connect_another_device
SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing
SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_connect_to_sync_account
SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth
SyncedTabsView.ErrorType.NO_TABS_AVAILABLE -> R.string.synced_tabs_no_tabs
}
sync_tabs_status.text = context.getText(stringResId)
synced_tabs_list.visibility = View.GONE
sync_tabs_status.visibility = View.VISIBLE
synced_tabs_pull_to_refresh.isEnabled = pullToRefreshEnableState(error)
}
sync_tabs_status.text = context.getText(stringResId)
synced_tabs_list.visibility = View.GONE
sync_tabs_status.visibility = View.VISIBLE
synced_tabs_pull_to_refresh.isEnabled = pullToRefreshEnableState(error)
}
override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) {
synced_tabs_list.visibility = View.VISIBLE
sync_tabs_status.visibility = View.GONE
coroutineScope.launch {
synced_tabs_list.visibility = View.VISIBLE
sync_tabs_status.visibility = View.GONE
val allDeviceTabs = emptyList<SyncedTabsAdapter.AdapterItem>().toMutableList()
val allDeviceTabs = emptyList<SyncedTabsAdapter.AdapterItem>().toMutableList()
syncedTabs.forEach { (device, tabs) ->
if (tabs.isEmpty()) {
return@forEach
syncedTabs.forEach { (device, tabs) ->
if (tabs.isEmpty()) {
return@forEach
}
val deviceTabs = tabs.map { SyncedTabsAdapter.AdapterItem.Tab(it) }
allDeviceTabs += listOf(SyncedTabsAdapter.AdapterItem.Device(device)) + deviceTabs
}
val deviceTabs = tabs.map { SyncedTabsAdapter.AdapterItem.Tab(it) }
allDeviceTabs += listOf(SyncedTabsAdapter.AdapterItem.Device(device)) + deviceTabs
adapter.submitList(allDeviceTabs)
}
adapter.submitList(allDeviceTabs)
}
override fun startLoading() {
@ -83,6 +92,11 @@ class SyncedTabsLayout @JvmOverloads constructor(
synced_tabs_pull_to_refresh.isRefreshing = false
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
coroutineScope.cancel()
}
companion object {
internal fun pullToRefreshEnableState(error: SyncedTabsView.ErrorType) = when (error) {
// Disable "pull-to-refresh" when we clearly can't sync tabs, and user needs to take an

View File

@ -47,8 +47,8 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
private fun bindHeader(device: AdapterItem.Device) {
val deviceLogoDrawable = when (device.device.deviceType) {
DeviceType.DESKTOP -> { R.drawable.mozac_ic_device_desktop }
else -> { R.drawable.mozac_ic_device_mobile }
DeviceType.DESKTOP -> R.drawable.mozac_ic_device_desktop
else -> R.drawable.mozac_ic_device_mobile
}
itemView.synced_tabs_group_name.text = device.device.displayName

View File

@ -19,6 +19,7 @@ import kotlinx.android.synthetic.main.component_tabstray.view.*
import kotlinx.android.synthetic.main.component_tabstray_fab.view.*
import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.*
import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.selector.normalTabs
@ -110,6 +111,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment() {
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate

View File

@ -11,6 +11,7 @@ import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.ContextCompat
import androidx.core.view.doOnNextLayout
import mozilla.components.browser.state.state.MediaState
import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.browser.tabstray.thumbnail.TabThumbnailView
@ -20,7 +21,7 @@ import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.feature.media.ext.pauseIfPlaying
import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ext.loadIntoView
import mozilla.components.support.images.loader.ImageLoader
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.mozilla.fenix.R
@ -72,7 +73,10 @@ class TabTrayViewHolder(
if (tab.thumbnail != null) {
thumbnailView.setImageBitmap(tab.thumbnail)
} else {
imageLoader.loadIntoView(thumbnailView, ImageRequest(tab.id))
// Make sure we have the view's dimensions so we can load the image at the correct size
thumbnailView.doOnNextLayout {
imageLoader.loadIntoView(thumbnailView, tab.id)
}
}
// Media state

View File

@ -5,17 +5,16 @@
package org.mozilla.fenix.trackingprotectionexceptions
import android.text.method.LinkMovementMethod
import android.text.style.UnderlineSpan
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_exceptions.*
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.addUnderline
/**
* Interface for the ExceptionsViewInteractor. This interface is implemented by objects that want
@ -62,10 +61,7 @@ class ExceptionsView(
}
with(exceptions_learn_more) {
val learnMoreText = text
text = learnMoreText.toSpannable().apply {
setSpan(UnderlineSpan(), 0, learnMoreText.length, 0)
}
addUnderline()
movementMethod = LinkMovementMethod.getInstance()
setOnClickListener { interactor.onLearnMore() }

View File

@ -18,29 +18,21 @@ import org.mozilla.fenix.utils.Settings
class SitePermissionsWifiIntegration(
private val settings: Settings,
private val wifiConnectionMonitor: WifiConnectionMonitor
) : LifecycleAwareFeature {
) : LifecycleAwareFeature, WifiConnectionMonitor.Observer {
/**
* Adds listener for autoplay setting [AUTOPLAY_ALLOW_ON_WIFI]. Sets all autoplay to allowed when
* WIFI is connected, blocked otherwise.
*/
private val wifiConnectedListener: ((Boolean) -> Unit) by lazy {
{ connected: Boolean ->
val setting =
if (connected) SitePermissionsRules.Action.ALLOWED else SitePermissionsRules.Action.BLOCKED
if (settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) == AUTOPLAY_ALLOW_ON_WIFI) {
settings.setSitePermissionsPhoneFeatureAction(
PhoneFeature.AUTOPLAY_AUDIBLE,
setting
)
settings.setSitePermissionsPhoneFeatureAction(
PhoneFeature.AUTOPLAY_INAUDIBLE,
setting
)
} else {
// The autoplay setting has changed, we can remove the listener
removeWifiConnectedListener()
}
override fun onWifiConnectionChanged(connected: Boolean) {
val setting =
if (connected) SitePermissionsRules.Action.ALLOWED else SitePermissionsRules.Action.BLOCKED
if (settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) == AUTOPLAY_ALLOW_ON_WIFI) {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_AUDIBLE, setting)
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_INAUDIBLE, setting)
} else {
// The autoplay setting has changed, we can remove the listener
removeWifiConnectedListener()
}
}
@ -55,11 +47,11 @@ class SitePermissionsWifiIntegration(
}
fun addWifiConnectedListener() {
wifiConnectionMonitor.addOnWifiConnectedChangedListener(wifiConnectedListener)
wifiConnectionMonitor.register(this)
}
fun removeWifiConnectedListener() {
wifiConnectionMonitor.removeOnWifiConnectedChangedListener(wifiConnectedListener)
wifiConnectionMonitor.unregister(this)
}
// Until https://bugzilla.mozilla.org/show_bug.cgi?id=1621825 is fixed, AUTOPLAY_ALLOW_ALL

View File

@ -5,11 +5,12 @@
package org.mozilla.fenix.wifi
import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
/**
* Attaches itself to the [Application] and listens for WIFI available/not available events. This
@ -25,30 +26,28 @@ import android.net.NetworkRequest
* app.components.wifiConnectionListener.start()
* ```
*/
class WifiConnectionMonitor(app: Application) {
private val callbacks = mutableSetOf<(Boolean) -> Unit>()
private val connectivityManager = app.getSystemService(Context.CONNECTIVITY_SERVICE) as
ConnectivityManager
class WifiConnectionMonitor(
private val connectivityManager: ConnectivityManager
) : Observable<WifiConnectionMonitor.Observer> by ObserverRegistry() {
private var lastKnownStateWasAvailable: Boolean? = null
private var callbackReceived: Boolean = false
private var isRegistered = false
private val frameworkListener = object : ConnectivityManager.NetworkCallback() {
override fun onLost(network: Network?) {
callbacks.forEach { it(false) }
lastKnownStateWasAvailable = false
notifyAtLeastOneObserver { onWifiConnectionChanged(connected = false) }
callbackReceived = true
}
override fun onAvailable(network: Network?) {
callbacks.forEach { it(true) }
lastKnownStateWasAvailable = true
notifyAtLeastOneObserver { onWifiConnectionChanged(connected = true) }
callbackReceived = true
}
}
/**
* Attaches the [WifiConnectionMonitor] to the application. After this has been called, callbacks
* added via [addOnWifiConnectedChangedListener] will be called until either the app exits, or
* [stop] is called.
* added via [register] will be called until either the app exits, or [stop] is called.
*
* Any existing callbacks will be called with the current state when this is called.
*/
@ -62,10 +61,8 @@ class WifiConnectionMonitor(app: Application) {
// AFAICT, the framework does not send an event when a new NetworkCallback is registered
// while the WIFI is not connected, so we push this manually. If the WIFI is on, it will send
// a follow up event shortly
val noCallbacksReceivedYet = lastKnownStateWasAvailable == null
if (noCallbacksReceivedYet) {
lastKnownStateWasAvailable = false
callbacks.forEach { it(false) }
if (!callbackReceived) {
notifyAtLeastOneObserver { onWifiConnectionChanged(connected = false) }
}
connectivityManager.registerNetworkCallback(request, frameworkListener)
@ -74,7 +71,7 @@ class WifiConnectionMonitor(app: Application) {
/**
* Detatches the [WifiConnectionMonitor] from the app. No callbacks added via
* [addOnWifiConnectedChangedListener] will be called after this has been called.
* [register] will be called after this has been called.
*/
fun stop() {
// Framework code will throw if an unregistered listener attempts to unregister.
@ -83,25 +80,7 @@ class WifiConnectionMonitor(app: Application) {
isRegistered = false
}
/**
* Adds [onWifiChanged] to a list of listeners that will be called whenever WIFI connects or
* disconnects.
*
* If [onWifiChanged] is successfully added (i.e., it is a new listener), it will be immediately
* called with the last known state.
*/
fun addOnWifiConnectedChangedListener(onWifiChanged: (Boolean) -> Unit) {
val lastKnownState = lastKnownStateWasAvailable
if (callbacks.add(onWifiChanged) && lastKnownState != null) {
onWifiChanged(lastKnownState)
}
}
/**
* Removes [onWifiChanged] from the list of listeners to be called whenever WIFI connects or
* disconnects.
*/
fun removeOnWifiConnectedChangedListener(onWifiChanged: (Boolean) -> Unit) {
callbacks.remove(onWifiChanged)
interface Observer {
fun onWifiConnectionChanged(connected: Boolean)
}
}

View File

@ -17,8 +17,8 @@
android:fontFamily="@font/metropolis_bold"
android:layout_width="wrap_content"
android:layout_height="@dimen/tracking_protection_item_height"
android:layout_marginStart="72dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="@dimen/top_bar_alignment_margin_start"
android:layout_marginEnd="@dimen/tracking_protection_item_margin_end"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/enhanced_tracking_protection_blocked" />

View File

@ -10,9 +10,9 @@
android:baselineAligned="false"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingStart="@dimen/radio_button_preference_horizontal"
android:layout_marginStart="@dimen/top_bar_alignment_margin_start"
android:layout_marginEnd="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/preference_seek_bar_padding"
android:paddingEnd="@dimen/radio_button_preference_horizontal"
android:paddingBottom="@dimen/preference_seek_bar_padding">
<TextView
@ -20,6 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textAlignment="viewStart"
app:layout_constraintTop_toTopOf="parent"
tools:text="Font size" />
@ -27,11 +28,10 @@
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintTop_toBottomOf="@android:id/title"
tools:layout_editor_absoluteX="16dp"
tools:text="Make text on websites larger or smaller" />
<SeekBar
@ -76,6 +76,7 @@
android:layout_height="match_parent"
android:layout_marginTop="33dp"
android:padding="16dp"
android:textAlignment="viewStart"
android:text="@string/accessibility_text_size_sample_text_1"
android:textColor="@color/text_scale_example_text_color"
android:textSize="16sp"

View File

@ -6,7 +6,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp">
android:layout_height="@dimen/locale_item_height">
<ImageView
android:id="@+id/locale_selected_icon"

View File

@ -14,10 +14,10 @@
<FrameLayout
android:id="@+id/favicon_wrapper"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="4dp"
android:layout_marginStart="16dp"
android:layout_width="@dimen/history_favicon_width_height"
android:layout_height="@dimen/history_favicon_width_height"
android:padding="@dimen/saved_logins_item_padding"
android:layout_marginStart="@dimen/saved_logins_item_margin_start"
android:background="@drawable/top_sites_background"
android:layout_gravity="center"
android:importantForAccessibility="noHideDescendants"
@ -26,8 +26,8 @@
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="@+id/favicon_image"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_width="@dimen/preference_icon_drawable_size"
android:layout_height="@dimen/preference_icon_drawable_size"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
@ -38,8 +38,8 @@
android:id="@+id/webAddressView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="48dp"
android:layout_marginStart="@dimen/saved_logins_item_margin_start"
android:layout_marginEnd="@dimen/saved_logins_item_margin_end"
android:ellipsize="middle"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
@ -55,8 +55,8 @@
android:id="@+id/usernameView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="48dp"
android:layout_marginStart="@dimen/saved_logins_item_margin_start"
android:layout_marginEnd="@dimen/saved_logins_item_margin_end"
android:ellipsize="middle"
android:singleLine="true"
android:textColor="?secondaryText"

View File

@ -8,7 +8,7 @@
style="?android:attr/listViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="72dp"
android:layout_marginStart="@dimen/top_bar_alignment_margin_start"
android:layout_marginTop="16dp"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"

View File

@ -29,7 +29,7 @@
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_marginStart="8dp"
android:layout_marginStart="@dimen/radiobutton_preference_margin_start"
android:layout_height="wrap_content"
android:gravity="center|start"
android:layout_marginEnd="@dimen/radio_button_preference_horizontal"

View File

@ -11,8 +11,8 @@
<ImageView
android:id="@+id/trackingProtectionCategoryIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_width="@dimen/preference_icon_drawable_size"
android:layout_height="@dimen/preference_icon_drawable_size"
android:layout_marginStart="@dimen/library_item_icon_margin_horizontal"
android:layout_marginTop="@dimen/library_item_icon_margin_vertical"
android:layout_marginEnd="@dimen/library_item_icon_margin_horizontal"
@ -27,8 +27,8 @@
android:id="@+id/trackingProtectionCategoryTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="12dp"
android:layout_marginStart="@dimen/radio_button_drawable_padding"
android:layout_marginTop="@dimen/about_list_margin_top"
android:layout_marginEnd="@dimen/library_item_icon_margin_horizontal"
android:clickable="false"
android:textAppearance="@style/ListItemTextStyle"
@ -44,9 +44,9 @@
android:id="@+id/trackingProtectionCategoryItemDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginStart="@dimen/radio_button_drawable_padding"
android:layout_marginEnd="@dimen/library_item_icon_margin_horizontal"
android:layout_marginBottom="18dp"
android:layout_marginBottom="@dimen/tracking_protection_item_margin_bottom"
android:clickable="false"
android:textColor="?attr/secondaryText"
app:layout_constraintBottom_toBottomOf="parent"

View File

@ -99,6 +99,9 @@
<action
android:id="@+id/action_global_tabTrayDialogFragment"
app:destination="@id/tabTrayDialogFragment" />
<action
android:id="@+id/action_global_savedLoginsAuthFragment"
app:destination="@id/savedLoginsAuthFragment" />
<dialog
android:id="@+id/tabTrayDialogFragment"

View File

@ -36,7 +36,7 @@
<dimen name="synced_tab_item_min_height">56dp</dimen>
<dimen name="component_collection_creation_list_margin">16dp</dimen>
<dimen name="exceptions_description_margin">12dp</dimen>
<dimen name="preference_seek_bar_padding">24dp</dimen>
<dimen name="preference_seek_bar_padding">16dp</dimen>
<dimen name="custom_checkbox_alignment_margin">68dp</dimen>
<dimen name="context_menu_height">48dp</dimen>
@ -50,10 +50,14 @@
<!--Preferences-->
<dimen name="checkbox_preference_padding_vertical">12dp</dimen>
<dimen name="radiobutton_preference_margin_start">8dp</dimen>
<dimen name="top_bar_alignment_margin_start">72dp</dimen>
<!--Quick Settings-->
<dimen name="quicksettings_item_height">28dp</dimen>
<dimen name="tracking_protection_item_height">48dp</dimen>
<dimen name="tracking_protection_item_margin_end">16dp</dimen>
<dimen name="tracking_protection_item_margin_bottom">18dp</dimen>
<dimen name="design_quick_action_sheet_peek_height_min">64dp</dimen>
@ -115,6 +119,7 @@
<dimen name="locale_item_text_margin_gone_start">72dp</dimen>
<dimen name="locale_item_title_size">16sp</dimen>
<dimen name="locale_item_subtitle_size">12sp</dimen>
<dimen name="locale_item_height">48dp</dimen>
<!--Migration Activity-->
<dimen name="migration_margin_horizontal_large">20dp</dimen>
@ -156,6 +161,9 @@
<dimen name="saved_logins_sort_menu_dropdown_chevron_icon_size">12dp</dimen>
<dimen name="saved_logins_detail_menu_vertical_padding">5dp</dimen>
<dimen name="saved_logins_end_icon_drawable_padding">16dp</dimen>
<dimen name="saved_logins_item_padding">4dp</dimen>
<dimen name="saved_logins_item_margin_start">16dp</dimen>
<dimen name="saved_logins_item_margin_end">48dp</dimen>
<!-- a11y -->
<dimen name="accessibility_min_height">48dp</dimen>

View File

@ -8,8 +8,7 @@
android:defaultValue="true"
android:key="@string/pref_key_accessibility_auto_size"
android:summary="@string/preference_accessibility_auto_size_summary"
android:title="@string/preference_accessibility_auto_size_2"
app:iconSpaceReserved="false" />
android:title="@string/preference_accessibility_auto_size_2" />
<!-- Custom Preference that scales from 50-200% by steps of 5 represented by 0-30 in steps of 1-->
<org.mozilla.fenix.settings.TextPercentageSeekBarPreference
android:defaultValue="10"
@ -29,6 +28,5 @@
android:key="@string/pref_key_accessibility_force_enable_zoom"
android:summary="@string/preference_accessibility_force_enable_zoom_summary"
android:title="@string/preference_accessibility_force_enable_zoom"
app:allowDividerAbove="true"
app:iconSpaceReserved="false" />
app:allowDividerAbove="true" />
</PreferenceScreen>

View File

@ -8,6 +8,7 @@ import android.content.Context
import kotlinx.coroutines.runBlocking
import mozilla.components.support.migration.FennecMigrator
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.migration.MigrationTelemetryListener
/**
* An application class which knows how to migrate Fennec data.

View File

@ -4,6 +4,7 @@
package org.mozilla.fenix
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import mozilla.components.concept.push.PushProcessor
import mozilla.components.lib.state.ext.flowScoped
@ -18,6 +19,8 @@ class MigrationPushRenewer(
private val service: PushProcessor?,
private val store: MigrationStore
) {
@OptIn(ExperimentalCoroutinesApi::class)
fun start() {
// Observe for migration completed.
store.flowScoped { flow ->

View File

@ -345,8 +345,7 @@ private fun EngineSession.TrackingProtectionPolicy.assertPolicyEquals(
checkPrivacy: Boolean
) {
assertEquals(this.cookiePolicy, actual.cookiePolicy)
// TODO Uncomment this assertion after the fix in AC#6079 lands
// assertEquals(this.strictSocialTrackingProtection, actual.strictSocialTrackingProtection)
assertEquals(this.strictSocialTrackingProtection, actual.strictSocialTrackingProtection)
// E.g., atm, RECOMMENDED == AD + ANALYTICS + SOCIAL + TEST + MOZILLA_SOCIAL + CRYPTOMINING.
// If all of these are set manually, the equality check should not fail
if (this.trackingCategories.toInt() != actual.trackingCategories.toInt()) {

View File

@ -69,7 +69,7 @@ import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.Tab
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
@ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(FenixRobolectricTestRunner::class)
class DefaultBrowserToolbarControllerTest {

View File

@ -9,18 +9,16 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.Engine
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.TabsUseCases
import org.junit.After
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
@ -35,10 +33,12 @@ import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController
import org.mozilla.fenix.settings.SupportUtils
import mozilla.components.feature.tab.collections.Tab as ComponentTab
@ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
class DefaultSessionControlControllerTest {
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
@get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private val activity: HomeActivity = mockk(relaxed = true)
private val fragmentStore: HomeFragmentStore = mockk(relaxed = true)
private val navController: NavController = mockk(relaxed = true)
@ -62,7 +62,6 @@ class DefaultSessionControlControllerTest {
@Before
fun setup() {
Dispatchers.setMain(mainThreadSurrogate)
mockkStatic("org.mozilla.fenix.ext.ContextKt")
every { activity.components.core.engine } returns engine
every { activity.components.core.sessionManager } returns sessionManager
@ -91,12 +90,6 @@ class DefaultSessionControlControllerTest {
)
}
@After
fun tearDown() {
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
@Test
fun handleCollectionAddTabTapped() {
val collection: TabCollection = mockk(relaxed = true)

View File

@ -8,17 +8,22 @@ import android.content.Intent
import androidx.core.net.toUri
import androidx.navigation.NavController
import io.mockk.Called
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import mozilla.appservices.places.BookmarkRoot
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.BrowserDirection
import org.mozilla.fenix.BuildConfig.DEEP_LINK_SCHEME
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
@ -48,7 +53,7 @@ class DeepLinkIntentProcessorTest {
@Test
fun `return true if scheme is fenix`() {
assertTrue(processor.process(testIntent("fenix://test"), navController, out))
assertTrue(processor.process(testIntent("test"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
@ -66,16 +71,40 @@ class DeepLinkIntentProcessorTest {
@Test
fun `process home deep link`() {
assertTrue(processor.process(testIntent("fenix://home"), navController, out))
assertTrue(processor.process(testIntent("home"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalHome()) }
verify { out wasNot Called }
}
@Test
fun `process urls_bookmarks deep link`() {
assertTrue(processor.process(testIntent("urls_bookmarks"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalBookmarkFragment(BookmarkRoot.Root.id)) }
verify { out wasNot Called }
}
@Test
fun `process urls_history deep link`() {
assertTrue(processor.process(testIntent("urls_history"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalHistoryFragment()) }
verify { out wasNot Called }
}
@Test
fun `process home_collections deep link`() {
assertTrue(processor.process(testIntent("home_collections"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalHome()) }
verify { out wasNot Called }
}
@Test
fun `process settings deep link`() {
assertTrue(processor.process(testIntent("fenix://settings"), navController, out))
assertTrue(processor.process(testIntent("settings"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalSettingsFragment()) }
@ -84,7 +113,7 @@ class DeepLinkIntentProcessorTest {
@Test
fun `process turn_on_sync deep link`() {
assertTrue(processor.process(testIntent("fenix://turn_on_sync"), navController, out))
assertTrue(processor.process(testIntent("turn_on_sync"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalTurnOnSync()) }
@ -93,7 +122,7 @@ class DeepLinkIntentProcessorTest {
@Test
fun `process settings_search_engine deep link`() {
assertTrue(processor.process(testIntent("fenix://settings_search_engine"), navController, out))
assertTrue(processor.process(testIntent("settings_search_engine"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalSearchEngineFragment()) }
@ -102,7 +131,7 @@ class DeepLinkIntentProcessorTest {
@Test
fun `process settings_accessibility deep link`() {
assertTrue(processor.process(testIntent("fenix://settings_accessibility"), navController, out))
assertTrue(processor.process(testIntent("settings_accessibility"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalAccessibilityFragment()) }
@ -111,16 +140,48 @@ class DeepLinkIntentProcessorTest {
@Test
fun `process settings_delete_browsing_data deep link`() {
assertTrue(processor.process(testIntent("fenix://settings_delete_browsing_data"), navController, out))
assertTrue(processor.process(testIntent("settings_delete_browsing_data"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalDeleteBrowsingDataFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_addon_manager deep link`() {
assertTrue(processor.process(testIntent("settings_addon_manager"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalSettingsAddonsManagementFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_logins deep link`() {
assertTrue(processor.process(testIntent("settings_logins"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalSavedLoginsAuthFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_tracking_protection deep link`() {
assertTrue(processor.process(testIntent("settings_tracking_protection"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalTrackingProtectionFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_privacy deep link`() {
assertTrue(processor.process(testIntent("settings_privacy"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalSettingsFragment()) }
verify { out wasNot Called }
}
@Test
fun `process enable_private_browsing deep link`() {
assertTrue(processor.process(testIntent("fenix://enable_private_browsing"), navController, out))
assertTrue(processor.process(testIntent("enable_private_browsing"), navController, out))
verify { activity.browsingModeManager.mode = BrowsingMode.Private }
verify { navController.navigate(NavGraphDirections.actionGlobalHome()) }
@ -129,13 +190,13 @@ class DeepLinkIntentProcessorTest {
@Test
fun `process open deep link`() {
assertTrue(processor.process(testIntent("fenix://open"), navController, out))
assertTrue(processor.process(testIntent("open"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
assertTrue(processor.process(testIntent("fenix://open?url=test"), navController, out))
assertTrue(processor.process(testIntent("open?url=test"), navController, out))
verify {
activity.openToBrowserAndLoad(
@ -150,19 +211,31 @@ class DeepLinkIntentProcessorTest {
@Test
fun `process make_default_browser deep link`() {
assertTrue(processor.process(testIntent("fenix://make_default_browser"), navController, out))
assertTrue(processor.process(testIntent("make_default_browser"), navController, out))
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
fun `process settings_addon_manager deep link`() {
assertTrue(processor.process(testIntent("fenix://settings_addon_manager"), navController, out))
fun `process settings_notifications deep link`() {
assertTrue(processor.process(testIntent("settings_notifications"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalSettingsAddonsManagementFragment()) }
verify { navController wasNot Called }
verify { out wasNot Called }
verify { activity.startActivity(any()) }
}
private fun testIntent(uri: String) = Intent("", uri.toUri())
@Test
fun `process install_search_widget deep link`() {
mockkObject(SearchWidgetCreator)
every { SearchWidgetCreator.createSearchWidget(any()) } returns true
assertTrue(processor.process(testIntent("install_search_widget"), navController, out))
verify { navController wasNot Called }
verify { out wasNot Called }
verify { activity wasNot Called }
}
private fun testIntent(uri: String) = Intent("", "$DEEP_LINK_SCHEME://$uri".toUri())
}

View File

@ -0,0 +1,39 @@
/* 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.sessioncontrol.viewholders
import android.view.LayoutInflater
import android.view.View
import io.mockk.mockk
import io.mockk.verify
import kotlinx.android.synthetic.main.private_browsing_description.view.*
import mozilla.components.support.test.robolectric.testContext
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.sessioncontrol.TabSessionInteractor
@RunWith(FenixRobolectricTestRunner::class)
class PrivateBrowsingDescriptionViewHolderTest {
private lateinit var view: View
private lateinit var interactor: TabSessionInteractor
@Before
fun setup() {
view = LayoutInflater.from(testContext)
.inflate(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID, null)
interactor = mockk(relaxed = true)
}
@Test
fun `call interactor on click`() {
PrivateBrowsingDescriptionViewHolder(view, interactor)
view.private_session_common_myths.performClick()
verify { interactor.onPrivateBrowsingLearnMoreClicked() }
}
}

View File

@ -4,18 +4,13 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import io.mockk.every
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.android.synthetic.main.onboarding_private_browsing.view.*
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@ -31,19 +26,10 @@ class OnboardingPrivacyBrowsingViewHolderTest {
@Before
fun setup() {
mockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
view = LayoutInflater.from(testContext)
val context = ContextThemeWrapper(testContext, R.style.NormalTheme)
view = LayoutInflater.from(context)
.inflate(OnboardingPrivateBrowsingViewHolder.LAYOUT_ID, null)
interactor = mockk(relaxed = true)
every {
any<Resources.Theme>().resolveAttribute(R.attr.onboardingSelected)
} returns R.color.onboarding_illustration_selected_normal_theme
}
@After
fun teardown() {
unmockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
}
@Test

View File

@ -4,18 +4,13 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import io.mockk.every
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.android.synthetic.main.onboarding_privacy_notice.view.*
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@ -31,19 +26,10 @@ class OnboardingPrivacyNoticeViewHolderTest {
@Before
fun setup() {
mockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
view = LayoutInflater.from(testContext)
val context = ContextThemeWrapper(testContext, R.style.NormalTheme)
view = LayoutInflater.from(context)
.inflate(OnboardingPrivacyNoticeViewHolder.LAYOUT_ID, null)
interactor = mockk(relaxed = true)
every {
any<Resources.Theme>().resolveAttribute(R.attr.onboardingSelected)
} returns R.color.onboarding_illustration_selected_normal_theme
}
@After
fun teardown() {
unmockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
}
@Test

View File

@ -4,16 +4,11 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import androidx.appcompat.view.ContextThemeWrapper
import kotlinx.android.synthetic.main.onboarding_tracking_protection.view.*
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@ -28,18 +23,9 @@ class OnboardingTrackingProtectionViewHolderTest {
@Before
fun setup() {
mockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
view = LayoutInflater.from(testContext)
val context = ContextThemeWrapper(testContext, R.style.NormalTheme)
view = LayoutInflater.from(context)
.inflate(OnboardingTrackingProtectionViewHolder.LAYOUT_ID, null)
every {
any<Resources.Theme>().resolveAttribute(R.attr.onboardingSelected)
} returns R.color.onboarding_illustration_selected_normal_theme
}
@After
fun teardown() {
unmockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
}
@Test

View File

@ -0,0 +1,74 @@
/* 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.sessioncontrol.viewholders.onboarding
import android.content.res.Resources
import android.text.Spanned
import android.view.LayoutInflater
import android.view.View
import androidx.core.text.HtmlCompat
import androidx.core.text.HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.android.synthetic.main.onboarding_whats_new.view.*
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.sessioncontrol.OnboardingInteractor
@RunWith(FenixRobolectricTestRunner::class)
class OnboardingWhatsNewViewHolderTest {
private lateinit var view: View
private lateinit var interactor: OnboardingInteractor
@Before
fun setup() {
mockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
view = LayoutInflater.from(testContext)
.inflate(OnboardingWhatsNewViewHolder.LAYOUT_ID, null)
interactor = mockk(relaxed = true)
every {
any<Resources.Theme>().resolveAttribute(R.attr.onboardingSelected)
} returns R.color.onboarding_illustration_selected_normal_theme
}
@After
fun teardown() {
unmockkStatic("mozilla.components.support.ktx.android.content.res.ThemeKt")
}
@Test
fun `sets and styles strings`() {
OnboardingWhatsNewViewHolder(view, interactor)
assertEquals(
"Have questions about the redesigned Firefox Preview? Want to know whats changed?",
view.description_text.text
)
val getAnswersHtml = HtmlCompat.toHtml(view.get_answers.text as Spanned, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
assertTrue(getAnswersHtml, "<u>Get answers here</u>" in getAnswersHtml)
}
@Test
fun `call interactor on click`() {
OnboardingWhatsNewViewHolder(view, interactor)
view.get_answers.performClick()
verify { interactor.onWhatsNewGetAnswersClicked() }
}
}

View File

@ -25,6 +25,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.ext.directionsEq
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
// Robolectric needed for `onShareItem()`
@ -189,15 +190,12 @@ class HistoryControllerTest {
fun onShareItem() {
controller.handleShare(historyItem)
// `verify` checks for referential equality.
// This would fail as the NavDirections are created and used in place in the tested method.
// Capture the NavDirections and `assert` for structural equality after.
verify {
navController.navigate(
navController.navigate(directionsEq(
HistoryFragmentDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = historyItem.url, title = historyItem.title))
)
)
))
}
}

View File

@ -0,0 +1,83 @@
/* 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.perf
import android.view.View
import android.view.ViewTreeObserver
import android.widget.LinearLayout
import io.mockk.Called
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.android.synthetic.main.top_site_item.view.*
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.TopSiteItemViewHolder
import org.mozilla.fenix.perf.StartupTimelineStateMachine.StartupDestination
import org.mozilla.fenix.perf.StartupTimelineStateMachine.StartupState
class StartupReportFullyDrawnTest {
@MockK private lateinit var activity: HomeActivity
private lateinit var holder: TopSiteItemViewHolder
@MockK(relaxed = true) private lateinit var rootContainer: LinearLayout
@MockK(relaxed = true) private lateinit var holderItemView: View
@MockK(relaxed = true) private lateinit var viewTreeObserver: ViewTreeObserver
private lateinit var fullyDrawn: StartupReportFullyDrawn
@Before
fun setup() {
MockKAnnotations.init(this)
every { activity.findViewById<LinearLayout>(R.id.rootContainer) } returns rootContainer
every { holderItemView.context } returns activity
every { holderItemView.top_site_item } returns mockk(relaxed = true)
holder = TopSiteItemViewHolder(holderItemView, mockk())
every { rootContainer.viewTreeObserver } returns viewTreeObserver
every { holderItemView.viewTreeObserver } returns viewTreeObserver
fullyDrawn = StartupReportFullyDrawn()
}
@Test
fun testOnActivityCreateEndHome() {
// Only APP_LINK destination
fullyDrawn.onActivityCreateEndHome(StartupState.Cold(StartupDestination.UNKNOWN), activity)
fullyDrawn.onActivityCreateEndHome(StartupState.Cold(StartupDestination.HOMESCREEN), activity)
verify { activity wasNot Called }
// Only run once
fullyDrawn.onActivityCreateEndHome(StartupState.Cold(StartupDestination.APP_LINK), activity)
verify(exactly = 1) { activity.findViewById<LinearLayout>(R.id.rootContainer) }
fullyDrawn.onActivityCreateEndHome(StartupState.Cold(StartupDestination.APP_LINK), activity)
verify(exactly = 1) { activity.findViewById<LinearLayout>(R.id.rootContainer) }
every { activity.reportFullyDrawn() } just Runs
triggerPreDraw()
verify { activity.reportFullyDrawn() }
}
@Test
fun testOnTopSitesItemBound() {
fullyDrawn.onTopSitesItemBound(StartupState.Cold(StartupDestination.HOMESCREEN), holder)
every { activity.reportFullyDrawn() } just Runs
triggerPreDraw()
verify { activity.reportFullyDrawn() }
}
private fun triggerPreDraw() {
val listener = slot<ViewTreeObserver.OnPreDrawListener>()
verify { viewTreeObserver.addOnPreDrawListener(capture(listener)) }
listener.captured.onPreDraw()
}
}

View File

@ -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.push
import android.app.Notification
import androidx.core.app.NotificationCompat
import io.mockk.Called
import io.mockk.mockk
import io.mockk.verify
import org.junit.Test
import org.mozilla.fenix.R
class LeanplumNotificationCustomizerTest {
private val customizer = LeanplumNotificationCustomizer()
@Test
fun `customize adds icon`() {
val builder = mockk<NotificationCompat.Builder>(relaxed = true)
customizer.customize(builder, mockk())
verify { builder.setSmallIcon(R.drawable.ic_status_logo) }
}
@Test
fun `customize for BigPictureStyle does nothing`() {
val builder = mockk<Notification.Builder>()
customizer.customize(builder, mockk(), mockk())
verify { builder wasNot Called }
}
}

View File

@ -4,6 +4,8 @@
package org.mozilla.fenix.search.toolbar
import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
@ -21,6 +23,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
@ -28,12 +31,14 @@ class ToolbarViewTest {
@MockK(relaxed = true) private lateinit var interactor: ToolbarInteractor
@MockK private lateinit var engine: Engine
private lateinit var context: Context
private lateinit var toolbar: BrowserToolbar
@Before
fun setup() {
MockKAnnotations.init(this)
toolbar = spyk(BrowserToolbar(testContext))
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
toolbar = spyk(BrowserToolbar(context))
}
@Test
@ -68,7 +73,7 @@ class ToolbarViewTest {
}
private fun buildToolbarView(isPrivate: Boolean) = ToolbarView(
testContext,
context,
interactor,
historyStorage = null,
isPrivate = isPrivate,

View File

@ -0,0 +1,59 @@
/* 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.settings.advanced
import android.content.Context
import android.view.View
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import mozilla.components.support.locale.LocaleManager
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.util.Locale
class BaseLocaleViewHolderTest {
private val selectedLocale = Locale("en", "UK")
private val view: View = mockk()
private val context: Context = mockk()
private val localeViewHolder = object : BaseLocaleViewHolder(view, selectedLocale) {
override fun bind(locale: Locale) = Unit
}
@Before
fun setup() {
mockkObject(LocaleManager)
every { view.context } returns context
}
@Test
fun `verify other locale checker returns false`() {
every { LocaleManager.getCurrentLocale(context) } returns mockk()
val otherLocale = mockk<Locale>()
assertFalse(localeViewHolder.isCurrentLocaleSelected(otherLocale, isDefault = true))
assertFalse(localeViewHolder.isCurrentLocaleSelected(otherLocale, isDefault = false))
}
@Test
fun `verify selected locale checker returns true`() {
every { LocaleManager.getCurrentLocale(context) } returns mockk()
assertFalse(localeViewHolder.isCurrentLocaleSelected(selectedLocale, isDefault = true))
assertTrue(localeViewHolder.isCurrentLocaleSelected(selectedLocale, isDefault = false))
}
@Test
fun `verify default locale checker returns true`() {
every { LocaleManager.getCurrentLocale(context) } returns null
assertTrue(localeViewHolder.isCurrentLocaleSelected(selectedLocale, isDefault = true))
assertFalse(localeViewHolder.isCurrentLocaleSelected(selectedLocale, isDefault = false))
}
}

View File

@ -1,48 +0,0 @@
package org.mozilla.fenix.settings.advanced
import android.content.Context
import android.view.View
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import mozilla.components.support.locale.LocaleManager
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.util.Locale
class LocaleAdapterTest {
private val selectedLocale = Locale("en", "UK")
private val view: View = mockk(relaxed = true)
private val context: Context = mockk(relaxed = true)
private val localeViewHolder: BaseLocaleViewHolder =
object : BaseLocaleViewHolder(view, selectedLocale) {
override fun bind(locale: Locale) {
// not required
}
}
@Before
fun setup() {
every { view.context } returns context
}
@Test
fun `verify selected locale checker returns true`() {
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
every { LocaleManager.isDefaultLocaleSelected(context) } returns false
assertTrue(localeViewHolder.isCurrentLocaleSelected(selectedLocale, isDefault = false))
}
@Test
fun `verify default locale checker returns true`() {
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
every { LocaleManager.isDefaultLocaleSelected(context) } returns true
assertTrue(localeViewHolder.isCurrentLocaleSelected(selectedLocale, isDefault = true))
}
}

View File

@ -8,7 +8,6 @@ import android.content.Context
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import mozilla.components.support.locale.LocaleManager
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@ -24,9 +23,12 @@ import java.util.Locale
@RunWith(FenixRobolectricTestRunner::class)
class LocaleManagerExtensionTest {
private lateinit var context: Context
@Before
fun setup() {
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
context = mockk()
mockkObject(LocaleManager)
}
@Test
@ -44,8 +46,6 @@ class LocaleManagerExtensionTest {
@Test
@Config(qualifiers = "en-rUS")
fun `default locale selected`() {
val context: Context = mockk()
mockkObject(LocaleManager)
every { LocaleManager.getCurrentLocale(context) } returns null
assertTrue(LocaleManager.isDefaultLocaleSelected(context))
@ -54,8 +54,6 @@ class LocaleManagerExtensionTest {
@Test
@Config(qualifiers = "en-rUS")
fun `custom locale selected`() {
val context: Context = mockk()
mockkObject(LocaleManager)
val selectedLocale = Locale("en", "UK")
every { LocaleManager.getCurrentLocale(context) } returns selectedLocale
@ -65,13 +63,9 @@ class LocaleManagerExtensionTest {
@Test
@Config(qualifiers = "en-rUS")
fun `match current stored locale string with a Locale from our list`() {
val context: Context = mockk()
mockkObject(LocaleManager)
val otherLocale = Locale("fr")
val selectedLocale = Locale("en", "UK")
val localeList = ArrayList<Locale>()
localeList.add(otherLocale)
localeList.add(selectedLocale)
val localeList = listOf(otherLocale, selectedLocale)
every { LocaleManager.getCurrentLocale(context) } returns selectedLocale
@ -81,13 +75,9 @@ class LocaleManagerExtensionTest {
@Test
@Config(qualifiers = "en-rUS")
fun `match null stored locale with the default Locale from our list`() {
val context: Context = mockk()
mockkObject(LocaleManager)
val firstLocale = Locale("fr")
val secondLocale = Locale("en", "UK")
val localeList = ArrayList<Locale>()
localeList.add(firstLocale)
localeList.add(secondLocale)
val localeList = listOf(firstLocale, secondLocale)
every { LocaleManager.getCurrentLocale(context) } returns null

View File

@ -5,14 +5,15 @@
package org.mozilla.fenix.settings.advanced
import android.app.Activity
import android.content.Context
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyAll
import mozilla.components.support.locale.LocaleManager
import org.junit.Before
import org.junit.Test
@ -20,118 +21,113 @@ import java.util.Locale
class LocaleSettingsControllerTest {
private val context: Context = mockk<Activity>(relaxed = true)
private val activity = mockk<Activity>(relaxed = true)
private val localeSettingsStore: LocaleSettingsStore = mockk(relaxed = true)
private val mockState = LocaleSettingsState(mockk(), mockk(), mockk())
private lateinit var controller: LocaleSettingsController
private lateinit var controller: DefaultLocaleSettingsController
@Before
fun setup() {
controller = DefaultLocaleSettingsController(context, localeSettingsStore)
controller = spyk(DefaultLocaleSettingsController(activity, localeSettingsStore))
mockkObject(LocaleManager)
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
}
@Test
fun `don't set locale if same locale is chosen`() {
val selectedLocale = Locale("en", "UK")
every { localeSettingsStore.state } returns LocaleSettingsState(
mockk(),
mockk(),
selectedLocale
)
mockkObject(LocaleManager)
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
every { LocaleManager.getCurrentLocale(context) } returns mockk()
every { LocaleManager.isDefaultLocaleSelected(context) } returns false
every { localeSettingsStore.state } returns mockState.copy(selectedLocale = selectedLocale)
every { LocaleManager.getCurrentLocale(activity) } returns mockk()
controller.handleLocaleSelected(selectedLocale)
verify(
inverse = true,
verifyBlock = { localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale)) })
verify(
inverse = true,
verifyBlock = { LocaleManager.setNewLocale(context, selectedLocale.toLanguageTag()) })
verify(
inverse = true,
verifyBlock = { LocaleManager.updateBaseConfiguration(context, selectedLocale) })
verify(inverse = true, verifyBlock = { (context as Activity).recreate() })
verifyAll(inverse = true) {
localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale))
LocaleManager.setNewLocale(activity, selectedLocale.toLanguageTag())
activity.recreate()
}
with(controller) {
verify(inverse = true) { LocaleManager.updateBaseConfiguration(activity, selectedLocale) }
}
}
@Test
fun `set a new locale from the list`() {
fun `set a new locale from the list if other locale is chosen`() {
val selectedLocale = Locale("en", "UK")
val otherLocale: Locale = mockk()
every { localeSettingsStore.state } returns LocaleSettingsState(
mockk(),
mockk(),
otherLocale
)
mockkObject(LocaleManager)
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
every { LocaleManager.updateBaseConfiguration(context, selectedLocale) } just Runs
every {
LocaleManager.setNewLocale(
context,
selectedLocale.toLanguageTag()
)
} returns context
every { localeSettingsStore.state } returns mockState.copy(selectedLocale = otherLocale)
every { LocaleManager.setNewLocale(activity, selectedLocale.toLanguageTag()) } returns activity
with(controller) {
every { LocaleManager.updateBaseConfiguration(activity, selectedLocale) } just Runs
}
controller.handleLocaleSelected(selectedLocale)
verify { localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale)) }
verify { LocaleManager.setNewLocale(context, selectedLocale.toLanguageTag()) }
verify { LocaleManager.updateBaseConfiguration(context, selectedLocale) }
verify { (context as Activity).recreate() }
verify { LocaleManager.setNewLocale(activity, selectedLocale.toLanguageTag()) }
verify { activity.recreate() }
with(controller) {
verify { LocaleManager.updateBaseConfiguration(activity, selectedLocale) }
}
}
@Test
fun `set a new locale from the list if default locale is not selected`() {
val selectedLocale = Locale("en", "UK")
every { localeSettingsStore.state } returns mockState.copy(selectedLocale = selectedLocale)
every { LocaleManager.getCurrentLocale(activity) } returns null
every { LocaleManager.setNewLocale(activity, selectedLocale.toLanguageTag()) } returns activity
with(controller) {
every { LocaleManager.updateBaseConfiguration(activity, selectedLocale) } just Runs
}
controller.handleLocaleSelected(selectedLocale)
verify { localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale)) }
verify { LocaleManager.setNewLocale(activity, selectedLocale.toLanguageTag()) }
verify { activity.recreate() }
with(controller) {
verify { LocaleManager.updateBaseConfiguration(activity, selectedLocale) }
}
}
@Test
fun `don't set default locale if default locale is already chosen`() {
val selectedLocale = Locale("en", "UK")
val localeList = ArrayList<Locale>()
localeList.add(selectedLocale)
every { localeSettingsStore.state } returns LocaleSettingsState(
localeList,
mockk(),
mockk()
)
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
every { LocaleManager.isDefaultLocaleSelected(context) } returns true
every { localeSettingsStore.state } returns mockState.copy(localeList = listOf(selectedLocale))
every { LocaleManager.getCurrentLocale(activity) } returns null
controller.handleDefaultLocaleSelected()
verify(
inverse = true,
verifyBlock = { localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale)) })
verify(
inverse = true,
verifyBlock = { LocaleManager.resetToSystemDefault(context) })
verify(
inverse = true,
verifyBlock = { LocaleManager.updateBaseConfiguration(context, selectedLocale) })
verify(inverse = true, verifyBlock = { (context as Activity).recreate() })
verifyAll(inverse = true) {
localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale))
LocaleManager.resetToSystemDefault(activity)
activity.recreate()
with(controller) {
LocaleManager.updateBaseConfiguration(activity, selectedLocale)
}
}
}
@Test
fun `set the default locale as the new locale`() {
val selectedLocale = Locale("en", "UK")
val localeList = ArrayList<Locale>()
localeList.add(selectedLocale)
every { localeSettingsStore.state } returns LocaleSettingsState(
localeList,
mockk(),
mockk()
)
mockkObject(LocaleManager)
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
every { LocaleManager.resetToSystemDefault(context) } just Runs
every { LocaleManager.updateBaseConfiguration(context, selectedLocale) } just Runs
every { localeSettingsStore.state } returns mockState.copy(localeList = listOf(selectedLocale))
every { LocaleManager.resetToSystemDefault(activity) } just Runs
with(controller) {
every { LocaleManager.updateBaseConfiguration(activity, selectedLocale) } just Runs
}
controller.handleDefaultLocaleSelected()
verify { localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale)) }
verify { LocaleManager.resetToSystemDefault(context) }
verify { LocaleManager.updateBaseConfiguration(context, selectedLocale) }
verify { (context as Activity).recreate() }
verify { LocaleManager.resetToSystemDefault(activity) }
verify { activity.recreate() }
with(controller) {
verify { LocaleManager.updateBaseConfiguration(activity, selectedLocale) }
}
}
@Test

View File

@ -18,10 +18,11 @@ class LocaleSettingsStoreTest {
@Before
fun setup() {
val localeList = ArrayList<Locale>()
localeList.add(Locale("fr")) // default
localeList.add(otherLocale)
localeList.add(selectedLocale)
val localeList = listOf(
Locale("fr"), // default
otherLocale,
selectedLocale
)
localeSettingsStore =
LocaleSettingsStore(LocaleSettingsState(localeList, localeList, selectedLocale))

View File

@ -0,0 +1,86 @@
/* 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.settings.advanced
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.isVisible
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import kotlinx.android.synthetic.main.locale_settings_item.view.*
import mozilla.components.support.locale.LocaleManager
import mozilla.components.support.test.robolectric.testContext
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.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import java.util.Locale
@RunWith(FenixRobolectricTestRunner::class)
class LocaleViewHoldersTest {
private val selectedLocale = Locale("en", "US")
private lateinit var view: View
private lateinit var interactor: LocaleSettingsViewInteractor
private lateinit var localeViewHolder: LocaleViewHolder
private lateinit var systemLocaleViewHolder: SystemLocaleViewHolder
@Before
fun setup() {
mockkObject(LocaleManager)
every { LocaleManager.getCurrentLocale(any()) } returns null
view = LayoutInflater.from(testContext)
.inflate(R.layout.locale_settings_item, null)
interactor = mockk()
localeViewHolder = LocaleViewHolder(view, selectedLocale, interactor)
systemLocaleViewHolder = SystemLocaleViewHolder(view, selectedLocale, interactor)
}
@Test
fun `bind LocaleViewHolder`() {
localeViewHolder.bind(selectedLocale)
assertEquals("English (United States)", view.locale_title_text.text)
assertEquals("English (United States)", view.locale_subtitle_text.text)
assertFalse(view.locale_selected_icon.isVisible)
}
@Test
fun `LocaleViewHolder calls interactor on click`() {
localeViewHolder.bind(selectedLocale)
every { interactor.onLocaleSelected(selectedLocale) } just Runs
view.performClick()
verify { interactor.onLocaleSelected(selectedLocale) }
}
@Test
fun `bind SystemLocaleViewHolder`() {
systemLocaleViewHolder.bind(selectedLocale)
assertEquals("Follow device language", view.locale_title_text.text)
assertFalse(view.locale_subtitle_text.isVisible)
assertTrue(view.locale_selected_icon.isVisible)
}
@Test
fun `SystemLocaleViewHolder calls interactor on click`() {
systemLocaleViewHolder.bind(selectedLocale)
every { interactor.onDefaultLocaleSelected() } just Runs
view.performClick()
verify { interactor.onDefaultLocaleSelected() }
}
}

View File

@ -10,39 +10,30 @@ import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.setMain
import mozilla.components.concept.engine.Engine
import org.junit.After
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.components
@ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
class DefaultDeleteBrowsingDataControllerTest {
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
@get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private val context: Context = mockk(relaxed = true)
private lateinit var controller: DefaultDeleteBrowsingDataController
@Before
fun setup() {
Dispatchers.setMain(mainThreadSurrogate)
every { context.components.core.engine.clearData(any()) } just Runs
}
@After
fun tearDown() {
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
@Test
fun deleteTabs() = runBlockingTest {
controller = DefaultDeleteBrowsingDataController(context, coroutineContext)

View File

@ -6,24 +6,21 @@
package org.mozilla.fenix.settings.deletebrowsingdata
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.setMain
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.engine.Engine
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity
@ -31,13 +28,15 @@ import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(FenixRobolectricTestRunner::class)
class DeleteAndQuitTest {
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
@get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private var activity: HomeActivity = mockk(relaxed = true)
lateinit var settings: Settings
@ -54,8 +53,6 @@ class DeleteAndQuitTest {
clear()
}
Dispatchers.setMain(mainThreadSurrogate)
every { activity.components.core.historyStorage } returns historyStorage
every { activity.components.core.permissionStorage } returns permissionStorage
every { activity.components.useCases.tabsUseCases } returns tabUseCases
@ -63,12 +60,6 @@ class DeleteAndQuitTest {
every { activity.components.core.engine } returns engine
}
@After
fun tearDown() {
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
private fun Settings.clear() {
preferences.clearAndCommit()
}

View File

@ -10,6 +10,7 @@ import mozilla.components.support.test.ext.joinBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
class LoginsFragmentStoreTest {
@ -123,4 +124,19 @@ class LoginsFragmentStoreTest {
assertEquals("example", store.state.searchedForText)
assertEquals(listOf(exampleLogin), store.state.filteredItems)
}
@Test
fun `LoginSelected action`() {
val store = LoginsFragmentStore(baseState.copy(
isLoading = false,
loginList = listOf(mockk()),
filteredItems = listOf(mockk())
))
store.dispatch(LoginsAction.LoginSelected(mockk())).joinBlocking()
assertTrue(store.state.isLoading)
assertTrue(store.state.loginList.isEmpty())
assertTrue(store.state.filteredItems.isEmpty())
}
}

View File

@ -52,6 +52,6 @@ class LoginsListViewHolderTest {
holder.bind(baseLogin)
view.performClick()
verify { interactor.itemClicked(baseLogin) }
verify { interactor.onItemClicked(baseLogin) }
}
}

View File

@ -4,20 +4,30 @@
package org.mozilla.fenix.settings.logins
import androidx.navigation.NavController
import io.mockk.mockk
import io.mockk.verify
import io.mockk.verifyAll
import mozilla.components.support.test.robolectric.testContext
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class SavedLoginsControllerTest {
private val store: LoginsFragmentStore = mockk(relaxed = true)
private val navController: NavController = mockk(relaxed = true)
private val browserNavigator: (String, Boolean, BrowserDirection) -> Unit = mockk(relaxed = true)
private val settings: Settings = mockk(relaxed = true)
private val metrics: MetricController = mockk(relaxed = true)
private val sortingStrategy: SortingStrategy = SortingStrategy.Alphabetically(testContext)
private val controller = SavedLoginsController(store, settings)
private val controller = SavedLoginsController(store, navController, browserNavigator, settings, metrics)
@Test
fun `GIVEN a sorting strategy, WHEN handleSort is called on the controller, THEN the correct action should be dispatched and the strategy saved in sharedPref`() {
@ -34,4 +44,32 @@ class SavedLoginsControllerTest {
settings.savedLoginsSortingStrategy = sortingStrategy
}
}
@Test
fun `GIVEN a SavedLogin, WHEN handleItemClicked is called for it, THEN LoginsAction$LoginSelected should be emitted`() {
val login: SavedLogin = mockk(relaxed = true)
controller.handleItemClicked(login)
verifyAll {
store.dispatch(LoginsAction.LoginSelected(login))
metrics.track(Event.OpenOneLogin)
navController.navigate(
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToLoginDetailFragment(login.guid)
)
}
}
@Test
fun `GIVEN the learn more option, WHEN handleLearnMoreClicked is called for it, then we should open the right support webpage`() {
controller.handleLearnMoreClicked()
verify {
browserNavigator.invoke(
SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.SYNC_SETUP),
true,
BrowserDirection.FromSavedLoginsFragment
)
}
}
}

View File

@ -5,7 +5,7 @@
package org.mozilla.fenix.settings.logins
import io.mockk.mockk
import io.mockk.verify
import io.mockk.verifyAll
import mozilla.components.support.test.robolectric.testContext
import org.junit.Test
import org.junit.runner.RunWith
@ -15,32 +15,35 @@ import kotlin.random.Random
@RunWith(FenixRobolectricTestRunner::class)
class SavedLoginsInteractorTest {
private val controller: SavedLoginsController = mockk(relaxed = true)
private val savedLoginClicked: (SavedLogin) -> Unit = mockk(relaxed = true)
private val learnMore: () -> Unit = mockk(relaxed = true)
private val interactor = SavedLoginsInteractor(
controller,
savedLoginClicked,
learnMore
)
private val interactor = SavedLoginsInteractor(controller)
@Test
fun itemClicked() {
fun `GIVEN a SavedLogin being clicked, WHEN the interactor is called for it, THEN it should just delegate the controller`() {
val item = SavedLogin("mozilla.org", "username", "password", "id", Random.nextLong())
interactor.itemClicked(item)
interactor.onItemClicked(item)
verify {
savedLoginClicked.invoke(item)
verifyAll {
controller.handleItemClicked(item)
}
}
@Test
fun `GIVEN a sorting strategy, WHEN sort method is called on the interactor, THEN controller should call handleSort with the same parameter`() {
fun `GIVEN a change in sorting strategy, WHEN the interactor is called for it, THEN it should just delegate the controller`() {
val sortingStrategy: SortingStrategy = SortingStrategy.Alphabetically(testContext)
interactor.sort(sortingStrategy)
interactor.onSortingStrategyChanged(sortingStrategy)
verify {
verifyAll {
controller.handleSort(sortingStrategy)
}
}
@Test
fun `GIVEN the learn more option is clicked, WHEN the interactor is called for it, THEN it should just delegate the controller`() {
interactor.onLearnMoreClicked()
verifyAll {
controller.handleLearnMoreClicked()
}
}
}

View File

@ -0,0 +1,55 @@
/* 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.sync
import android.content.Context
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage
import mozilla.components.service.fxa.manager.FxaAccountManager
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.FenixApplication
class SyncedTabsIntegrationTest {
@MockK private lateinit var context: Context
@MockK private lateinit var syncedTabsStorage: SyncedTabsStorage
@MockK private lateinit var accountManager: FxaAccountManager
@Before
fun setup() {
MockKAnnotations.init(this)
every { syncedTabsStorage.stop() } just Runs
every { accountManager.register(any(), owner = any(), autoPause = true) } just Runs
every { context.applicationContext } returns mockk<FenixApplication> {
every { components } returns mockk {
every { backgroundServices.syncedTabsStorage } returns syncedTabsStorage
}
}
}
@Test
fun `starts and stops syncedTabsStorage on user authentication`() {
val observer = slot<AccountObserver>()
SyncedTabsIntegration(context, accountManager).launch()
verify { accountManager.register(capture(observer), owner = any(), autoPause = true) }
every { syncedTabsStorage.start() } just Runs
observer.captured.onAuthenticated(mockk(), mockk())
verify { syncedTabsStorage.start() }
every { syncedTabsStorage.stop() } just Runs
observer.captured.onLoggedOut()
verify { syncedTabsStorage.stop() }
}
}

View File

@ -0,0 +1,112 @@
/* 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.sync
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.android.synthetic.main.sync_tabs_list_item.view.*
import kotlinx.android.synthetic.main.view_synced_tabs_group.view.*
import mozilla.components.browser.storage.sync.Tab
import mozilla.components.browser.storage.sync.TabEntry
import mozilla.components.concept.sync.Device
import mozilla.components.concept.sync.DeviceType
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class SyncedTabsViewHolderTest {
private lateinit var tabViewHolder: SyncedTabsViewHolder.TabViewHolder
private lateinit var tabView: View
private lateinit var deviceViewHolder: SyncedTabsViewHolder.DeviceViewHolder
private lateinit var deviceView: View
private lateinit var deviceViewGroupName: TextView
private val tab = Tab(
history = listOf(
mockk(),
TabEntry(
title = "Firefox",
url = "https://firefox.com",
iconUrl = "https://firefox.com/favicon.ico"
),
mockk()
),
active = 1,
lastUsed = 0L
)
@Before
fun setup() {
val inflater = LayoutInflater.from(testContext)
tabView = inflater.inflate(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, null)
tabViewHolder = SyncedTabsViewHolder.TabViewHolder(tabView)
deviceViewGroupName = mockk(relaxUnitFun = true)
deviceView = mockk {
every { synced_tabs_group_name } returns deviceViewGroupName
}
deviceViewHolder = SyncedTabsViewHolder.DeviceViewHolder(deviceView)
}
@Test
fun `TabViewHolder binds active tab`() {
tabViewHolder.bind(SyncedTabsAdapter.AdapterItem.Tab(tab), mockk())
assertEquals("Firefox", tabView.synced_tab_item_title.text)
assertEquals("https://firefox.com", tabView.synced_tab_item_url.text)
}
@Test
fun `TabViewHolder calls interactor on click`() {
val interactor = mockk<(Tab) -> Unit>(relaxed = true)
tabViewHolder.bind(SyncedTabsAdapter.AdapterItem.Tab(tab), interactor)
tabView.performClick()
verify { interactor(tab) }
}
@Test
fun `DeviceViewHolder binds desktop device`() {
val device = mockk<Device> {
every { displayName } returns "Charcoal"
every { deviceType } returns DeviceType.DESKTOP
}
deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk())
verify { deviceViewGroupName.text = "Charcoal" }
verify {
deviceViewGroupName.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.mozac_ic_device_desktop, 0, 0, 0
)
}
}
@Test
fun `DeviceViewHolder binds mobile device`() {
val device = mockk<Device> {
every { displayName } returns "Emerald"
every { deviceType } returns DeviceType.MOBILE
}
deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk())
verify { deviceViewGroupName.text = "Emerald" }
verify {
deviceViewGroupName.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.mozac_ic_device_mobile, 0, 0, 0
)
}
}
}

View File

@ -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.wifi
import io.mockk.Called
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_AUDIBLE
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_INAUDIBLE
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_ALLOW_ALL
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_ALLOW_ON_WIFI
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
import org.mozilla.fenix.utils.Settings
class SitePermissionsWifiIntegrationTest {
private lateinit var settings: Settings
private lateinit var wifiConnectionMonitor: WifiConnectionMonitor
private lateinit var wifiIntegration: SitePermissionsWifiIntegration
@Before
fun setup() {
settings = mockk()
wifiConnectionMonitor = mockk(relaxed = true)
wifiIntegration = SitePermissionsWifiIntegration(settings, wifiConnectionMonitor)
every { settings.setSitePermissionsPhoneFeatureAction(any(), any()) } just Runs
}
@Test
fun `add and remove wifi connected listener`() {
wifiIntegration.addWifiConnectedListener()
verify { wifiConnectionMonitor.register(any()) }
wifiIntegration.removeWifiConnectedListener()
verify { wifiConnectionMonitor.unregister(any()) }
}
@Test
fun `start and stop wifi connection monitor`() {
wifiIntegration.start()
verify { wifiConnectionMonitor.start() }
wifiIntegration.stop()
verify { wifiConnectionMonitor.stop() }
}
@Test
fun `add only if autoplay is only allowed on wifi`() {
every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ALL
wifiIntegration.maybeAddWifiConnectedListener()
verify { wifiConnectionMonitor wasNot Called }
every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ON_WIFI
wifiIntegration.maybeAddWifiConnectedListener()
verify { wifiConnectionMonitor.register(any()) }
}
@Test
fun `listener removes itself if autoplay is not only allowed on wifi`() {
every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ALL
wifiIntegration.onWifiConnectionChanged(connected = true)
verify { wifiConnectionMonitor.unregister(any()) }
}
@Test
fun `listener sets audible and inaudible settings to allowed on connect`() {
every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ON_WIFI
wifiIntegration.onWifiConnectionChanged(connected = true)
verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_AUDIBLE, Action.ALLOWED) }
verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_INAUDIBLE, Action.ALLOWED) }
}
@Test
fun `listener sets audible and inaudible settings to blocked on disconnected`() {
every { settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) } returns AUTOPLAY_ALLOW_ON_WIFI
wifiIntegration.onWifiConnectionChanged(connected = false)
verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_AUDIBLE, Action.BLOCKED) }
verify { settings.setSitePermissionsPhoneFeatureAction(AUTOPLAY_INAUDIBLE, Action.BLOCKED) }
}
}

Some files were not shown because too many files have changed in this diff Show More