diff --git a/.gitignore b/.gitignore index 24d282a79..d5e16cb60 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,7 @@ test_artifacts/ /build/test-tools/google-cloud-sdk/ /build/test-tools/*.jar /build/test-tools/*.gz + + +# Web extensions: manifest.json files are generated +manifest.json diff --git a/app/build.gradle b/app/build.gradle index 3ac53b31e..37fffdca6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,6 +186,10 @@ android { // GeckoView must uncompress it before it can do anything else which // causes a significant delay on startup. noCompress 'ja' + + // manifest.template.json is converted to manifest.json at build time. + // No need to package the template in the APK. + ignoreAssetsPattern "manifest.template.json" } testOptions { @@ -755,3 +759,34 @@ if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) ext.appServicesSrcDir = gradle."localProperties.autoPublish.application-services.dir" apply from: "../${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle" } + +// Define a reusable task for updating the versions of our built-in web extensions. We automate this +// to make sure we never forget to update the version, either in local development or for releases. +// In both cases, we want to make sure the latest version of all extensions (including their latest +// changes) are installed on first start-up. +// We're using the A-C version here as we want to uplift all built-in extensions to A-C (Once that's +// done we can also remove the task below): +// https://github.com/mozilla-mobile/android-components/issues/7249 +ext.updateExtensionVersion = { task, extDir -> + configure(task) { + from extDir + include 'manifest.template.json' + rename { 'manifest.json' } + into extDir + + def values = ['version': AndroidComponents.VERSION + "." + new Date().format('MMddHHmmss')] + inputs.properties(values) + expand(values) + } +} + +tasks.register("updateAdsExtensionVersion", Copy) { task -> + updateExtensionVersion(task, 'src/main/assets/extensions/ads') +} + +tasks.register("updateCookiesExtensionVersion", Copy) { task -> + updateExtensionVersion(task, 'src/main/assets/extensions/cookies') +} + +preBuild.dependsOn updateAdsExtensionVersion +preBuild.dependsOn updateCookiesExtensionVersion diff --git a/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt index 8ccd7f313..0fa99cc35 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt @@ -4,12 +4,14 @@ package org.mozilla.fenix.screenshots +import android.os.SystemClock import androidx.test.rule.ActivityTestRule import org.junit.After import org.junit.Rule import org.junit.Test import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.mDevice import tools.fastlane.screengrab.Screengrab @@ -30,15 +32,17 @@ class DefaultHomeScreenTest : ScreenshotTest() { fun showDefaultHomeScreen() { homeScreen { verifyAccountsSignInButton() - Screengrab.screenshot("HomeScreenRobot_home-screen") swipeToBottom() Screengrab.screenshot("HomeScreenRobot_home-screen-scroll") + TestAssetHelper.waitingTime } } @Test fun privateBrowsingTest() { homeScreen { + SystemClock.sleep(TestAssetHelper.waitingTimeShort) + Screengrab.screenshot("HomeScreenRobot_home-screen") }.openThreeDotMenu { }.openSettings { } // To get private screenshot, @@ -48,6 +52,7 @@ class DefaultHomeScreenTest : ScreenshotTest() { togglePrivateBrowsingModeOnOff() Screengrab.screenshot("HomeScreenRobot_private-browsing-menu") togglePrivateBrowsingModeOnOff() + Screengrab.screenshot("HomeScreenRobot_after-onboarding") } } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt b/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt index 0a339e410..663e96b01 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt @@ -4,10 +4,13 @@ package org.mozilla.fenix.screenshots +import android.os.SystemClock import androidx.test.espresso.Espresso.onView import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.rule.ActivityTestRule +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule import okhttp3.mockwebserver.MockWebServer @@ -21,6 +24,7 @@ import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.click +import org.mozilla.fenix.helpers.ext.waitNotNull import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.bookmarksMenu import org.mozilla.fenix.ui.robots.mDevice @@ -125,13 +129,13 @@ class MenuScreenShotTest : ScreenshotTest() { openBookmarksThreeDotMenu() Screengrab.screenshot("BookmarksRobot_bookmarks-menu") bookmarksMenu { - clickAddFolderButton() + clickAddFolderButtonUsingId() Screengrab.screenshot("BookmarksRobot_add-folder-view") saveNewFolder() Screengrab.screenshot("BookmarksRobot_error-empty-folder-name") addNewFolderName("test") saveNewFolder() - }.openThreeDotMenu { + }.openThreeDotMenu("test") { Screengrab.screenshot("ThreeDotMenuBookmarksRobot_folder-menu") } editBookmarkFolder() @@ -139,7 +143,7 @@ class MenuScreenShotTest : ScreenshotTest() { // It may be needed to wait here to have the screenshot mDevice.pressBack() bookmarksMenu { - }.openThreeDotMenu { + }.openThreeDotMenu("test") { deleteBookmarkFolder() Screengrab.screenshot("ThreeDotMenuBookmarksRobot_delete-bookmark-folder-menu") } @@ -152,21 +156,12 @@ class MenuScreenShotTest : ScreenshotTest() { Screengrab.screenshot("NavigationToolbarRobot_navigation-toolbar") }.enterURLAndEnterToBrowser(defaultWebPage.url) { Screengrab.screenshot("BrowserRobot_enter-url") - } - tapOnTabCounter() - // Homescreen with visited tabs - Screengrab.screenshot("HomeScreenRobot_homescreen-with-tabs-open") - homeScreen { + }.openTabDrawer { + TestAssetHelper.waitingTime + Screengrab.screenshot("TabDrawerRobot_one-tab-open") }.openTabsListThreeDotMenu { - Screengrab.screenshot("open-tabs-menu") - }.close { - // It may be needed to wait here for tests working on Firebase - saveToCollectionButton() - Screengrab.screenshot("HomeScreenRobot_save-collection-view") - typeCollectionName("CollectionName") - mDevice.pressBack() - // It may be needed to wait here for tests working on Firebase - Screengrab.screenshot("HomeScreenRobot_saved-collection") + TestAssetHelper.waitingTime + Screengrab.screenshot("TabDrawerRobot_three-dot-menu") } } @@ -176,12 +171,13 @@ class MenuScreenShotTest : ScreenshotTest() { navigationToolbar { }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { - Screengrab.screenshot("browser-tab-menu") + Screengrab.screenshot("TabDrawerRobot_browser-tab-menu") }.closeBrowserMenuToBrowser { }.openTabDrawer { - Screengrab.screenshot("tab-drawer-with-tabs") + Screengrab.screenshot("TabDrawerRobot_tab-drawer-with-tabs") closeTab() - Screengrab.screenshot("remove-tab") + TestAssetHelper.waitingTime + Screengrab.screenshot("TabDrawerRobot_remove-tab") } } @@ -189,12 +185,11 @@ class MenuScreenShotTest : ScreenshotTest() { fun saveLoginPromptTest() { val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer) - TestAssetHelper.waitingTimeShort navigationToolbar { }.enterURLAndEnterToBrowser(saveLoginTest.url) { + verifySaveLoginPromptIsShownNotSave() + SystemClock.sleep(TestAssetHelper.waitingTimeShort) Screengrab.screenshot("save-login-prompt") - TestAssetHelper.waitingTime - // verifySaveLoginPromptIsShown() } } } @@ -236,3 +231,11 @@ fun loginsAndPassword() = onView(withText(R.string.preferences_passwords_logins_ fun addOns() = onView(withText(R.string.preferences_addons)).click() fun settingsLanguage() = onView(withText(R.string.preferences_language)).click() + +fun verifySaveLoginPromptIsShownNotSave() { + mDevice.waitNotNull(Until.findObjects(By.text("test@example.com")), TestAssetHelper.waitingTime) + val submitButton = mDevice.findObject(By.res("submit")) + submitButton.clickAndWait(Until.newWindow(), TestAssetHelper.waitingTime) +} + +fun clickAddFolderButtonUsingId() = onView(withId(R.id.add_bookmark_folder)).click() diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt index 26eee5e0b..fa22d40a2 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt @@ -131,7 +131,7 @@ class ContextMenusTest { clickContextShareLink(genericURL.url) // verify share intent is matched with associated URL } } - + @Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12367") @Test fun verifyContextOpenImageNewTab() { val pageLinks = diff --git a/app/src/main/assets/extensions/ads/manifest.json b/app/src/main/assets/extensions/ads/manifest.template.json similarity index 71% rename from app/src/main/assets/extensions/ads/manifest.json rename to app/src/main/assets/extensions/ads/manifest.template.json index f7cd42792..41d9e9bd1 100644 --- a/app/src/main/assets/extensions/ads/manifest.json +++ b/app/src/main/assets/extensions/ads/manifest.template.json @@ -1,7 +1,12 @@ { "manifest_version": 2, + "applications": { + "gecko": { + "id": "ads@mozac.org" + } + }, "name": "Mozilla Android Components - Ads", - "version": "1.0", + "version": "${version}", "content_scripts": [ { "matches": ["https://*/*"], @@ -16,6 +21,7 @@ ], "permissions": [ "geckoViewAddons", - "nativeMessaging" + "nativeMessaging", + "nativeMessagingFromContent" ] } diff --git a/app/src/main/assets/extensions/cookies/manifest.json b/app/src/main/assets/extensions/cookies/manifest.template.json similarity index 83% rename from app/src/main/assets/extensions/cookies/manifest.json rename to app/src/main/assets/extensions/cookies/manifest.template.json index dd56d6ea2..06440bccf 100644 --- a/app/src/main/assets/extensions/cookies/manifest.json +++ b/app/src/main/assets/extensions/cookies/manifest.template.json @@ -1,7 +1,12 @@ { "manifest_version": 2, + "applications": { + "gecko": { + "id": "cookies@mozac.org" + } + }, "name": "Mozilla Android Components - Cookies", - "version": "1.0", + "version": "${version}", "content_scripts": [ { "matches": ["https://*/*"], @@ -23,6 +28,7 @@ "permissions": [ "geckoViewAddons", "nativeMessaging", + "nativeMessagingFromContent", "webNavigation", "webRequest", "webRequestBlocking", diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index c59a39990..1648cd7c4 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -277,8 +277,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { EngineView::class.java.name -> components.core.engine.createView(context, attrs).apply { selectionActionDelegate = DefaultSelectionActionDelegate( getSearchAdapter(components.core.store), - resources = context.resources, - appName = getString(R.string.app_name) + resources = context.resources ) { share(it) } diff --git a/app/src/main/java/org/mozilla/fenix/browser/FenixSnackbarDelegate.kt b/app/src/main/java/org/mozilla/fenix/browser/FenixSnackbarDelegate.kt index 2547fd871..6d2a4cdfc 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/FenixSnackbarDelegate.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/FenixSnackbarDelegate.kt @@ -9,8 +9,8 @@ import androidx.annotation.StringRes import mozilla.components.feature.contextmenu.ContextMenuCandidate import org.mozilla.fenix.components.FenixSnackbar -class FenixSnackbarDelegate(val view: View) : - ContextMenuCandidate.SnackbarDelegate { +class FenixSnackbarDelegate(private val view: View) : ContextMenuCandidate.SnackbarDelegate { + override fun show( snackBarParentView: View, @StringRes text: Int, diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt index 1b6e64e5c..c5011f1e5 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt @@ -13,6 +13,7 @@ import android.view.ViewGroup import android.widget.LinearLayout import android.widget.PopupWindow import androidx.annotation.LayoutRes +import androidx.annotation.VisibleForTesting import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -29,6 +30,8 @@ import kotlinx.android.synthetic.main.component_browser_top_toolbar.* import kotlinx.android.synthetic.main.component_browser_top_toolbar.view.* import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider import mozilla.components.browser.session.Session +import mozilla.components.browser.state.selector.selectedTab +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.behavior.BrowserToolbarBottomBehavior import mozilla.components.browser.toolbar.display.DisplayToolbar @@ -94,9 +97,6 @@ class BrowserToolbarView( view.context.resources.getDimensionPixelSize(R.dimen.context_menu_height), true ) - - val selectedSession = container.context.components.core.sessionManager.selectedSession - popupWindow.elevation = view.context.resources.getDimension(R.dimen.mozac_browser_menu_elevation) @@ -110,11 +110,7 @@ class BrowserToolbarView( customView.copy.setOnClickListener { popupWindow.dismiss() - if (isCustomTabSession) { - clipboard.text = customTabSession?.url - } else { - clipboard.text = selectedSession?.url - } + clipboard.text = getUrlForClipboard(it.context.components.core.store, customTabSession) FenixSnackbar.make( view = view, @@ -300,5 +296,15 @@ class BrowserToolbarView( companion object { private const val TOOLBAR_ELEVATION = 16 + + @VisibleForTesting + internal fun getUrlForClipboard(store: BrowserStore, customTabSession: Session? = null): String? { + return if (customTabSession != null) { + customTabSession.url + } else { + val selectedTab = store.state.selectedTab + selectedTab?.readerState?.activeUrl ?: selectedTab?.content?.url + } + } } } diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt index 92053ba9e..1f5c62dd7 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt @@ -14,19 +14,13 @@ import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder -sealed class AdapterItem { - object DeleteButton : AdapterItem() - object Header : AdapterItem() - data class Item(val item: TrackingProtectionException) : AdapterItem() -} - /** * Adapter for a list of sites that are exempted from Tracking Protection, * along with controls to remove the exception. */ class ExceptionsAdapter( private val interactor: ExceptionsInteractor -) : ListAdapter(DiffCallback) { +) : ListAdapter(DiffCallback) { /** * Change the list of items that are displayed. @@ -67,6 +61,12 @@ class ExceptionsAdapter( } } + sealed class AdapterItem { + object DeleteButton : AdapterItem() + object Header : AdapterItem() + data class Item(val item: TrackingProtectionException) : AdapterItem() + } + private object DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = areContentsTheSame(oldItem, newItem) diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt index 8f193f222..f1ba7582e 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt @@ -28,6 +28,7 @@ import org.mozilla.fenix.settings.SupportUtils * along with controls to remove the exception. */ class ExceptionsFragment : Fragment() { + private lateinit var exceptionsStore: ExceptionsFragmentStore private lateinit var exceptionsView: ExceptionsView private lateinit var exceptionsInteractor: ExceptionsInteractor @@ -48,7 +49,7 @@ class ExceptionsFragment : Fragment() { exceptionsStore = StoreProvider.get(this) { ExceptionsFragmentStore( ExceptionsFragmentState( - items = listOf() + items = emptyList() ) ) } @@ -61,7 +62,6 @@ class ExceptionsFragment : Fragment() { @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) consumeFrom(exceptionsStore) { exceptionsView.update(it) } diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt index c9cbe39c2..b40dcf5f0 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt @@ -4,16 +4,16 @@ package org.mozilla.fenix.exceptions -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.text.toSpannable import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.component_exceptions.view.* +import kotlinx.android.synthetic.main.component_exceptions.* import mozilla.components.concept.engine.content.blocking.TrackingProtectionException import org.mozilla.fenix.R @@ -42,35 +42,36 @@ interface ExceptionsViewInteractor { * View that contains and configures the Exceptions List */ class ExceptionsView( - override val containerView: ViewGroup, - val interactor: ExceptionsInteractor + container: ViewGroup, + interactor: ExceptionsInteractor ) : LayoutContainer { - val view: FrameLayout = LayoutInflater.from(containerView.context) - .inflate(R.layout.component_exceptions, containerView, true) + override val containerView: FrameLayout = LayoutInflater.from(container.context) + .inflate(R.layout.component_exceptions, container, true) .findViewById(R.id.exceptions_wrapper) private val exceptionsAdapter = ExceptionsAdapter(interactor) init { - view.exceptions_list.apply { + exceptions_list.apply { adapter = exceptionsAdapter - layoutManager = LinearLayoutManager(containerView.context) + layoutManager = LinearLayoutManager(container.context) } - val learnMoreText = view.exceptions_learn_more.text.toString() - val textWithLink = SpannableString(learnMoreText).apply { - setSpan(UnderlineSpan(), 0, learnMoreText.length, 0) - } - with(view.exceptions_learn_more) { + + with(exceptions_learn_more) { + val learnMoreText = text + text = learnMoreText.toSpannable().apply { + setSpan(UnderlineSpan(), 0, learnMoreText.length, 0) + } + movementMethod = LinkMovementMethod.getInstance() - text = textWithLink setOnClickListener { interactor.onLearnMore() } } } fun update(state: ExceptionsFragmentState) { - view.exceptions_empty_view.isVisible = state.items.isEmpty() - view.exceptions_list.isVisible = state.items.isNotEmpty() + exceptions_empty_view.isVisible = state.items.isEmpty() + exceptions_list.isVisible = state.items.isNotEmpty() exceptionsAdapter.updateData(state.items) } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt index 76d8e9e6f..39c49a414 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt @@ -5,12 +5,14 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding import android.view.View +import android.widget.Button +import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult import mozilla.components.service.fxa.sharing.ShareableAccount @@ -20,42 +22,18 @@ import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components -class OnboardingAutomaticSignInViewHolder(view: View) : RecyclerView.ViewHolder(view) { +class OnboardingAutomaticSignInViewHolder( + view: View, + private val scope: CoroutineScope = MainScope() +) : RecyclerView.ViewHolder(view) { private lateinit var shareableAccount: ShareableAccount private val headerText = view.header_text init { view.turn_on_sync_button.setOnClickListener { - it.context.components.analytics.metrics.track(Event.OnboardingAutoSignIn) - - it.turn_on_sync_button.text = it.context.getString( - R.string.onboarding_firefox_account_signing_in - ) - it.turn_on_sync_button.isEnabled = false - - CoroutineScope(Dispatchers.Main).launch { - val result = view.context.components.backgroundServices.accountManager - .signInWithShareableAccountAsync(shareableAccount).await() - when (result) { - SignInWithShareableAccountResult.Failure -> { - // Failed to sign-in (e.g. bad credentials). Allow to try again. - it.turn_on_sync_button.text = it.context.getString( - R.string.onboarding_firefox_account_auto_signin_confirm - ) - it.turn_on_sync_button.isEnabled = true - FenixSnackbar.make( - view = it, - duration = Snackbar.LENGTH_SHORT, - isDisplayedWithBrowserToolbar = false - ).setText( - it.context.getString(R.string.onboarding_firefox_account_automatic_signin_failed) - ).show() - } - SignInWithShareableAccountResult.WillRetry, SignInWithShareableAccountResult.Success -> { - // We consider both of these as a 'success'. - } - } + scope.launch { + onClick(it.turn_on_sync_button) } } } @@ -69,6 +47,34 @@ class OnboardingAutomaticSignInViewHolder(view: View) : RecyclerView.ViewHolder( headerText.putCompoundDrawablesRelativeWithIntrinsicBounds(start = icon) } + @VisibleForTesting + internal suspend fun onClick(button: Button) { + val context = button.context + context.components.analytics.metrics.track(Event.OnboardingAutoSignIn) + + button.text = context.getString(R.string.onboarding_firefox_account_signing_in) + button.isEnabled = false + + val accountManager = context.components.backgroundServices.accountManager + when (accountManager.signInWithShareableAccountAsync(shareableAccount).await()) { + SignInWithShareableAccountResult.Failure -> { + // Failed to sign-in (e.g. bad credentials). Allow to try again. + button.text = context.getString(R.string.onboarding_firefox_account_auto_signin_confirm) + button.isEnabled = true + FenixSnackbar.make( + view = button, + duration = Snackbar.LENGTH_SHORT, + isDisplayedWithBrowserToolbar = false + ).setText( + context.getString(R.string.onboarding_firefox_account_automatic_signin_failed) + ).show() + } + SignInWithShareableAccountResult.WillRetry, SignInWithShareableAccountResult.Success -> { + // We consider both of these as a 'success'. + } + } + } + companion object { const val LAYOUT_ID = R.layout.onboarding_automatic_signin } diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetry.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetry.kt index 94fa72e42..7768a6423 100644 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetry.kt +++ b/app/src/main/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetry.kt @@ -93,7 +93,6 @@ abstract class BaseSearchTelemetry { engine.installWebExtension( id = extensionInfo.id, url = extensionInfo.resourceUrl, - allowContentMessaging = true, onSuccess = { extension -> store.flowScoped { flow -> subscribeToUpdates(flow, extension, extensionInfo) } }, diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetry.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetry.kt index 97dfcc56d..7044af09b 100644 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetry.kt +++ b/app/src/main/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetry.kt @@ -51,7 +51,7 @@ class AdsTelemetry(private val metrics: MetricController) : BaseSearchTelemetry( companion object { @VisibleForTesting - internal const val ADS_EXTENSION_ID = "mozacBrowserAds" + internal const val ADS_EXTENSION_ID = "ads@mozac.org" @VisibleForTesting internal const val ADS_EXTENSION_RESOURCE_URL = "resource://android/assets/extensions/ads/" @VisibleForTesting diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetry.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetry.kt index d98423970..1be9e832c 100644 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetry.kt +++ b/app/src/main/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetry.kt @@ -131,7 +131,7 @@ class InContentTelemetry(private val metrics: MetricController) : BaseSearchTele companion object { @VisibleForTesting - internal const val COOKIES_EXTENSION_ID = "BrowserCookiesExtension" + internal const val COOKIES_EXTENSION_ID = "cookies@mozac.org" @VisibleForTesting internal const val COOKIES_EXTENSION_RESOURCE_URL = "resource://android/assets/extensions/cookies/" diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt index 56c761ef3..7b1014df3 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -192,6 +192,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment() { getString(R.string.snackbar_deleted_undo), { sessionManager.add(snapshot.session, isSelected, engineSessionState = state) + tabTrayView.scrollToTab(snapshot.session.id) }, operation = { }, elevation = ELEVATION, diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 0562f2fba..f82e5f923 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -126,7 +126,7 @@ class TabTrayView( } if (!hasLoaded) { hasLoaded = true - scrollToSelectedTab() + scrollToTab(view.context.components.core.store.state.selectedTabId) if (view.context.settings().accessibilityServicesEnabled) { lifecycleScope.launch { delay(SELECTION_DELAY.toLong()) @@ -145,27 +145,30 @@ class TabTrayView( } } - tabTrayItemMenu = TabTrayItemMenu(view.context, { view.tab_layout.selectedTabPosition == 0 }) { - when (it) { - is TabTrayItemMenu.Item.ShareAllTabs -> interactor.onShareTabsClicked( - isPrivateModeSelected - ) - is TabTrayItemMenu.Item.SaveToCollection -> interactor.onSaveToCollectionClicked() - is TabTrayItemMenu.Item.CloseAllTabs -> interactor.onCloseAllTabsClicked( - isPrivateModeSelected - ) + tabTrayItemMenu = + TabTrayItemMenu(view.context, { view.tab_layout.selectedTabPosition == 0 }) { + when (it) { + is TabTrayItemMenu.Item.ShareAllTabs -> interactor.onShareTabsClicked( + isPrivateModeSelected + ) + is TabTrayItemMenu.Item.SaveToCollection -> interactor.onSaveToCollectionClicked() + is TabTrayItemMenu.Item.CloseAllTabs -> interactor.onCloseAllTabsClicked( + isPrivateModeSelected + ) + } } - } view.tab_tray_overflow.setOnClickListener { container.context.components.analytics.metrics.track(Event.TabsTrayMenuOpened) menu = tabTrayItemMenu.menuBuilder.build(container.context) menu?.show(it) ?.also { pu -> - (pu.contentView as? CardView)?.setCardBackgroundColor(ContextCompat.getColor( - view.context, - R.color.foundation_normal_theme - )) + (pu.contentView as? CardView)?.setCardBackgroundColor( + ContextCompat.getColor( + view.context, + R.color.foundation_normal_theme + ) + ) } } @@ -211,7 +214,7 @@ class TabTrayView( filterTabs.invoke(filter) updateState(view.context.components.core.store.state) - scrollToSelectedTab() + scrollToTab(view.context.components.core.store.state.selectedTabId) if (isPrivateModeSelected) { container.context.components.analytics.metrics.track(Event.TabsTrayPrivateModeTapped) @@ -220,8 +223,11 @@ class TabTrayView( } } - override fun onTabReselected(tab: TabLayout.Tab?) { /*noop*/ } - override fun onTabUnselected(tab: TabLayout.Tab?) { /*noop*/ } + override fun onTabReselected(tab: TabLayout.Tab?) { /*noop*/ + } + + override fun onTabUnselected(tab: TabLayout.Tab?) { /*noop*/ + } fun updateState(state: BrowserState) { view.let { @@ -266,14 +272,16 @@ class TabTrayView( private fun toggleFabText(private: Boolean) { if (private) { fabView.new_tab_button.extend() - fabView.new_tab_button.contentDescription = view.context.resources.getString(R.string.add_private_tab) + fabView.new_tab_button.contentDescription = + view.context.resources.getString(R.string.add_private_tab) } else { fabView.new_tab_button.shrink() - fabView.new_tab_button.contentDescription = view.context.resources.getString(R.string.add_tab) + fabView.new_tab_button.contentDescription = + view.context.resources.getString(R.string.add_tab) } } - private fun scrollToSelectedTab() { + fun scrollToTab(sessionId: String?) { (view.tabsTray as? BrowserTabsTray)?.also { tray -> val tabs = if (isPrivateModeSelected) { view.context.components.core.store.state.privateTabs @@ -282,7 +290,7 @@ class TabTrayView( } val selectedBrowserTabIndex = tabs - .indexOfFirst { it.id == view.context.components.core.store.state.selectedTabId } + .indexOfFirst { it.id == sessionId } tray.layoutManager?.scrollToPosition(selectedBrowserTabIndex) } diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml index 614f3204a..e2c2ea022 100644 --- a/app/src/main/res/drawable/ic_close.xml +++ b/app/src/main/res/drawable/ic_close.xml @@ -7,7 +7,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index c5a80e4c8..6539c58d4 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -11,12 +11,15 @@ عطّل التصفّح الخاص ابحث أو أدخِل عنوانا - - لا ألسنة مفتوحة ستظهر الألسنة المفتوحة هنا. + + ستظهر الألسنة الخاصة هنا. + + لسان واحد مفتوح. انقر لتبديل الألسنة. + أنت في جلسة خاصة @@ -28,7 +31,7 @@ احذف الجلسة - + أضِف اختصارًا لفتح الألسنة الخاصة من الشاشة الرئيسية. @@ -48,6 +51,10 @@ الألسنة المفتوحة + + السابق + + التالي أعِد التحميل @@ -82,8 +89,6 @@ لسان جديد احفظ في التجميعة - - أبلِغ عن مشكلة بالموقع شارِك @@ -97,13 +102,17 @@ تدعمها %1$s - + منظور القارئ افتح في التطبيق المظهر + + تعذّر الاتصال، لم أتعرّف على مخطّط المسار. + اللغة المحددة @@ -335,6 +344,11 @@ بالأسفل + + + فاتحة + + داكنة حسب وضع توفير الطاقة diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 723632cce..6f0d792a3 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -489,6 +489,11 @@ Няма гісторыі + + + Выбачайце. %1$s не можа загрузіць гэтую старонку. + + Вы можаце паспрабаваць аднавіць ці закрыць гэтую картку ніжэй. Адправіць справаздачу аб краху ў Mozilla diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml index 8640775b5..8980b89d5 100644 --- a/app/src/main/res/values-es-rMX/strings.xml +++ b/app/src/main/res/values-es-rMX/strings.xml @@ -19,6 +19,11 @@ Tus pestañas privadas aparecerán aquí. + + 1 pestaña abierta. Tocar para cambiar de pestaña. + + %1$s pestañas abiertas. Tocar para cambiar de pestaña. + %1$s es producido por Mozilla. @@ -116,6 +121,8 @@ Patrocinado por %1$s Vista de lectura + + Salir de vista de lectura Abrir en la aplicación @@ -288,6 +295,8 @@ Marcadores Inicios de sesión + + Pestañas abiertas Cerrar sesión @@ -442,6 +451,24 @@ Pestañas privadas Agregar pestaña + + Agregar pestaña privada + + Privada + + Pestañas abiertas + + Guardar en colección + + Compartir todas las pestañas + + Cerrar todas las pestañas + + Nueva pestaña + + Ir a Inicio + + Alternar modo de pestaña Eliminar la pestaña de la colección @@ -454,6 +481,8 @@ Cerrar todas las pestañas Compartir pestañas + + Guardar pestañas en la colección Menú de pestaña @@ -668,6 +697,10 @@ Colecciones Menú de la colección + + Colecciona las cosas que te importan + + Agrupa búsquedas, sitios y pestañas similares para un acceso rápido después. Seleccionar pestañas @@ -697,6 +730,9 @@ Guardar + + Ver + Colección %d @@ -845,8 +881,6 @@ Eliminar automáticamente los datos de navegación cuando selecciones "Salir" en el menú principal Eliminar automáticamente los datos de navegación cuando selecciones \"Salir\" en el menú principal - - Historial de navegación Salir @@ -872,9 +906,26 @@ Sin embargo, puede ser menos estable. Descarga nuestro navegador beta para una experiencia más estable. + + Obtener Firefox para Android Beta + Firefox Nightly se movió + + Esta aplicación ya no recibirá actualizaciones de seguridad. Deja de usarla y cambia al nuevo Nightly. + \n\nPara transferir tus marcadores, inicios de sesión e historial a otra aplicación, crea una cuenta de Firefox. + + Cambia al nuevo Nightly + + + Firefox Nightly se ha trasladado + + Esta aplicación ya no recibirá actualizaciones de seguridad. Obtén el nuevo Nightly y deja de usar esta aplicación. + \n\nPara transferir tus marcadores, inicios de sesión e historial a otra aplicación, crea una cuenta de Firefox. + + Obtén el nuevo Nightly + @@ -910,11 +961,22 @@ Sync está activado Error al iniciar sesión + + Privacidad automática + + La configuración de privacidad y seguridad bloquea los rastreadores, el malware y las compañías que te siguen. + + Estándar (predeterminado) + + Bloquea menos rastreadores. Las páginas se cargarán normalmente. Estricta (recomendada) Estricto + + Bloquear más rastreadores, publicidad y ventanas emergentes. Las páginas cargan más rápido, pero pueden tener problemas de funcionalidad. @@ -999,14 +1061,22 @@ Protege tus datos. %s te protege de muchos de los rastreadores más comunes que siguen lo que haces en línea. Saber más + + Estándar (predeterminado) + + Bloquea menos rastreadores. Las páginas se cargarán normalmente. Qué es lo que está bloqueado por la protección estándar contra el rastreo Estricto + + Bloquear más rastreadores, publicidad y ventanas emergentes. Las páginas cargan más rápido, pero pueden tener problemas de funcionalidad. Qué es lo que está bloqueado por la protección estricta contra el rastreo Personalizado + + Elige qué rastreadores y secuencias de comandos bloquear Esto es lo que está bloqueado por la protección de rastreo estándar @@ -1057,6 +1127,8 @@ Bloquea la carga de anuncios externos, vídeos y contenido que contenga código de rastreo. Puede afectar a la funcionalidad del sitio web. + + Cada vez que el escudo es morado, %s bloqueó rastreadores en un sitio. Toca para más información. Las protecciones están ACTIVAS para este sitio @@ -1065,4 +1137,305 @@ La protección de rastreo mejorada está desactivada para estos sitios - + + Regresar + + Tus derechos + + Bibliotecas de código abierto que utilizamos + + Novedades de %s + + %s | Bibliotecas OSS + + + Ayuda + + Fallos + + Aviso de privacidad + + Conoce tus derechos + + Información de licencia + + Bibliotecas que usamos + + Menú de depuración: quedan %1$d click(s) para habilitarlo + Menú de depuración habilitado + + + 1 pestaña + + %d pestañas + + + + Copiar + + Pegar e ir + + Pegar + + URL copiada al portapapeles + + + Agregar a la pantalla de inicio + + Cancelar + + Agregar + + Continuar al sitio web + + Nombre del acceso directo + + Puedes agregar fácilmente este sitio web a la página de inicio de tu teléfono para navegar más rápido con una experiencia similar a una app. + + + Inicios de sesión y contraseñas + + Guardar inicios de sesión y contraseñas + + Preguntar para guardar + + Nunca guardar + + Autocompletar + + Sincronizar inicios de sesión + + Habilitado + + Deshabilitado + + Reconectar + + Iniciar sesión en Sync + + Inicios de sesión guardados + + Los inicios de sesión que guardes o sincronices en %s se mostrarán aquí + + Saber más acerca de Sync. + + Excepciones + + Los inicios de sesión y contraseñas no guardados se mostrarán aquí. + + + Los inicios de sesión y contraseñas no serán guardados para estos sitios. + + Buscar inicios de sesión + + Alfabéticamente + + Usados recientemente + + Sitio + + Nombre de usuario + + Contraseña + + Vuelve a ingresar tu PIN + + Desbloquear para ver los inicios de sesión guardados + + Esta conexión no es segura. Los inicios de sesión ingresados aquí pueden verse comprometidos. + + Saber más + + ¿Quieres que %s guarde este inicio de sesión? + + Guardar + + No guardar + + Contraseña copiada al portapapeles + + Nombre de usuario copiado al portapapeles + + Sitio copiado al portapapeles + + Copiar contraseña + + Copiar nombre de usuario + + Copiar sitio + + Mostrar contraseña + + Ocultar contraseña + + Desbloquear para ver tus inicios de sesión guardados + + + Asegurar tus usuarios y contraseñas + + Configura un patrón de bloqueo de dispositivo, PIN o contraseña para proteger el acceso a tus inicios de sesión y contraseñas guardadas si alguien más tiene tu dispositivo. + + Más tarde + + Configurar ahora + + Desbloquear tu dispositivo + + Zoom en todos los sitios + + Habilitar para permitir pellizcar y hacer zoom, incluso en sitios web que previenen este gesto. + + Nombre (A-Z) + + Último uso + + Ordenar menú de inicios de sesión + + + Agregar motor de búsqueda + + Editar motor de búsqueda + + Agregar + + Guardar + + Editar + + Eliminar + + + Otro + + Nombre + + Cadena de búsqueda a usar + + Reemplazar la consulta con “%s”. Ejemplo:\n https://www.google.com/search?q=%s + + Saber más + + Detalles del motor de búsqueda personalizado + + Enlace para saber más + + + Introducir nombre de motor de búsqueda + + Ya existe un motor de búsqueda con nombre “%s”. + + Ingresa una cadena de búsqueda + + Verifica que la cadena de búsqueda corresponde con el formato de ejemplo + + Error al conectarse a “%s” + + Se creó %s + + Se guardó %s + + Se eliminó %s + + + Bienvenido a un %s completamente nuevo + + Un navegador completamente re-diseñado te espera, con un mejor desempeño y funciones que te ayudarán a hacer más en línea.\n\nPor favor espera mientras actualizamos %s con tus + + Actualizando %s… + + Iniciar %s + + Migración completada + + Contraseñas + + + Para permitirlo: + + 1. Ve a los ajustes de Android + + Permisos]]> + + %1$s a activado]]> + + + Conexión segura + + + Conexión insegura + + ¿Seguro que quieres borrar todos los permisos de todos los sitios? + + ¿Seguro que quieres eliminar todos los permisos para este sitio? + + ¿Seguro que quieres eliminar este permiso para este sitio? + + Sin excepciones de sitio + + Artículos principales + + ¿Seguro que quiere eliminar este marcador? + + Agregar a los sitios principales + + Verificado por: %1$s + + Eliminar + + Editar + + ¿Seguro que quieres eliminar este inicio de sesión? + + Eliminar + + Opciones de inicio de sesión + + El campo de texto editable para la dirección web del inicio de sesión. + + El campo de texto editable para el nombre de usuario del inicio de sesión. + + El campo de texto editable para la contraseña del inicio de sesión. + + Guardar cambios al inicio de sesión. + + Descartar cambios + + Editar + + Se requiere contraseña + + Búsqueda por voz + + Habla ahora + + Ya existe un inicio de sesión con ese nombre de usuario + + + + Conectar con una cuenta de Firefox. + + Conectar otro dispositivo. + + Por favor, vuelve a autenticarte. + + Por favor habilita la sincronización de pestañas. + + No tienes ninguna pestaña abierta en Firefox en tus otros dispositivos. + + Ver una lista de pestañas de tus otros dispositivos. + + Iniciar sesión para sincronizar + + + + Límite de sitios frecuentes alcanzado + + Para agregar un nuevo sitio frecuente, elimina uno. Mantén presionado el sitio y selecciona eliminar. + + Vale, entendido + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9fef11b9a..d7167c0c5 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -370,7 +370,7 @@ Eksperymenty - Pozwala Mozilli instalować i zbierać dane dla funkcji eksperymentalnych + Pozwala Mozilli instalować i zbierać dane dla funkcji eksperymentalnych. Zgłaszanie awarii @@ -892,8 +892,6 @@ Automatycznie usuwa dane przeglądania po wybraniu „Zakończ” z głównego menu Automatycznie usuwa dane przeglądania po wybraniu „Zakończ” z głównego menu - - Historia przeglądania Zakończ diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 68c9d17b8..4561e4760 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -954,10 +954,14 @@ ไม่สามารถเข้าสู่ระบบ ความเป็นส่วนตัวอัตโนมัติ + + มาตรฐาน (ค่าเริ่มต้น) เข้มงวด (แนะนำ) เข้มงวด + + ปิดกั้นตัวติดตาม โฆษณา และป๊อปอัปมากขึ้น เว็บโหลดเร็วขึ้น แต่ฟังก์ชั่นบางอย่างอาจไม่ทำงาน @@ -1043,14 +1047,20 @@ เก็บข้อมูลของคุณไว้กับตัวคุณเอง %s ปกป้องคุณจากตัวติดตามที่พบบ่อยที่สุดซึ่งติดตามสิ่งที่คุณทำทางออนไลน์ เรียนรู้เพิ่มเติม + + มาตรฐาน (ค่าเริ่มต้น) สิ่งที่ถูกปิดกั้นโดยการป้องกันการติดตามแบบมาตรฐาน เข้มงวด + + ปิดกั้นตัวติดตาม โฆษณา และป๊อปอัปมากขึ้น เว็บโหลดเร็วขึ้น แต่ฟังก์ชั่นบางอย่างอาจไม่ทำงาน สิ่งที่ถูกปิดกั้นโดยการป้องกันการติดตามแบบเข้มงวด กำหนดเอง + + เลือกตัวติดตามหรือสคริปต์ที่ต้องการปิดกั้น สิ่งที่ถูกปิดกั้นโดยการป้องกันการติดตามแบบกำหนดเอง @@ -1365,6 +1375,8 @@ ลบ ตัวเลือกการเข้าสู่ระบบ + + บันทึกการเปลี่ยนแปลงเพื่อเข้าสู่ระบบ ละทิ้งการเปลี่ยนแปลง @@ -1376,12 +1388,19 @@ พูดเลย + + การเข้าสู่ระบบที่มีชื่อผู้ใช้นี้มีอยู่แล้ว + เชื่อมต่อกับบัญชี Firefox เชื่อมต่ออุปกรณ์อื่น + + คุณไม่มีแท็บใด ๆ ที่เปิดอยู่ใน Firefox บนอุปกรณ์อื่น ๆ ของคุณ + + ดูรายการแท็บจากอุปกรณ์อื่น ๆ ของคุณ ลงชื่อเข้าใช้เพื่อซิงค์ diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6f7d7c1e6..5b6a42ab9 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -940,7 +940,9 @@ Firefox Nightly 已接手原 Firefox Preview 功能 - Firefox Nightly 每天都會更新,當中包含實驗中的新功能,然而這些新功能可能會較不穩定。\n\n若想要有較穩定的使用體驗,請下載我們的 Beta 測試版。 + Firefox Nightly 每天都會更新,當中包含實驗中的新功能,然而這些新功能可能會較不穩定。\n\n + +若想要有較穩定的使用體驗,請下載我們的 Beta 測試版。 下載 Firefox for Android Beta 測試版 diff --git a/app/src/test/java/org/mozilla/fenix/browser/FenixSnackbarDelegateTest.kt b/app/src/test/java/org/mozilla/fenix/browser/FenixSnackbarDelegateTest.kt new file mode 100644 index 000000000..bbf50f419 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/browser/FenixSnackbarDelegateTest.kt @@ -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.browser + +import android.view.View +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.R +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.components.FenixSnackbar.Companion.LENGTH_SHORT + +class FenixSnackbarDelegateTest { + + @MockK private lateinit var view: View + @MockK(relaxed = true) private lateinit var snackbar: FenixSnackbar + private lateinit var delegate: FenixSnackbarDelegate + + @Before + fun setup() { + MockKAnnotations.init(this) + mockkObject(FenixSnackbar.Companion) + + delegate = FenixSnackbarDelegate(view) + every { + FenixSnackbar.make(view, LENGTH_SHORT, isDisplayedWithBrowserToolbar = true) + } returns snackbar + every { snackbar.setText(any()) } returns snackbar + every { snackbar.setAction(any(), any()) } returns snackbar + every { view.context.getString(R.string.app_name) } returns "Firefox" + every { view.context.getString(R.string.edit) } returns "Edit" + } + + @After + fun teardown() { + unmockkObject(FenixSnackbar.Companion) + } + + @Test + fun `show with no listener nor action`() { + delegate.show( + snackBarParentView = mockk(), + text = R.string.app_name, + duration = 0, + action = 0, + listener = null + ) + + verify { snackbar.setText("Firefox") } + verify(exactly = 0) { snackbar.setAction(any(), any()) } + verify { snackbar.show() } + } + + @Test + fun `show with listener but no action`() { + delegate.show( + snackBarParentView = mockk(), + text = R.string.app_name, + duration = 0, + action = 0, + listener = {} + ) + + verify { snackbar.setText("Firefox") } + verify(exactly = 0) { snackbar.setAction(any(), any()) } + verify { snackbar.show() } + } + + @Test + fun `show with action but no listener`() { + delegate.show( + snackBarParentView = mockk(), + text = R.string.app_name, + duration = 0, + action = R.string.edit, + listener = null + ) + + verify { snackbar.setText("Firefox") } + verify(exactly = 0) { snackbar.setAction(any(), any()) } + verify { snackbar.show() } + } + + @Test + fun `show with listener and action`() { + val listener = mockk<(View) -> Unit>(relaxed = true) + delegate.show( + snackBarParentView = mockk(), + text = R.string.app_name, + duration = 0, + action = R.string.edit, + listener = listener + ) + + verify { snackbar.setText("Firefox") } + verify { snackbar.setAction("Edit", withArg { + verify(exactly = 0) { listener(view) } + it.invoke() + verify { listener(view) } + }) } + verify { snackbar.show() } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt b/app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt index 517d1cb24..5f914bb54 100644 --- a/app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt +++ b/app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt @@ -10,7 +10,8 @@ import io.mockk.mockk import io.mockk.verify import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager -import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import org.mozilla.fenix.components.metrics.Event @@ -59,8 +60,8 @@ class TelemetrySessionObserverTest { triggeredByRedirect = false, triggeredByWebContent = false ) - Assert.assertEquals(sessionUrl, singleSessionObserver.originSessionUrl) - Assert.assertEquals(url, singleSessionObserver.redirectChain[0]) + assertEquals(sessionUrl, singleSessionObserver.originSessionUrl) + assertEquals(url, singleSessionObserver.redirectChain[0]) } @Test @@ -77,9 +78,9 @@ class TelemetrySessionObserverTest { triggeredByRedirect = false, triggeredByWebContent = false ) - Assert.assertEquals(url, singleSessionObserver.originSessionUrl) - Assert.assertEquals(url, singleSessionObserver.redirectChain[0]) - Assert.assertEquals(newUrl, singleSessionObserver.redirectChain[1]) + assertEquals(url, singleSessionObserver.originSessionUrl) + assertEquals(url, singleSessionObserver.redirectChain[0]) + assertEquals(newUrl, singleSessionObserver.redirectChain[1]) } @Test @@ -93,8 +94,8 @@ class TelemetrySessionObserverTest { triggeredByRedirect = false, triggeredByWebContent = false ) - Assert.assertNull(singleSessionObserver.originSessionUrl) - Assert.assertEquals(0, singleSessionObserver.redirectChain.size) + assertNull(singleSessionObserver.originSessionUrl) + assertEquals(0, singleSessionObserver.redirectChain.size) } @Test @@ -116,7 +117,7 @@ class TelemetrySessionObserverTest { redirectChain ) } - Assert.assertNull(singleSessionObserver.originSessionUrl) - Assert.assertEquals(0, singleSessionObserver.redirectChain.size) + assertNull(singleSessionObserver.originSessionUrl) + assertEquals(0, singleSessionObserver.redirectChain.size) } } diff --git a/app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt b/app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt index 2b1224ba4..7822da7b0 100644 --- a/app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt +++ b/app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.browser import androidx.lifecycle.LifecycleOwner +import io.mockk.every import io.mockk.mockk import io.mockk.verify import mozilla.components.browser.session.Session @@ -41,7 +42,34 @@ class UriOpenedObserverTest { observer.onSessionAdded(session) verify { session.register(observer.singleSessionObserver, owner) } + observer.onSessionSelected(session) + verify { session.register(observer.singleSessionObserver, owner) } + observer.onSessionRemoved(session) verify { session.unregister(observer.singleSessionObserver) } } + + @Test + fun `registers when all sessions are restored`() { + val session1: Session = mockk(relaxed = true) + val session2: Session = mockk(relaxed = true) + every { sessionManager.sessions } returns listOf(session1, session2) + + observer.onSessionsRestored() + + verify { session1.register(observer.singleSessionObserver, owner) } + verify { session2.register(observer.singleSessionObserver, owner) } + } + + @Test + fun `unregisters when all sessions are removed`() { + val session1: Session = mockk(relaxed = true) + val session2: Session = mockk(relaxed = true) + every { sessionManager.sessions } returns listOf(session1, session2) + + observer.onAllSessionsRemoved() + + verify { session1.unregister(observer.singleSessionObserver) } + verify { session2.unregister(observer.singleSessionObserver) } + } } diff --git a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt index 8764a7d68..cfe3ba62d 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt @@ -18,7 +18,7 @@ import mozilla.components.feature.pwa.WebAppShortcutManager class TestCore(context: Context) : Core(context) { override val engine = mockk(relaxed = true) { - every { this@mockk getProperty "settings" } returns mockk() + every { this@mockk getProperty "settings" } returns mockk(relaxed = true) } override val sessionManager = SessionManager(engine) override val store = mockk() diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarViewTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarViewTest.kt new file mode 100644 index 000000000..28871dade --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarViewTest.kt @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.toolbar + +import io.mockk.every +import io.mockk.mockk +import mozilla.components.browser.session.Session +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.ReaderState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.components.toolbar.BrowserToolbarView.Companion.getUrlForClipboard +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class BrowserToolbarViewTest { + + @Test + fun getUrlForClipboard() { + val customTabSession: Session = mockk() + every { customTabSession.url } returns "https://mozilla.org" + + // Custom tab + assertEquals("https://mozilla.org", getUrlForClipboard(mockk(), customTabSession)) + + // Regular tab + val regularTab = createTab(url = "http://firefox.com") + var store = BrowserStore(BrowserState(tabs = listOf(regularTab), selectedTabId = regularTab.id)) + assertEquals(regularTab.content.url, getUrlForClipboard(store)) + + // Reader Tab + val readerTab = createTab(url = "moz-extension://1234", + readerState = ReaderState(active = true, activeUrl = "https://blog.mozilla.org/123") + ) + store = BrowserStore(BrowserState(tabs = listOf(readerTab), selectedTabId = readerTab.id)) + assertEquals(readerTab.readerState.activeUrl, getUrlForClipboard(store)) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt new file mode 100644 index 000000000..dae9ff464 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt @@ -0,0 +1,42 @@ +/* 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.exceptions + +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsAdapterTest { + + private lateinit var interactor: ExceptionsInteractor + private lateinit var adapter: ExceptionsAdapter + + @Before + fun setup() { + interactor = mockk() + adapter = ExceptionsAdapter(interactor) + } + + @Test + fun `binds header and delete button with other adapter items`() = runBlockingTest { + adapter.updateData(listOf(mockk(), mockk())) + + assertEquals(4, adapter.itemCount) + assertEquals(ExceptionsHeaderViewHolder.LAYOUT_ID, adapter.getItemViewType(0)) + assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(1)) + assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(2)) + assertEquals(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, adapter.getItemViewType(3)) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt new file mode 100644 index 000000000..4adc07709 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt @@ -0,0 +1,82 @@ +/* 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.exceptions + +import android.text.Spannable +import android.text.method.LinkMovementMethod +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.view.isVisible +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.unmockkConstructor +import io.mockk.verify +import kotlinx.android.synthetic.main.component_exceptions.* +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +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.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsViewTest { + + private lateinit var container: ViewGroup + private lateinit var interactor: ExceptionsInteractor + private lateinit var exceptionsView: ExceptionsView + + @Before + fun setup() { + mockkConstructor(ExceptionsAdapter::class) + container = FrameLayout(testContext) + interactor = mockk() + + exceptionsView = ExceptionsView(container, interactor) + every { anyConstructed().updateData(any()) } just Runs + } + + @After + fun teardown() { + unmockkConstructor(ExceptionsAdapter::class) + } + + @Test + fun `binds exception text`() { + assertTrue(exceptionsView.exceptions_learn_more.movementMethod is LinkMovementMethod) + assertTrue(exceptionsView.exceptions_learn_more.text is Spannable) + assertEquals("Learn more", exceptionsView.exceptions_learn_more.text.toString()) + + every { interactor.onLearnMore() } just Runs + exceptionsView.exceptions_learn_more.performClick() + verify { interactor.onLearnMore() } + } + + @Test + fun `binds empty list to adapter`() { + exceptionsView.update(ExceptionsFragmentState(emptyList())) + + assertTrue(exceptionsView.exceptions_empty_view.isVisible) + assertFalse(exceptionsView.exceptions_list.isVisible) + verify { anyConstructed().updateData(emptyList()) } + } + + @Test + fun `binds list with items to adapter`() { + val items = listOf(mockk(), mockk()) + exceptionsView.update(ExceptionsFragmentState(items)) + + assertFalse(exceptionsView.exceptions_empty_view.isVisible) + assertTrue(exceptionsView.exceptions_list.isVisible) + verify { anyConstructed().updateData(items) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt new file mode 100644 index 000000000..28bb40dc2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt @@ -0,0 +1,43 @@ +/* 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.exceptions.viewholders + +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.view.ContextThemeWrapper +import io.mockk.mockk +import io.mockk.verify +import kotlinx.android.synthetic.main.delete_exceptions_button.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.R +import org.mozilla.fenix.exceptions.ExceptionsInteractor +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsDeleteButtonViewHolderTest { + + private lateinit var view: View + private lateinit var interactor: ExceptionsInteractor + private lateinit var viewHolder: ExceptionsDeleteButtonViewHolder + + @Before + fun setup() { + val appCompatContext = ContextThemeWrapper(testContext, R.style.NormalTheme) + view = LayoutInflater.from(appCompatContext) + .inflate(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, null) + interactor = mockk(relaxed = true) + viewHolder = ExceptionsDeleteButtonViewHolder(view, interactor) + } + + @Test + fun `calls onDeleteAll on click`() { + view.removeAllExceptions.performClick() + + verify { interactor.onDeleteAll() } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt new file mode 100644 index 000000000..7dd1ddb5e --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt @@ -0,0 +1,56 @@ +/* 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.exceptions.viewholders + +import android.view.LayoutInflater +import android.view.View +import io.mockk.mockk +import io.mockk.verify +import kotlinx.android.synthetic.main.exception_item.view.* +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +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.exceptions.ExceptionsInteractor +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsListItemViewHolderTest { + + private lateinit var view: View + private lateinit var interactor: ExceptionsInteractor + private lateinit var viewHolder: ExceptionsListItemViewHolder + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(ExceptionsListItemViewHolder.LAYOUT_ID, null) + interactor = mockk(relaxed = true) + viewHolder = ExceptionsListItemViewHolder(view, interactor) + } + + @Test + fun `bind url and icon`() { + val exception = object : TrackingProtectionException { + override val url = "https://example.com/icon.svg" + } + viewHolder.bind(exception) + + assertEquals(exception.url, view.webAddressView.text) + } + + @Test + fun `calls onDeleteOne on click`() { + val exception = object : TrackingProtectionException { + override val url = "https://example.com/icon.svg" + } + viewHolder.bind(exception) + view.delete_exception.performClick() + + verify { interactor.onDeleteOne(exception) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt new file mode 100644 index 000000000..51464285a --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt @@ -0,0 +1,105 @@ +/* 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.view.LayoutInflater +import android.view.View +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import io.mockk.verify +import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runBlockingTest +import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult +import mozilla.components.service.fxa.sharing.ShareableAccount +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +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.components.BackgroundServices +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingAutomaticSignInViewHolderTest { + + private lateinit var view: View + private lateinit var backgroundServices: BackgroundServices + private lateinit var snackbar: FenixSnackbar + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingAutomaticSignInViewHolder.LAYOUT_ID, null) + snackbar = mockk(relaxed = true) + mockkObject(FenixSnackbar.Companion) + + backgroundServices = testContext.components.backgroundServices + every { FenixSnackbar.make(any(), any(), any(), any()) } returns snackbar + } + + @After + fun teardown() { + unmockkObject(FenixSnackbar.Companion) + } + + @Test + fun `bind updates header text`() { + val holder = OnboardingAutomaticSignInViewHolder(view) + holder.bind(mockk { + every { email } returns "email@example.com" + }) + assertEquals( + "You are signed in as email@example.com on another Firefox browser on this phone. Would you like to sign in with this account?", + view.header_text.text + ) + assertTrue(view.turn_on_sync_button.isEnabled) + } + + @Test + fun `sign in on click`() = runBlocking { + val account = mockk { + every { email } returns "email@example.com" + } + every { + backgroundServices.accountManager.signInWithShareableAccountAsync(account) + } returns CompletableDeferred(SignInWithShareableAccountResult.Success) + + val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) + holder.bind(account) + holder.onClick(view.turn_on_sync_button) + + assertEquals("Signing in…", view.turn_on_sync_button.text) + assertFalse(view.turn_on_sync_button.isEnabled) + } + + @Test + fun `show error if sign in fails`() = runBlockingTest { + val account = mockk { + every { email } returns "email@example.com" + } + every { + backgroundServices.accountManager.signInWithShareableAccountAsync(account) + } returns CompletableDeferred(SignInWithShareableAccountResult.Failure) + + val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) + holder.bind(account) + holder.onClick(view.turn_on_sync_button) + + assertEquals("Yes, sign me in", view.turn_on_sync_button.text) + assertTrue(view.turn_on_sync_button.isEnabled) + verify { snackbar.setText("Failed to sign-in") } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFinishViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFinishViewHolderTest.kt new file mode 100644 index 000000000..80616898b --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFinishViewHolderTest.kt @@ -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.onboarding + +import android.view.LayoutInflater +import android.view.View +import io.mockk.mockk +import io.mockk.verify +import kotlinx.android.synthetic.main.onboarding_finish.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.OnboardingInteractor + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingFinishViewHolderTest { + + private lateinit var view: View + private lateinit var interactor: OnboardingInteractor + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingFinishViewHolder.LAYOUT_ID, null) + interactor = mockk(relaxed = true) + } + + @Test + fun `call interactor on click`() { + OnboardingFinishViewHolder(view, interactor) + + view.finish_button.performClick() + verify { interactor.onStartBrowsingClicked() } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingHeaderViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingHeaderViewHolderTest.kt new file mode 100644 index 000000000..f29821142 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingHeaderViewHolderTest.kt @@ -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.home.sessioncontrol.viewholders.onboarding + +import android.view.LayoutInflater +import android.view.View +import kotlinx.android.synthetic.main.onboarding_header.view.* +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.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingHeaderViewHolderTest { + + private lateinit var view: View + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingHeaderViewHolder.LAYOUT_ID, null) + } + + @Test + fun `bind header text`() { + OnboardingHeaderViewHolder(view) + + assertEquals("Welcome to Firefox Preview!", view.header_text.text) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolderTest.kt new file mode 100644 index 000000000..5fc02b4e7 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolderTest.kt @@ -0,0 +1,61 @@ +/* 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.view.LayoutInflater +import android.view.View +import androidx.navigation.NavController +import androidx.navigation.Navigation +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_manual_signin.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.home.HomeFragmentDirections + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingManualSignInViewHolderTest { + + private lateinit var view: View + private lateinit var navController: NavController + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingManualSignInViewHolder.LAYOUT_ID, null) + navController = mockk(relaxed = true) + + mockkStatic(Navigation::class) + every { Navigation.findNavController(view) } returns navController + } + + @After + fun teardown() { + unmockkStatic(Navigation::class) + } + + @Test + fun `bind header text`() { + OnboardingManualSignInViewHolder(view).bind() + + assertEquals("Get the most out of Firefox Preview.", view.header_text.text) + } + + @Test + fun `navigate on click`() { + OnboardingManualSignInViewHolder(view) + view.turn_on_sync_button.performClick() + + verify { navController.navigate(HomeFragmentDirections.actionGlobalTurnOnSync()) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingSectionHeaderViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingSectionHeaderViewHolderTest.kt new file mode 100644 index 000000000..bd74bc6b4 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingSectionHeaderViewHolderTest.kt @@ -0,0 +1,38 @@ +/* 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.view.LayoutInflater +import android.view.View +import kotlinx.android.synthetic.main.onboarding_section_header.view.* +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.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingSectionHeaderViewHolderTest { + + private lateinit var view: View + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingSectionHeaderViewHolder.LAYOUT_ID, null) + } + + @Test + fun `bind text`() { + val holder = OnboardingSectionHeaderViewHolder(view) + holder.bind { "Hello world" } + + assertEquals( + "Hello world", + view.section_header_text.text + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingToolbarPositionPickerViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingToolbarPositionPickerViewHolderTest.kt new file mode 100644 index 000000000..ef4361de2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingToolbarPositionPickerViewHolderTest.kt @@ -0,0 +1,65 @@ +/* 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.view.LayoutInflater +import android.view.View +import io.mockk.every +import io.mockk.mockk +import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +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.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.utils.Settings + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingToolbarPositionPickerViewHolderTest { + + private lateinit var view: View + private lateinit var settings: Settings + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID, null) + settings = mockk(relaxed = true) + + Settings.instance = settings + } + + @After + fun teardown() { + Settings.instance = null + } + + @Test + fun `bottom illustration should select corresponding radio button`() { + every { settings.shouldUseBottomToolbar } returns false + OnboardingToolbarPositionPickerViewHolder(view) + assertTrue(view.toolbar_top_radio_button.isChecked) + assertFalse(view.toolbar_bottom_radio_button.isChecked) + + view.toolbar_bottom_image.performClick() + assertFalse(view.toolbar_top_radio_button.isChecked) + assertTrue(view.toolbar_bottom_radio_button.isChecked) + } + + @Test + fun `top illustration should select corresponding radio button`() { + every { settings.shouldUseBottomToolbar } returns true + OnboardingToolbarPositionPickerViewHolder(view) + assertFalse(view.toolbar_top_radio_button.isChecked) + assertTrue(view.toolbar_bottom_radio_button.isChecked) + + view.toolbar_top_image.performClick() + assertTrue(view.toolbar_top_radio_button.isChecked) + assertFalse(view.toolbar_bottom_radio_button.isChecked) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetryTest.kt b/app/src/test/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetryTest.kt index 244e9d1cf..fd87665d4 100644 --- a/app/src/test/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetryTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetryTest.kt @@ -44,7 +44,6 @@ class BaseSearchTelemetryTest { engine.installWebExtension( id = id, url = resourceUrl, - allowContentMessaging = true, onSuccess = any(), onError = any() ) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index b3656d4c3..08a220aee 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "49.0.20200707131055" + const val VERSION = "50.0.20200708130551" }