1
0
Fork 0

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

master
blallo 2020-07-09 00:00:47 +02:00
commit acf33e754f
43 changed files with 1388 additions and 150 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -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()

View File

@ -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 =

View File

@ -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"
]
}

View File

@ -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",

View File

@ -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)
}

View File

@ -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,

View File

@ -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
}
}
}
}

View File

@ -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<AdapterItem, RecyclerView.ViewHolder>(DiffCallback) {
) : ListAdapter<ExceptionsAdapter.AdapterItem, RecyclerView.ViewHolder>(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<AdapterItem>() {
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
areContentsTheSame(oldItem, newItem)

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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) }
},

View File

@ -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

View File

@ -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/"

View File

@ -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,

View File

@ -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)
}

View File

@ -7,7 +7,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M13.36 11.98l4.38-4.38a0.95 0.95 0 0 0-1.34-1.34l-4.38 4.38-4.38-4.38A0.95 0.95 0 0 0 6.3 7.6l4.38 4.38-4.38 4.38a0.95 0.95 0 1 0 1.34 1.34l4.38-4.38 4.38 4.38a0.95 0.95 0 0 0 1.34-1.34l-4.38-4.38z"
android:fillColor="?primaryText" />
<path
android:fillColor="?primaryText"
android:pathData="m18.5,17.0962 l-5.1,-5.0962 5.1,-5.0962c0.3905,-0.3905 0.3905,-1.0133 0,-1.4038s-1.0133,-0.3905 -1.4038,0l-5.0962,5.1 -5.0962,-5.1c-0.3905,-0.3905 -1.0133,-0.3905 -1.4038,0s-0.3905,1.0133 0,1.4038l5.1,5.0962 -5.1,5.0962c-0.3905,0.3905 -0.3905,1.0133 0,1.4038s1.0133,0.3905 1.4038,0l5.0962,-5.1 5.0962,5.1c0.3905,0.3905 1.0133,0.3905 1.4038,0s0.3905,-1.0133 0,-1.4038z" />
</vector>

View File

@ -11,12 +11,15 @@
<string name="content_description_disable_private_browsing_button">عطّل التصفّح الخاص</string>
<!-- Placeholder text shown in the search bar before a user enters text -->
<string name="search_hint">ابحث أو أدخِل عنوانا</string>
<!-- No Open Tabs Message Header -->
<string name="no_open_tabs_header_2">لا ألسنة مفتوحة</string>
<!-- No Open Tabs Message Description -->
<string name="no_open_tabs_description">ستظهر الألسنة المفتوحة هنا.</string>
<!-- No Private Tabs Message Description -->
<string name="no_private_tabs_description">ستظهر الألسنة الخاصة هنا.</string>
<!-- Message announced to the user when tab tray is selected with 1 tab -->
<string name="open_tab_tray_single">لسان واحد مفتوح. انقر لتبديل الألسنة.</string>
<!-- Private Browsing -->
<!-- Title for private session option -->
<string name="private_browsing_title">أنت في جلسة خاصة</string>
@ -28,7 +31,7 @@
<!-- Delete session button to erase your history in a private session -->
<string name="private_browsing_delete_session">احذف الجلسة</string>
<!-- Private mode shortcut "contextual feature recommender" (CFR) -->
<!-- Private mode shortcut "contextual feature recommendation" (CFR) -->
<!-- Text for the main message -->
<string name="cfr_message">أضِف اختصارًا لفتح الألسنة الخاصة من الشاشة الرئيسية.</string>
<!-- Text for the positive button -->
@ -48,6 +51,10 @@
<!-- Browser Fragment -->
<!-- Content description (not visible, for screen readers etc.): Navigate to open tabs -->
<string name="browser_tabs_button">الألسنة المفتوحة</string>
<!-- Content description (not visible, for screen readers etc.): Navigate backward (browsing history) -->
<string name="browser_menu_back">السابق</string>
<!-- Content description (not visible, for screen readers etc.): Navigate forward (browsing history) -->
<string name="browser_menu_forward">التالي</string>
<!-- Content description (not visible, for screen readers etc.): Refresh current website -->
<string name="browser_menu_refresh">أعِد التحميل</string>
<!-- Content description (not visible, for screen readers etc.): Stop loading current website -->
@ -82,8 +89,6 @@
<string name="browser_menu_new_tab">لسان جديد</string>
<!-- Browser menu button that saves the current tab to a collection -->
<string name="browser_menu_save_to_collection_2">احفظ في التجميعة</string>
<!-- Browser menu button that opens a dialog to report issues with the current site -->
<string name="browser_menu_report_issue">أبلِغ عن مشكلة بالموقع</string>
<!-- Browser menu button that open a share menu to share the current site -->
<string name="browser_menu_share">شارِك</string>
<!-- Share menu title, displayed when a user is sharing their current site -->
@ -97,13 +102,17 @@
<!-- Browser menu text shown in custom tabs to indicate this is a Fenix tab
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="browser_menu_powered_by2">تدعمها %1$s</string>
<!-- Browser menu button to put the the current page in reader mode -->
<!-- Browser menu button to put the current page in reader mode -->
<string name="browser_menu_read">منظور القارئ</string>
<!-- Browser menu button to open the current page in an external app -->
<string name="browser_menu_open_app_link">افتح في التطبيق</string>
<!-- Browser menu button to configure reader mode appearance e.g. the used font type and size -->
<string name="browser_menu_read_appearance">المظهر</string>
<!-- Error message to show when the user tries to access a scheme not
handled by the app (Ex: blob, tel etc) -->
<string name="unknown_scheme_error_message">تعذّر الاتصال، لم أتعرّف على مخطّط المسار.</string>
<!-- Locale Settings Fragment -->
<!-- Content description for tick mark on selected language -->
<string name="a11y_selected_locale_content_description">اللغة المحددة</string>
@ -335,6 +344,11 @@
<!-- Preference for using bottom toolbar -->
<string name="preference_bottom_toolbar">بالأسفل</string>
<!-- Theme Preferences -->
<!-- Preference for using light theme -->
<string name="preference_light_theme">فاتحة</string>
<!-- Preference for using dark theme -->
<string name="preference_dark_theme">داكنة</string>
<!-- Preference for using using dark or light theme automatically set by battery -->
<string name="preference_auto_battery_theme">حسب وضع توفير الطاقة</string>
<!-- Preference for using following device theme -->

View File

@ -489,6 +489,11 @@
<!-- Text shown when no history exists -->
<string name="history_empty_message">Няма гісторыі</string>
<!-- Crashes -->
<!-- Title text displayed on the tab crash page. This first parameter is the name of the application (For example: Fenix) -->
<string name="tab_crash_title_2">Выбачайце. %1$s не можа загрузіць гэтую старонку.</string>
<!-- Description text displayed on the tab crash page -->
<string name="tab_crash_description">Вы можаце паспрабаваць аднавіць ці закрыць гэтую картку ніжэй.</string>
<!-- Send crash report checkbox text on the tab crash page -->
<string name="tab_crash_send_report">Адправіць справаздачу аб краху ў Mozilla</string>
<!-- Close tab button text on the tab crash page -->

View File

@ -19,6 +19,11 @@
<!-- No Private Tabs Message Description -->
<string name="no_private_tabs_description">Tus pestañas privadas aparecerán aquí.</string>
<!-- Message announced to the user when tab tray is selected with 1 tab -->
<string name="open_tab_tray_single">1 pestaña abierta. Tocar para cambiar de pestaña.</string>
<!-- Message announced to the user when tab tray is selected with 0 or 2+ tabs -->
<string name="open_tab_tray_plural">%1$s pestañas abiertas. Tocar para cambiar de pestaña.</string>
<!-- About content. The first parameter is the name of the application. (For example: Fenix) -->
<string name="about_content">%1$s es producido por Mozilla.</string>
@ -116,6 +121,8 @@
<string name="browser_menu_powered_by2">Patrocinado por %1$s</string>
<!-- Browser menu button to put the current page in reader mode -->
<string name="browser_menu_read">Vista de lectura</string>
<!-- Browser menu button content description to close reader mode and return the user to the regular browser -->
<string name="browser_menu_read_close">Salir de vista de lectura</string>
<!-- Browser menu button to open the current page in an external app -->
<string name="browser_menu_open_app_link">Abrir en la aplicación</string>
@ -288,6 +295,8 @@
<string name="preferences_sync_bookmarks">Marcadores</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">Inicios de sesión</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs_2">Pestañas abiertas</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">Cerrar sesión</string>
<!-- Preference displays and allows changing current FxA device name -->
@ -442,6 +451,24 @@
<string name="tabs_header_private_tabs_title">Pestañas privadas</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed -->
<string name="add_tab">Agregar pestaña</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed -->
<string name="add_private_tab">Agregar pestaña privada</string>
<!-- Text for the new tab button to indicate adding a new private tab in the tab -->
<string name="tab_drawer_fab_content">Privada</string>
<!-- Text shown as the title of the open tab tray -->
<string name="tab_tray_title">Pestañas abiertas</string>
<!-- Text shown in the menu for saving tabs to a collection -->
<string name="tab_tray_menu_item_save">Guardar en colección</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Compartir todas las pestañas</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Cerrar todas las pestañas</string>
<!-- Shortcut action to open new tab -->
<string name="tab_tray_menu_open_new_tab">Nueva pestaña</string>
<!-- Shortcut action to open the home screen -->
<string name="tab_tray_menu_home">Ir a Inicio</string>
<!-- Shortcut action to toggle private mode -->
<string name="tab_tray_menu_toggle">Alternar modo de pestaña</string>
<!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed -->
<string name="remove_tab_from_collection">Eliminar la pestaña de la colección</string>
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
@ -454,6 +481,8 @@
<string name="tabs_menu_close_all_tabs">Cerrar todas las pestañas</string>
<!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Compartir pestañas</string>
<!-- Open tabs menu item to save tabs to collection -->
<string name="tabs_menu_save_to_collection1">Guardar pestañas en la colección</string>
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Menú de pestaña</string>
<!-- Tab menu item to share the tab -->
@ -668,6 +697,10 @@
<string name="collections_header">Colecciones</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Menú de la colección</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Colecciona las cosas que te importan</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Agrupa búsquedas, sitios y pestañas similares para un acceso rápido después.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Seleccionar pestañas</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -697,6 +730,9 @@
<!-- Button to save currently selected tabs in the "select tabs" step of the collection creator-->
<string name="create_collection_save">Guardar</string>
<!-- Snackbar action to view the collection the user just created or updated -->
<string name="create_collection_view">Ver</string>
<!-- Default name for a new collection in "name new collection" step of the collection creator. %d is a placeholder for the number of collections-->
<string name="create_collection_default_name">Colección %d</string>
@ -845,8 +881,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Eliminar automáticamente los datos de navegación cuando selecciones &quot;Salir&quot; en el menú principal</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Eliminar automáticamente los datos de navegación cuando selecciones \&quot;Salir\&quot; en el menú principal</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Historial de navegación</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Salir</string>
@ -872,9 +906,26 @@
Sin embargo, puede ser menos estable. Descarga nuestro navegador beta para una experiencia más estable.</string>
<!-- text for firefox preview moving tip button. "Firefox for Android Beta" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_button_2">Obtener Firefox para Android Beta</string>
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly se movió</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description_preview_installed">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.</string>
<!-- text for firefox preview moving tip button -->
<string name="tip_firefox_preview_moved_button_preview_installed">Cambia al nuevo Nightly</string>
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_not_installed">Firefox Nightly se ha trasladado</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description_preview_not_installed">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.</string>
<!-- text for firefox preview moving tip button -->
<string name="tip_firefox_preview_moved_button_preview_not_installed">Obtén el nuevo Nightly</string>
<!-- Onboarding -->
<!-- Text for onboarding welcome message
The first parameter is the name of the app (e.g. Firefox Preview) -->
@ -910,11 +961,22 @@
<string name="onboarding_firefox_account_sync_is_on">Sync está activado</string>
<!-- text to display in the snackbar if automatic sign-in fails. user may try again -->
<string name="onboarding_firefox_account_automatic_signin_failed">Error al iniciar sesión</string>
<!-- text for the tracking protection onboarding card header -->
<string name="onboarding_tracking_protection_header_2">Privacidad automática</string>
<!-- text for the tracking protection card description
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_tracking_protection_description_2">La configuración de privacidad y seguridad bloquea los rastreadores, el malware y las compañías que te siguen.</string>
<!-- text for tracking protection radio button option for standard level of blocking -->
<string name="onboarding_tracking_protection_standard_button_2">Estándar (predeterminado)</string>
<!-- text for standard blocking option button description -->
<string name="onboarding_tracking_protection_standard_button_description_2">Bloquea menos rastreadores. Las páginas se cargarán normalmente.</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">Estricta (recomendada)</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_option">Estricto</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description_2">Bloquear más rastreadores, publicidad y ventanas emergentes. Las páginas cargan más rápido, pero pueden tener problemas de funcionalidad.</string>
<!-- text for the toolbar position card header
In English this is an idiom for "choose a side as in an argument or fight"
but it is ok to make this more literally about "choosing a position in a physical space -->
@ -999,14 +1061,22 @@
<string name="preference_enhanced_tracking_protection_explanation">Protege tus datos. %s te protege de muchos de los rastreadores más comunes que siguen lo que haces en línea.</string>
<!-- Text displayed that links to website about enhanced tracking protection -->
<string name="preference_enhanced_tracking_protection_explanation_learn_more">Saber más</string>
<!-- Preference for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_default_1">Estándar (predeterminado)</string>
<!-- Preference description for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_description_3">Bloquea menos rastreadores. Las páginas se cargarán normalmente.</string>
<!-- Accessibility text for the Standard protection information icon -->
<string name="preference_enhanced_tracking_protection_standard_info_button">Qué es lo que está bloqueado por la protección estándar contra el rastreo</string>
<!-- Preference for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict">Estricto</string>
<!-- Preference description for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict_description_2">Bloquear más rastreadores, publicidad y ventanas emergentes. Las páginas cargan más rápido, pero pueden tener problemas de funcionalidad.</string>
<!-- Accessibility text for the Strict protection information icon -->
<string name="preference_enhanced_tracking_protection_strict_info_button">Qué es lo que está bloqueado por la protección estricta contra el rastreo</string>
<!-- Preference for enhanced tracking protection for the custom protection settings -->
<string name="preference_enhanced_tracking_protection_custom">Personalizado</string>
<!-- Preference description for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_custom_description_2">Elige qué rastreadores y secuencias de comandos bloquear</string>
<!-- Accessibility text for the Strict protection information icon -->
<string name="preference_enhanced_tracking_protection_custom_info_button">Esto es lo que está bloqueado por la protección de rastreo estándar</string>
<!-- Header for categories that are being blocked by current Enhanced Tracking Protection settings -->
@ -1057,6 +1127,8 @@
<!-- Description of tracking content that can be blocked by Enhanced Tracking Protection -->
<string name="etp_tracking_content_description">Bloquea la carga de anuncios externos, vídeos y contenido que contenga código de rastreo. Puede afectar a la funcionalidad del sitio web.</string>
<!-- Enhanced Tracking Protection Onboarding Message shown in a dialog above the toolbar. The first parameter is the name of the application (For example: Fenix) -->
<string name="etp_onboarding_cfr_message">Cada vez que el escudo es morado, %s bloqueó rastreadores en un sitio. Toca para más información.</string>
<!-- Enhanced Tracking Protection message that protection is currently on for this site -->
<string name="etp_panel_on">Las protecciones están ACTIVAS para este sitio</string>
@ -1065,4 +1137,305 @@
<!-- Header for exceptions list for which sites enhanced tracking protection is always off -->
<string name="enhanced_tracking_protection_exceptions">La protección de rastreo mejorada está desactivada para estos sitios</string>
</resources>
<!-- Content description (not visible, for screen readers etc.): Navigate
back from ETP details (Ex: Tracking content) -->
<string name="etp_back_button_content_description">Regresar</string>
<!-- About page Your rights link text -->
<string name="about_your_rights">Tus derechos</string>
<!-- About page link text to open open source licenses screen -->
<string name="about_open_source_licenses">Bibliotecas de código abierto que utilizamos</string>
<!-- About page link text to open what's new link -->
<string name="about_whats_new">Novedades de %s</string>
<!-- Open source licenses page title
The first parameter is the app name -->
<string name="open_source_licenses_title">%s | Bibliotecas OSS</string>
<!-- About page link text to open support link -->
<string name="about_support">Ayuda</string>
<!-- About page link text to list of past crashes (like about:crashes on desktop) -->
<string name="about_crashes">Fallos</string>
<!-- About page link text to open privacy notice link -->
<string name="about_privacy_notice">Aviso de privacidad</string>
<!-- About page link text to open know your rights link -->
<string name="about_know_your_rights">Conoce tus derechos</string>
<!-- About page link text to open licensing information link -->
<string name="about_licensing_information">Información de licencia</string>
<!-- About page link text to open a screen with libraries that are used -->
<string name="about_other_open_source_libraries">Bibliotecas que usamos</string>
<!-- Toast shown to the user when they are activating the secret dev menu
The first parameter is number of long clicks left to enable the menu -->
<string name="about_debug_menu_toast_progress">Menú de depuración: quedan %1$d click(s) para habilitarlo</string>
<string name="about_debug_menu_toast_done">Menú de depuración habilitado</string>
<!-- Content description of the tab counter toolbar button when one tab is open -->
<string name="tab_counter_content_description_one_tab">1 pestaña</string>
<!-- Content description of the tab counter toolbar button when multiple tabs are open. First parameter will be replaced with the number of tabs (always more than one) -->
<string name="tab_counter_content_description_multi_tab">%d pestañas</string>
<!-- Browser long press popup menu -->
<!-- Copy the current url -->
<string name="browser_toolbar_long_press_popup_copy">Copiar</string>
<!-- Paste & go the text in the clipboard. '&amp;' is replaced with the ampersand symbol: & -->
<string name="browser_toolbar_long_press_popup_paste_and_go">Pegar e ir</string>
<!-- Paste the text in the clipboard -->
<string name="browser_toolbar_long_press_popup_paste">Pegar</string>
<!-- Snackbar message shown after an URL has been copied to clipboard. -->
<string name="browser_toolbar_url_copied_to_clipboard_snackbar">URL copiada al portapapeles</string>
<!-- Title text for the Add To Homescreen dialog -->
<string name="add_to_homescreen_title">Agregar a la pantalla de inicio</string>
<!-- Cancel button text for the Add to Homescreen dialog -->
<string name="add_to_homescreen_cancel">Cancelar</string>
<!-- Add button text for the Add to Homescreen dialog -->
<string name="add_to_homescreen_add">Agregar</string>
<!-- Continue to website button text for the first-time Add to Homescreen dialog -->
<string name="add_to_homescreen_continue">Continuar al sitio web</string>
<!-- Placeholder text for the TextView in the Add to Homescreen dialog -->
<string name="add_to_homescreen_text_placeholder">Nombre del acceso directo</string>
<!-- Describes the add to homescreen functionality -->
<string name="add_to_homescreen_description">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.</string>
<!-- Preference for managing the settings for logins and passwords in Fenix -->
<string name="preferences_passwords_logins_and_passwords">Inicios de sesión y contraseñas</string>
<!-- Preference for managing the saving of logins and passwords in Fenix -->
<string name="preferences_passwords_save_logins">Guardar inicios de sesión y contraseñas</string>
<!-- Preference option for asking to save passwords in Fenix -->
<string name="preferences_passwords_save_logins_ask_to_save">Preguntar para guardar</string>
<!-- Preference option for never saving passwords in Fenix -->
<string name="preferences_passwords_save_logins_never_save">Nunca guardar</string>
<!-- Preference for autofilling saved logins in Fenix -->
<string name="preferences_passwords_autofill">Autocompletar</string>
<!-- Preference for syncing saved logins in Fenix -->
<string name="preferences_passwords_sync_logins">Sincronizar inicios de sesión</string>
<!-- Syncing saved logins in Fenix is on -->
<string name="preferences_passwords_sync_logins_on">Habilitado</string>
<!-- Syncing saved logins in Fenix is off -->
<string name="preferences_passwords_sync_logins_off">Deshabilitado</string>
<!-- Syncing saved logins in Fenix needs reconnect to sync -->
<string name="preferences_passwords_sync_logins_reconnect">Reconectar</string>
<!-- Syncing saved logins in Fenix needs login -->
<string name="preferences_passwords_sync_logins_sign_in">Iniciar sesión en Sync</string>
<!-- Preference to access list of saved logins -->
<string name="preferences_passwords_saved_logins">Inicios de sesión guardados</string>
<!-- Description of empty list of saved passwords. Placeholder is replaced with app name. -->
<string name="preferences_passwords_saved_logins_description_empty_text">Los inicios de sesión que guardes o sincronices en %s se mostrarán aquí</string>
<!-- Preference to access list of saved logins -->
<string name="preferences_passwords_saved_logins_description_empty_learn_more_link">Saber más acerca de Sync.</string>
<!-- Preference to access list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions">Excepciones</string>
<!-- Empty description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description_empty">Los inicios de sesión y contraseñas no guardados se mostrarán aquí.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Los inicios de sesión y contraseñas no serán guardados para estos sitios.</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Buscar inicios de sesión</string>
<!-- Option to sort logins list A-Z, alphabetically -->
<string name="preferences_passwords_saved_logins_alphabetically">Alfabéticamente</string>
<!-- Option to sort logins list by most recently used -->
<string name="preferences_passwords_saved_logins_recently_used">Usados recientemente</string>
<!-- The header for the site that a login is for -->
<string name="preferences_passwords_saved_logins_site">Sitio</string>
<!-- The header for the username for a login -->
<string name="preferences_passwords_saved_logins_username">Nombre de usuario</string>
<!-- The header for the password for a login -->
<string name="preferences_passwords_saved_logins_password">Contraseña</string>
<!-- Message displayed in security prompt to reenter a secret pin to access saved logins -->
<string name="preferences_passwords_saved_logins_enter_pin">Vuelve a ingresar tu PIN</string>
<!-- Message displayed in security prompt to access saved logins -->
<string name="preferences_passwords_saved_logins_enter_pin_description">Desbloquear para ver los inicios de sesión guardados</string>
<!-- Message displayed when a connection is insecure and we detect the user is entering a password -->
<string name="logins_insecure_connection_warning">Esta conexión no es segura. Los inicios de sesión ingresados aquí pueden verse comprometidos.</string>
<!-- Learn more link that will link to a page with more information displayed when a connection is insecure and we detect the user is entering a password -->
<string name="logins_insecure_connection_warning_learn_more">Saber más</string>
<!-- Prompt message displayed when Fenix detects a user has entered a password and user decides if Fenix should save it. The first parameter is the name of the application (For example: Fenix) -->
<string name="logins_doorhanger_save">¿Quieres que %s guarde este inicio de sesión?</string>
<!-- Positive confirmation that Fenix should save the new or updated login -->
<string name="logins_doorhanger_save_confirmation">Guardar</string>
<!-- Negative confirmation that Fenix should not save the new or updated login -->
<string name="logins_doorhanger_save_dont_save">No guardar</string>
<!-- Shown in snackbar to tell user that the password has been copied -->
<string name="logins_password_copied">Contraseña copiada al portapapeles</string>
<!-- Shown in snackbar to tell user that the username has been copied -->
<string name="logins_username_copied">Nombre de usuario copiado al portapapeles</string>
<!-- Shown in snackbar to tell user that the site has been copied -->
<string name="logins_site_copied">Sitio copiado al portapapeles</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a password in logins-->
<string name="saved_logins_copy_password">Copiar contraseña</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a username in logins -->
<string name="saved_login_copy_username">Copiar nombre de usuario</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Copiar sitio</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Mostrar contraseña</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->
<string name="saved_login_hide_password">Ocultar contraseña</string>
<!-- Message displayed in biometric prompt displayed for authentication before allowing users to view their logins -->
<string name="logins_biometric_prompt_message">Desbloquear para ver tus inicios de sesión guardados</string>
<!-- Title of warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_title">Asegurar tus usuarios y contraseñas</string>
<!-- Message of warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_message">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.</string>
<!-- Negative button to ignore warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_later">Más tarde</string>
<!-- Positive button to send users to set up a pin of warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_set_up_now">Configurar ahora</string>
<!-- Title of PIN verification dialog to direct users to re-enter their device credentials to access their logins -->
<string name="logins_biometric_prompt_message_pin">Desbloquear tu dispositivo</string>
<!-- Title for Accessibility Force Enable Zoom Preference -->
<string name="preference_accessibility_force_enable_zoom">Zoom en todos los sitios</string>
<!-- Summary for Accessibility Force Enable Zoom Preference -->
<string name="preference_accessibility_force_enable_zoom_summary">Habilitar para permitir pellizcar y hacer zoom, incluso en sitios web que previenen este gesto.</string>
<!-- Saved logins sorting strategy menu item -by name- (if selected, it will sort saved logins alphabetically) -->
<string name="saved_logins_sort_strategy_alphabetically">Nombre (A-Z)</string>
<!-- Saved logins sorting strategy menu item -by last used- (if selected, it will sort saved logins by last used) -->
<string name="saved_logins_sort_strategy_last_used">Último uso</string>
<!-- Content description (not visible, for screen readers etc.): Sort saved logins dropdown menu chevron icon -->
<string name="saved_logins_menu_dropdown_chevron_icon_content_description">Ordenar menú de inicios de sesión</string>
<!-- Title of the Add search engine screen -->
<string name="search_engine_add_custom_search_engine_title">Agregar motor de búsqueda</string>
<!-- Title of the Edit search engine screen -->
<string name="search_engine_edit_custom_search_engine_title">Editar motor de búsqueda</string>
<!-- Content description (not visible, for screen readers etc.): Title for the button to add a search engine in the action bar -->
<string name="search_engine_add_button_content_description">Agregar</string>
<!-- Content description (not visible, for screen readers etc.): Title for the button to save a search engine in the action bar -->
<string name="search_engine_add_custom_search_engine_edit_button_content_description">Guardar</string>
<!-- Text for the menu button to edit a search engine -->
<string name="search_engine_edit">Editar</string>
<!-- Text for the menu button to delete a search engine -->
<string name="search_engine_delete">Eliminar</string>
<!-- Text for the button to create a custom search engine on the Add search engine screen -->
<string name="search_add_custom_engine_label_other">Otro</string>
<!-- Placeholder text shown in the Search Engine Name TextField before a user enters text -->
<string name="search_add_custom_engine_name_hint">Nombre</string>
<!-- Placeholder text shown in the Search String TextField before a user enters text -->
<string name="search_add_custom_engine_search_string_hint">Cadena de búsqueda a usar</string>
<!-- Description text for the Search String TextField. The %s is part of the string -->
<string name="search_add_custom_engine_search_string_example">Reemplazar la consulta con “%s”. Ejemplo:\n https://www.google.com/search?q=%s</string>
<!-- Text for the button to learn more about adding a custom search engine -->
<string name="search_add_custom_engine_learn_more_label">Saber más</string>
<!-- Accessibility description for the form in which details about the custom search engine are entered -->
<string name="search_add_custom_engine_form_description">Detalles del motor de búsqueda personalizado</string>
<!-- Accessibility description for the 'Learn more' link -->
<string name="search_add_custom_engine_learn_more_description">Enlace para saber más</string>
<!-- Text shown when a user leaves the name field empty -->
<string name="search_add_custom_engine_error_empty_name">Introducir nombre de motor de búsqueda</string>
<!-- Text shown when a user tries to add a search engine that already exists -->
<string name="search_add_custom_engine_error_existing_name">Ya existe un motor de búsqueda con nombre “%s”.</string>
<!-- Text shown when a user leaves the search string field empty -->
<string name="search_add_custom_engine_error_empty_search_string">Ingresa una cadena de búsqueda</string>
<!-- Text shown when a user leaves out the required template string -->
<string name="search_add_custom_engine_error_missing_template">Verifica que la cadena de búsqueda corresponde con el formato de ejemplo</string>
<!-- Text shown when we aren't able to validate the custom search query. The first parameter is the url of the custom search engine -->
<string name="search_add_custom_engine_error_cannot_reach">Error al conectarse a “%s”</string>
<!-- Text shown when a user creates a new search engine -->
<string name="search_add_custom_engine_success_message">Se creó %s</string>
<!-- Text shown when a user successfully edits a custom search engine -->
<string name="search_edit_custom_engine_success_message">Se guardó %s</string>
<!-- Text shown when a user successfully deletes a custom search engine -->
<string name="search_delete_search_engine_success_message">Se eliminó %s</string>
<!-- Title text shown for the migration screen to the new browser. Placeholder replaced with app name -->
<string name="migration_title">Bienvenido a un %s completamente nuevo</string>
<!-- Description text followed by a list of things migrating (e.g. Bookmarks, History). Placeholder replaced with app name-->
<string name="migration_description">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</string>
<!-- Text on the disabled button while in progress. Placeholder replaced with app name -->
<string name="migration_updating_app_button_text">Actualizando %s…</string>
<!-- Text on the enabled button. Placeholder replaced with app name-->
<string name="migration_update_app_button">Iniciar %s</string>
<!-- Accessibility description text for a completed migration item -->
<string name="migration_icon_description">Migración completada</string>
<!--Text on list of migrated items (e.g. Settings, History, etc.)-->
<string name="migration_text_passwords">Contraseñas</string>
<!-- Heading for the instructions to allow a permission -->
<string name="phone_feature_blocked_intro">Para permitirlo:</string>
<!-- First step for the allowing a permission -->
<string name="phone_feature_blocked_step_settings">1. Ve a los ajustes de Android</string>
<!-- Second step for the allowing a permission -->
<string name="phone_feature_blocked_step_permissions"><![CDATA[2. Toca en <b>Permisos</b>]]></string>
<!-- Third step for the allowing a permission (Fore example: Camera) -->
<string name="phone_feature_blocked_step_feature"><![CDATA[3. Cambia <b>%1$s</b> a activado]]></string>
<!-- Label that indicates a site is using a secure connection -->
<string name="quick_settings_sheet_secure_connection">Conexión segura</string>
<!-- Label that indicates a site is using a insecure connection -->
<string name="quick_settings_sheet_insecure_connection">Conexión insegura</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete all the permissions for all sites-->
<string name="confirm_clear_permissions_on_all_sites">¿Seguro que quieres borrar todos los permisos de todos los sitios?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete all the permissions for a site-->
<string name="confirm_clear_permissions_site">¿Seguro que quieres eliminar todos los permisos para este sitio?</string>
<!-- Confirmation message for a dialog confirming if the user wants to set default value a permission for a site-->
<string name="confirm_clear_permission_site">¿Seguro que quieres eliminar este permiso para este sitio?</string>
<!-- label shown when there are not site exceptions to show in the site exception settings -->
<string name="no_site_exceptions">Sin excepciones de sitio</string>
<!-- Label for the Pocket default top site -->
<string name="pocket_top_articles">Artículos principales</string>
<!-- Bookmark deletion confirmation -->
<string name="bookmark_deletion_confirmation">¿Seguro que quiere eliminar este marcador?</string>
<!-- Browser menu button that adds a top site to the home fragment -->
<string name="browser_menu_add_to_top_sites">Agregar a los sitios principales</string>
<!-- text shown before the issuer name to indicate who its verified by, parameter is the name of
the certificate authority that verified the ticket-->
<string name="certificate_info_verified_by">Verificado por: %1$s</string>
<!-- Login overflow menu delete button -->
<string name="login_menu_delete_button">Eliminar</string>
<!-- Login overflow menu edit button -->
<string name="login_menu_edit_button">Editar</string>
<!-- Message in delete confirmation dialog for logins -->
<string name="login_deletion_confirmation">¿Seguro que quieres eliminar este inicio de sesión?</string>
<!-- Positive action of a dialog asking to delete -->
<string name="dialog_delete_positive">Eliminar</string>
<!-- The saved login options menu description. -->
<string name="login_options_menu">Opciones de inicio de sesión</string>
<!-- The editable text field for a login's web address. -->
<string name="saved_login_hostname_description">El campo de texto editable para la dirección web del inicio de sesión.</string>
<!-- The editable text field for a login's username. -->
<string name="saved_login_username_description">El campo de texto editable para el nombre de usuario del inicio de sesión.</string>
<!-- The editable text field for a login's password. -->
<string name="saved_login_password_description">El campo de texto editable para la contraseña del inicio de sesión.</string>
<!-- The button description to save changes to an edited login. -->
<string name="save_changes_to_login">Guardar cambios al inicio de sesión.</string>
<!-- The button description to discard changes to an edited login. -->
<string name="discard_changes">Descartar cambios</string>
<!-- The page title for editing a saved login. -->
<string name="edit">Editar</string>
<!-- The error message in edit login view when password field is blank. -->
<string name="saved_login_password_required">Se requiere contraseña</string>
<!-- Voice search button content description -->
<string name="voice_search_content_description">Búsqueda por voz</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Habla ahora</string>
<!-- The error message in edit login view when a duplicate username exists. -->
<string name="saved_login_duplicate">Ya existe un inicio de sesión con ese nombre de usuario</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Conectar con una cuenta de Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Conectar otro dispositivo.</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Por favor, vuelve a autenticarte.</string>
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
<string name="synced_tabs_enable_tab_syncing">Por favor habilita la sincronización de pestañas.</string>
<!-- Text displayed when user has no tabs that have been synced -->
<string name="synced_tabs_no_tabs">No tienes ninguna pestaña abierta en Firefox en tus otros dispositivos.</string>
<!-- Text displayed in the synced tabs screen when a user is not signed in to Firefox Sync describing Synced Tabs -->
<string name="synced_tabs_sign_in_message">Ver una lista de pestañas de tus otros dispositivos.</string>
<!-- Text displayed on a button in the synced tabs screen to link users to sign in when a user is not signed in to Firefox Sync -->
<string name="synced_tabs_sign_in_button">Iniciar sesión para sincronizar</string>
<!-- Top Sites -->
<!-- Title text displayed in the dialog when top sites limit is reached. -->
<string name="top_sites_max_limit_title">Límite de sitios frecuentes alcanzado</string>
<!-- Content description text displayed in the dialog when top sites limit is reached. -->
<string name="top_sites_max_limit_content">Para agregar un nuevo sitio frecuente, elimina uno. Mantén presionado el sitio y selecciona eliminar.</string>
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Vale, entendido</string>
</resources>

View File

@ -370,7 +370,7 @@
<!-- Title for experiments preferences -->
<string name="preference_experiments">Eksperymenty</string>
<!-- Summary for experiments preferences -->
<string name="preference_experiments_summary">Pozwala Mozilli instalować i zbierać dane dla funkcji eksperymentalnych</string>
<string name="preference_experiments_summary">Pozwala Mozilli instalować i zbierać dane dla funkcji eksperymentalnych.</string>
<!-- Preference switch for crash reporter -->
<string name="preferences_crash_reporter">Zgłaszanie awarii</string>
<!-- Preference switch for Mozilla location service -->
@ -892,8 +892,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Automatycznie usuwa dane przeglądania po wybraniu „Zakończ” z głównego menu</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Automatycznie usuwa dane przeglądania po wybraniu „Zakończ” z głównego menu</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Historia przeglądania</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Zakończ</string>

View File

@ -954,10 +954,14 @@
<string name="onboarding_firefox_account_automatic_signin_failed">ไม่สามารถเข้าสู่ระบบ</string>
<!-- text for the tracking protection onboarding card header -->
<string name="onboarding_tracking_protection_header_2">ความเป็นส่วนตัวอัตโนมัติ</string>
<!-- text for tracking protection radio button option for standard level of blocking -->
<string name="onboarding_tracking_protection_standard_button_2">มาตรฐาน (ค่าเริ่มต้น)</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">เข้มงวด (แนะนำ)</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_option">เข้มงวด</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description_2">ปิดกั้นตัวติดตาม โฆษณา และป๊อปอัปมากขึ้น เว็บโหลดเร็วขึ้น แต่ฟังก์ชั่นบางอย่างอาจไม่ทำงาน</string>
<!-- text for the toolbar position card header
In English this is an idiom for "choose a side as in an argument or fight"
but it is ok to make this more literally about "choosing a position in a physical space -->
@ -1043,14 +1047,20 @@
<string name="preference_enhanced_tracking_protection_explanation">เก็บข้อมูลของคุณไว้กับตัวคุณเอง %s ปกป้องคุณจากตัวติดตามที่พบบ่อยที่สุดซึ่งติดตามสิ่งที่คุณทำทางออนไลน์</string>
<!-- Text displayed that links to website about enhanced tracking protection -->
<string name="preference_enhanced_tracking_protection_explanation_learn_more">เรียนรู้เพิ่มเติม</string>
<!-- Preference for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_default_1">มาตรฐาน (ค่าเริ่มต้น)</string>
<!-- Accessibility text for the Standard protection information icon -->
<string name="preference_enhanced_tracking_protection_standard_info_button">สิ่งที่ถูกปิดกั้นโดยการป้องกันการติดตามแบบมาตรฐาน</string>
<!-- Preference for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict">เข้มงวด</string>
<!-- Preference description for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict_description_2">ปิดกั้นตัวติดตาม โฆษณา และป๊อปอัปมากขึ้น เว็บโหลดเร็วขึ้น แต่ฟังก์ชั่นบางอย่างอาจไม่ทำงาน</string>
<!-- Accessibility text for the Strict protection information icon -->
<string name="preference_enhanced_tracking_protection_strict_info_button">สิ่งที่ถูกปิดกั้นโดยการป้องกันการติดตามแบบเข้มงวด</string>
<!-- Preference for enhanced tracking protection for the custom protection settings -->
<string name="preference_enhanced_tracking_protection_custom">กำหนดเอง</string>
<!-- Preference description for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_custom_description_2">เลือกตัวติดตามหรือสคริปต์ที่ต้องการปิดกั้น</string>
<!-- Accessibility text for the Strict protection information icon -->
<string name="preference_enhanced_tracking_protection_custom_info_button">สิ่งที่ถูกปิดกั้นโดยการป้องกันการติดตามแบบกำหนดเอง</string>
<!-- Header for categories that are being blocked by current Enhanced Tracking Protection settings -->
@ -1365,6 +1375,8 @@
<string name="dialog_delete_positive">ลบ</string>
<!-- The saved login options menu description. -->
<string name="login_options_menu">ตัวเลือกการเข้าสู่ระบบ</string>
<!-- The button description to save changes to an edited login. -->
<string name="save_changes_to_login">บันทึกการเปลี่ยนแปลงเพื่อเข้าสู่ระบบ</string>
<!-- The button description to discard changes to an edited login. -->
<string name="discard_changes">ละทิ้งการเปลี่ยนแปลง</string>
<!-- The page title for editing a saved login. -->
@ -1376,12 +1388,19 @@
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">พูดเลย</string>
<!-- The error message in edit login view when a duplicate username exists. -->
<string name="saved_login_duplicate">การเข้าสู่ระบบที่มีชื่อผู้ใช้นี้มีอยู่แล้ว</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">เชื่อมต่อกับบัญชี Firefox</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">เชื่อมต่ออุปกรณ์อื่น</string>
<!-- Text displayed when user has no tabs that have been synced -->
<string name="synced_tabs_no_tabs">คุณไม่มีแท็บใด ๆ ที่เปิดอยู่ใน Firefox บนอุปกรณ์อื่น ๆ ของคุณ</string>
<!-- Text displayed in the synced tabs screen when a user is not signed in to Firefox Sync describing Synced Tabs -->
<string name="synced_tabs_sign_in_message">ดูรายการแท็บจากอุปกรณ์อื่น ๆ ของคุณ</string>
<!-- Text displayed on a button in the synced tabs screen to link users to sign in when a user is not signed in to Firefox Sync -->
<string name="synced_tabs_sign_in_button">ลงชื่อเข้าใช้เพื่อซิงค์</string>

View File

@ -940,7 +940,9 @@
<!-- text for firefox preview moving tip header "Firefox Preview" and "Firefox Nightly" are intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header">Firefox Nightly 已接手原 Firefox Preview 功能</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description">Firefox Nightly 每天都會更新,當中包含實驗中的新功能,然而這些新功能可能會較不穩定。\n\n若想要有較穩定的使用體驗請下載我們的 Beta 測試版。</string>
<string name="tip_firefox_preview_moved_description">Firefox Nightly 每天都會更新,當中包含實驗中的新功能,然而這些新功能可能會較不穩定。\n\n
若想要有較穩定的使用體驗,請下載我們的 Beta 測試版。</string>
<!-- text for firefox preview moving tip button. "Firefox for Android Beta" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_button_2">下載 Firefox for Android Beta 測試版</string>

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.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() }
}
}

View File

@ -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)
}
}

View File

@ -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) }
}
}

View File

@ -18,7 +18,7 @@ import mozilla.components.feature.pwa.WebAppShortcutManager
class TestCore(context: Context) : Core(context) {
override val engine = mockk<Engine>(relaxed = true) {
every { this@mockk getProperty "settings" } returns mockk<Settings>()
every { this@mockk getProperty "settings" } returns mockk<Settings>(relaxed = true)
}
override val sessionManager = SessionManager(engine)
override val store = mockk<BrowserStore>()

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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<ExceptionsAdapter>().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<ExceptionsAdapter>().updateData(emptyList()) }
}
@Test
fun `binds list with items to adapter`() {
val items = listOf<TrackingProtectionException>(mockk(), mockk())
exceptionsView.update(ExceptionsFragmentState(items))
assertFalse(exceptionsView.exceptions_empty_view.isVisible)
assertTrue(exceptionsView.exceptions_list.isVisible)
verify { anyConstructed<ExceptionsAdapter>().updateData(items) }
}
}

View File

@ -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() }
}
}

View File

@ -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) }
}
}

View File

@ -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<ShareableAccount> {
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<ShareableAccount> {
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") }
}
}

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.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() }
}
}

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.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)
}
}

View File

@ -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()) }
}
}

View File

@ -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
)
}
}

View File

@ -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)
}
}

View File

@ -44,7 +44,6 @@ class BaseSearchTelemetryTest {
engine.installWebExtension(
id = id,
url = resourceUrl,
allowContentMessaging = true,
onSuccess = any(),
onError = any()
)

View File

@ -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"
}