From a47a51639b47f8b5fcff18493af86ad073df6499 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Wed, 27 Apr 2022 12:43:44 +0000 Subject: [PATCH 001/160] Update GeckoView (Nightly) to 101.0.20220427094429. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 5686fc92976..96f3feae6ce 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220426094609" + const val version = "101.0.20220427094429" /** * GeckoView channel From 36b5f5b0f31a637f60d1b167c3a1d77370631621 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Thu, 28 Apr 2022 01:09:06 +0000 Subject: [PATCH 002/160] Update GeckoView (Nightly) to 101.0.20220427213431. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 96f3feae6ce..cd21ad4144d 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220427094429" + const val version = "101.0.20220427213431" /** * GeckoView channel From 2dded847668074d17b0a8050cecc9754a7ad9d80 Mon Sep 17 00:00:00 2001 From: mcarare Date: Wed, 20 Apr 2022 14:57:46 +0300 Subject: [PATCH 003/160] For #12028: Disable click for disabled choice items. --- .../feature/prompts/dialog/ChoiceAdapter.kt | 6 + .../dialog/ChoiceDialogFragmentTest.kt | 186 ++++++++++++++++++ 2 files changed, 192 insertions(+) diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/ChoiceAdapter.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/ChoiceAdapter.kt index ea9f20e64a5..50882d1b3ea 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/ChoiceAdapter.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/ChoiceAdapter.kt @@ -122,6 +122,8 @@ internal class ChoiceAdapter( fragment.onSelect(actualChoice) labelView.toggle() } + } else { + itemView.isClickable = false } } } @@ -148,6 +150,8 @@ internal class ChoiceAdapter( } labelView.toggle() } + } else { + itemView.isClickable = false } } } @@ -167,6 +171,8 @@ internal class ChoiceAdapter( val actualChoice = labelView.choice fragment.onSelect(actualChoice) } + } else { + itemView.isClickable = false } } } diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/ChoiceDialogFragmentTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/ChoiceDialogFragmentTest.kt index 53f8f2d70a0..4dc9970e7f6 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/ChoiceDialogFragmentTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/ChoiceDialogFragmentTest.kt @@ -481,6 +481,192 @@ class ChoiceDialogFragmentTest { } } + @Test + fun `disabled single choice item is not clickable`() { + val choices = arrayOf( + Choice(id = "item1", label = "Enabled choice"), + Choice(id = "item2", enable = false, label = "Disabled choice") + ) + + val fragment = + spy(newInstance(choices, "sessionId", "uid", false, SINGLE_CHOICE_DIALOG_TYPE)) + fragment.feature = mockFeature + doReturn(appCompatContext).`when`(fragment).requireContext() + doNothing().`when`(fragment).dismiss() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val adapter = dialog.findViewById(R.id.recyclerView).adapter as ChoiceAdapter + + // test disabled item + val disabledItemViewHolder = + adapter.onCreateViewHolder(LinearLayout(testContext), adapter.getItemViewType(1)) + as SingleViewHolder + + adapter.bindViewHolder(disabledItemViewHolder, 1) + + with(disabledItemViewHolder) { + assertEquals(labelView.text, "Disabled choice") + assertFalse(labelView.isEnabled) + assertFalse(itemView.isClickable) + } + } + + @Test + fun `enabled single choice item is clickable`() { + val choices = arrayOf( + Choice(id = "item1", label = "Enabled choice"), + Choice(id = "item2", enable = false, label = "Disabled choice") + ) + + val fragment = spy(newInstance(choices, "sessionId", "uid", false, SINGLE_CHOICE_DIALOG_TYPE)) + fragment.feature = mockFeature + doReturn(appCompatContext).`when`(fragment).requireContext() + doNothing().`when`(fragment).dismiss() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val adapter = dialog.findViewById(R.id.recyclerView).adapter as ChoiceAdapter + + // test enabled item + val enabledItemViewHolder = + adapter.onCreateViewHolder(LinearLayout(testContext), adapter.getItemViewType(0)) as SingleViewHolder + + adapter.bindViewHolder(enabledItemViewHolder, 0) + + with(enabledItemViewHolder) { + assertEquals(labelView.text, "Enabled choice") + assertTrue(labelView.isEnabled) + assertTrue(itemView.isClickable) + } + } + + @Test + fun `disabled multiple choice item is not clickable`() { + val choices = arrayOf( + Choice(id = "item1", label = "Enabled choice"), + Choice(id = "item2", enable = false, label = "Disabled choice") + ) + + val fragment = + spy(newInstance(choices, "sessionId", "uid", false, MULTIPLE_CHOICE_DIALOG_TYPE)) + fragment.feature = mockFeature + doReturn(appCompatContext).`when`(fragment).requireContext() + doNothing().`when`(fragment).dismiss() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val adapter = dialog.findViewById(R.id.recyclerView).adapter as ChoiceAdapter + + // test disabled item + val disabledItemViewHolder = + adapter.onCreateViewHolder(LinearLayout(testContext), adapter.getItemViewType(1)) + as MultipleViewHolder + + adapter.bindViewHolder(disabledItemViewHolder, 1) + + with(disabledItemViewHolder) { + assertEquals(labelView.text, "Disabled choice") + assertFalse(labelView.isEnabled) + assertFalse(itemView.isClickable) + } + } + + @Test + fun `enabled multiple choice item is clickable`() { + val choices = arrayOf( + Choice(id = "item1", label = "Enabled choice"), + Choice(id = "item2", enable = false, label = "Disabled choice") + ) + + val fragment = spy(newInstance(choices, "sessionId", "uid", false, MULTIPLE_CHOICE_DIALOG_TYPE)) + fragment.feature = mockFeature + doReturn(appCompatContext).`when`(fragment).requireContext() + doNothing().`when`(fragment).dismiss() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val adapter = dialog.findViewById(R.id.recyclerView).adapter as ChoiceAdapter + + // test enabled item + val enabledItemViewHolder = + adapter.onCreateViewHolder(LinearLayout(testContext), adapter.getItemViewType(0)) as MultipleViewHolder + + adapter.bindViewHolder(enabledItemViewHolder, 0) + + with(enabledItemViewHolder) { + assertEquals(labelView.text, "Enabled choice") + assertTrue(labelView.isEnabled) + assertTrue(itemView.isClickable) + } + } + + @Test + fun `disabled menu choice item is not clickable`() { + val choices = arrayOf( + Choice(id = "item1", label = "Enabled choice"), + Choice(id = "item2", enable = false, label = "Disabled choice") + ) + + val fragment = + spy(newInstance(choices, "sessionId", "uid", false, MENU_CHOICE_DIALOG_TYPE)) + fragment.feature = mockFeature + doReturn(appCompatContext).`when`(fragment).requireContext() + doNothing().`when`(fragment).dismiss() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val adapter = dialog.findViewById(R.id.recyclerView).adapter as ChoiceAdapter + + // test disabled item + val disabledItemViewHolder = + adapter.onCreateViewHolder(LinearLayout(testContext), adapter.getItemViewType(1)) + as MenuViewHolder + + adapter.bindViewHolder(disabledItemViewHolder, 1) + + with(disabledItemViewHolder) { + assertEquals(labelView.text, "Disabled choice") + assertFalse(labelView.isEnabled) + assertFalse(itemView.isClickable) + } + } + + @Test + fun `enabled menu choice item is clickable`() { + val choices = arrayOf( + Choice(id = "item1", label = "Enabled choice"), + Choice(id = "item2", enable = false, label = "Disabled choice") + ) + + val fragment = spy(newInstance(choices, "sessionId", "uid", false, MENU_CHOICE_DIALOG_TYPE)) + fragment.feature = mockFeature + doReturn(appCompatContext).`when`(fragment).requireContext() + doNothing().`when`(fragment).dismiss() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val adapter = dialog.findViewById(R.id.recyclerView).adapter as ChoiceAdapter + + // test enabled item + val enabledItemViewHolder = + adapter.onCreateViewHolder(LinearLayout(testContext), adapter.getItemViewType(0)) as MenuViewHolder + + adapter.bindViewHolder(enabledItemViewHolder, 0) + + with(enabledItemViewHolder) { + assertEquals(labelView.text, "Enabled choice") + assertTrue(labelView.isEnabled) + assertTrue(itemView.isClickable) + } + } + @Test fun `scroll to selected item`() { // array of 20 choices; 10th one is selected From f406c858182fbc5e8f78a309cc8b5c9aeaa4668f Mon Sep 17 00:00:00 2001 From: mcarare Date: Fri, 18 Mar 2022 14:53:37 +0200 Subject: [PATCH 004/160] For #9684: Run tests on sdk 30. --- .../session-storage/src/test/resources/robolectric.properties | 1 - .../browser/state/src/test/resources/robolectric.properties | 1 - .../storage-sync/src/test/resources/robolectric.properties | 1 - .../browser/tabstray/src/test/resources/robolectric.properties | 1 - .../browser/thumbnails/src/test/resources/robolectric.properties | 1 - .../browser/toolbar/src/test/resources/robolectric.properties | 1 - .../compose/awesomebar/src/test/resources/robolectric.properties | 1 - .../browser-toolbar/src/test/resources/robolectric.properties | 1 - 8 files changed, 8 deletions(-) delete mode 100644 components/browser/session-storage/src/test/resources/robolectric.properties delete mode 100644 components/browser/state/src/test/resources/robolectric.properties delete mode 100644 components/browser/storage-sync/src/test/resources/robolectric.properties delete mode 100644 components/browser/tabstray/src/test/resources/robolectric.properties delete mode 100644 components/browser/thumbnails/src/test/resources/robolectric.properties delete mode 100644 components/browser/toolbar/src/test/resources/robolectric.properties delete mode 100644 components/compose/awesomebar/src/test/resources/robolectric.properties delete mode 100644 components/compose/browser-toolbar/src/test/resources/robolectric.properties diff --git a/components/browser/session-storage/src/test/resources/robolectric.properties b/components/browser/session-storage/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/browser/session-storage/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/browser/state/src/test/resources/robolectric.properties b/components/browser/state/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/browser/state/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/browser/storage-sync/src/test/resources/robolectric.properties b/components/browser/storage-sync/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/browser/storage-sync/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/browser/tabstray/src/test/resources/robolectric.properties b/components/browser/tabstray/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/browser/tabstray/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/browser/thumbnails/src/test/resources/robolectric.properties b/components/browser/thumbnails/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/browser/thumbnails/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/browser/toolbar/src/test/resources/robolectric.properties b/components/browser/toolbar/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/browser/toolbar/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/compose/awesomebar/src/test/resources/robolectric.properties b/components/compose/awesomebar/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/compose/awesomebar/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/compose/browser-toolbar/src/test/resources/robolectric.properties b/components/compose/browser-toolbar/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/compose/browser-toolbar/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file From fb6cc9cc25ebd73acd5e320ee1a4675bc68fba4a Mon Sep 17 00:00:00 2001 From: mcarare Date: Fri, 18 Mar 2022 17:28:51 +0200 Subject: [PATCH 005/160] For #9684: Add test for sdk 30 with compressFormat WEBP_LOSSY. --- .../utils/ThumbnailDiskCacheTest.kt | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/utils/ThumbnailDiskCacheTest.kt b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/utils/ThumbnailDiskCacheTest.kt index eeb830a7be4..236df232022 100644 --- a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/utils/ThumbnailDiskCacheTest.kt +++ b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/utils/ThumbnailDiskCacheTest.kt @@ -18,6 +18,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.Mockito.`when` +import org.robolectric.annotation.Config import java.io.IOException import java.io.OutputStream @@ -25,15 +26,40 @@ import java.io.OutputStream class ThumbnailDiskCacheTest { @Test - fun `Writing and reading bitmap bytes`() { + fun `Writing and reading bitmap bytes for sdk higher than 29`() { val cache = ThumbnailDiskCache() val request = ImageLoadRequest("123", 100) val bitmap: Bitmap = mock() `when`(bitmap.compress(any(), ArgumentMatchers.anyInt(), any())).thenAnswer { Assert.assertEquals( - @Suppress("DEPRECATION") - // Deprecation will be handled in https://github.com/mozilla-mobile/android-components/issues/9555 + Bitmap.CompressFormat.WEBP_LOSSY, + it.arguments[0] as Bitmap.CompressFormat + ) + Assert.assertEquals(90, it.arguments[1] as Int) // Quality + + val stream = it.arguments[2] as OutputStream + stream.write("Hello World".toByteArray()) + true + } + + cache.putThumbnailBitmap(testContext, request.id, bitmap) + + val data = cache.getThumbnailData(testContext, request) + assertNotNull(data!!) + Assert.assertEquals("Hello World", String(data)) + } + + @Config(sdk = [29]) + @Test + fun `Writing and reading bitmap bytes for sdk lower or equal to 29`() { + val cache = ThumbnailDiskCache() + val request = ImageLoadRequest("123", 100) + + val bitmap: Bitmap = mock() + `when`(bitmap.compress(any(), ArgumentMatchers.anyInt(), any())).thenAnswer { + Assert.assertEquals( + @Suppress("DEPRECATION") // not deprecated in sdk 29 Bitmap.CompressFormat.WEBP, it.arguments[0] as Bitmap.CompressFormat ) From b89dc0c73823cc62f1cedd766fd4e6715489dcc1 Mon Sep 17 00:00:00 2001 From: mcarare Date: Mon, 21 Mar 2022 18:19:50 +0200 Subject: [PATCH 006/160] For #9684: Send accessibility event directly. This removed the need for using a delegate. --- .../browser/toolbar/display/DisplayToolbar.kt | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt index b3b72fd92b9..2e408559ef9 100644 --- a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt +++ b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt @@ -157,19 +157,7 @@ class DisplayToolbar internal constructor( origin = rootView.findViewById(R.id.mozac_browser_toolbar_origin_view).also { it.toolbar = toolbar }, - progress = rootView.findViewById(R.id.mozac_browser_toolbar_progress).apply { - accessibilityDelegate = object : View.AccessibilityDelegate() { - override fun onInitializeAccessibilityEvent(host: View?, event: AccessibilityEvent?) { - super.onInitializeAccessibilityEvent(host, event) - if (event?.eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - // Populate the scroll event with the current progress. - // See accessibility note in `updateProgress()`. - event.scrollY = progress - event.maxScrollY = max - } - } - } - }, + progress = rootView.findViewById(R.id.mozac_browser_toolbar_progress), highlight = rootView.findViewById(R.id.mozac_browser_toolbar_permission_indicator) ) @@ -567,7 +555,12 @@ class DisplayToolbar internal constructor( } views.progress.progress = progress - views.progress.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED) + val event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED).apply { + scrollY = progress + maxScrollY = views.progress.max + } + + views.progress.parent.requestSendAccessibilityEvent(views.progress, event) if (progress >= views.progress.max) { // Loading is done, hide progress bar. From a14393f26f6e2acd29808a53e4ffd2cdcbf6a2fd Mon Sep 17 00:00:00 2001 From: mcarare Date: Tue, 22 Mar 2022 13:33:30 +0200 Subject: [PATCH 007/160] For #9684: Run time picker test on sdk 30. This also removes unnecessary deprecation suppression. --- .../feature/prompts/dialog/TimePickerDialogFragmentTest.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt index 32a6f9c9ad2..ea11ebee102 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt @@ -9,7 +9,6 @@ import android.app.DatePickerDialog import android.app.TimePickerDialog import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.DialogInterface.BUTTON_POSITIVE -import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Looper.getMainLooper import android.widget.DatePicker import android.widget.NumberPicker @@ -39,7 +38,6 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.openMocks import org.robolectric.Shadows.shadowOf -import org.robolectric.annotation.Config import java.util.Calendar import java.util.Date @@ -211,8 +209,6 @@ class TimePickerDialogFragmentTest { } @Test - @Config(sdk = [LOLLIPOP]) - @Suppress("DEPRECATION") fun `building a time picker`() { val initialDate = "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm") val minDate = "2018-06-07T00:00".toDate("yyyy-MM-dd'T'HH:mm") From e115cdbe65de283889908e775da3de18914e82f3 Mon Sep 17 00:00:00 2001 From: mcarare Date: Tue, 22 Mar 2022 13:50:26 +0200 Subject: [PATCH 008/160] For #9684: Run all view tests on sdk 30. --- .../test/java/mozilla/components/browser/menu2/ext/ViewTest.kt | 3 --- .../mozilla/components/support/ktx/android/view/ViewTest.kt | 3 --- 2 files changed, 6 deletions(-) diff --git a/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/ViewTest.kt b/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/ViewTest.kt index 237200dc22c..0e2067c9e7b 100644 --- a/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/ViewTest.kt +++ b/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/ViewTest.kt @@ -8,7 +8,6 @@ import android.content.res.ColorStateList import android.graphics.Color import android.graphics.Typeface import android.graphics.drawable.Drawable -import android.os.Build import android.view.View import android.widget.ImageView import android.widget.TextView @@ -30,7 +29,6 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.doReturn import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class ViewTest { @@ -115,7 +113,6 @@ class ViewTest { verify(view).imageTintList = null } - @Config(sdk = [Build.VERSION_CODES.M]) @Test fun `sets highlight effect`() { val view: View = mock() diff --git a/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt b/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt index 1e39b950c85..9b81d0f6cfe 100644 --- a/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt +++ b/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt @@ -6,7 +6,6 @@ package mozilla.components.support.ktx.android.view import android.app.Activity import android.content.Context -import android.os.Build import android.os.Looper.getMainLooper import android.view.View import android.view.WindowManager @@ -39,7 +38,6 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.robolectric.Robolectric import org.robolectric.Shadows.shadowOf -import org.robolectric.annotation.Config import org.robolectric.shadows.ShadowLooper import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -62,7 +60,6 @@ class ViewTest { assertTrue(view.hasFocus()) } - @Config(sdk = [Build.VERSION_CODES.M]) @Test fun `hideKeyboard should hide soft keyboard`() { val view = mock() From 589e19ca1d6e9b64dc4a515fdd83dd39c29617dc Mon Sep 17 00:00:00 2001 From: mcarare Date: Thu, 28 Apr 2022 12:46:27 +0300 Subject: [PATCH 009/160] For #9684: Return false instead of throwing NPE. --- .../mozilla/components/support/ktx/android/content/Context.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/content/Context.kt b/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/content/Context.kt index 0c4fa60626a..bd03b1c3b37 100644 --- a/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/content/Context.kt +++ b/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/content/Context.kt @@ -264,7 +264,7 @@ fun Context.addContact( * (via https://stackoverflow.com/a/12362545/512580) */ inline val Context.isScreenReaderEnabled: Boolean - get() = getSystemService()!!.isTouchExplorationEnabled + get() = getSystemService()?.isTouchExplorationEnabled ?: false @VisibleForTesting internal var isMainProcess: Boolean? = null From 7f636693bd186fc0937b54a12c7305c3b4bf1f24 Mon Sep 17 00:00:00 2001 From: mcarare Date: Thu, 28 Apr 2022 12:47:23 +0300 Subject: [PATCH 010/160] For #9684: Check a11y enabled state when sending a11y event. --- .../browser/toolbar/display/DisplayToolbar.kt | 5 ++- .../browser/toolbar/BrowserToolbarTest.kt | 33 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt index 2e408559ef9..30e1d903907 100644 --- a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt +++ b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt @@ -27,6 +27,7 @@ import mozilla.components.browser.toolbar.R import mozilla.components.browser.toolbar.internal.ActionContainer import mozilla.components.concept.menu.MenuController import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.support.ktx.android.content.isScreenReaderEnabled /** * Sub-component of the browser toolbar responsible for displaying the URL and related controls ("display mode"). @@ -560,7 +561,9 @@ class DisplayToolbar internal constructor( maxScrollY = views.progress.max } - views.progress.parent.requestSendAccessibilityEvent(views.progress, event) + if (context.isScreenReaderEnabled) { + views.progress.parent.requestSendAccessibilityEvent(views.progress, event) + } if (progress >= views.progress.max) { // Loading is done, hide progress bar. diff --git a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt index 3f186cd14ad..a41b625667a 100644 --- a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt +++ b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt @@ -50,7 +50,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.robolectric.Robolectric -import org.robolectric.Shadows +import org.robolectric.Shadows.shadowOf @RunWith(AndroidJUnit4::class) class BrowserToolbarTest { @@ -169,10 +169,12 @@ class BrowserToolbarTest { fun `displayProgress will send accessibility events`() { val toolbar = BrowserToolbar(testContext) val root = mock(ViewParent::class.java) - Shadows.shadowOf(toolbar).setMyParent(root) + shadowOf(toolbar).setMyParent(root) `when`(root.requestSendAccessibilityEvent(any(), any())).thenReturn(false) - Shadows.shadowOf(testContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager).setEnabled(true) + val shadowAccessibilityManager = shadowOf(testContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager) + shadowAccessibilityManager.setEnabled(true) + shadowAccessibilityManager.setTouchExplorationEnabled(true) toolbar.displayProgress(10) toolbar.displayProgress(50) @@ -205,6 +207,31 @@ class BrowserToolbarTest { assertEquals(100, captor.allValues[3].maxScrollY) } + @Test + fun `displayProgress will not send send view scrolled accessibility events if touch exploration is disabled`() { + val toolbar = BrowserToolbar(testContext) + val root = mock(ViewParent::class.java) + shadowOf(toolbar).setMyParent(root) + `when`(root.requestSendAccessibilityEvent(any(), any())).thenReturn(false) + + val shadowAccessibilityManager = shadowOf(testContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager) + shadowAccessibilityManager.setEnabled(true) + shadowAccessibilityManager.setTouchExplorationEnabled(false) + + toolbar.displayProgress(10) + toolbar.displayProgress(50) + toolbar.displayProgress(100) + + // make sure multiple calls to 100% does not trigger "loading" announcement + toolbar.displayProgress(100) + + val captor = ArgumentCaptor.forClass(AccessibilityEvent::class.java) + + verify(root, times(1)).requestSendAccessibilityEvent(any(), captor.capture()) + + assertEquals(AccessibilityEvent.TYPE_ANNOUNCEMENT, captor.allValues[0].eventType) + assertEquals(testContext.getString(R.string.mozac_browser_toolbar_progress_loading), captor.allValues[0].text[0]) + } @Test fun `displayProgress will be forwarded to display toolbar`() { val toolbar = BrowserToolbar(testContext) From 7c842e311c04428ba7a938f923504e741a3eb738 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Thu, 28 Apr 2022 13:07:50 +0000 Subject: [PATCH 011/160] Update GeckoView (Nightly) to 101.0.20220428070126. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index cd21ad4144d..754e43610c7 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220427213431" + const val version = "101.0.20220428070126" /** * GeckoView channel From beadf090fca94a4a567d2ff65c2cc7f529bf8631 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Thu, 28 Apr 2022 17:44:59 +0200 Subject: [PATCH 012/160] Always unregister the BecomingNoisyReceiver when the state change to other than PLAYING --- .../service/MediaSessionServiceDelegate.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt b/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt index 4240259c397..ed7514a1c4e 100644 --- a/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt +++ b/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt @@ -135,21 +135,18 @@ internal class MediaSessionServiceDelegate( when (state.mediaSessionState?.playbackState) { MediaSession.PlaybackState.PLAYING -> { - noisyAudioStreamReceiver = BecomingNoisyReceiver(state.mediaSessionState?.controller) + registerBecomingNoisyListener(state) audioFocus.request(state.id) - context.registerReceiver(noisyAudioStreamReceiver, intentFilter) emitStatePlayFact() startForegroundNotificationIfNeeded() } MediaSession.PlaybackState.PAUSED -> { - noisyAudioStreamReceiver?.let { - context.unregisterReceiver(noisyAudioStreamReceiver) - noisyAudioStreamReceiver = null - } + unregisterBecomingNoisyListener() emitStatePauseFact() stopForeground() } else -> { + unregisterBecomingNoisyListener() emitStateStopFact() stopForeground() } @@ -196,6 +193,18 @@ internal class MediaSessionServiceDelegate( isForegroundService = false } + private fun registerBecomingNoisyListener(state: SessionState) { + noisyAudioStreamReceiver = BecomingNoisyReceiver(state.mediaSessionState?.controller) + context.registerReceiver(noisyAudioStreamReceiver, intentFilter) + } + + private fun unregisterBecomingNoisyListener() { + noisyAudioStreamReceiver?.let { + context.unregisterReceiver(noisyAudioStreamReceiver) + noisyAudioStreamReceiver = null + } + } + private suspend fun updateNotification(sessionState: SessionState?) { val notification = notification.create(sessionState, mediaSession) From fadd3f3dfee23d1071fa7695410999c3394ef3ec Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Fri, 29 Apr 2022 00:01:57 +0000 Subject: [PATCH 013/160] Import l10n. --- .../src/main/res/values-ast/strings.xml | 2 +- .../src/main/res/values-ast/strings.xml | 4 +- .../src/main/res/values-is/strings.xml | 4 +- .../src/main/res/values-ast/strings.xml | 15 +++---- .../media/src/main/res/values-ast/strings.xml | 4 +- .../src/main/res/values-ast/strings.xml | 40 ++++++++++++------- .../src/main/res/values-ast/strings.xml | 24 ++++++----- .../crash/src/main/res/values-ast/strings.xml | 5 ++- .../base/src/main/res/values-ast/strings.xml | 2 +- .../src/main/res/values-ast/strings.xml | 2 + .../src/main/res/values-is/strings.xml | 2 +- 11 files changed, 63 insertions(+), 41 deletions(-) diff --git a/components/browser/awesomebar/src/main/res/values-ast/strings.xml b/components/browser/awesomebar/src/main/res/values-ast/strings.xml index 1cd6d971f70..be4ad492455 100644 --- a/components/browser/awesomebar/src/main/res/values-ast/strings.xml +++ b/components/browser/awesomebar/src/main/res/values-ast/strings.xml @@ -1,5 +1,5 @@ - Aceutar y editar la suxerencia + Aceptar y editar la suxerencia diff --git a/components/feature/contextmenu/src/main/res/values-ast/strings.xml b/components/feature/contextmenu/src/main/res/values-ast/strings.xml index ebeed679f92..8626ca3c65b 100644 --- a/components/feature/contextmenu/src/main/res/values-ast/strings.xml +++ b/components/feature/contextmenu/src/main/res/values-ast/strings.xml @@ -33,7 +33,7 @@ Compartir la direición de corréu - Copiar la direición de corréu + Copiar la direición de corréu electrónicu La direición de corréu electrónicu copióse al cartafueyu @@ -45,7 +45,7 @@ Compartir - Unviar per corréu + Unviar per corréu electrónicu Llamar diff --git a/components/feature/contextmenu/src/main/res/values-is/strings.xml b/components/feature/contextmenu/src/main/res/values-is/strings.xml index 4ca5116a1ec..cb04f5170de 100644 --- a/components/feature/contextmenu/src/main/res/values-is/strings.xml +++ b/components/feature/contextmenu/src/main/res/values-is/strings.xml @@ -3,7 +3,7 @@ Opna tengil í nýjum flipa - Opna tengil í einkaflipa + Opna tengil í huliðsflipa Opna mynd í nýjum flipa @@ -23,7 +23,7 @@ Nýr flipi opnaður - Nýr einkaflipi opnaður + Nýr huliðsflipi opnaður Tengill afritaður á klippispjald diff --git a/components/feature/downloads/src/main/res/values-ast/strings.xml b/components/feature/downloads/src/main/res/values-ast/strings.xml index bade1812a80..115e56b98e4 100644 --- a/components/feature/downloads/src/main/res/values-ast/strings.xml +++ b/components/feature/downloads/src/main/res/values-ast/strings.xml @@ -4,11 +4,11 @@ Descargues - Posó la descarga + Descarga en posa Completóse la descarga - Falló la descarga + La descarga falló Baxar (%1$s) @@ -20,10 +20,10 @@ %1$s nun pue baxar esti tipu de ficheru - Nun pudo abrise\'l ficheru + Nun se pudo abrir el ficheru - Nun s\'atopó nenguna aplicación p\'abrir ficheros %1$s + Nun s\'atopó nenguna aplicación p\'abrir ficheros «%1$s» Posar @@ -39,7 +39,8 @@ Zarrar - Completar l\'aición col usu de: + Completar l\'aición con: --> - Nun ye posible abrir %1$s - + Nun ye posible abrir «%1$s» + + diff --git a/components/feature/media/src/main/res/values-ast/strings.xml b/components/feature/media/src/main/res/values-ast/strings.xml index ce308b4e07f..a1b5a656f5e 100644 --- a/components/feature/media/src/main/res/values-ast/strings.xml +++ b/components/feature/media/src/main/res/values-ast/strings.xml @@ -2,7 +2,7 @@ - Multimedia + Conteníu multimedia La cámara ta activada @@ -18,5 +18,5 @@ Posar - Un sitiu ta reproduciendo multimedia + Un sitiu ta reproduciendo conteníu multimedia diff --git a/components/feature/prompts/src/main/res/values-ast/strings.xml b/components/feature/prompts/src/main/res/values-ast/strings.xml index 90b90a00f6b..9d6d6b4fef6 100644 --- a/components/feature/prompts/src/main/res/values-ast/strings.xml +++ b/components/feature/prompts/src/main/res/values-ast/strings.xml @@ -1,7 +1,7 @@ - Aceutar + D\'acuerdu Encaboxar @@ -27,15 +27,15 @@ Anovar - El campu Contraseña nun ha tar baleru + El campu «Contraseña» nun ha tar baleru - Nun ye posible guardar l\'aniciu de sesión + Nun ye posible guardar la cuenta - ¿Guardar esti aniciu de sesión? + ¿Quies guardar esta cuenta? - ¿Anovar esti aniciu de sesión? + ¿Quies anovar esta cuenta? - ¿Amestar el nome d\'usuariu a la contraseña guardada? + ¿Quies amestar el nome d\'usuariu a la contraseña guardada? Etiqueta pa introducir un campu d\'entrada de testu @@ -44,11 +44,11 @@ Permitir - Ñegar + Negar ¿De xuru? - ¿Quies colar d\'esti sitiu? Los datos qu\'introduxeres nun van guardase + ¿Quies colar d\'esti sitiu? Ye posible que nun se guarden los datos introducíos Quedar @@ -80,19 +80,29 @@ Avi - Xestión d\'anicios de sesión + Xestión de cuentes - Espander los anicios de sesión suxeríos + Espander les cuentes suxeríes - Recoyer los anicios de sesión suxeríos + Recoyer les cuentes suxeríes - Anicios de sesión suxeríos + Cuentes suxeríes - ¿Reunviar los datos a esti sitiu? - Refrescar esta páxina podría duplicar les aiciones de recién como l\'unviu d\'un pagu o l\'espublizamientu d\'un artículu. + ¿Quies volver unviar los datos a esti sitiu? + Refrescar esta páxina podría duplicar les aiciones de recién como l\'unviu d\'un pagu o l\'espublizamientu d\'un artículu dos vegaes. - Reunviar los datos + Volver unviar Encaboxar + + + + Esbilla una tarxeta de creitu + + Espander les tarxetes de creitu + + Recoyer les tarxetes de creitu + + Xestión de tarxetes de creitu diff --git a/components/feature/sitepermissions/src/main/res/values-ast/strings.xml b/components/feature/sitepermissions/src/main/res/values-ast/strings.xml index 1ef515b4ba5..f8c586ece35 100644 --- a/components/feature/sitepermissions/src/main/res/values-ast/strings.xml +++ b/components/feature/sitepermissions/src/main/res/values-ast/strings.xml @@ -1,24 +1,24 @@ - ¿Permitir a %1$s qu\'unvie avisos? + ¿Quies permitir a «%1$s» qu\'unvie avisos? - ¿Permitir a %1$s qu\'use la cámara? + ¿Quies permitir a «%1$s» qu\'use la cámara? - ¿Permitir a %1$s qu\'use\'l micrófonu? + ¿Quies permitir a «%1$s» qu\'use\'l micrófonu? - ¿Permitr a %1$s qu\'use\'l to allugamientu? + ¿Quies permitir a «%1$s» qu\'use\'l to allugamientu? - ¿Permitir a %1$s qu\'use la cámara y el micrófonu? + ¿Quies permitir a «%1$s» qu\'use la cámara y el micrófonu? Micrófonu 1 - Cámara trasera + Cámara d\'atrás - Cámara delantera + Cámara d\'alantre Permitir @@ -32,9 +32,15 @@ Enxamás - ¿Permitir a %1$s qu\'atroxe datos nel almacenamientu permanente? + ¿Quies permitir a «%1$s» qu\'atroxe datos nel almacenamientu permanente? - ¿Permitir a %1$s que reproduza conteníu con DRM? + ¿Quies permitir a «%1$s» que reproduza conteníu con DRM? + + ¿Quies permitir a «%1$s» qu\'use les sos cookies en «%2$s»? + + Si nun ta claro por qué %s precisa estos datos, ye probable que quieras bloquiar l\'accesu. Saber más diff --git a/components/lib/crash/src/main/res/values-ast/strings.xml b/components/lib/crash/src/main/res/values-ast/strings.xml index db77b03b0df..59e83337367 100644 --- a/components/lib/crash/src/main/res/values-ast/strings.xml +++ b/components/lib/crash/src/main/res/values-ast/strings.xml @@ -1,7 +1,7 @@ - Perdona mas %1$s tuvo un problema y cascó. + Sentímoslo, %1$s tuvo un problema y cascó. Unviar l\'informe del fallu a %1$s @@ -15,6 +15,9 @@ Casques + + Sentímoslo, asocedió un problema en %1$s. + Informar diff --git a/components/support/base/src/main/res/values-ast/strings.xml b/components/support/base/src/main/res/values-ast/strings.xml index 22510e31af9..fc2b1716c54 100644 --- a/components/support/base/src/main/res/values-ast/strings.xml +++ b/components/support/base/src/main/res/values-ast/strings.xml @@ -1,7 +1,7 @@ - Dir a Axustes + Dir a los axustes Escartar diff --git a/components/ui/tabcounter/src/main/res/values-ast/strings.xml b/components/ui/tabcounter/src/main/res/values-ast/strings.xml index de4904fbd1d..ed05585c87a 100644 --- a/components/ui/tabcounter/src/main/res/values-ast/strings.xml +++ b/components/ui/tabcounter/src/main/res/values-ast/strings.xml @@ -10,6 +10,8 @@ Llingüeta privada nueva Zarrar la llingüeta + + Duplicar la llingüeta El botón del contador de llingüetes de la barra de ferramientes. diff --git a/components/ui/tabcounter/src/main/res/values-is/strings.xml b/components/ui/tabcounter/src/main/res/values-is/strings.xml index 3e08398d1a5..85bf97412ab 100644 --- a/components/ui/tabcounter/src/main/res/values-is/strings.xml +++ b/components/ui/tabcounter/src/main/res/values-is/strings.xml @@ -7,7 +7,7 @@ Nýr flipi - Nýr einkaflipi + Nýr huliðsflipi Loka flipa From 8c2e29c98ac5269e66eb59c3ea5bc216fdcc2116 Mon Sep 17 00:00:00 2001 From: indu Date: Thu, 21 Apr 2022 11:22:44 +0530 Subject: [PATCH 014/160] For #12034 - Support reverse landscape orientation for fullscreen videos This allows users to view the full screen video both in landscape and reverse landscape even when auto rotate option is disabled in device settings --- .../feature/media/fullscreen/MediaSessionFullscreenFeature.kt | 2 +- .../media/fullscreen/MediaSessionFullscreenFeatureTest.kt | 2 +- docs/changelog.md | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/feature/media/src/main/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeature.kt b/components/feature/media/src/main/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeature.kt index 4f523a109f2..0a364cf9eb4 100644 --- a/components/feature/media/src/main/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeature.kt +++ b/components/feature/media/src/main/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeature.kt @@ -53,7 +53,7 @@ class MediaSessionFullscreenFeature( ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT false -> activity.requestedOrientation = - ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE + ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE else -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER } } diff --git a/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt b/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt index 962c55c2c16..75ca3e1485d 100644 --- a/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt +++ b/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt @@ -52,7 +52,7 @@ class MediaSessionFullscreenFeatureTest { ) feature.start() - verify(mockActivity).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE) + verify(mockActivity).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) store.dispatch( MediaSessionAction.UpdateMediaFullscreenAction( diff --git a/docs/changelog.md b/docs/changelog.md index 5eafdcf99af..ed0ea270c75 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,9 @@ permalink: /changelog/ * [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt) * [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml) +* **feature-media** + * Support reverse landscape orientation for fullscreen videos + [issue # 12034](https://github.com/mozilla-mobile/android-components/issues/12034) * **feature-downloads**: * 🚒 Bug fixed [issue #11259](https://github.com/mozilla-mobile/android-components/issues/11259) - Improved mime type inference for when sharing images from the contextual menu. From 69f4a3aaa192923b434dacdc4019e95fa1735940 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Fri, 29 Apr 2022 13:02:53 +0000 Subject: [PATCH 015/160] Update GeckoView (Nightly) to 101.0.20220429094842. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 754e43610c7..54a38e26a69 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220428070126" + const val version = "101.0.20220429094842" /** * GeckoView channel From 8124908c4bf7cdd701ac7184988e63fe13b4cb84 Mon Sep 17 00:00:00 2001 From: Dennis Schubert Date: Fri, 29 Apr 2022 18:14:47 +0200 Subject: [PATCH 016/160] Bug 1752988 - Ship WebCompat Interventions Addon v101.0.0. --- .../extensions/webcompat/data/injections.js | 14 ++++++++++++ .../extensions/webcompat/data/ua_overrides.js | 22 +++---------------- ...bug1765947-veniceincoming.com-left-fix.css | 9 ++++++++ .../assets/extensions/webcompat/manifest.json | 2 +- 4 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css diff --git a/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js b/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js index a90690b240c..7b424ff43a7 100644 --- a/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js +++ b/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js @@ -608,6 +608,20 @@ const AVAILABLE_INJECTIONS = [ ], }, }, + { + id: "bug1765947", + platform: "android", + domain: "veniceincoming.com", + bug: "1765947", + contentScripts: { + matches: ["*://veniceincoming.com/*"], + js: [ + { + file: "injections/css/bug1765947-veniceincoming.com-left-fix.css", + }, + ], + }, + }, ]; module.exports = AVAILABLE_INJECTIONS; diff --git a/components/feature/webcompat/src/main/assets/extensions/webcompat/data/ua_overrides.js b/components/feature/webcompat/src/main/assets/extensions/webcompat/data/ua_overrides.js index c84baf622b8..5096c4d8abf 100644 --- a/components/feature/webcompat/src/main/assets/extensions/webcompat/data/ua_overrides.js +++ b/components/feature/webcompat/src/main/assets/extensions/webcompat/data/ua_overrides.js @@ -719,6 +719,8 @@ const AVAILABLE_UA_OVERRIDES = [ "*://bethesda.net/*", // #94607 "*://cdn-vzn.yottaa.net/*", // Bug 1764795 "*://citoyens.telerecours.fr/*", // #101066 + "*://www.connexus.com/*", // Bug 1765925 + "*://dsae.co.za/*", // Bug 1765925 "*://genehmigung.ahs-vwa.at/*", // #100063 "*://moje.pzu.pl/*", // #99772 "*://mon.allianzbanque.fr/*", // #101074 @@ -809,6 +811,7 @@ const AVAILABLE_UA_OVERRIDES = [ "*://www.southportvisiter.co.uk/*", // Bug 1762928 (Reach Plc) "*://www.staffordshire-live.co.uk/*", // Bug 1762928 (Reach Plc) "*://www.stokesentinel.co.uk/*", // Bug 1762928 (Reach Plc) + "*://survey.sogosurvey.com/*", // Bug 1765925 "*://www.sussexlive.co.uk/*", // Bug 1762928 (Reach Plc) "*://www.tm-awx.com/*", // Bug 1762928 (Reach Plc) "*://www.twitch.tv/*", // Bug 1764591 @@ -871,25 +874,6 @@ const AVAILABLE_UA_OVERRIDES = [ }, }, }, - { - /* - * Bug 1177298 - UA overrides for expertflyer.com - * Webcompat issue #96685 - https://webcompat.com/issues/96685 - * - * The site does not offer a stylesheet unless AppleWebKit - * is part of the user-agent string. - */ - id: "bug1753631", - platform: "android", - domain: "expertflyer.com", - bug: "1753631", - config: { - matches: ["*://*.expertflyer.com/*"], - uaTransformer: originalUA => { - return originalUA + " AppleWebKit"; - }, - }, - }, { /* * Bug 1753461 - UA override for serieson.naver.com diff --git a/components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css b/components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css new file mode 100644 index 00000000000..4c2df651ba0 --- /dev/null +++ b/components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css @@ -0,0 +1,9 @@ +/** + * veniceincoming.com - site is not usable + * Bug #1765947 - https://bugzilla.mozilla.org/show_bug.cgi?id=1765947 + * WebCompat issue #102133 - https://webcompat.com/issues/102133 + */ + +.tour-list .single-tour .mobile-link { + left: 0; +} diff --git a/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json b/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json index 5167df771bf..02ae664ab51 100644 --- a/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json +++ b/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Mozilla Android Components - Web Compatibility Interventions", "description": "Urgent post-release fixes for web compatibility.", - "version": "100.2.0", + "version": "101.0.0", "applications": { "gecko": { "id": "webcompat@mozilla.org", From 07eaa82e2542ba7d8eb4253b5e463dd9fd532605 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Sat, 30 Apr 2022 00:04:58 +0000 Subject: [PATCH 017/160] Import l10n. --- .../src/main/res/values-ast/strings.xml | 6 +-- .../src/main/res/values-ast/strings.xml | 7 ++- .../src/main/res/values-ast/strings.xml | 4 +- .../src/main/res/values-ast/strings.xml | 5 ++ .../src/main/res/values-te/strings.xml | 5 ++ .../src/main/res/values-ast/strings.xml | 48 +++++++++---------- .../src/main/res/values-kab/strings.xml | 2 +- .../src/main/res/values-kab/strings.xml | 6 +-- .../src/main/res/values-ast/strings.xml | 2 +- 9 files changed, 49 insertions(+), 36 deletions(-) create mode 100644 components/compose/browser-toolbar/src/main/res/values-ast/strings.xml create mode 100644 components/compose/browser-toolbar/src/main/res/values-te/strings.xml diff --git a/components/browser/engine-system/src/main/res/values-ast/strings.xml b/components/browser/engine-system/src/main/res/values-ast/strings.xml index 02e8f021e81..3f503d35e7f 100644 --- a/components/browser/engine-system/src/main/res/values-ast/strings.xml +++ b/components/browser/engine-system/src/main/res/values-ast/strings.xml @@ -3,8 +3,8 @@ La páxina en %1$s diz: - %2$s ta solicitando un nome d\'usuariu y una contraseña. El sitiu diz «%1$s» + %1$s will be replaced by the hostname or a description of the protected area/site, %2$s will be replaced with the URL of the current page (displaying the dialog). --> + «%2$s» solicita un nome d\'usuariu y una contraseña. El sitiu diz «%1$s» - %1$s ta solicitando un nome d\'usuariu y una contraseña. + «%1$s» solicita un nome d\'usuariu y una contraseña. diff --git a/components/browser/errorpages/src/main/res/values-ast/strings.xml b/components/browser/errorpages/src/main/res/values-ast/strings.xml index 84e68d78f51..12a2594a57c 100644 --- a/components/browser/errorpages/src/main/res/values-ast/strings.xml +++ b/components/browser/errorpages/src/main/res/values-ast/strings.xml @@ -5,7 +5,7 @@ Retentar - Nun pue completase la solicitú + Nun se pue completar la solicitú La información adicional tocante a esti problema o fallu nun ta disponible anguaño.

]]>
@@ -238,4 +238,7 @@ Problema de sitiu engañosu Informóse de que la páxina web de %1$s ye engañosa y bloquióse según les tos preferencies de seguranza.

]]>
- + + + El sitiu seguru nun ta disponible + diff --git a/components/browser/toolbar/src/main/res/values-ast/strings.xml b/components/browser/toolbar/src/main/res/values-ast/strings.xml index efd50cb44c7..4edbea0c526 100644 --- a/components/browser/toolbar/src/main/res/values-ast/strings.xml +++ b/components/browser/toolbar/src/main/res/values-ast/strings.xml @@ -2,7 +2,7 @@ Menú - Llimpiar + Borrar La proteición antirrastrexu ta activada @@ -14,5 +14,5 @@ Cargando - Bloquióse parte del conteníu pol axuste de reproducción automática + L\'axuste de reproducción automática bloquió parte del conteníu diff --git a/components/compose/browser-toolbar/src/main/res/values-ast/strings.xml b/components/compose/browser-toolbar/src/main/res/values-ast/strings.xml new file mode 100644 index 00000000000..8969801e9c2 --- /dev/null +++ b/components/compose/browser-toolbar/src/main/res/values-ast/strings.xml @@ -0,0 +1,5 @@ + + + + Borrar + diff --git a/components/compose/browser-toolbar/src/main/res/values-te/strings.xml b/components/compose/browser-toolbar/src/main/res/values-te/strings.xml new file mode 100644 index 00000000000..d7291694e8d --- /dev/null +++ b/components/compose/browser-toolbar/src/main/res/values-te/strings.xml @@ -0,0 +1,5 @@ + + + + తుడిచివేయి + diff --git a/components/feature/addons/src/main/res/values-ast/strings.xml b/components/feature/addons/src/main/res/values-ast/strings.xml index 0f7075cfac4..fd4c7f7b37e 100644 --- a/components/feature/addons/src/main/res/values-ast/strings.xml +++ b/components/feature/addons/src/main/res/values-ast/strings.xml @@ -75,7 +75,7 @@ Páxina d\'aniciu - Deprender más tocante a los permisos + Saber más tocante a los permisos Valoración @@ -97,7 +97,7 @@ Aconséyase - Entá nun se sofita + Entá nun ye compatible Entá nun ta disponible @@ -109,7 +109,7 @@ Quitar - ¿Amestar %1$s? + ¿Quies amestar «%1$s»? Rique\'l to permisu pa: @@ -131,9 +131,9 @@ Permitir - Ñegar + Negar - %1$s tien un anovamientu + «%1$s» tien un anovamientu Ríquense %1$d permisos nuevos @@ -141,17 +141,17 @@ Anovamientos de complementos - Comprobador de complementos sofitaos + Comprobador de complementos compatibles Hai un complementu nuevu disponible Hai complementos nuevos disponibles - Amestar %1$s a %2$s + Amestar «%1$s» a %2$s - Amestar %1$s y %2$s a %3$s + Amestar «%1$s» y «%2$s» a «%3$s» - Amestalos a %1$s + Amiéstalos a %1$s La teunoloxía de complementos pa Firefox ta modernizándose. Estos complementos usen frameworks que nun son compatibles a partir de Firefox 75. @@ -161,27 +161,27 @@ ¡Hebo un fallu al solicitar los complementos! - Nun s\'atopó la traducción de la locale %1$s nin de la llingua predeterminada %2$s + Nun s\'atopó la traducción de la locale «%1$s» nin de la llingua predeterminada «%2$s» - %1$s instalóse con ésitu + «%1$s» instalóse correutamente - Hebo un fallu al instalar %1$s + Hebo un fallu al instalar «%1$s» - %1$s activóse con ésitu + «%1$s» activóse correutamente - Hebo un fallu al activar %1$s + Hebo un fallu al activar «%1$s» - %1$s desactivóse con ésitu + «%1$s» desactivóse correutamente - Hebo un fallu al desactivar %1$s + Hebo un fallu al desactivar «%1$s» - %1$s desinstalóse con ésitu + «%1$s» desinstalóse correutamente - Hebo un fallu al desinstalar %1$s + Hebo un fallu al desinstalar «%1$s» - %1$s quitóse con ésitu + «%1$s» quitóse correutamante - Hebo un fallu al quitar %1$s + Hebo un fallu al quitar «%1$s» Esti complementu migró dende una versión anterior de %1$s @@ -189,9 +189,9 @@ %1$s complementos - Deprender más + Saber más - Anovóse con ésitu + Anovóse correutamente Nun hai nengún anovamientu disponible @@ -203,9 +203,9 @@ Estáu: - %1$s amestóse a %2$s + «%1$s» amestóse a %2$s Ábrilu nel menú - Val, entiéndolo + Entiéndolo diff --git a/components/feature/addons/src/main/res/values-kab/strings.xml b/components/feature/addons/src/main/res/values-kab/strings.xml index 09640eed0fa..d5d1517a507 100644 --- a/components/feature/addons/src/main/res/values-kab/strings.xml +++ b/components/feature/addons/src/main/res/values-kab/strings.xml @@ -39,7 +39,7 @@ Sekcem isefka ɣer tkatut Ɣef afus - Sider-d ifuyla, ɣeṛ daɣen beddel amazray n usider deg iminig + Sader-d ifuyla, ɣeṛ daɣen beddel amazray n usader deg iminig Ldi ifuya i d-yudren deg yibenk-ik diff --git a/components/feature/downloads/src/main/res/values-kab/strings.xml b/components/feature/downloads/src/main/res/values-kab/strings.xml index 00fc60d1e87..6d7c2867521 100644 --- a/components/feature/downloads/src/main/res/values-kab/strings.xml +++ b/components/feature/downloads/src/main/res/values-kab/strings.xml @@ -6,14 +6,14 @@ Asader yesteɛfa - Asider yemmed + Asader yemmed Ifuyla ittwazedmen - Asider (%1$s) + Asader (%1$s) - Sider + Sader Sefsex diff --git a/components/feature/prompts/src/main/res/values-ast/strings.xml b/components/feature/prompts/src/main/res/values-ast/strings.xml index 9d6d6b4fef6..1ff80ddac8b 100644 --- a/components/feature/prompts/src/main/res/values-ast/strings.xml +++ b/components/feature/prompts/src/main/res/values-ast/strings.xml @@ -9,7 +9,7 @@ Afitar - Llimpiar + Borrar Aniciu de sesión From 830e66d739535270b34f6bc17b8bcbcb412c088e Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sat, 30 Apr 2022 12:41:42 +0000 Subject: [PATCH 018/160] Update GeckoView (Nightly) to 101.0.20220430091922. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 54a38e26a69..86b877b9470 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220429094842" + const val version = "101.0.20220430091922" /** * GeckoView channel From fdce9c6b7a71aac1268a0ce2fbad6bc1a2fc499c Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Sun, 1 May 2022 00:05:11 +0000 Subject: [PATCH 019/160] Import l10n. --- .../app-links/src/main/res/values-ast/strings.xml | 2 -- .../autofill/src/main/res/values-ast/strings.xml | 15 ++++++++++++--- .../downloads/src/main/res/values-ast/strings.xml | 10 +++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/components/feature/app-links/src/main/res/values-ast/strings.xml b/components/feature/app-links/src/main/res/values-ast/strings.xml index e42b6550310..62205d808ad 100644 --- a/components/feature/app-links/src/main/res/values-ast/strings.xml +++ b/components/feature/app-links/src/main/res/values-ast/strings.xml @@ -2,8 +2,6 @@ Abrir en… - - ¿Abrir na aplicación? La to actividá yá nun va ser privada. Abrir diff --git a/components/feature/autofill/src/main/res/values-ast/strings.xml b/components/feature/autofill/src/main/res/values-ast/strings.xml index 85e19d6c869..e4dd33d5aea 100644 --- a/components/feature/autofill/src/main/res/values-ast/strings.xml +++ b/components/feature/autofill/src/main/res/values-ast/strings.xml @@ -3,7 +3,7 @@ - Desbloquiar %1$s + Desbloquiar «%1$s» - Contraseña de %1$s + Contraseña de: %1$s @@ -24,7 +24,7 @@ Links" this application is not the official Twitter application for twitter.com credentials. %1$s will be replaced with the name of the browser application (e.g. Firefox). --> - %1$s nun pudo verificar l\'autenticidá de l\'aplicación. ¿Quies siguir col rellenu automáticu de los datos esbillaos? + «%1$s» nun pudo verificar l\'autenticidá de l\'aplicación. ¿Quies siguir col rellenu automáticu de los datos esbillaos? @@ -33,4 +33,13 @@ Non + + + Buscar «%1$s» + + + Buscar nes cuentes diff --git a/components/feature/downloads/src/main/res/values-ast/strings.xml b/components/feature/downloads/src/main/res/values-ast/strings.xml index 115e56b98e4..ae218de9c46 100644 --- a/components/feature/downloads/src/main/res/values-ast/strings.xml +++ b/components/feature/downloads/src/main/res/values-ast/strings.xml @@ -43,4 +43,12 @@ --> Nun ye posible abrir «%1$s» - + + ¿Quies encaboxar les descargues privaes? + + Si zarres agora toles llingüetes privaes, va encaboxase la descarga de «%1$s». ¿De xuru que quies colar del mou de restolar en privao? + + Encaboxales + + Quedar + From aeb7f3344a2123b0ece63f24cf4a571fef21b5ee Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sun, 1 May 2022 12:42:07 +0000 Subject: [PATCH 020/160] Update GeckoView (Nightly) to 101.0.20220501092542. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 86b877b9470..6b95b1c363e 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220430091922" + const val version = "101.0.20220501092542" /** * GeckoView channel From 690485bf7769a67665956659c74cfba2e28f96cd Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Mon, 2 May 2022 13:02:27 +0000 Subject: [PATCH 021/160] Update GeckoView (Nightly) to 101.0.20220502094329. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 6b95b1c363e..b8d595d10b6 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220501092542" + const val version = "101.0.20220502094329" /** * GeckoView channel From 4eef6c129c9611b6927bd50a5a1620ede57744b1 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Mon, 2 May 2022 12:54:51 -0400 Subject: [PATCH 022/160] Update GeckoView (Beta) to 101.0.20220502145843. --- buildSrc/src/main/java/Gecko.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index b8d595d10b6..78f89860473 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,12 +9,12 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220502094329" + const val version = "101.0.20220502145843" /** * GeckoView channel */ - val channel = GeckoChannel.NIGHTLY + val channel = GeckoChannel.BETA } /** From befea69dae4b4f06dd46ed5c6dee02bfce1c40bc Mon Sep 17 00:00:00 2001 From: Arturo Mejia Date: Mon, 2 May 2022 15:51:28 -0400 Subject: [PATCH 023/160] Start 102.0.0 development cycle --- .buildconfig.yml | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.buildconfig.yml b/.buildconfig.yml index 26c732d2c1f..33ced725f5a 100644 --- a/.buildconfig.yml +++ b/.buildconfig.yml @@ -1,6 +1,6 @@ # Please keep this version in sync with version.txt # version.txt should be the new source of truth for version numbers -componentsVersion: 101.0.0 +componentsVersion: 102.0.0 # Please add a treeherder group in taskcluster/ci/config.yml if you add a new project here. projects: compose-awesomebar: diff --git a/version.txt b/version.txt index 6288eefbac9..d4641b132dc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -101.0.0 +102.0.0 From 9765e5d085a894df7094ec5c93a3063c73d7c594 Mon Sep 17 00:00:00 2001 From: Arturo Mejia Date: Mon, 2 May 2022 16:24:08 -0400 Subject: [PATCH 024/160] Update changelog for 102.0.0 release --- docs/changelog.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index ed0ea270c75..e0e4c3c82b8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,13 +4,20 @@ title: Changelog permalink: /changelog/ --- -# 101.0.0 (In Development) -* [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...main) -* [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) +# 102.0.0 (In Development) +* [Commits](https://github.com/mozilla-mobile/android-components/compare/v101.0.0...main) +* [Milestone](https://github.com/mozilla-mobile/android-components/milestone/149?closed=1) * [Dependencies](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Dependencies.kt) * [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt) * [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml) +# 101.0.0 +* [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0) +* [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) +* [Dependencies](https://github.com/mozilla-mobile/android-components/blob/v101.0.0/buildSrc/src/main/java/Dependencies.kt) +* [Gecko](https://github.com/mozilla-mobile/android-components/blob/v101.0.0/buildSrc/src/main/java/Gecko.kt) +* [Configuration](https://github.com/mozilla-mobile/android-components/blob/v101.0.0/.config.yml) + * **feature-media** * Support reverse landscape orientation for fullscreen videos [issue # 12034](https://github.com/mozilla-mobile/android-components/issues/12034) From 29c68f43bf446bc481f4a2537b6feef349b9b096 Mon Sep 17 00:00:00 2001 From: Arturo Mejia Date: Mon, 2 May 2022 16:24:16 -0400 Subject: [PATCH 025/160] Update to GeckoView 102.0.0 --- buildSrc/src/main/java/Gecko.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 78f89860473..6eadc20f9a1 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,12 +9,12 @@ object Gecko { /** * GeckoView Version. */ - const val version = "101.0.20220502145843" + const val version = "102.0.20220502144301" /** * GeckoView channel */ - val channel = GeckoChannel.BETA + val channel = GeckoChannel.NIGHTLY } /** From 42ae06f659e546c22367ae43effbf3a26c87c0b4 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Tue, 3 May 2022 00:05:46 +0000 Subject: [PATCH 026/160] Import l10n. --- .../src/main/res/values-tg/strings.xml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/components/browser/errorpages/src/main/res/values-tg/strings.xml b/components/browser/errorpages/src/main/res/values-tg/strings.xml index 39e90f03d73..e9672befb35 100644 --- a/components/browser/errorpages/src/main/res/values-tg/strings.xml +++ b/components/browser/errorpages/src/main/res/values-tg/strings.xml @@ -68,7 +68,7 @@
  • Эҳтимол сервер бо дархостҳои зиёд машғул аст ё ин ки муваққатан дастнорас аст? Баъдтар аз нав кӯшиш кунед.
  • Шумо метавонед, ки сомонаҳои дигарро бинед? Пайвасти шабакавии дастгоҳро санҷед.
  • Дастгоҳ ё шабакаи шумо бо девори оташ ё прокси муҳофизат шудааст? Танзимоти нодуруст метавонад ба дидани сомона халал расонад.
  • -
  • Ҳанӯз мушкилӣ мекашед? Барои кумак ба маъмури шабака ё провайдери интернети худ муроҷиат намоед.
  • +
  • Ҳоло ҳам мушкилӣ мекашед? Барои кумак ба маъмури шабака ё провайдери интернети худ муроҷиат намоед.
  • ]]> @@ -120,7 +120,7 @@ Нишонии дархостшуда портеро муайян кард (масалан, mozilla.org:80 барои порти 80 дар mozilla.org). Одатан, порт барои мақсадҳои ғайр аз тамошокунии сомонаҳо истифода мешавад. Браузер барои муҳофизат ва амнияти шумо дархостро бекор кард.

    +

    Нишонии дархостшуда портеро муайян кард (масалан, mozilla.org:80 барои порти 80 дар mozilla.org), ки одатан барои мақсадҳои ба ғайр аз тамошокунии сомонаҳо истифода мешавад. Браузер барои муҳофизат ва амнияти шумо дархостро бекор кард.

    ]]>
    @@ -231,8 +231,8 @@
  • Эҳтимол аст, ки номи мавод иваз карда шуд, ё ин ки мавод тоза карда шуд, ё ба ҷойи дигар гузошта шуд?
  • -
  • Оё дар нишонӣ хатои имлоӣ, ҳуруфчинӣ ё дигар хатои типографӣ вуҷуд дорад?
  • -
  • Оё шумо ба маводи дархостшуда иҷозати дастрасии кофӣ доред?
  • +
  • Дар нишонӣ ягон хатои имлоӣ, ҳуруфчинӣ ё дигар хатои типографӣ вуҷуд дорад?
  • +
  • Шумо ба маводи дархостшуда иҷозати дастрасии кофӣ доред?
  • ]]>
    @@ -251,9 +251,9 @@ Браузер барои истифодаи сервери прокси танзим карда шудааст, аммо прокси аз пайвастшавӣ даст кашид.

      -
    • Оё танзимоти прокси дар браузер дуруст аст? Танзимотро санҷед ва аз нав кӯшиш кунед.
    • -
    • Оё хидмати прокси барои пайвастшавӣ аз ин шабака иҷозат медиҳад?
    • -
    • Ҳанӯз мушкилӣ мекашед? Барои кумак ба маъмури шабака ё провайдери интернети худ муроҷиат кунед.
    • +
    • Танзимоти прокси дар браузер дуруст аст? Танзимотро санҷед ва аз нав кӯшиш кунед.
    • +
    • Хидмати прокси барои пайвастшавӣ аз ин шабака иҷозат медиҳад?
    • +
    • Ҳоло ҳам мушкилӣ мекашед? Барои кумак ба маъмури шабака ё провайдери интернети худ муроҷиат кунед.
    ]]>
    @@ -263,9 +263,9 @@ Браузер барои истифодаи сервери прокси танзим карда шудааст, аммо прокси ёфт нашуд.

      -
    • Оё танзимоти прокси дар браузер дуруст аст? Танзимотро санҷед ва аз нав кӯшиш кунед.
    • -
    • Оё дастгоҳ ба шабакаи фаъол пайваст аст?
    • -
    • Ҳанӯз мушкилӣ мекашед? Барои кумак ба маъмури шабака ё провайдери интернети худ муроҷиат кунед.
    • +
    • Танзимоти прокси дар браузер дуруст аст? Танзимотро санҷед ва аз нав кӯшиш кунед.
    • +
    • Дастгоҳ ба шабакаи фаъол пайваст аст?
    • +
    • Ҳоло ҳам мушкилӣ мекашед? Барои кумак ба маъмури шабака ё провайдери интернети худ муроҷиат кунед.
    ]]>
    From 43a5927a5de203a6a2589a22ab60e34b177debcb Mon Sep 17 00:00:00 2001 From: Mugurell Date: Mon, 2 May 2022 10:31:34 +0300 Subject: [PATCH 027/160] For #12079 - Add 'additionalValidation' parameter to context menu options builders This will allow clients know when the context menu is opened and what options it contains while also allowing clients to lazily control what options are shown without the need for complex if-else conditions when the menu is set up. --- .../contextmenu/ContextMenuCandidate.kt | 161 +++++-- .../contextmenu/ContextMenuCandidateTest.kt | 433 ++++++++++++++++++ docs/changelog.md | 3 + 3 files changed, 568 insertions(+), 29 deletions(-) diff --git a/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt b/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt index 3fd5ef1f9a5..24c4a1397b5 100644 --- a/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt +++ b/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt @@ -92,19 +92,28 @@ data class ContextMenuCandidate( /** * Context Menu item: "Open Link in New Tab". + * + * @param context [Context] used for various system interactions. + * @param tabsUseCases [TabsUseCases] used for adding new tabs. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createOpenInNewTabCandidate( context: Context, tabsUseCases: TabsUseCases, snackBarParentView: View, - snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate() + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.open_in_new_tab", label = context.getString(R.string.mozac_feature_contextmenu_open_link_in_new_tab), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && hitResult.isHttpLink() && - !tab.content.private + !tab.content.private && + additionalValidation(tab, hitResult) }, action = { parent, hitResult -> val tab = tabsUseCases.addTab( @@ -128,18 +137,26 @@ data class ContextMenuCandidate( /** * Context Menu item: "Open Link in Private Tab". - */ + * + * @param context [Context] used for various system interactions. + * @param tabsUseCases [TabsUseCases] used for adding new tabs. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createOpenInPrivateTabCandidate( context: Context, tabsUseCases: TabsUseCases, snackBarParentView: View, - snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate() + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.open_in_private_tab", label = context.getString(R.string.mozac_feature_contextmenu_open_link_in_private_tab), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isHttpLink() + hitResult.isHttpLink() && + additionalValidation(tab, hitResult) }, action = { parent, hitResult -> val tab = tabsUseCases.addTab( @@ -163,16 +180,23 @@ data class ContextMenuCandidate( /** * Context Menu item: "Open Link in external App". + * + * @param context [Context] used for various system interactions. + * @param appLinksUseCases [AppLinksUseCases] used to interact with urls that can be opened in 3rd party apps. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createOpenInExternalAppCandidate( context: Context, - appLinksUseCases: AppLinksUseCases + appLinksUseCases: AppLinksUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.open_in_external_app", label = context.getString(R.string.mozac_feature_contextmenu_open_link_in_external_app), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.canOpenInExternalApp(appLinksUseCases) + hitResult.canOpenInExternalApp(appLinksUseCases) && + additionalValidation(tab, hitResult) }, action = { _, hitResult -> val link = hitResult.getLink() @@ -189,47 +213,67 @@ data class ContextMenuCandidate( /** * Context Menu item: "Add to contact". + * + * @param context [Context] used for various system interactions. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createAddContactCandidate( - context: Context + context: Context, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.add_to_contact", label = context.getString(R.string.mozac_feature_contextmenu_add_to_contact), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isMailto() + hitResult.isMailto() && + additionalValidation(tab, hitResult) }, action = { _, hitResult -> context.addContact(hitResult.getLink().stripMailToProtocol()) } ) /** * Context Menu item: "Share email address". + * + * @param context [Context] used for various system interactions. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createShareEmailAddressCandidate( - context: Context + context: Context, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.share_email", label = context.getString(R.string.mozac_feature_contextmenu_share_email_address), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isMailto() + hitResult.isMailto() && + additionalValidation(tab, hitResult) }, action = { _, hitResult -> context.share(hitResult.getLink().stripMailToProtocol()) } ) /** * Context Menu item: "Copy email address". + * + * @param context [Context] used for various system interactions. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createCopyEmailAddressCandidate( context: Context, snackBarParentView: View, - snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate() + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.copy_email_address", label = context.getString(R.string.mozac_feature_contextmenu_copy_email_address), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isMailto() + hitResult.isMailto() && + additionalValidation(tab, hitResult) }, action = { _, hitResult -> val email = hitResult.getLink().stripMailToProtocol() @@ -243,18 +287,27 @@ data class ContextMenuCandidate( /** * Context Menu item: "Open Image in New Tab". + * + * @param context [Context] used for various system interactions. + * @param tabsUseCases [TabsUseCases] used for adding new tabs. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createOpenImageInNewTabCandidate( context: Context, tabsUseCases: TabsUseCases, snackBarParentView: View, - snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate() + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.open_image_in_new_tab", label = context.getString(R.string.mozac_feature_contextmenu_open_image_in_new_tab), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isImage() + hitResult.isImage() && + additionalValidation(tab, hitResult) }, action = { parent, hitResult -> val tab = tabsUseCases.addTab( @@ -279,16 +332,23 @@ data class ContextMenuCandidate( /** * Context Menu item: "Save image". + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createSaveImageCandidate( context: Context, - contextMenuUseCases: ContextMenuUseCases + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.save_image", label = context.getString(R.string.mozac_feature_contextmenu_save_image), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isImage() + hitResult.isImage() && + additionalValidation(tab, hitResult) }, action = { tab, hitResult -> contextMenuUseCases.injectDownload( @@ -300,16 +360,23 @@ data class ContextMenuCandidate( /** * Context Menu item: "Save video". + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createSaveVideoAudioCandidate( context: Context, - contextMenuUseCases: ContextMenuUseCases + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.save_video", label = context.getString(R.string.mozac_feature_contextmenu_save_file_to_device), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isVideoAudio() + hitResult.isVideoAudio() && + additionalValidation(tab, hitResult) }, action = { tab, hitResult -> contextMenuUseCases.injectDownload( @@ -321,16 +388,23 @@ data class ContextMenuCandidate( /** * Context Menu item: "Save link". + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createDownloadLinkCandidate( context: Context, - contextMenuUseCases: ContextMenuUseCases + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.download_link", label = context.getString(R.string.mozac_feature_contextmenu_download_link), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isLinkForOtherThanWebpage() + hitResult.isLinkForOtherThanWebpage() && + additionalValidation(tab, hitResult) }, action = { tab, hitResult -> contextMenuUseCases.injectDownload( @@ -342,15 +416,21 @@ data class ContextMenuCandidate( /** * Context Menu item: "Share Link". + * + * @param context [Context] used for various system interactions. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createShareLinkCandidate( - context: Context + context: Context, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.share_link", label = context.getString(R.string.mozac_feature_contextmenu_share_link), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - (hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio()) + (hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio()) && + additionalValidation(tab, hitResult) }, action = { _, hitResult -> val intent = Intent(Intent.ACTION_SEND).apply { @@ -428,16 +508,23 @@ data class ContextMenuCandidate( /** * Context Menu item: "Share image" + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createShareImageCandidate( context: Context, - contextMenuUseCases: ContextMenuUseCases + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.share_image", label = context.getString(R.string.mozac_feature_contextmenu_share_image), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isImage() + hitResult.isImage() && + additionalValidation(tab, hitResult) }, action = { tab, hitResult -> contextMenuUseCases.injectShareFromInternet( @@ -452,17 +539,25 @@ data class ContextMenuCandidate( /** * Context Menu item: "Copy Link". + * + * @param context [Context] used for various system interactions. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createCopyLinkCandidate( context: Context, snackBarParentView: View, - snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate() + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.copy_link", label = context.getString(R.string.mozac_feature_contextmenu_copy_link), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - (hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio()) + (hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio()) && + additionalValidation(tab, hitResult) }, action = { _, hitResult -> clipPlaintText( @@ -475,17 +570,25 @@ data class ContextMenuCandidate( /** * Context Menu item: "Copy Image Location". + * + * @param context [Context] used for various system interactions. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ fun createCopyImageLocationCandidate( context: Context, snackBarParentView: View, - snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate() + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, ) = ContextMenuCandidate( id = "mozac.feature.contextmenu.copy_image_location", label = context.getString(R.string.mozac_feature_contextmenu_copy_image_location), showFor = { tab, hitResult -> tab.isUrlSchemeAllowed(hitResult.getLink()) && - hitResult.isImage() + hitResult.isImage() && + additionalValidation(tab, hitResult) }, action = { _, hitResult -> clipPlaintText( diff --git a/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt b/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt index 9b0d033afff..9ebe4ba0010 100644 --- a/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt +++ b/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt @@ -15,6 +15,7 @@ import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ContentState import mozilla.components.browser.state.state.EngineState +import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.content.ShareInternetResourceState import mozilla.components.browser.state.state.createTab @@ -108,6 +109,30 @@ class ContextMenuCandidateTest { ) } + @Test + fun `Candidate 'Open Link in New Tab' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openInNewTab = ContextMenuCandidate.createOpenInNewTabCandidate( + testContext, mock(), mock(), snackbarDelegate, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Open Link in New Tab' action properly executes for session with a contextId`() { val store = BrowserStore( @@ -280,6 +305,37 @@ class ContextMenuCandidateTest { ) } + @Test + fun `Candidate 'Open Link in Private Tab' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openInPrivateTab = ContextMenuCandidate.createOpenInPrivateTabCandidate( + testContext, mock(), mock(), snackbarDelegate, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Open Link in Private Tab' action properly executes and shows snackbar`() { val store = BrowserStore( @@ -458,6 +514,30 @@ class ContextMenuCandidateTest { assertEquals("https://firefox.com", store.state.selectedTab!!.content.url) } + @Test + fun `Candidate 'Open Image in New Tab' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openImageInTab = ContextMenuCandidate.createOpenImageInNewTabCandidate( + testContext, mock(), mock(), snackbarDelegate, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + + assertFalse( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Open Image in New Tab' opens in private tab if session is private`() { val store = BrowserStore( @@ -601,6 +681,30 @@ class ContextMenuCandidateTest { ) } + @Test + fun `Candidate 'Save image' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val saveImage = ContextMenuCandidate.createSaveImageCandidate( + testContext, mock(), additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + + assertFalse( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Save video and audio'`() { val store = BrowserStore( @@ -686,6 +790,30 @@ class ContextMenuCandidateTest { ) } + @Test + fun `Candidate 'Save video and audio' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val saveVideoAudio = ContextMenuCandidate.createSaveVideoAudioCandidate( + testContext, mock(), additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org") + ) + ) + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.AUDIO("https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'download link'`() { val store = BrowserStore( @@ -800,6 +928,37 @@ class ContextMenuCandidateTest { assertTrue(store.state.tabs.first().content.download!!.private) } + @Test + fun `Candidate 'download link' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val downloadLink = ContextMenuCandidate.createDownloadLinkCandidate( + testContext, mock(), additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + } + @Test fun `Get link for image, video, audio gets title if title is set`() { val titleString = "test title" @@ -943,6 +1102,58 @@ class ContextMenuCandidateTest { verify(context).startActivity(any()) } + @Test + fun `Candidate 'Share Link' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val shareLink = ContextMenuCandidate.createShareLinkCandidate( + testContext, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + + assertFalse( + shareLink.showFor( + createTab("test://www.mozilla.org"), + HitResult.UNKNOWN("test://www.mozilla.org") + ) + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org") + ) + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Share image'`() { val store = BrowserStore( @@ -992,6 +1203,30 @@ class ContextMenuCandidateTest { assertEquals(store.state.tabs.first().content.private, shareStateCaptor.value.private) } + @Test + fun `Candidate 'Share image' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val shareImage = ContextMenuCandidate.createShareImageCandidate( + testContext, mock(), additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + shareImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org") + ) + ) + + assertFalse( + shareImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Copy Link'`() { val parentView = CoordinatorLayout(testContext) @@ -1069,6 +1304,58 @@ class ContextMenuCandidateTest { ) } + @Test + fun `Candidate 'Copy Link' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val copyLink = ContextMenuCandidate.createCopyLinkCandidate( + testContext, mock(), snackbarDelegate, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org") + ) + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + + assertFalse( + copyLink.showFor( + createTab("test://www.mozilla.org"), + HitResult.UNKNOWN("test://www.mozilla.org") + ) + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org") + ) + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Copy Image Location'`() { val parentView = CoordinatorLayout(testContext) @@ -1139,6 +1426,30 @@ class ContextMenuCandidateTest { ) } + @Test + fun `Candidate 'Copy Image Location' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val copyImageLocation = ContextMenuCandidate.createCopyImageLocationCandidate( + testContext, mock(), snackbarDelegate, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org") + ) + ) + + assertFalse( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org") + ) + ) + } + @Test fun `Candidate 'Open in external app'`() { val tab = createTab("https://www.mozilla.org") @@ -1238,6 +1549,56 @@ class ContextMenuCandidateTest { verify(openAppLinkRedirectMock, times(2)).invoke(any(), anyBoolean(), any()) } + @Test + fun `Candidate 'Open in external app' allows for an additional validation for it to be shown`() { + val tab = createTab("https://www.mozilla.org") + val getAppLinkRedirectMock: AppLinksUseCases.GetAppLinkRedirect = mock() + doReturn( + AppLinkRedirect(mock(), null, null) + ).`when`(getAppLinkRedirectMock).invoke(eq("https://www.example.com")) + doReturn( + AppLinkRedirect(null, null, mock()) + ).`when`(getAppLinkRedirectMock).invoke(eq("intent:www.example.com#Intent;scheme=https;package=org.mozilla.fenix;end")) + val openAppLinkRedirectMock: AppLinksUseCases.OpenAppLinkRedirect = mock() + val appLinksUseCasesMock: AppLinksUseCases = mock() + doReturn(getAppLinkRedirectMock).`when`(appLinksUseCasesMock).appLinkRedirectIncludeInstall + doReturn(openAppLinkRedirectMock).`when`(appLinksUseCasesMock).openAppLink + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openLinkInExternalApp = ContextMenuCandidate.createOpenInExternalAppCandidate( + testContext, appLinksUseCasesMock, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.UNKNOWN("https://www.example.com") + ) + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.UNKNOWN("intent:www.example.com#Intent;scheme=https;package=org.mozilla.fenix;end") + ) + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.VIDEO("https://www.example.com") + ) + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.AUDIO("https://www.example.com") + ) + ) + } + @Test fun `Candidate 'Copy email address'`() { val parentView = CoordinatorLayout(testContext) @@ -1301,6 +1662,30 @@ class ContextMenuCandidateTest { ) } + @Test + fun `Candidate 'Copy email address' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val copyEmailAddress = ContextMenuCandidate.createCopyEmailAddressCandidate( + testContext, mock(), snackbarDelegate, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com") + ) + ) + + assertFalse( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com") + ) + ) + } + @Test fun `Candidate 'Share email address'`() { val context = spy(testContext) @@ -1356,6 +1741,30 @@ class ContextMenuCandidateTest { verify(context).startActivity(any()) } + @Test + fun `Candidate 'Share email address' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val shareEmailAddress = ContextMenuCandidate.createShareEmailAddressCandidate( + testContext, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com") + ) + ) + + assertFalse( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com") + ) + ) + } + @Test fun `Candidate 'Add to contacts'`() { val context = spy(testContext) @@ -1411,6 +1820,30 @@ class ContextMenuCandidateTest { verify(context).startActivity(any()) } + @Test + fun `Candidate 'Add to contacts' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val addToContacts = ContextMenuCandidate.createAddContactCandidate( + testContext, additionalValidation + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + addToContacts.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com") + ) + ) + + assertFalse( + addToContacts.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com") + ) + ) + } + @Test fun `GIVEN SessionState with null EngineSession WHEN isUrlSchemeAllowed is called THEN it returns true`() { val sessionState = TabSessionState( diff --git a/docs/changelog.md b/docs/changelog.md index e0e4c3c82b8..b330ff5f5fc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,9 @@ permalink: /changelog/ * [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt) * [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml) +* **feature-contextmenu** + * 🌟 Add new `additionalValidation` parameter to context menu options builders allowing clients to know when these options to be shown and potentially block showing them. + # 101.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) From fd0947ed5402ff76cdaf965e5d0ca54c46c66f6e Mon Sep 17 00:00:00 2001 From: mcarare Date: Wed, 20 Apr 2022 18:12:47 +0300 Subject: [PATCH 028/160] For #12024: Deprecate TrustedWebActivityIntentProcessor. --- .../pwa/intent/TrustedWebActivityIntentProcessor.kt | 1 + .../pwa/intent/TrustedWebActivityIntentProcessorTest.kt | 3 +++ docs/changelog.md | 3 +++ .../java/org/mozilla/samples/browser/DefaultComponents.kt | 7 ------- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessor.kt b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessor.kt index c115a3350dd..3857bc92d68 100644 --- a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessor.kt +++ b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessor.kt @@ -33,6 +33,7 @@ import mozilla.components.support.utils.toSafeIntent /** * Processor for intents which open Trusted Web Activities. */ +@Deprecated("TWAs are not supported. See https://github.com/mozilla-mobile/android-components/issues/12024") class TrustedWebActivityIntentProcessor( private val addNewTabUseCase: CustomTabsUseCases.AddCustomTabUseCase, packageManager: PackageManager, diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessorTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessorTest.kt index fcb0ced8526..24d763c034a 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessorTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/TrustedWebActivityIntentProcessorTest.kt @@ -23,12 +23,15 @@ import mozilla.components.support.test.mock import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) @ExperimentalCoroutinesApi +@Suppress("DEPRECATION") +@Ignore("TrustedWebActivityIntentProcessorTest] is deprecated. See https://github.com/mozilla-mobile/android-components/issues/12024") class TrustedWebActivityIntentProcessorTest { private lateinit var store: BrowserStore diff --git a/docs/changelog.md b/docs/changelog.md index b330ff5f5fc..b5dcbbd6c62 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -14,6 +14,9 @@ permalink: /changelog/ * **feature-contextmenu** * 🌟 Add new `additionalValidation` parameter to context menu options builders allowing clients to know when these options to be shown and potentially block showing them. +* **feature-pwa** + * [TrustedWebActivityIntentProcessor] is now deprecated. See [issue #12024](https://github.com/mozilla-mobile/android-components/issues/12024). + # 101.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) diff --git a/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt b/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt index d5f13ed87e7..16fe137a7de 100644 --- a/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt +++ b/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt @@ -53,7 +53,6 @@ import mozilla.components.feature.pwa.ManifestStorage import mozilla.components.feature.pwa.WebAppInterceptor import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.pwa.WebAppUseCases -import mozilla.components.feature.pwa.intent.TrustedWebActivityIntentProcessor import mozilla.components.feature.pwa.intent.WebAppIntentProcessor import mozilla.components.feature.readerview.ReaderViewMiddleware import mozilla.components.feature.search.SearchUseCases @@ -240,12 +239,6 @@ open class DefaultComponents(private val applicationContext: Context) { val externalAppIntentProcessors by lazy { listOf( WebAppIntentProcessor(store, customTabsUseCases.addWebApp, sessionUseCases.loadUrl, webAppManifestStorage), - TrustedWebActivityIntentProcessor( - customTabsUseCases.add, - applicationContext.packageManager, - relationChecker, - customTabsStore - ), CustomTabIntentProcessor(customTabsUseCases.add, applicationContext.resources) ) } From 89c54fb2c9e0f35c996cc29a2599e5c770247cb0 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Tue, 3 May 2022 12:44:02 +0000 Subject: [PATCH 029/160] Update GeckoView (Nightly) to 102.0.20220503094208. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 6eadc20f9a1..8461b912a39 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220502144301" + const val version = "102.0.20220503094208" /** * GeckoView channel From 9ca101412248cfec3a00127a3408fa3252a0b7d0 Mon Sep 17 00:00:00 2001 From: Alexandru2909 Date: Wed, 20 Apr 2022 16:41:27 +0300 Subject: [PATCH 030/160] For #12036 - Add provided top sites filter --- .../top/sites/DefaultTopSitesStorage.kt | 1 + .../feature/top/sites/TopSitesConfig.kt | 4 +- .../top/sites/DefaultTopSitesStorageTest.kt | 84 +++++++++++++++++++ docs/changelog.md | 3 + 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/DefaultTopSitesStorage.kt b/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/DefaultTopSitesStorage.kt index b8dc6a2fdaf..6e87e2b06b0 100644 --- a/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/DefaultTopSitesStorage.kt +++ b/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/DefaultTopSitesStorage.kt @@ -102,6 +102,7 @@ class DefaultTopSitesStorage( try { providerTopSites = topSitesProvider .getTopSites(allowCache = true) + .filter { providerConfig.providerFilter?.invoke(it) ?: true } .take(numSitesRequired) .take(providerConfig.maxThreshold - pinnedSites.size) topSites.addAll(providerTopSites) diff --git a/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/TopSitesConfig.kt b/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/TopSitesConfig.kt index a3a10a9fbf5..bffff0a66f3 100644 --- a/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/TopSitesConfig.kt +++ b/components/feature/top-sites/src/main/java/mozilla/components/feature/top/sites/TopSitesConfig.kt @@ -29,8 +29,10 @@ data class TopSitesConfig( * @property showProviderTopSites Whether or not to display the top sites from the provider. * @property maxThreshold Only fetch the top sites from the provider if the number of top sites are * below the maximum threshold. + * @property providerFilter Optional function used to filter the top sites from the provider. */ data class TopSitesProviderConfig( val showProviderTopSites: Boolean, - val maxThreshold: Int = Int.MAX_VALUE + val maxThreshold: Int = Int.MAX_VALUE, + val providerFilter: ((TopSite) -> Boolean)? = null ) diff --git a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt index 52bd4a47111..801bce380e2 100644 --- a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt +++ b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt @@ -765,6 +765,90 @@ class DefaultTopSitesStorageTest { assertEquals(defaultTopSitesStorage.cachedTopSites, topSites) } + @Test + fun `GIVEN providerFilter is set WHEN getTopSites is called THEN the provided top sites are filtered`() = runBlockingTest { + val defaultTopSitesStorage = DefaultTopSitesStorage( + pinnedSitesStorage = pinnedSitesStorage, + historyStorage = historyStorage, + topSitesProvider = topSitesProvider, + coroutineContext = coroutineContext + ) + + val filteredUrl = "https://test.com" + + val providerConfig = TopSitesProviderConfig( + showProviderTopSites = true, + providerFilter = { topSite -> topSite.url != filteredUrl } + ) + + val defaultSite = TopSite.Default( + id = 1, + title = "Firefox", + url = "https://firefox.com", + createdAt = 1 + ) + val pinnedSite = TopSite.Pinned( + id = 2, + title = "Test", + url = filteredUrl, + createdAt = 2 + ) + val providedSite = TopSite.Provided( + id = 3, + title = "Mozilla", + url = "https://mozilla.com", + clickUrl = "https://mozilla.com/click", + imageUrl = "https://test.com/image2.jpg", + impressionUrl = "https://example.com", + createdAt = 3 + ) + val providedFilteredSite = TopSite.Provided( + id = 3, + title = "Filtered", + url = filteredUrl, + clickUrl = "https://test.com/click", + imageUrl = "https://test.com/image2.jpg", + impressionUrl = "https://example.com", + createdAt = 3 + ) + + whenever(pinnedSitesStorage.getPinnedSites()).thenReturn( + listOf( + defaultSite, + pinnedSite + ) + ) + whenever(topSitesProvider.getTopSites()).thenReturn(listOf(providedSite, providedFilteredSite)) + + val frecentSite1 = TopFrecentSiteInfo("https://getpocket.com", "Pocket") + whenever(historyStorage.getTopFrecentSites(anyInt(), any())).thenReturn(listOf(frecentSite1)) + + var topSites = defaultTopSitesStorage.getTopSites( + totalSites = 3, + frecencyConfig = FrecencyThresholdOption.NONE, + providerConfig = providerConfig + ) + + assertEquals(3, topSites.size) + assertEquals(providedSite, topSites[0]) + assertEquals(defaultSite, topSites[1]) + assertEquals(pinnedSite, topSites[2]) + assertEquals(defaultTopSitesStorage.cachedTopSites, topSites) + + topSites = defaultTopSitesStorage.getTopSites( + totalSites = 4, + frecencyConfig = FrecencyThresholdOption.NONE, + providerConfig = providerConfig + ) + + assertEquals(4, topSites.size) + assertEquals(providedSite, topSites[0]) + assertEquals(defaultSite, topSites[1]) + assertEquals(pinnedSite, topSites[2]) + assertEquals(frecentSite1.toTopSite(), topSites[3]) + assertEquals(defaultTopSitesStorage.cachedTopSites, topSites) + } + @Test fun `GIVEN frecent top sites exist as a pinned or provided site WHEN top sites are retrieved THEN filters out frecent sites that already exist in pinned or provided sites`() = runBlockingTest { val defaultTopSitesStorage = DefaultTopSitesStorage( diff --git a/docs/changelog.md b/docs/changelog.md index b5dcbbd6c62..69940f63195 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -17,6 +17,9 @@ permalink: /changelog/ * **feature-pwa** * [TrustedWebActivityIntentProcessor] is now deprecated. See [issue #12024](https://github.com/mozilla-mobile/android-components/issues/12024). +* **feature-top-sites** + * Added `providerFilter` to `TopSitesProviderConfig`, allowing the client to filter the provided top sites. + # 101.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) From d297773a9e6664f106b0bc06eaa7ff60db9a76cf Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Wed, 2 Mar 2022 13:33:27 +1100 Subject: [PATCH 031/160] app-services tabs component constructor now wants a database filename --- .../browser/storage/sync/RemoteTabsStorage.kt | 7 ++++++- .../browser/storage/sync/RemoteTabsStorageTest.kt | 3 ++- .../feature/syncedtabs/storage/SyncedTabsStorage.kt | 2 +- docs/changelog.md | 10 ++++++++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt index 1186bbdaa13..e70bbff3624 100644 --- a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt +++ b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt @@ -4,6 +4,7 @@ package mozilla.components.browser.storage.sync +import android.content.Context import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancelChildren @@ -15,16 +16,20 @@ import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.SyncableStore import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.utils.logElapsedTime +import java.io.File import mozilla.appservices.remotetabs.InternalException as RemoteTabProviderException import mozilla.appservices.remotetabs.TabsStore as RemoteTabsProvider +private const val TABS_DB_NAME = "tabs.sqlite" + /** * An interface which defines read/write methods for remote tabs data. */ open class RemoteTabsStorage( + private val context: Context, private val crashReporter: CrashReporting? = null ) : Storage, SyncableStore { - internal val api by lazy { RemoteTabsProvider() } + internal val api by lazy { RemoteTabsProvider(File(context.filesDir, TABS_DB_NAME).canonicalPath) } private val scope by lazy { CoroutineScope(Dispatchers.IO) } internal val logger = Logger("RemoteTabsStorage") diff --git a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt index 6fbe31ac985..5c3baab473d 100644 --- a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt +++ b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt @@ -13,6 +13,7 @@ import mozilla.appservices.remotetabs.RemoteTab import mozilla.components.concept.base.crash.CrashReporting import mozilla.components.support.test.any import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -36,7 +37,7 @@ class RemoteTabsStorageTest { @Before fun setup() { crashReporter = mock() - remoteTabs = spy(RemoteTabsStorage(crashReporter)) + remoteTabs = spy(RemoteTabsStorage(testContext, crashReporter)) apiMock = mock(RemoteTabsProvider::class.java) `when`(remoteTabs.api).thenReturn(apiMock) } diff --git a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt index 14f2b6e9300..321b90ae491 100644 --- a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt +++ b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt @@ -38,7 +38,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged class SyncedTabsStorage( private val accountManager: FxaAccountManager, private val store: BrowserStore, - private val tabsStorage: RemoteTabsStorage = RemoteTabsStorage(), + private val tabsStorage: RemoteTabsStorage, private val debounceMillis: Long = 1000L, ) : SyncedTabsProvider { private var scope: CoroutineScope? = null diff --git a/docs/changelog.md b/docs/changelog.md index 69940f63195..aa99b0059be 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -88,13 +88,19 @@ permalink: /changelog/ * **lib-crash-sentry** * 🌟️️ Add `sendCaughtExceptions` config flag to `SentryService`, allowing consumers to disable submitting caught exceptions. By default it's enabled, maintaining prior behaviour. Useful in projects with high volumes of caught exceptions and multiple release channels. - + * **site-permission-feature** * 🆕 New Add to SitePermissionsFeature a property to set visibility for NotAskAgainCheckBox * **feature-search** * 🆕 Update search Engines and Search Engine Icons - + +* **browser-storage-sync** + * ⚠️ **This is a breaking change**: When constructing a `RemoteTabsStorage` object you must now a `Context` which is used to determine the location of the sqlite database which is used to persist the remote tabs [#11799](https://github.com/mozilla-mobile/android-components/pull/11799). + +* **feature-syncedtabs** + * ⚠️ **This is a breaking change**: When constructing a `SyncedTabsStorage`, the `tabsStorage: RemoteTabsStorage` parameter is no longer optional so must be supplied [#11799](https://github.com/mozilla-mobile/android-components/pull/11799). + # 99.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v98.0.0...v99.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/146?closed=1) From 5513d433b69d5916823ecc59c58ce35809b86093 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 20 Apr 2022 20:23:30 +0100 Subject: [PATCH 032/160] Incrment to 93.0.1 --- buildSrc/src/main/java/Dependencies.kt | 2 +- .../mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b85282dcc3e..13d675380f1 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -30,7 +30,7 @@ object Versions { const val disklrucache = "2.0.2" const val leakcanary = "2.8.1" - const val mozilla_appservices = "91.1.2" + const val mozilla_appservices = "93.0.1" const val mozilla_glean = "44.1.1" diff --git a/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy b/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy index 9a5d6cbda32..9a79dd4c189 100644 --- a/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy +++ b/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy @@ -171,7 +171,7 @@ class NimbusPlugin implements Plugin { // a) this plugin is going to live in the AS repo (eventually) // See https://github.com/mozilla-mobile/android-components/issues/11422 for tying this // to a version that is specified in buildSrc/src/main/java/Dependencies.kt - return "92.0.0" + return "93.0.1" } // Try one or more hosts to download the given file. From d82765b8ab6ba042588df555b8b0a355fbfb102a Mon Sep 17 00:00:00 2001 From: Matthew Tighe Date: Tue, 3 May 2022 14:44:33 -0700 Subject: [PATCH 033/160] Refresh tabs in synced tab feature only when syncs are completed. (#12067) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../feature/syncedtabs/controller/DefaultController.kt | 5 ++--- .../feature/syncedtabs/presenter/DefaultPresenter.kt | 7 ++++--- .../feature/syncedtabs/controller/DefaultControllerTest.kt | 4 ++-- .../feature/syncedtabs/presenter/DefaultPresenterTest.kt | 1 - 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/controller/DefaultController.kt b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/controller/DefaultController.kt index 53d601b2ec7..1abfab6923d 100644 --- a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/controller/DefaultController.kt +++ b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/controller/DefaultController.kt @@ -29,8 +29,6 @@ internal class DefaultController( * See [SyncedTabsController.refreshSyncedTabs] */ override fun refreshSyncedTabs() { - view.startLoading() - scope.launch { accountManager.withConstellation { val syncedDeviceTabs = provider.getSyncedDeviceTabs() @@ -39,7 +37,7 @@ internal class DefaultController( scope.launch(Dispatchers.Main) { if (syncedDeviceTabs.isEmpty() && otherDevices?.isEmpty() == true) { view.onError(ErrorType.MULTIPLE_DEVICES_UNAVAILABLE) - } else if (!syncedDeviceTabs.any { it.tabs.isNotEmpty() }) { + } else if (syncedDeviceTabs.all { it.tabs.isEmpty() }) { view.onError(ErrorType.NO_TABS_AVAILABLE) } else { view.displaySyncedTabs(syncedDeviceTabs) @@ -57,6 +55,7 @@ internal class DefaultController( * See [SyncedTabsController.syncAccount] */ override fun syncAccount() { + view.startLoading() scope.launch { accountManager.withConstellation { refreshDevices() } accountManager.syncNow(SyncReason.User, customEngineSubset = listOf(SyncEngine.Tabs)) diff --git a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenter.kt b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenter.kt index f4320eaa52d..bd6b20e602c 100644 --- a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenter.kt +++ b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenter.kt @@ -77,7 +77,6 @@ internal class DefaultPresenter( return } - controller.refreshSyncedTabs() controller.syncAccount() } @@ -86,8 +85,11 @@ internal class DefaultPresenter( } companion object { + // This status isn't always set before it's inspected. This causes erroneous reports of the + // sync engine being unavailable. Tabs are included in sync by default, so it's safe to + // default to true until they are deliberately disabled. private fun isSyncedTabsEngineEnabled(context: Context): Boolean { - return SyncEnginesStorage(context).getStatus()[SyncEngine.Tabs] ?: false + return SyncEnginesStorage(context).getStatus()[SyncEngine.Tabs] ?: true } } @@ -103,7 +105,6 @@ internal class DefaultPresenter( override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { CoroutineScope(Dispatchers.Main).launch { controller.syncAccount() - controller.refreshSyncedTabs() } } diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt index 8b2ad5e12cf..cd0f704ed0b 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt @@ -46,7 +46,6 @@ class DefaultControllerTest { controller.refreshSyncedTabs() - verify(view).startLoading() verify(view).stopLoading() verifyNoMoreInteractions(view) @@ -130,7 +129,7 @@ class DefaultControllerTest { } @Test - fun `syncAccount refreshes devices and syncs`() = runBlockingTest { + fun `WHEN syncAccount is called THEN view is loading, devices are refreshed, and sync started`() = runBlockingTest { val controller = DefaultController( storage, accountManager, @@ -146,6 +145,7 @@ class DefaultControllerTest { controller.syncAccount() + verify(view).startLoading() verify(constellation).refreshDevices() verify(accountManager).syncNow(SyncReason.User, false, listOf(SyncEngine.Tabs)) } diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt index 9f8d4733b9e..3be69de6ed4 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt @@ -156,7 +156,6 @@ class DefaultPresenterTest { shadowOf(getMainLooper()).idle() verify(controller).syncAccount() - verify(controller).refreshSyncedTabs() } @Test From e2d8990f784236f4565981b913746fce916295bd Mon Sep 17 00:00:00 2001 From: Rohan Maity Date: Sun, 27 Feb 2022 10:49:30 +0530 Subject: [PATCH 034/160] For #11783 Improve Perceived Performance of Browser tab thumbnails --- .../browser/thumbnails/loader/ThumbnailLoader.kt | 8 +++++--- .../browser/thumbnails/loader/ThumbnailLoaderTest.kt | 3 --- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/components/browser/thumbnails/src/main/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoader.kt b/components/browser/thumbnails/src/main/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoader.kt index fffc3af269d..72de8dc9384 100644 --- a/components/browser/thumbnails/src/main/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoader.kt +++ b/components/browser/thumbnails/src/main/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoader.kt @@ -46,8 +46,6 @@ class ThumbnailLoader(private val storage: ThumbnailStorage) : ImageLoader { val existingJob = view.get()?.getTag(R.id.mozac_browser_thumbnails_tag_job) as? Job existingJob?.cancel() - view.get()?.setImageDrawable(placeholder) - // Create a loading job val deferredThumbnail = storage.loadThumbnail(request) @@ -58,7 +56,11 @@ class ThumbnailLoader(private val storage: ThumbnailStorage) : ImageLoader { try { val thumbnail = deferredThumbnail.await() - view.get()?.setImageBitmap(thumbnail) + if (thumbnail != null) { + view.get()?.setImageBitmap(thumbnail) + } else { + view.get()?.setImageDrawable(placeholder) + } } catch (e: CancellationException) { view.get()?.setImageDrawable(error) } finally { diff --git a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoaderTest.kt b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoaderTest.kt index 4cbf9988253..be3781d49d3 100644 --- a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoaderTest.kt +++ b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/loader/ThumbnailLoaderTest.kt @@ -44,7 +44,6 @@ class ThumbnailLoaderTest { loader.loadIntoView(view, request) - verify(view).setImageDrawable(null) verify(view).addOnAttachStateChangeListener(any()) verify(view).setTag(eq(R.id.mozac_browser_thumbnails_tag_job), any()) verify(view, never()).setImageBitmap(any()) @@ -70,8 +69,6 @@ class ThumbnailLoaderTest { loader.loadIntoView(view, request, placeholder = placeholder, error = error) - verify(view).setImageDrawable(placeholder) - result.cancel() verify(view).setImageDrawable(error) From abb72af0f458039c6ab164bb1baf88e353a10d1c Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Wed, 4 May 2022 04:45:06 +0400 Subject: [PATCH 035/160] Revert "Application services 93.0.1" (#12100) * Revert "Incrment to 93.0.1" This reverts commit 5513d433b69d5916823ecc59c58ce35809b86093. * Revert "app-services tabs component constructor now wants a database filename" This reverts commit d297773a9e6664f106b0bc06eaa7ff60db9a76cf. --- buildSrc/src/main/java/Dependencies.kt | 2 +- .../browser/storage/sync/RemoteTabsStorage.kt | 7 +------ .../browser/storage/sync/RemoteTabsStorageTest.kt | 3 +-- .../feature/syncedtabs/storage/SyncedTabsStorage.kt | 2 +- .../tooling/nimbus/NimbusGradlePlugin.groovy | 2 +- docs/changelog.md | 10 ++-------- 6 files changed, 7 insertions(+), 19 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 13d675380f1..b85282dcc3e 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -30,7 +30,7 @@ object Versions { const val disklrucache = "2.0.2" const val leakcanary = "2.8.1" - const val mozilla_appservices = "93.0.1" + const val mozilla_appservices = "91.1.2" const val mozilla_glean = "44.1.1" diff --git a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt index e70bbff3624..1186bbdaa13 100644 --- a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt +++ b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt @@ -4,7 +4,6 @@ package mozilla.components.browser.storage.sync -import android.content.Context import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancelChildren @@ -16,20 +15,16 @@ import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.SyncableStore import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.utils.logElapsedTime -import java.io.File import mozilla.appservices.remotetabs.InternalException as RemoteTabProviderException import mozilla.appservices.remotetabs.TabsStore as RemoteTabsProvider -private const val TABS_DB_NAME = "tabs.sqlite" - /** * An interface which defines read/write methods for remote tabs data. */ open class RemoteTabsStorage( - private val context: Context, private val crashReporter: CrashReporting? = null ) : Storage, SyncableStore { - internal val api by lazy { RemoteTabsProvider(File(context.filesDir, TABS_DB_NAME).canonicalPath) } + internal val api by lazy { RemoteTabsProvider() } private val scope by lazy { CoroutineScope(Dispatchers.IO) } internal val logger = Logger("RemoteTabsStorage") diff --git a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt index 5c3baab473d..6fbe31ac985 100644 --- a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt +++ b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt @@ -13,7 +13,6 @@ import mozilla.appservices.remotetabs.RemoteTab import mozilla.components.concept.base.crash.CrashReporting import mozilla.components.support.test.any import mozilla.components.support.test.mock -import mozilla.components.support.test.robolectric.testContext import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -37,7 +36,7 @@ class RemoteTabsStorageTest { @Before fun setup() { crashReporter = mock() - remoteTabs = spy(RemoteTabsStorage(testContext, crashReporter)) + remoteTabs = spy(RemoteTabsStorage(crashReporter)) apiMock = mock(RemoteTabsProvider::class.java) `when`(remoteTabs.api).thenReturn(apiMock) } diff --git a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt index 321b90ae491..14f2b6e9300 100644 --- a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt +++ b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt @@ -38,7 +38,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged class SyncedTabsStorage( private val accountManager: FxaAccountManager, private val store: BrowserStore, - private val tabsStorage: RemoteTabsStorage, + private val tabsStorage: RemoteTabsStorage = RemoteTabsStorage(), private val debounceMillis: Long = 1000L, ) : SyncedTabsProvider { private var scope: CoroutineScope? = null diff --git a/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy b/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy index 9a79dd4c189..9a5d6cbda32 100644 --- a/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy +++ b/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy @@ -171,7 +171,7 @@ class NimbusPlugin implements Plugin { // a) this plugin is going to live in the AS repo (eventually) // See https://github.com/mozilla-mobile/android-components/issues/11422 for tying this // to a version that is specified in buildSrc/src/main/java/Dependencies.kt - return "93.0.1" + return "92.0.0" } // Try one or more hosts to download the given file. diff --git a/docs/changelog.md b/docs/changelog.md index aa99b0059be..69940f63195 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -88,19 +88,13 @@ permalink: /changelog/ * **lib-crash-sentry** * 🌟️️ Add `sendCaughtExceptions` config flag to `SentryService`, allowing consumers to disable submitting caught exceptions. By default it's enabled, maintaining prior behaviour. Useful in projects with high volumes of caught exceptions and multiple release channels. - + * **site-permission-feature** * 🆕 New Add to SitePermissionsFeature a property to set visibility for NotAskAgainCheckBox * **feature-search** * 🆕 Update search Engines and Search Engine Icons - -* **browser-storage-sync** - * ⚠️ **This is a breaking change**: When constructing a `RemoteTabsStorage` object you must now a `Context` which is used to determine the location of the sqlite database which is used to persist the remote tabs [#11799](https://github.com/mozilla-mobile/android-components/pull/11799). - -* **feature-syncedtabs** - * ⚠️ **This is a breaking change**: When constructing a `SyncedTabsStorage`, the `tabsStorage: RemoteTabsStorage` parameter is no longer optional so must be supplied [#11799](https://github.com/mozilla-mobile/android-components/pull/11799). - + # 99.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v98.0.0...v99.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/146?closed=1) From b4b0dcae31d1dfd6922925d3f15e336005879d3a Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Tue, 3 May 2022 14:03:32 -0400 Subject: [PATCH 036/160] No issue: Update the README status of components --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5c7e69a8842..5edec069552 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ High-level components for building browser(-like) apps. * 🔵 [**Icons**](components/browser/icons/README.md) - A component for loading and storing website icons (like [Favicons](https://en.wikipedia.org/wiki/Favicon)). -* ⚪ [**Menu**](components/browser/menu/README.md) - A generic menu with customizable items primarily for browser toolbars. +* 🔵 [**Menu**](components/browser/menu/README.md) - A generic menu with customizable items primarily for browser toolbars. * ⚪ [**Menu 2**](components/browser/menu2/README.md) - A generic menu with customizable items primarily for browser toolbars. @@ -81,9 +81,9 @@ High-level components for building browser(-like) apps. * 🔵 [**Storage-Sync**](components/browser/storage-sync/README.md) - A syncable implementation of browser storage backed by [application-services' Places lib](https://github.com/mozilla/application-services). -* ⚪ [**Tabstray**](components/browser/tabstray/README.md) - A customizable tabs tray for browsers. +* 🔵 [**Tabstray**](components/browser/tabstray/README.md) - A customizable tabs tray for browsers. -* 🔴 [**Thumbnails**](components/browser/thumbnails/README.md) - A component for loading and storing website thumbnails (screenshot of the website). +* 🔵 [**Thumbnails**](components/browser/thumbnails/README.md) - A component for loading and storing website thumbnails (screenshot of the website). * 🔵 [**Toolbar**](components/browser/toolbar/README.md) - A customizable toolbar for browsers. @@ -101,7 +101,7 @@ _API contracts and abstraction layers for browser components._ * 🔵 [**Storage**](components/concept/storage/README.md) - Abstract definition of a browser storage component. -* ⚪ [**Tabstray**](components/concept/tabstray/README.md) - Abstract definition of a tabs tray component. +* 🔵 [**Tabstray**](components/concept/tabstray/README.md) - Abstract definition of a tabs tray component. * 🔵 [**Toolbar**](components/concept/toolbar/README.md) - Abstract definition of a browser toolbar component. @@ -113,7 +113,7 @@ _Combined components to implement feature-specific use cases._ * 🔵 [**Accounts Push**](components/feature/accounts-push/README.md) - Feature of use cases for FxA Account that work with push support. -* 🔴 [**Autofill**](components/feature/autofill/README.md) - A component that provides support for Android's Autofill framework. +* 🔵 [**Autofill**](components/feature/autofill/README.md) - A component that provides support for Android's Autofill framework. * 🔵 [**Awesomebar**](components/feature/awesomebar/README.md) - A component that connects a [concept-awesomebar](components/concept/awesomebar/README.md) implementation to a [concept-toolbar](components/concept/toolbar/README.md) implementation and provides implementations of various suggestion providers. @@ -123,7 +123,7 @@ _Combined components to implement feature-specific use cases._ * 🔵 [**Custom Tabs**](components/feature/customtabs/README.md) - A component for providing [Custom Tabs](https://developer.chrome.com/multidevice/android/customtabs) functionality in browsers. -* ⚪ [**Downloads**](components/feature/downloads/README.md) - A component to perform downloads using the [Android downloads manager](https://developer.android.com/reference/android/app/DownloadManager). +* 🔵 [**Downloads**](components/feature/downloads/README.md) - A component to perform downloads using the [Android downloads manager](https://developer.android.com/reference/android/app/DownloadManager). * 🔵 [**Intent**](components/feature/intent/README.md) - A component that provides intent processing functionality by combining various other feature modules. @@ -155,11 +155,11 @@ _Combined components to implement feature-specific use cases._ * 🔵 [**Find In Page**](components/feature/findinpage/README.md) - A component that provides an UI widget for [find in page functionality](https://support.mozilla.org/en-US/kb/search-contents-current-page-text-or-links). -* ⚪ [**Remote Tabs**](components/feature/remotetabs/README.md) - Feature that provides access to other device's tabs in the same account. +* 🔵 [**Remote Tabs**](components/feature/remotetabs/README.md) - Feature that provides access to other device's tabs in the same account. * 🔵 [**Site Permissions**](components/feature/sitepermissions/README.md) - A feature for showing site permission request prompts. -* ⚪ [**WebAuthn**](components/feature/webauthn/README.md) - A feature that provides WebAuthn functionality for supported engines. +* 🔵 [**WebAuthn**](components/feature/webauthn/README.md) - A feature that provides WebAuthn functionality for supported engines. * 🔵 [**Web Notifications**](components/feature/webnotifications/README.md) - A component for displaying web notifications. @@ -191,7 +191,7 @@ _Components and libraries to interact with backend services._ * 🔵 [**Firefox Sync - Logins**](components/service/sync-logins/README.md) - A library for integrating with Firefox Sync - Logins. -* 🔴 [**Firefox Sync - Autofill**](components/service/sync-autofill/README.md) - A library for integrating with Firefox Sync - Autofill. +* 🔵 [**Firefox Sync - Autofill**](components/service/sync-autofill/README.md) - A library for integrating with Firefox Sync - Autofill. * 🔵 [**Glean**](components/service/glean/README.md) - A client-side telemetry SDK for collecting metrics and sending them to Mozilla's telemetry service (eventually replacing [service-telemetry](components/service/telemetry/README.md)). @@ -199,9 +199,9 @@ _Components and libraries to interact with backend services._ * 🔴 [**Nimbus**](components/service/nimbus/README.md) - A wrapper for the Nimbus SDK. -* 🔴 [**Pocket**](components/service/pocket/README.md) - A library for communicating with the Pocket API. +* 🔵 [**Pocket**](components/service/pocket/README.md) - A library for communicating with the Pocket API. -* 🔴 [**Contile**](components/service/contile/README.md) - A library for communicating with the Contile services API. +* 🔵 [**Contile**](components/service/contile/README.md) - A library for communicating with the Contile services API. ## Support From c277fe7b61f75001f5b39c7206048123178cb0cb Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Thu, 11 Mar 2021 11:46:16 -0500 Subject: [PATCH 037/160] Issue #9838: Introduce CreditCardValidationDelegate and implement onCreditCardSave in GeckoCreditCardsAddressesStorageDelegate - Introduces `CreditCardValidationDelegate` and a default implementation in `DefaultCreditCardValidationDelegate` - Implements `onCreditCardSave` in `GeckoCreditCardsAddressesStorageDelegate` - Refactors `CreditCard` from concept-engine to `CreditCardEntry` in concept-storage so that it can validated with the `CreditCardValidationDelegate` --- .../GeckoAutocompleteStorageDelegate.kt | 11 +- .../browser/engine/gecko/ext/CreditCard.kt | 10 +- .../gecko/prompt/GeckoPromptDelegate.kt | 8 +- .../gecko/prompt/GeckoPromptDelegateTest.kt | 6 +- .../concept/engine/prompt/CreditCard.kt | 50 ------- .../concept/engine/prompt/PromptRequest.kt | 7 +- .../concept/engine/prompt/CreditCardTest.kt | 44 ------ .../engine/prompt/PromptRequestTest.kt | 5 +- components/concept/storage/build.gradle | 2 + .../storage/CreditCardsAddressesStorage.kt | 98 +++++++++++-- .../feature/prompts/PromptFeature.kt | 8 +- .../creditcard/CreditCardItemViewHolder.kt | 12 +- .../prompts/creditcard/CreditCardPicker.kt | 10 +- .../prompts/creditcard/CreditCardSelectBar.kt | 8 +- .../prompts/creditcard/CreditCardsAdapter.kt | 12 +- .../feature/prompts/PromptFeatureTest.kt | 28 ++-- .../CreditCardItemViewHolderTest.kt | 10 +- .../creditcard/CreditCardPickerTest.kt | 6 +- .../creditcard/CreditCardSelectBarTest.kt | 8 +- .../creditcard/CreditCardsAdapterTest.kt | 6 +- components/service/sync-autofill/build.gradle | 1 + .../DefaultCreditCardValidationDelegate.kt | 51 +++++++ ...eckoCreditCardsAddressesStorageDelegate.kt | 77 +++++++--- ...DefaultCreditCardValidationDelegateTest.kt | 134 ++++++++++++++++++ ...CreditCardsAddressesStorageDelegateTest.kt | 118 ++++++++++++--- .../components/support/ktx/kotlin/String.kt | 10 ++ .../support/ktx/kotlin/StringTest.kt | 10 ++ docs/changelog.md | 4 + 28 files changed, 537 insertions(+), 217 deletions(-) delete mode 100644 components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/CreditCard.kt delete mode 100644 components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/CreditCardTest.kt create mode 100644 components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegate.kt create mode 100644 components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegateTest.kt diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt index 66770616917..6f766d00fc8 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt @@ -31,15 +31,17 @@ class GeckoAutocompleteStorageDelegate( private val loginStorageDelegate: LoginStorageDelegate ) : Autocomplete.StorageDelegate { - override fun onCreditCardFetch(): GeckoResult>? { + override fun onCreditCardFetch(): GeckoResult> { val result = GeckoResult>() @OptIn(DelicateCoroutinesApi::class) GlobalScope.launch(IO) { - val creditCards = creditCardsAddressesStorageDelegate.onCreditCardsFetch().await() + val key = creditCardsAddressesStorageDelegate.getOrGenerateKey() + + val creditCards = creditCardsAddressesStorageDelegate.onCreditCardsFetch() .mapNotNull { val plaintextCardNumber = - creditCardsAddressesStorageDelegate.decrypt(it.encryptedCardNumber)?.number + creditCardsAddressesStorageDelegate.decrypt(key, it.encryptedCardNumber)?.number if (plaintextCardNumber == null) { null @@ -54,6 +56,7 @@ class GeckoAutocompleteStorageDelegate( } } .toTypedArray() + result.complete(creditCards) } @@ -64,7 +67,7 @@ class GeckoAutocompleteStorageDelegate( loginStorageDelegate.onLoginSave(login.toLoginEntry()) } - override fun onLoginFetch(domain: String): GeckoResult>? { + override fun onLoginFetch(domain: String): GeckoResult> { val result = GeckoResult>() @OptIn(DelicateCoroutinesApi::class) diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/CreditCard.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/CreditCard.kt index b3b55760b8f..3706281d2dc 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/CreditCard.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/CreditCard.kt @@ -4,14 +4,14 @@ package mozilla.components.browser.engine.gecko.ext -import mozilla.components.concept.engine.prompt.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.support.utils.creditCardIIN import org.mozilla.geckoview.Autocomplete /** - * Converts a GeckoView [Autocomplete.CreditCard] to an Android Components [CreditCard]. + * Converts a GeckoView [Autocomplete.CreditCard] to an Android Components [CreditCardEntry]. */ -fun Autocomplete.CreditCard.toCreditCard() = CreditCard( +fun Autocomplete.CreditCard.toCreditCardEntry() = CreditCardEntry( guid = guid, name = name, number = number, @@ -21,9 +21,9 @@ fun Autocomplete.CreditCard.toCreditCard() = CreditCard( ) /** - * Converts an Android Components [CreditCard] to a GeckoView [Autocomplete.CreditCard]. + * Converts an Android Components [CreditCardEntry] to a GeckoView [Autocomplete.CreditCard]. */ -fun CreditCard.toAutocompleteCreditCard() = Autocomplete.CreditCard.Builder() +fun CreditCardEntry.toAutocompleteCreditCard() = Autocomplete.CreditCard.Builder() .guid(guid) .name(name) .number(number) diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt index 877723a49b3..fe065d6389c 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt @@ -9,15 +9,15 @@ import android.net.Uri import androidx.annotation.VisibleForTesting import mozilla.components.browser.engine.gecko.GeckoEngineSession import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard -import mozilla.components.browser.engine.gecko.ext.toCreditCard +import mozilla.components.browser.engine.gecko.ext.toCreditCardEntry import mozilla.components.browser.engine.gecko.ext.toLoginEntry import mozilla.components.concept.engine.prompt.Choice -import mozilla.components.concept.engine.prompt.CreditCard import mozilla.components.concept.engine.prompt.PromptRequest import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.concept.engine.prompt.ShareData +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry import mozilla.components.support.base.log.logger.Logger @@ -77,7 +77,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe ): GeckoResult? { val geckoResult = GeckoResult() - val onConfirm: (CreditCard) -> Unit = { creditCard -> + val onConfirm: (CreditCardEntry) -> Unit = { creditCard -> if (!request.isComplete) { geckoResult.complete( request.confirm( @@ -94,7 +94,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe geckoEngineSession.notifyObservers { onPromptRequest( PromptRequest.SelectCreditCard( - creditCards = request.options.map { it.value.toCreditCard() }, + creditCards = request.options.map { it.value.toCreditCardEntry() }, onDismiss = onDismiss, onConfirm = onConfirm ) diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt index 9a67b9ebc34..760a17bfa38 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt @@ -12,10 +12,10 @@ import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard import mozilla.components.browser.engine.gecko.ext.toLoginEntry import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.prompt.Choice -import mozilla.components.concept.engine.prompt.CreditCard import mozilla.components.concept.engine.prompt.PromptRequest import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry import mozilla.components.support.ktx.kotlin.toDate @@ -850,7 +850,7 @@ class GeckoPromptDelegateTest { } }) - val creditCard1 = CreditCard( + val creditCard1 = CreditCardEntry( guid = "1", name = "Banana Apple", number = "4111111111111110", @@ -861,7 +861,7 @@ class GeckoPromptDelegateTest { val creditCardSelectOption1 = Autocomplete.CreditCardSelectOption(creditCard1.toAutocompleteCreditCard()) - val creditCard2 = CreditCard( + val creditCard2 = CreditCardEntry( guid = "2", name = "Orange Pineapple", number = "4111111111115555", diff --git a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/CreditCard.kt b/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/CreditCard.kt deleted file mode 100644 index 1b3b650e219..00000000000 --- a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/CreditCard.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* 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 mozilla.components.concept.engine.prompt - -import android.annotation.SuppressLint -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -/** - * Value type that represents a credit card. - * - * @property guid The unique identifier for this credit card. - * @property name The credit card billing name. - * @property number The credit card number. - * @property expiryMonth The credit card expiry month. - * @property expiryYear The credit card expiry year. - * @property cardType The credit card network ID. - */ -@SuppressLint("ParcelCreator") -@Parcelize -data class CreditCard( - val guid: String?, - val name: String, - val number: String, - val expiryMonth: String, - val expiryYear: String, - val cardType: String -) : Parcelable { - val obfuscatedCardNumber: String - get() = ellipsesStart + - ellipsis + ellipsis + ellipsis + ellipsis + - number.substring(number.length - digitsToShow) + - ellipsesEnd - - companion object { - // Left-To-Right Embedding (LTE) mark - const val ellipsesStart = "\u202A" - - // One dot ellipsis - const val ellipsis = "\u2022\u2060\u2006\u2060" - - // Pop Directional Formatting (PDF) mark - const val ellipsesEnd = "\u202C" - - // Number of digits to be displayed after ellipses on an obfuscated credit card number. - const val digitsToShow = 4 - } -} diff --git a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt b/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt index 9e9635c1574..68d85c512a8 100644 --- a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt +++ b/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt @@ -9,6 +9,7 @@ import android.net.Uri import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry import java.util.UUID @@ -93,13 +94,13 @@ sealed class PromptRequest( /** * Value type that represents a request for a select credit card prompt. - * @property creditCards a list of [CreditCard]s to select from. + * @property creditCards a list of [CreditCardEntry]s to select from. * @property onConfirm callback that is called when the user confirms the credit card selection. * @property onDismiss callback to let the page know the user dismissed the dialog. */ data class SelectCreditCard( - val creditCards: List, - val onConfirm: (CreditCard) -> Unit, + val creditCards: List, + val onConfirm: (CreditCardEntry) -> Unit, override val onDismiss: () -> Unit ) : PromptRequest(), Dismissible diff --git a/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/CreditCardTest.kt b/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/CreditCardTest.kt deleted file mode 100644 index 2d2ec2ac4fb..00000000000 --- a/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/CreditCardTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* 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 mozilla.components.concept.engine.prompt - -import org.junit.Assert.assertEquals -import org.junit.Test - -class CreditCardTest { - - @Test - fun `Create a CreditCard`() { - val guid = "1" - val name = "Banana Apple" - val number = "4111111111111110" - val last4Digits = "1110" - val expiryMonth = "5" - val expiryYear = "2030" - val cardType = "amex" - val creditCard = CreditCard( - guid = guid, - name = name, - number = number, - expiryMonth = expiryMonth, - expiryYear = expiryYear, - cardType = cardType - ) - - assertEquals(guid, creditCard.guid) - assertEquals(name, creditCard.name) - assertEquals(number, creditCard.number) - assertEquals(expiryMonth, creditCard.expiryMonth) - assertEquals(expiryYear, creditCard.expiryYear) - assertEquals(cardType, creditCard.cardType) - assertEquals( - CreditCard.ellipsesStart + - CreditCard.ellipsis + CreditCard.ellipsis + CreditCard.ellipsis + CreditCard.ellipsis + - last4Digits + - CreditCard.ellipsesEnd, - creditCard.obfuscatedCardNumber - ) - } -} diff --git a/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt b/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt index 72f700878bf..ef0e3a41540 100644 --- a/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt +++ b/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt @@ -20,6 +20,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.concept.engine.prompt.PromptRequest.TextPrompt import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry import mozilla.components.support.test.mock @@ -271,7 +272,7 @@ class PromptRequestTest { @Test fun `GIVEN a list of credit cards WHEN SelectCreditCard is confirmed or dismissed THEN their respective callback is invoked`() { - val creditCard = CreditCard( + val creditCard = CreditCardEntry( guid = "id", name = "Banana Apple", number = "4111111111111110", @@ -281,7 +282,7 @@ class PromptRequestTest { ) var onDismissCalled = false var onConfirmCalled = false - var confirmedCreditCard: CreditCard? = null + var confirmedCreditCard: CreditCardEntry? = null val selectCreditCardRequest = SelectCreditCard( creditCards = listOf(creditCard), diff --git a/components/concept/storage/build.gradle b/components/concept/storage/build.gradle index ecc355a17e4..587ba4a5d1c 100644 --- a/components/concept/storage/build.gradle +++ b/components/concept/storage/build.gradle @@ -27,6 +27,8 @@ dependencies { // dependency, but it will crash at runtime. // Included via 'api' because this module is unusable without coroutines. api Dependencies.kotlin_coroutines + + implementation project(':support-ktx') } apply from: '../../../publish.gradle' diff --git a/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt b/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt index 7ea2412eb51..663c03db27e 100644 --- a/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt +++ b/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt @@ -6,8 +6,11 @@ package mozilla.components.concept.storage import android.annotation.SuppressLint import android.os.Parcelable -import kotlinx.coroutines.Deferred import kotlinx.parcelize.Parcelize +import mozilla.components.concept.storage.CreditCard.Companion.ellipsesEnd +import mozilla.components.concept.storage.CreditCard.Companion.ellipsesStart +import mozilla.components.concept.storage.CreditCard.Companion.ellipsis +import mozilla.components.support.ktx.kotlin.last4Digits /** * An interface which defines read/write methods for credit card and address data. @@ -49,6 +52,7 @@ interface CreditCardsAddressesStorage { /** * Deletes the credit card with the given [guid]. * + * @param guid Unique identifier for the desired credit card. * @return True if the deletion did anything, false otherwise. */ suspend fun deleteCreditCard(guid: String): Boolean @@ -196,10 +200,10 @@ data class CreditCard( val expiryMonth: Long, val expiryYear: Long, val cardType: String, - val timeCreated: Long, - val timeLastUsed: Long?, - val timeLastModified: Long, - val timesUsed: Long + val timeCreated: Long = 0L, + val timeLastUsed: Long? = 0L, + val timeLastModified: Long = 0L, + val timesUsed: Long = 0L ) : Parcelable { val obfuscatedCardNumber: String get() = ellipsesStart + @@ -219,6 +223,33 @@ data class CreditCard( } } +/** + * Credit card autofill entry. + * + * This contains the data needed to handle autofill but not the data related to the DB record. + * + * @property guid The unique identifier for this credit card. + * @property name The credit card billing name. + * @property number The credit card number. + * @property expiryMonth The credit card expiry month. + * @property expiryYear The credit card expiry year. + * @property cardType The credit card network ID. + */ +data class CreditCardEntry( + val guid: String? = null, + val name: String, + val number: String, + val expiryMonth: String, + val expiryYear: String, + val cardType: String +) { + val obfuscatedCardNumber: String + get() = ellipsesStart + + ellipsis + ellipsis + ellipsis + ellipsis + + number.last4Digits() + + ellipsesEnd +} + /** * Information about a new credit card. * Use this when creating a credit card via [CreditCardsAddressesStorage.addCreditCard]. @@ -334,40 +365,85 @@ data class UpdatableAddressFields( val email: String ) +/** + * Provides a method for checking whether or not a given credit card can be stored. + */ +interface CreditCardValidationDelegate { + + /** + * The result from validating a given [CreditCard] against the credit card storage. This will + * include whether or not it can be created or updated. + */ + sealed class Result { + /** + * Indicates that the [CreditCard] does not currently exist in the storage, and a new + * credit card entry can be created. + */ + object CanBeCreated : Result() + + /** + * Indicates that a matching [CreditCard] was found in the storage, and the [CreditCard] + * can be used to update its information. + */ + data class CanBeUpdated(val foundCreditCard: CreditCard) : Result() + } + + /** + * Determines whether a [CreditCardEntry] can be added or updated in the credit card storage. + * + * @param creditCard [CreditCardEntry] to be added or updated in the credit card storage. + * @return [Result] that indicates whether or not the [CreditCardEntry] should be saved or + * updated. + */ + suspend fun shouldCreateOrUpdate(creditCard: CreditCardEntry): Result +} + /** * Used to handle [Address] and [CreditCard] storage so that the underlying engine doesn't have to. * An instance of this should be attached to the Gecko runtime in order to be used. */ -interface CreditCardsAddressesStorageDelegate { +interface CreditCardsAddressesStorageDelegate : KeyProvider { /** * Decrypt a [CreditCardNumber.Encrypted] into its plaintext equivalent or `null` if * it fails to decrypt. * + * @param key The encryption key to decrypt the decrypt credit card number. * @param encryptedCardNumber An encrypted credit card number to be decrypted. * @return A plaintext, non-encrypted credit card number. */ - suspend fun decrypt(encryptedCardNumber: CreditCardNumber.Encrypted): CreditCardNumber.Plaintext? + suspend fun decrypt( + key: ManagedKey, + encryptedCardNumber: CreditCardNumber.Encrypted + ): CreditCardNumber.Plaintext? /** * Returns all stored addresses. This is called when the engine believes an address field * should be autofilled. + * + * @return A list of all stored addresses. */ - fun onAddressesFetch(): Deferred> + suspend fun onAddressesFetch(): List
    /** * Saves the given address to storage. + * + * @param address [Address] to be saved or updated in the address storage. */ - fun onAddressSave(address: Address) + suspend fun onAddressSave(address: Address) /** * Returns all stored credit cards. This is called when the engine believes a credit card * field should be autofilled. + * + * @return A list of all stored credit cards. */ - fun onCreditCardsFetch(): Deferred> + suspend fun onCreditCardsFetch(): List /** * Saves the given credit card to storage. + * + * @param creditCard [CreditCardEntry] to be saved or updated in the credit card storage. */ - fun onCreditCardSave(creditCard: CreditCard) + suspend fun onCreditCardSave(creditCard: CreditCardEntry) } diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt index e7529ee3967..0f1a58449bc 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt @@ -21,7 +21,6 @@ import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.prompt.Choice -import mozilla.components.concept.engine.prompt.CreditCard import mozilla.components.concept.engine.prompt.PromptRequest import mozilla.components.concept.engine.prompt.PromptRequest.Alert import mozilla.components.concept.engine.prompt.PromptRequest.Authentication @@ -41,6 +40,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.Share import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.concept.engine.prompt.PromptRequest.TextPrompt import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry import mozilla.components.concept.storage.LoginValidationDelegate @@ -143,7 +143,7 @@ class PromptFeature private constructor( override val loginExceptionStorage: LoginExceptions? = null, private val loginPickerView: SelectablePromptView? = null, private val onManageLogins: () -> Unit = {}, - private val creditCardPickerView: SelectablePromptView? = null, + private val creditCardPickerView: SelectablePromptView? = null, private val onManageCreditCards: () -> Unit = {}, private val onSelectCreditCard: () -> Unit = {}, onNeedToRequestPermissions: OnNeedToRequestPermissions @@ -181,7 +181,7 @@ class PromptFeature private constructor( loginExceptionStorage: LoginExceptions? = null, loginPickerView: SelectablePromptView? = null, onManageLogins: () -> Unit = {}, - creditCardPickerView: SelectablePromptView? = null, + creditCardPickerView: SelectablePromptView? = null, onManageCreditCards: () -> Unit = {}, onSelectCreditCard: () -> Unit = {}, onNeedToRequestPermissions: OnNeedToRequestPermissions @@ -215,7 +215,7 @@ class PromptFeature private constructor( loginExceptionStorage: LoginExceptions? = null, loginPickerView: SelectablePromptView? = null, onManageLogins: () -> Unit = {}, - creditCardPickerView: SelectablePromptView? = null, + creditCardPickerView: SelectablePromptView? = null, onManageCreditCards: () -> Unit = {}, onSelectCreditCard: () -> Unit = {}, onNeedToRequestPermissions: OnNeedToRequestPermissions diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt index 4f2749cd404..e4ffe121c80 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt @@ -8,7 +8,7 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView -import mozilla.components.concept.engine.prompt.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.feature.prompts.R import mozilla.components.support.utils.creditCardIssuerNetwork import java.text.SimpleDateFormat @@ -22,15 +22,15 @@ import java.util.Locale */ class CreditCardItemViewHolder( view: View, - private val onCreditCardSelected: (CreditCard) -> Unit + private val onCreditCardSelected: (CreditCardEntry) -> Unit ) : RecyclerView.ViewHolder(view) { /** - * Binds the view with the provided [CreditCard]. + * Binds the view with the provided [CreditCardEntry]. * - * @param creditCard The [CreditCard] to display. + * @param creditCard The [CreditCardEntry] to display. */ - fun bind(creditCard: CreditCard) { + fun bind(creditCard: CreditCardEntry) { itemView.findViewById(R.id.credit_card_logo) .setImageResource(creditCard.cardType.creditCardIssuerNetwork().icon) @@ -47,7 +47,7 @@ class CreditCardItemViewHolder( /** * Set the credit card expiry date formatted according to the locale. */ - private fun bindCreditCardExpiryDate(creditCard: CreditCard) { + private fun bindCreditCardExpiryDate(creditCard: CreditCardEntry) { val dateFormat = SimpleDateFormat(DATE_PATTERN, Locale.getDefault()) val calendar = Calendar.getInstance() diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardPicker.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardPicker.kt index eace0537467..cc789af4a2f 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardPicker.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardPicker.kt @@ -6,8 +6,8 @@ package mozilla.components.feature.prompts.creditcard import androidx.annotation.VisibleForTesting import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.prompt.CreditCard import mozilla.components.concept.engine.prompt.PromptRequest +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.feature.prompts.concept.SelectablePromptView import mozilla.components.feature.prompts.consumePromptFrom import mozilla.components.feature.prompts.facts.emitCreditCardAutofillDismissedFact @@ -29,11 +29,11 @@ import mozilla.components.support.base.log.logger.Logger */ class CreditCardPicker( private val store: BrowserStore, - private val creditCardSelectBar: SelectablePromptView, + private val creditCardSelectBar: SelectablePromptView, private val manageCreditCardsCallback: () -> Unit = {}, private val selectCreditCardCallback: () -> Unit = {}, private var sessionId: String? = null -) : SelectablePromptView.Listener { +) : SelectablePromptView.Listener { init { creditCardSelectBar.listener = this @@ -41,14 +41,14 @@ class CreditCardPicker( // The selected credit card option to confirm. @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal var selectedCreditCard: CreditCard? = null + internal var selectedCreditCard: CreditCardEntry? = null override fun onManageOptions() { manageCreditCardsCallback.invoke() dismissSelectCreditCardRequest() } - override fun onOptionSelect(option: CreditCard) { + override fun onOptionSelect(option: CreditCardEntry) { selectedCreditCard = option creditCardSelectBar.hidePrompt() selectCreditCardCallback.invoke() diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBar.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBar.kt index 4bb38070835..6cf9a5bffb1 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBar.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBar.kt @@ -17,7 +17,7 @@ import androidx.core.widget.ImageViewCompat import androidx.core.widget.TextViewCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import mozilla.components.concept.engine.prompt.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.feature.prompts.R import mozilla.components.feature.prompts.concept.SelectablePromptView import mozilla.components.feature.prompts.facts.emitCreditCardAutofillExpandedFact @@ -31,7 +31,7 @@ class CreditCardSelectBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr), SelectablePromptView { +) : ConstraintLayout(context, attrs, defStyleAttr), SelectablePromptView { private var view: View? = null private var recyclerView: RecyclerView? = null @@ -47,7 +47,7 @@ class CreditCardSelectBar @JvmOverloads constructor( } } - override var listener: SelectablePromptView.Listener? = null + override var listener: SelectablePromptView.Listener? = null init { context.withStyledAttributes( @@ -78,7 +78,7 @@ class CreditCardSelectBar @JvmOverloads constructor( toggleSelectCreditCardHeader(shouldExpand = false) } - override fun showPrompt(options: List) { + override fun showPrompt(options: List) { if (view == null) { view = View.inflate(context, LAYOUT_ID, this) bindViews() diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapter.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapter.kt index 4ea8571e1bf..14f8e06276d 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapter.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapter.kt @@ -8,7 +8,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter -import mozilla.components.concept.engine.prompt.CreditCard +import mozilla.components.concept.storage.CreditCardEntry /** * Adapter for a list of credit cards to be displayed. @@ -16,8 +16,8 @@ import mozilla.components.concept.engine.prompt.CreditCard * @param onCreditCardSelected Callback invoked when a credit card item is selected. */ class CreditCardsAdapter( - private val onCreditCardSelected: (CreditCard) -> Unit -) : ListAdapter(DiffCallback) { + private val onCreditCardSelected: (CreditCardEntry) -> Unit +) : ListAdapter(DiffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CreditCardItemViewHolder { val view = LayoutInflater.from(parent.context) @@ -29,11 +29,11 @@ class CreditCardsAdapter( holder.bind(getItem(position)) } - internal object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: CreditCard, newItem: CreditCard) = + internal object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: CreditCardEntry, newItem: CreditCardEntry) = oldItem.guid == newItem.guid - override fun areContentsTheSame(oldItem: CreditCard, newItem: CreditCard) = + override fun areContentsTheSame(oldItem: CreditCardEntry, newItem: CreditCardEntry) = oldItem == newItem } } diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt index af42a910830..77724d30f91 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt @@ -31,7 +31,6 @@ import mozilla.components.browser.state.state.createCustomTab import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.prompt.Choice -import mozilla.components.concept.engine.prompt.CreditCard import mozilla.components.concept.engine.prompt.PromptRequest import mozilla.components.concept.engine.prompt.PromptRequest.Alert import mozilla.components.concept.engine.prompt.PromptRequest.Authentication @@ -43,6 +42,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.concept.engine.prompt.PromptRequest.TextPrompt import mozilla.components.concept.engine.prompt.ShareData +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry import mozilla.components.feature.prompts.concept.SelectablePromptView @@ -410,7 +410,7 @@ class PromptFeatureTest { @Test fun `GIVEN creditCardPickerView is visible WHEN dismissSelectPrompts is called THEN dismissSelectCreditCardRequest returns true`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = spy( PromptFeature( mock(), @@ -434,7 +434,7 @@ class PromptFeatureTest { @Test fun `GIVEN creditCardPickerView is not visible WHEN dismissSelectPrompts is called THEN dismissSelectPrompt returns false`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = spy( PromptFeature( mock(), @@ -457,7 +457,7 @@ class PromptFeatureTest { @Test fun `GIVEN an active select credit card request WHEN onBackPressed is called THEN dismissSelectPrompts is called`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = spy( PromptFeature( mock(), @@ -481,7 +481,7 @@ class PromptFeatureTest { @Test fun `WHEN dismissSelectPrompts is called THEN the active credit card picker should be dismissed`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = spy( PromptFeature( mock(), @@ -817,7 +817,7 @@ class PromptFeatureTest { @Test fun `WHEN onActivityResult is called with PIN_REQUEST and RESULT_OK THEN onAuthSuccess) is called`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = PromptFeature( activity = mock(), @@ -836,7 +836,7 @@ class PromptFeatureTest { @Test fun `WHEN onActivityResult is called with PIN_REQUEST and RESULT_CANCELED THEN onAuthFailure is called`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = PromptFeature( activity = mock(), @@ -855,7 +855,7 @@ class PromptFeatureTest { @Test fun `GIVEN user successfully authenticates by biometric prompt WHEN onBiometricResult is called THEN onAuthSuccess is called`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = PromptFeature( activity = mock(), @@ -873,7 +873,7 @@ class PromptFeatureTest { @Test fun `GIVEN user fails to authenticate by biometric prompt WHEN onBiometricResult is called THEN onAuthFailure) is called`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = PromptFeature( activity = mock(), @@ -925,7 +925,7 @@ class PromptFeatureTest { @Test fun `WHEN a credit card is selected THEN confirm the prompt request with the selected credit card`() { - val creditCard = CreditCard( + val creditCard = CreditCardEntry( guid = "id", name = "Banana Apple", number = "4111111111111110", @@ -935,7 +935,7 @@ class PromptFeatureTest { ) var onDismissCalled = false var onConfirmCalled = false - var confirmedCreditCard: CreditCard? = null + var confirmedCreditCard: CreditCardEntry? = null val selectCreditCardRequest = PromptRequest.SelectCreditCard( creditCards = listOf(creditCard), @@ -1393,7 +1393,7 @@ class PromptFeatureTest { @Test fun `WHEN page is refreshed THEN credit card prompt is dismissed`() { - val creditCardPickerView: SelectablePromptView = mock() + val creditCardPickerView: SelectablePromptView = mock() val feature = PromptFeature( activity = mock(), @@ -1404,8 +1404,8 @@ class PromptFeatureTest { ) { } feature.creditCardPicker = creditCardPicker val onDismiss: () -> Unit = {} - val onConfirm: (CreditCard) -> Unit = {} - val creditCard = CreditCard( + val onConfirm: (CreditCardEntry) -> Unit = {} + val creditCard = CreditCardEntry( guid = "1", name = "Banana Apple", number = "4111111111111110", diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolderTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolderTest.kt index 0cb08d848e0..aecf8fe4775 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolderTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolderTest.kt @@ -9,7 +9,7 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 -import mozilla.components.concept.engine.prompt.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.feature.prompts.R import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext @@ -27,9 +27,9 @@ class CreditCardItemViewHolderTest { private lateinit var cardLogoView: ImageView private lateinit var cardNumberView: TextView private lateinit var expirationDateView: TextView - private lateinit var onCreditCardSelected: (CreditCard) -> Unit + private lateinit var onCreditCardSelected: (CreditCardEntry) -> Unit - private val creditCard = CreditCard( + private val creditCard = CreditCardEntry( guid = "1", name = "Banana Apple", number = "4111111111111111", @@ -58,8 +58,8 @@ class CreditCardItemViewHolderTest { @Test fun `GIVEN a credit card item WHEN a credit item is clicked THEN onCreditCardSelected is called with the given credit card item`() { - var onCreditCardSelectedCalled: CreditCard? = null - val onCreditCardSelected = { creditCard: CreditCard -> + var onCreditCardSelectedCalled: CreditCardEntry? = null + val onCreditCardSelected = { creditCard: CreditCardEntry -> onCreditCardSelectedCalled = creditCard } CreditCardItemViewHolder(view, onCreditCardSelected).bind(creditCard) diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardPickerTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardPickerTest.kt index 3f7d07e4741..242661c0bbb 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardPickerTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardPickerTest.kt @@ -10,8 +10,8 @@ import mozilla.components.browser.state.state.ContentState import mozilla.components.browser.state.state.CustomTabSessionState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.prompt.CreditCard import mozilla.components.concept.engine.prompt.PromptRequest +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.support.test.mock import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals @@ -30,7 +30,7 @@ class CreditCardPickerTest { private lateinit var creditCardPicker: CreditCardPicker private lateinit var creditCardSelectBar: CreditCardSelectBar - private val creditCard = CreditCard( + private val creditCard = CreditCardEntry( guid = "1", name = "Banana Apple", number = "4111111111111110", @@ -39,7 +39,7 @@ class CreditCardPickerTest { cardType = "" ) var onDismissCalled = false - var confirmedCreditCard: CreditCard? = null + var confirmedCreditCard: CreditCardEntry? = null private val promptRequest = PromptRequest.SelectCreditCard( creditCards = listOf(creditCard), onDismiss = { onDismissCalled = true }, diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt index deb09d182cc..ed695b2285a 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt @@ -10,7 +10,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import mozilla.components.concept.engine.prompt.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.feature.prompts.R import mozilla.components.feature.prompts.concept.SelectablePromptView import mozilla.components.feature.prompts.facts.CreditCardAutofillDialogFacts @@ -36,7 +36,7 @@ class CreditCardSelectBarTest { private lateinit var creditCardSelectBar: CreditCardSelectBar - private val creditCard = CreditCard( + private val creditCard = CreditCardEntry( guid = "1", name = "Banana Apple", number = "4111111111111110", @@ -68,7 +68,7 @@ class CreditCardSelectBarTest { @Test fun `GIVEN a listener WHEN manage credit cards button is clicked THEN onManageOptions is called`() { - val listener: SelectablePromptView.Listener = mock() + val listener: SelectablePromptView.Listener = mock() assertNull(creditCardSelectBar.listener) @@ -82,7 +82,7 @@ class CreditCardSelectBarTest { @Test fun `GIVEN a listener WHEN a credit card is selected THEN onOptionSelect is called`() = runBlocking { - val listener: SelectablePromptView.Listener = mock() + val listener: SelectablePromptView.Listener = mock() creditCardSelectBar.listener = listener val facts = mutableListOf() diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapterTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapterTest.kt index 414a0ec3fa1..6fbfc06a23e 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapterTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardsAdapterTest.kt @@ -1,7 +1,7 @@ package mozilla.components.feature.prompts.creditcard -import mozilla.components.concept.engine.prompt.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -10,7 +10,7 @@ class CreditCardsAdapterTest { @Test fun testDiffCallback() { - val creditCard1 = CreditCard( + val creditCard1 = CreditCardEntry( guid = "1", name = "Banana Apple", number = "4111111111111110", @@ -27,7 +27,7 @@ class CreditCardsAdapterTest { CreditCardsAdapter.DiffCallback.areContentsTheSame(creditCard1, creditCard2) ) - val creditCard3 = CreditCard( + val creditCard3 = CreditCardEntry( guid = "2", name = "Pineapple Orange", number = "4111111111115555", diff --git a/components/service/sync-autofill/build.gradle b/components/service/sync-autofill/build.gradle index 87bb3bbf66a..db95a8a066a 100644 --- a/components/service/sync-autofill/build.gradle +++ b/components/service/sync-autofill/build.gradle @@ -32,6 +32,7 @@ dependencies { api project(':lib-dataprotect') implementation project(':support-utils') + implementation project(':support-ktx') implementation Dependencies.kotlin_stdlib diff --git a/components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegate.kt b/components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegate.kt new file mode 100644 index 00000000000..c42e2bc7f0f --- /dev/null +++ b/components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegate.kt @@ -0,0 +1,51 @@ +/* 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 mozilla.components.service.sync.autofill + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import mozilla.components.concept.storage.CreditCard +import mozilla.components.concept.storage.CreditCardEntry +import mozilla.components.concept.storage.CreditCardValidationDelegate +import mozilla.components.concept.storage.CreditCardValidationDelegate.Result +import mozilla.components.concept.storage.CreditCardsAddressesStorage + +/** + * A delegate that will check against the [CreditCardsAddressesStorage] to determine if a given + * [CreditCard] can be persisted and returns information about why it can or cannot. + * + * @param storage An instance of [CreditCardsAddressesStorage]. + */ +class DefaultCreditCardValidationDelegate( + private val storage: Lazy +) : CreditCardValidationDelegate { + + private val coroutineContext by lazy { Dispatchers.IO } + + override suspend fun shouldCreateOrUpdate(creditCard: CreditCardEntry): Result = + withContext(coroutineContext) { + val creditCards = storage.value.getAllCreditCards() + + val foundCreditCard = if (creditCards.isEmpty()) { + // No credit cards exist in the storage -> create a new credit card + null + } else { + val crypto = storage.value.getCreditCardCrypto() + val key = crypto.getOrGenerateKey() + + creditCards.find { + val cardNumber = crypto.decrypt(key, it.encryptedCardNumber)?.number + + it.guid == creditCard.guid || cardNumber == creditCard.number + } + } + + if (foundCreditCard == null) { + Result.CanBeCreated + } else { + Result.CanBeUpdated(foundCreditCard) + } + } +} diff --git a/components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegate.kt b/components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegate.kt index 1f6f52b5dd0..1c49c81722e 100644 --- a/components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegate.kt +++ b/components/service/sync-autofill/src/main/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegate.kt @@ -4,16 +4,21 @@ package mozilla.components.service.sync.autofill -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import mozilla.components.concept.storage.Address import mozilla.components.concept.storage.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.CreditCardNumber +import mozilla.components.concept.storage.CreditCardValidationDelegate import mozilla.components.concept.storage.CreditCardsAddressesStorage import mozilla.components.concept.storage.CreditCardsAddressesStorageDelegate +import mozilla.components.concept.storage.ManagedKey +import mozilla.components.concept.storage.NewCreditCardFields +import mozilla.components.concept.storage.UpdatableCreditCardFields +import mozilla.components.support.ktx.kotlin.last4Digits /** * [CreditCardsAddressesStorageDelegate] implementation. @@ -28,33 +33,67 @@ class GeckoCreditCardsAddressesStorageDelegate( private val isCreditCardAutofillEnabled: () -> Boolean = { false } ) : CreditCardsAddressesStorageDelegate { - override suspend fun decrypt(encryptedCardNumber: CreditCardNumber.Encrypted): CreditCardNumber.Plaintext? { + override suspend fun getOrGenerateKey(): ManagedKey { + val crypto = storage.value.getCreditCardCrypto() + return crypto.getOrGenerateKey() + } + + override suspend fun decrypt( + key: ManagedKey, + encryptedCardNumber: CreditCardNumber.Encrypted + ): CreditCardNumber.Plaintext? { val crypto = storage.value.getCreditCardCrypto() - val key = crypto.getOrGenerateKey() return crypto.decrypt(key, encryptedCardNumber) } - override fun onAddressesFetch(): Deferred> { - return scope.async { - storage.value.getAllAddresses() - } + override suspend fun onAddressesFetch(): List
    = withContext(scope.coroutineContext) { + storage.value.getAllAddresses() } - override fun onAddressSave(address: Address) { + override suspend fun onAddressSave(address: Address) { TODO("Not yet implemented") } - override fun onCreditCardsFetch(): Deferred> { - if (isCreditCardAutofillEnabled().not()) { - return CompletableDeferred(listOf()) + override suspend fun onCreditCardsFetch(): List = + withContext(scope.coroutineContext) { + if (!isCreditCardAutofillEnabled()) { + emptyList() + } else { + storage.value.getAllCreditCards() + } } - return scope.async { - storage.value.getAllCreditCards() - } - } + override suspend fun onCreditCardSave(creditCard: CreditCardEntry) { + val validationDelegate = DefaultCreditCardValidationDelegate(storage) - override fun onCreditCardSave(creditCard: CreditCard) { - TODO("Not yet implemented") + scope.launch { + when (val result = validationDelegate.shouldCreateOrUpdate(creditCard)) { + is CreditCardValidationDelegate.Result.CanBeCreated -> { + storage.value.addCreditCard( + NewCreditCardFields( + billingName = creditCard.name, + plaintextCardNumber = CreditCardNumber.Plaintext(creditCard.number), + cardNumberLast4 = creditCard.number.last4Digits(), + expiryMonth = creditCard.expiryMonth.toLong(), + expiryYear = creditCard.expiryYear.toLong(), + cardType = creditCard.cardType + ) + ) + } + is CreditCardValidationDelegate.Result.CanBeUpdated -> { + storage.value.updateCreditCard( + guid = result.foundCreditCard.guid, + creditCardFields = UpdatableCreditCardFields( + billingName = creditCard.name, + cardNumber = CreditCardNumber.Plaintext(creditCard.number), + cardNumberLast4 = creditCard.number.last4Digits(), + expiryMonth = creditCard.expiryMonth.toLong(), + expiryYear = creditCard.expiryYear.toLong(), + cardType = creditCard.cardType + ) + ) + } + } + } } } diff --git a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegateTest.kt b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegateTest.kt new file mode 100644 index 00000000000..40a4311e4e8 --- /dev/null +++ b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/DefaultCreditCardValidationDelegateTest.kt @@ -0,0 +1,134 @@ +/* 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 mozilla.components.service.sync.autofill + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import mozilla.components.concept.storage.CreditCardEntry +import mozilla.components.concept.storage.CreditCardNumber +import mozilla.components.concept.storage.CreditCardValidationDelegate.Result +import mozilla.components.concept.storage.NewCreditCardFields +import mozilla.components.lib.dataprotect.SecureAbove22Preferences +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 + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class DefaultCreditCardValidationDelegateTest { + + private lateinit var storage: AutofillCreditCardsAddressesStorage + private lateinit var securePrefs: SecureAbove22Preferences + private lateinit var validationDelegate: DefaultCreditCardValidationDelegate + + @Before + fun before() = runBlocking { + // forceInsecure is set in the tests because a keystore wouldn't be configured in the test environment. + securePrefs = SecureAbove22Preferences(testContext, "autofill", forceInsecure = true) + storage = AutofillCreditCardsAddressesStorage(testContext, lazy { securePrefs }) + validationDelegate = DefaultCreditCardValidationDelegate(storage = lazy { storage }) + } + + @Test + fun `WHEN no credit cards exist in the storage, THEN add the new credit card to storage`() = + runBlocking { + val newCreditCard = createCreditCardEntry(guid = "1") + val result = validationDelegate.shouldCreateOrUpdate(newCreditCard) + + assertEquals(Result.CanBeCreated, result) + } + + @Test + fun `WHEN existing credit card matches by guid and card number, THEN update the credit card in storage`() = + runBlocking { + val creditCardFields = NewCreditCardFields( + billingName = "Pineapple Orange", + plaintextCardNumber = CreditCardNumber.Plaintext("4111111111111111"), + cardNumberLast4 = "1111", + expiryMonth = 12, + expiryYear = 2028, + cardType = "visa" + ) + val creditCard = storage.addCreditCard(creditCardFields) + val newCreditCard = createCreditCardEntry(guid = creditCard.guid) + val result = validationDelegate.shouldCreateOrUpdate(newCreditCard) + + assertEquals(Result.CanBeUpdated(creditCard), result) + } + + @Test + fun `WHEN existing credit card matches by guid only, THEN update the credit card in storage`() = + runBlocking { + val creditCardFields = NewCreditCardFields( + billingName = "Pineapple Orange", + plaintextCardNumber = CreditCardNumber.Plaintext("4111111111111111"), + cardNumberLast4 = "1111", + expiryMonth = 12, + expiryYear = 2028, + cardType = "visa" + ) + val creditCard = storage.addCreditCard(creditCardFields) + val newCreditCard = createCreditCardEntry(guid = creditCard.guid) + val result = validationDelegate.shouldCreateOrUpdate(newCreditCard) + + assertEquals(Result.CanBeUpdated(creditCard), result) + } + + @Test + fun `WHEN existing credit card matches by card number only, THEN update the credit card in storage`() = + runBlocking { + val creditCardFields = NewCreditCardFields( + billingName = "Pineapple Orange", + plaintextCardNumber = CreditCardNumber.Plaintext("4111111111111111"), + cardNumberLast4 = "1111", + expiryMonth = 12, + expiryYear = 2028, + cardType = "visa" + ) + val creditCard = storage.addCreditCard(creditCardFields) + val newCreditCard = createCreditCardEntry(cardNumber = "4111111111111111") + val result = validationDelegate.shouldCreateOrUpdate(newCreditCard) + + assertEquals(Result.CanBeUpdated(creditCard), result) + } + + @Test + fun `WHEN existing credit card does not match by guid and card number, THEN add the new credit card to storage`() = + runBlocking { + val newCreditCard = createCreditCardEntry(guid = "2") + val creditCardFields = NewCreditCardFields( + billingName = "Pineapple Orange", + plaintextCardNumber = CreditCardNumber.Plaintext("4111111111111111"), + cardNumberLast4 = "1111", + expiryMonth = 12, + expiryYear = 2028, + cardType = "visa" + ) + storage.addCreditCard(creditCardFields) + + val result = validationDelegate.shouldCreateOrUpdate(newCreditCard) + + assertEquals(Result.CanBeCreated, result) + } +} + +fun createCreditCardEntry( + guid: String = "id", + billingName: String = "Banana Apple", + cardNumber: String = "4111111111111110", + expiryMonth: String = "1", + expiryYear: String = "2030", + cardType: String = "amex" +) = CreditCardEntry( + guid = guid, + name = billingName, + number = cardNumber, + expiryMonth = expiryMonth, + expiryYear = expiryYear, + cardType = cardType +) diff --git a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt index 4dd998ce0eb..4ebba7c9185 100644 --- a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt +++ b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt @@ -10,9 +10,12 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.concept.storage.CreditCard +import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.CreditCardNumber import mozilla.components.concept.storage.NewCreditCardFields +import mozilla.components.concept.storage.UpdatableCreditCardFields import mozilla.components.lib.dataprotect.SecureAbove22Preferences +import mozilla.components.support.ktx.kotlin.last4Digits import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals @@ -46,26 +49,28 @@ class GeckoCreditCardsAddressesStorageDelegateTest { } @Test - fun `decrypt`() = runBlocking { - val plaintextNumber = CreditCardNumber.Plaintext("4111111111111111") - val creditCardFields = NewCreditCardFields( - billingName = "Jon Doe", - plaintextCardNumber = plaintextNumber, - cardNumberLast4 = "1111", - expiryMonth = 12, - expiryYear = 2028, - cardType = "amex" - ) - val creditCard = storage.addCreditCard(creditCardFields) - - assertEquals( - plaintextNumber, - delegate.decrypt(creditCard.encryptedCardNumber) - ) - } + fun `GIVEN a newly added credit card WHEN decrypt is called THEN it returns the plain credit card number`() = + runBlocking { + val plaintextNumber = CreditCardNumber.Plaintext("4111111111111111") + val creditCardFields = NewCreditCardFields( + billingName = "Jon Doe", + plaintextCardNumber = plaintextNumber, + cardNumberLast4 = "1111", + expiryMonth = 12, + expiryYear = 2028, + cardType = "amex" + ) + val creditCard = storage.addCreditCard(creditCardFields) + val key = delegate.getOrGenerateKey() + + assertEquals( + plaintextNumber, + delegate.decrypt(key, creditCard.encryptedCardNumber) + ) + } @Test - fun `onAddressFetch`() { + fun `WHEN onAddressFetch is called THEN the storage is called to gett all addresses`() { scope.launch { delegate.onAddressesFetch() verify(storage, times(1)).getAllAddresses() @@ -101,4 +106,81 @@ class GeckoCreditCardsAddressesStorageDelegateTest { assertEquals(emptyList(), result) } } + + @Test + fun `GIVEN a new credit card WHEN onCreditCardSave is called THEN it adds a new credit card in storage`() { + scope.launch { + val billingName = "Jon Doe" + val cardNumber = "4111111111111111" + val expiryMonth = 12L + val expiryYear = 2028L + val cardType = "amex" + + delegate.onCreditCardSave( + CreditCardEntry( + name = billingName, + number = cardNumber, + expiryMonth = expiryMonth.toString(), + expiryYear = expiryYear.toString(), + cardType = cardType + ) + ) + + verify(storage, times(1)).addCreditCard( + creditCardFields = NewCreditCardFields( + billingName = billingName, + plaintextCardNumber = CreditCardNumber.Plaintext(cardNumber), + cardNumberLast4 = cardNumber.last4Digits(), + expiryMonth = expiryMonth, + expiryYear = expiryYear, + cardType = cardType + ) + ) + } + } + + @Test + fun `GIVEN an existing credit card WHEN onCreditCardSave is called THEN it updates the existing credit card in storage`() { + scope.launch { + val billingName = "Jon Doe" + val cardNumber = "4111111111111111" + val expiryMonth = 12L + val expiryYear = 2028L + val cardType = "amex" + + val creditCard = storage.addCreditCard( + NewCreditCardFields( + billingName = "Jon Doe", + plaintextCardNumber = CreditCardNumber.Plaintext(cardNumber), + cardNumberLast4 = "1111", + expiryMonth = expiryMonth, + expiryYear = expiryYear, + cardType = cardType + ) + ) + + delegate.onCreditCardSave( + CreditCardEntry( + guid = creditCard.guid, + name = billingName, + number = "4111111111111112", + expiryMonth = expiryMonth.toString(), + expiryYear = expiryYear.toString(), + cardType = cardType + ) + ) + + verify(storage, times(1)).updateCreditCard( + guid = creditCard.guid, + creditCardFields = UpdatableCreditCardFields( + billingName = billingName, + cardNumber = CreditCardNumber.Plaintext("4111111111111112"), + cardNumberLast4 = "4111111111111112".last4Digits(), + expiryMonth = expiryMonth, + expiryYear = expiryYear, + cardType = cardType + ) + ) + } + } } diff --git a/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt b/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt index 58a2441c694..a314e3fcde1 100644 --- a/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt +++ b/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt @@ -29,6 +29,9 @@ private val re = object { private const val MAILTO = "mailto:" +// Number of last digits to be shown when credit card number is obfuscated. +private const val LAST_VISIBLE_DIGITS_COUNT = 4 + /** * Checks if this String is a URL. */ @@ -280,3 +283,10 @@ fun String.getRepresentativeCharacter(): String { return "?" } + +/** + * Returns the last 4 digits from a formatted credit card number string. + */ +fun String.last4Digits(): String { + return this.takeLast(LAST_VISIBLE_DIGITS_COUNT) +} diff --git a/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt b/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt index 41decc904a8..1e6b59fec89 100644 --- a/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt +++ b/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt @@ -278,4 +278,14 @@ class StringTest { // IP assertEquals("1", "https://192.168.0.1".getRepresentativeCharacter()) } + + @Test + fun `last4Digits returns a string with only last 4 digits `() { + assertEquals("8431", "371449635398431".last4Digits()) + assertEquals("2345", "12345".last4Digits()) + assertEquals("1234", "1234".last4Digits()) + assertEquals("123", "123".last4Digits()) + assertEquals("1", "1".last4Digits()) + assertEquals("", "".last4Digits()) + } } diff --git a/docs/changelog.md b/docs/changelog.md index 69940f63195..3bbe1d9cef0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -20,6 +20,10 @@ permalink: /changelog/ * **feature-top-sites** * Added `providerFilter` to `TopSitesProviderConfig`, allowing the client to filter the provided top sites. +* **concept-storage**: + * Added `CreditCardValidationDelegate` which is a delegate that will check against the `CreditCardsAddressesStorage` to determine if a `CreditCard` can be persisted in storage. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838) + * Refactors `CreditCard` from `concept-engine` to `CreditCardEntry` in `concept-storage` so that it can validated with the `CreditCardValidationDelegate`. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838) + # 101.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) From 316a0d07fff9b05426893d85bfe52e32271f0a36 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Wed, 4 May 2022 12:44:06 +0000 Subject: [PATCH 038/160] Update GeckoView (Nightly) to 102.0.20220504093759. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 8461b912a39..563658d8316 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220503094208" + const val version = "102.0.20220504093759" /** * GeckoView channel From 54ebe0f292dbb7fd5d93e83048eda3b74f9450c5 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 21 Apr 2022 12:44:25 +0200 Subject: [PATCH 039/160] Auto-publish & substitute local Glean package Careful with this: If anything inside Glean Core changes this will still require a local substitute for the Gecko build. --- build.gradle | 6 ++++++ settings.gradle | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 5a16523eaad..5a1bb4a5d2c 100644 --- a/build.gradle +++ b/build.gradle @@ -109,6 +109,12 @@ subprojects { } } + // Allow local Glean substitution in each subproject. + if (gradle.hasProperty('localProperties.autoPublish.glean.dir')) { + ext.gleanSrcDir = gradle."localProperties.autoPublish.glean.dir" + apply from: "${rootProject.projectDir}/${gleanSrcDir}/build-scripts/substitute-local-glean.gradle" + } + if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) { if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) { ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir" diff --git a/settings.gradle b/settings.gradle index a3359354d8f..0c43795265a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -117,7 +117,7 @@ def runCmd(cmd, workingDir, successMessage) { Properties localProperties = null; String settingAppServicesPath = "autoPublish.application-services.dir" -String settingGleanPath = "substitutions.glean.dir"; +String settingGleanPath = "autoPublish.glean.dir"; if (file('local.properties').canRead()) { localProperties = new Properties() @@ -151,15 +151,16 @@ if (localProperties != null) { String gleanLocalPath = localProperties.getProperty(settingGleanPath); if (gleanLocalPath != null) { - logger.lifecycle("Local configuration: substituting glean module from path: $gleanLocalPath") - - includeBuild(gleanLocalPath) { - dependencySubstitution { - substitute module('org.mozilla.telemetry:glean') with project(':glean') - substitute module('org.mozilla.telemetry:glean-forUnitTests') with project(':glean') - } + logger.lifecycle("Enabling automatic publication of Glean from: $gleanLocalPath") + // Windows can't execute .py files directly, so we assume a "manually installed" python, + // which comes with a "py" launcher and respects the shebang line to specify the version. + def publishGleanCmd = []; + if (System.properties['os.name'].toLowerCase().contains('windows')) { + publishGleanCmd << "py"; } + publishGleanCmd << "./build-scripts/publish_to_maven_local_if_modified.py"; + runCmd(publishGleanCmd, gleanLocalPath, "Published Glean for local development.") } else { - logger.lifecycle("Local configuration: glean substitution path missing. You may specify it via '$settingGleanPath' setting.") + logger.lifecycle("Disabled auto-publication of Glean. Enable it by settings '$settingGleanPath' in local.properties") } } From 0559e5a47e708b2f450d50621590c3e605486012 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Tue, 22 Mar 2022 16:06:20 +0200 Subject: [PATCH 040/160] For #11892 - Pass ParsedStructure as bytearray instead of as parcelable This avoids the system trying to remap our ParsedStructure parcelable failing by not using the right ClassLoader. Implementing Parcelable on our own was needed to be able to manually create `ParsedStructure` from the unmarshalled parcel. --- .../response/fill/AuthFillResponseBuilder.kt | 20 +++++-- .../autofill/structure/ParsedStructure.kt | 39 ++++++++++++-- .../ui/AbstractAutofillUnlockActivity.kt | 20 ++++--- .../autofill/structure/ParsedStructureTest.kt | 52 +++++++++++++++++++ docs/changelog.md | 3 ++ 5 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt diff --git a/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt b/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt index d9459f08a77..e71b6416d0b 100644 --- a/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt +++ b/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt @@ -11,6 +11,7 @@ import android.content.Intent import android.content.IntentSender import android.graphics.drawable.Icon import android.os.Build +import android.os.Parcel import android.service.autofill.FillResponse import android.service.autofill.InlinePresentation import android.view.autofill.AutofillId @@ -52,10 +53,21 @@ internal data class AuthFillResponseBuilder( } val authIntent = Intent(context, configuration.unlockActivity) - authIntent.putExtra( - AbstractAutofillUnlockActivity.EXTRA_PARSED_STRUCTURE, - parsedStructure - ) + + // Pass `ParsedStructure` as raw bytes to prevent the system throwing a ClassNotFoundException + // when updating the PendingIntent and trying to create and remap `ParsedStructure` + // from the parcelable extra because of an unknown ClassLoader. + with(Parcel.obtain()) { + parsedStructure.writeToParcel(this, 0) + + authIntent.putExtra( + AbstractAutofillUnlockActivity.EXTRA_PARSED_STRUCTURE, + this.marshall() + ) + + recycle() + } + authIntent.putExtra(AbstractAutofillUnlockActivity.EXTRA_IME_SPEC, imeSpec) authIntent.putExtra( AbstractAutofillUnlockActivity.EXTRA_MAX_SUGGESTION_COUNT, diff --git a/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt b/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt index b6c4c4f3f7b..d04c4070e30 100644 --- a/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt +++ b/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt @@ -6,10 +6,11 @@ package mozilla.components.feature.autofill.structure import android.content.Context import android.os.Build +import android.os.Parcel import android.os.Parcelable +import android.os.Parcelable.Creator import android.view.autofill.AutofillId import androidx.annotation.RequiresApi -import kotlinx.parcelize.Parcelize import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.utils.Browsers @@ -20,13 +21,43 @@ import mozilla.components.support.utils.Browsers * https://github.com/mozilla-lockwise/lockwise-android/blob/d3c0511f73c34e8759e1bb597f2d3dc9bcc146f0/app/src/main/java/mozilla/lockbox/autofill/ParsedStructure.kt#L52 */ @RequiresApi(Build.VERSION_CODES.O) -@Parcelize -internal data class ParsedStructure( +data class ParsedStructure( val usernameId: AutofillId? = null, val passwordId: AutofillId? = null, val webDomain: String? = null, val packageName: String -) : Parcelable +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readParcelable(AutofillId::class.java.classLoader), + parcel.readParcelable(AutofillId::class.java.classLoader), + parcel.readString(), + parcel.readString() ?: "" + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(usernameId, flags) + parcel.writeParcelable(passwordId, flags) + parcel.writeString(webDomain) + parcel.writeString(packageName) + } + + override fun describeContents(): Int { + return 0 + } + + /** + * Create instances of [ParsedStructure] from a [Parcel]. + */ + companion object CREATOR : Creator { + override fun createFromParcel(parcel: Parcel): ParsedStructure { + return ParsedStructure(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} /** * Try to find a domain in the [ParsedStructure] for looking up logins. This is either a "web domain" diff --git a/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt b/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt index 6931cd1ea63..04b368f55e5 100644 --- a/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt +++ b/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt @@ -7,6 +7,7 @@ package mozilla.components.feature.autofill.ui import android.content.Intent import android.os.Build import android.os.Bundle +import android.os.Parcel import android.service.autofill.FillResponse import android.view.autofill.AutofillManager import android.widget.inline.InlinePresentationSpec @@ -40,16 +41,21 @@ abstract class AbstractAutofillUnlockActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val parsedStructure = intent.getParcelableExtra(EXTRA_PARSED_STRUCTURE) + val parsedStructure = with(Parcel.obtain()) { + val rawBytes = intent.getByteArrayExtra(EXTRA_PARSED_STRUCTURE) + unmarshall(rawBytes!!, 0, rawBytes.size) + setDataPosition(0) + ParsedStructure(this).also { + recycle() + } + } val imeSpec = intent.getImeSpec() val maxSuggestionCount = intent.getIntExtra(EXTRA_MAX_SUGGESTION_COUNT, MAX_LOGINS) // While the user is asked to authenticate, we already try to build the fill response asynchronously. - if (parsedStructure != null) { - fillResponse = lifecycleScope.async(Dispatchers.IO) { - val builder = fillHandler.handle(parsedStructure, forceUnlock = true, maxSuggestionCount) - val result = builder.build(this@AbstractAutofillUnlockActivity, configuration, imeSpec) - result - } + fillResponse = lifecycleScope.async(Dispatchers.IO) { + val builder = fillHandler.handle(parsedStructure, forceUnlock = true, maxSuggestionCount) + val result = builder.build(this@AbstractAutofillUnlockActivity, configuration, imeSpec) + result } if (authenticator == null) { diff --git a/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt b/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt new file mode 100644 index 00000000000..88504a061b8 --- /dev/null +++ b/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.structure + +import android.os.Parcel +import android.view.autofill.AutofillId +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ParsedStructureTest { + @Test + fun `Given a ParsedStructure WHEN parcelling and unparcelling it THEN get the same object`() { + // AutofillId constructor is private but it can be constructed from a parcel. + // Use this route instead of mocking to avoid errors like below: + // org.robolectric.shadows.ShadowParcel$UnreliableBehaviorError: Looking for Integer at position 72, found String + val usernameIdAutofillIdParcel = Parcel.obtain().apply { + writeInt(1) // viewId + writeInt(3) // flags + writeInt(78) // virtualIntId + setDataPosition(0) // be a good citizen + } + val passwordIdAutofillParcel = Parcel.obtain().apply { + writeInt(11) // viewId + writeInt(31) // flags + writeInt(781) // virtualIntId + setDataPosition(0) // be a good citizen + } + val parsedStructure = ParsedStructure( + usernameId = AutofillId.CREATOR.createFromParcel(usernameIdAutofillIdParcel), + passwordId = AutofillId.CREATOR.createFromParcel(passwordIdAutofillParcel), + packageName = "test", + webDomain = "https://mozilla.org" + ) + + // Write the object in a new Parcel. + val parcel = Parcel.obtain() + parsedStructure.writeToParcel(parcel, 0) + + // Reset Parcel r/w position to be read from beginning afterwards. + parcel.setDataPosition(0) + + // Reconstruct the original object from the Parcel. + val result = ParsedStructure(parcel) + + assertEquals(parsedStructure, result) + } +} diff --git a/docs/changelog.md b/docs/changelog.md index 3bbe1d9cef0..8678f08dde7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,9 @@ permalink: /changelog/ * [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt) * [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml) +* **feature-autofill** + * 🚒 Bug fixed [issue #11893](https://github.com/mozilla-mobile/android-components/issues/11893) - Fix issue with autofilling in 3rd party applications not being immediately available after unlocking the autofill service. + * **feature-contextmenu** * 🌟 Add new `additionalValidation` parameter to context menu options builders allowing clients to know when these options to be shown and potentially block showing them. From 2e60347468e7f2684aaac31cb849628d21ac55fe Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Thu, 5 May 2022 00:05:31 +0000 Subject: [PATCH 041/160] Import l10n. --- .../browser/errorpages/src/main/res/values-tg/strings.xml | 4 ++-- l10n.toml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/browser/errorpages/src/main/res/values-tg/strings.xml b/components/browser/errorpages/src/main/res/values-tg/strings.xml index e9672befb35..a0844b1755c 100644 --- a/components/browser/errorpages/src/main/res/values-tg/strings.xml +++ b/components/browser/errorpages/src/main/res/values-tg/strings.xml @@ -96,9 +96,9 @@ Браузер кӯшиши ҷустуҷӯи маводи дархостшударо қатъ кард. Сомона дархостро тавре равон мекунад, ки ҳеҷ гоҳ ба анҷом нарасад.

    +

    Браузер кӯшиши ҷустуҷӯи маводи дархостшударо қатъ кард. Сомона дархостро тавре равона мекунад, ки раванд ҳеҷ гоҳ ба анҷом намерасад.

      -
    • Оё шумо кукиҳоеро, ки ин сомона талаб мекунад, ғайрифаъол кардед ё бастед?
    • +
    • Шумо кукиҳоеро, ки ин сомона талаб мекунад, ғайрифаъол кардед ё бастед?
    • Агар қабули кукиҳои сомона мушкилиро ҳал накунад, ин эҳтимолан мушкилии танзимоти сервер мебошад, на дастгоҳи шумо.
    ]]>
    diff --git a/l10n.toml b/l10n.toml index 3887082296e..31fafe52521 100644 --- a/l10n.toml +++ b/l10n.toml @@ -88,6 +88,7 @@ locales = [ "sat", "szl", "sk", + "skr", "sl", "sq", "sr", From 56177f815fd2732dcd7a37db3da78bd861ebbbf5 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Thu, 5 May 2022 03:05:23 +0000 Subject: [PATCH 042/160] Update GeckoView (Nightly) to 102.0.20220504234551. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 563658d8316..40d1a409f77 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220504093759" + const val version = "102.0.20220504234551" /** * GeckoView channel From 7219f3956183781db8563c46573ee2344c41b667 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Thu, 5 May 2022 13:03:20 +0000 Subject: [PATCH 043/160] Update GeckoView (Nightly) to 102.0.20220505094230. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 40d1a409f77..d121a9afd42 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220504234551" + const val version = "102.0.20220505094230" /** * GeckoView channel From 4585352cffa9be1431b8bb8810b30ef7b1655fce Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Thu, 5 May 2022 12:39:42 -0400 Subject: [PATCH 044/160] Close #12109: Support removing edit action end in edit toolbar --- .../components/browser/toolbar/BrowserToolbar.kt | 7 +++++++ .../browser/toolbar/edit/EditToolbar.kt | 4 ++++ .../browser/toolbar/BrowserToolbarTest.kt | 16 ++++++++++++++++ .../components/concept/toolbar/Toolbar.kt | 9 +++++++-- .../feature/CustomTabSessionTitleObserverTest.kt | 1 + .../toolbar/ToolbarAutocompleteFeatureTest.kt | 4 ++++ .../feature/toolbar/ToolbarInteractorTest.kt | 4 ++++ 7 files changed, 43 insertions(+), 2 deletions(-) diff --git a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/BrowserToolbar.kt b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/BrowserToolbar.kt index d49e4fc3f7f..53ac54e36d6 100644 --- a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/BrowserToolbar.kt +++ b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/BrowserToolbar.kt @@ -323,6 +323,13 @@ class BrowserToolbar @JvmOverloads constructor( edit.addEditActionEnd(action) } + /** + * Removes an action end of the URL in edit mode. + */ + override fun removeEditActionEnd(action: Toolbar.Action) { + edit.removeEditActionEnd(action) + } + /** * Switches to URL editing mode. */ diff --git a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/edit/EditToolbar.kt b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/edit/EditToolbar.kt index bcbf9ac3c46..1748da51303 100644 --- a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/edit/EditToolbar.kt +++ b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/edit/EditToolbar.kt @@ -248,6 +248,10 @@ class EditToolbar internal constructor( views.editActionsEnd.addAction(action) } + internal fun removeEditActionEnd(action: Toolbar.Action) { + views.editActionsEnd.removeAction(action) + } + /** * Updates the text of the URL input field. Note: this does *not* affect the value of url itself * and is only a visual change diff --git a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt index a41b625667a..4671891a492 100644 --- a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt +++ b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/BrowserToolbarTest.kt @@ -492,6 +492,22 @@ class BrowserToolbarTest { verify(edit).addEditActionEnd(action) } + @Test + fun `WHEN removing action end THEN it will be forwarded to the edit toolbar`() { + val toolbar = BrowserToolbar(testContext) + + val edit: EditToolbar = mock() + toolbar.edit = edit + + val action = BrowserToolbar.Button(mock(), "QR code scanner") { + // Do nothing + } + + toolbar.removeEditActionEnd(action) + + verify(edit).removeEditActionEnd(action) + } + @Test fun `cast to view`() { // Given diff --git a/components/concept/toolbar/src/main/java/mozilla/components/concept/toolbar/Toolbar.kt b/components/concept/toolbar/src/main/java/mozilla/components/concept/toolbar/Toolbar.kt index 733f48ee599..bfd405496cb 100644 --- a/components/concept/toolbar/src/main/java/mozilla/components/concept/toolbar/Toolbar.kt +++ b/components/concept/toolbar/src/main/java/mozilla/components/concept/toolbar/Toolbar.kt @@ -153,15 +153,20 @@ interface Toolbar { fun addNavigationAction(action: Action) /** - * Adds an action to be displayed in edit mode. + * Adds an action to be displayed at the start of the URL in edit mode. */ fun addEditActionStart(action: Action) /** - * Adds an action to be displayed in edit mode. + * Adds an action to be displayed at the end of the URL in edit mode. */ fun addEditActionEnd(action: Action) + /** + * Removes an action at the end of the URL in edit mode. + */ + fun removeEditActionEnd(action: Action) + /** * Casts this toolbar to an Android View object. */ diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt index 9f8a7c2ab35..c5eb522a37c 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt @@ -90,6 +90,7 @@ class CustomTabSessionTitleObserverTest { override fun removeNavigationAction(action: Toolbar.Action) = Unit override fun addEditActionStart(action: Toolbar.Action) = Unit override fun addEditActionEnd(action: Toolbar.Action) = Unit + override fun removeEditActionEnd(action: Toolbar.Action) = Unit override fun setOnEditListener(listener: Toolbar.OnEditListener) = Unit override fun displayMode() = Unit override fun editMode() = Unit diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt index a01c429566e..543dbacf78e 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt @@ -112,6 +112,10 @@ class ToolbarAutocompleteFeatureTest { fail() } + override fun removeEditActionEnd(action: Toolbar.Action) { + fail() + } + override fun invalidateActions() { fail() } diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarInteractorTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarInteractorTest.kt index d7de85916af..bc60e356acf 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarInteractorTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarInteractorTest.kt @@ -97,6 +97,10 @@ class ToolbarInteractorTest { fail() } + override fun removeEditActionEnd(action: Toolbar.Action) { + fail() + } + override fun invalidateActions() { fail() } From 0dac5b896a3cae3d4d5d05b5613c3ac0ae72eabb Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Thu, 5 May 2022 17:21:52 -0400 Subject: [PATCH 045/160] No issue: Register becoming noisy listener only if needed --- .../media/service/MediaSessionServiceDelegate.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt b/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt index ed7514a1c4e..1a4d052a4b0 100644 --- a/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt +++ b/components/feature/media/src/main/java/mozilla/components/feature/media/service/MediaSessionServiceDelegate.kt @@ -135,18 +135,18 @@ internal class MediaSessionServiceDelegate( when (state.mediaSessionState?.playbackState) { MediaSession.PlaybackState.PLAYING -> { - registerBecomingNoisyListener(state) + registerBecomingNoisyListenerIfNeeded(state) audioFocus.request(state.id) emitStatePlayFact() startForegroundNotificationIfNeeded() } MediaSession.PlaybackState.PAUSED -> { - unregisterBecomingNoisyListener() + unregisterBecomingNoisyListenerIfNeeded() emitStatePauseFact() stopForeground() } else -> { - unregisterBecomingNoisyListener() + unregisterBecomingNoisyListenerIfNeeded() emitStateStopFact() stopForeground() } @@ -193,12 +193,16 @@ internal class MediaSessionServiceDelegate( isForegroundService = false } - private fun registerBecomingNoisyListener(state: SessionState) { + private fun registerBecomingNoisyListenerIfNeeded(state: SessionState) { + if (noisyAudioStreamReceiver != null) { + return + } + noisyAudioStreamReceiver = BecomingNoisyReceiver(state.mediaSessionState?.controller) context.registerReceiver(noisyAudioStreamReceiver, intentFilter) } - private fun unregisterBecomingNoisyListener() { + private fun unregisterBecomingNoisyListenerIfNeeded() { noisyAudioStreamReceiver?.let { context.unregisterReceiver(noisyAudioStreamReceiver) noisyAudioStreamReceiver = null From c17e9e19b3011fe483258f7aeba137cbd179bcf0 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Fri, 6 May 2022 00:01:31 +0000 Subject: [PATCH 046/160] Import l10n. --- .../src/main/res/values-skr/strings.xml | 5 + .../src/main/res/values-skr/strings.xml | 10 ++ .../src/main/res/values-skr/strings.xml | 95 +++++++++++++++++++ .../menu/src/main/res/values-skr/strings.xml | 9 ++ .../menu2/src/main/res/values-skr/strings.xml | 7 ++ .../src/main/res/values-skr/strings.xml | 10 ++ .../src/main/res/values-skr/strings.xml | 5 + .../src/main/res/values-skr/strings.xml | 5 + .../src/main/res/values-skr/strings.xml | 71 ++++++++++++++ .../src/main/res/values-skr/strings.xml | 5 + .../src/main/res/values-skr/strings.xml | 12 +++ .../src/main/res/values-skr/strings.xml | 11 +++ .../src/main/res/values-skr/strings.xml | 4 + .../src/main/res/values-skr/strings.xml | 20 ++++ .../src/main/res/values-skr/strings.xml | 8 ++ .../media/src/main/res/values-skr/strings.xml | 6 ++ .../src/main/res/values-skr/strings.xml | 39 ++++++++ .../src/main/res/values-skr/strings.xml | 12 +++ 18 files changed, 334 insertions(+) create mode 100644 components/browser/awesomebar/src/main/res/values-skr/strings.xml create mode 100644 components/browser/engine-system/src/main/res/values-skr/strings.xml create mode 100644 components/browser/errorpages/src/main/res/values-skr/strings.xml create mode 100644 components/browser/menu/src/main/res/values-skr/strings.xml create mode 100644 components/browser/menu2/src/main/res/values-skr/strings.xml create mode 100644 components/browser/toolbar/src/main/res/values-skr/strings.xml create mode 100644 components/compose/awesomebar/src/main/res/values-skr/strings.xml create mode 100644 components/compose/browser-toolbar/src/main/res/values-skr/strings.xml create mode 100644 components/feature/addons/src/main/res/values-skr/strings.xml create mode 100644 components/feature/app-links/src/main/res/values-skr/strings.xml create mode 100644 components/feature/autofill/src/main/res/values-skr/strings.xml create mode 100644 components/feature/contextmenu/src/main/res/values-skr/strings.xml create mode 100644 components/feature/customtabs/src/main/res/values-skr/strings.xml create mode 100644 components/feature/downloads/src/main/res/values-skr/strings.xml create mode 100644 components/feature/findinpage/src/main/res/values-skr/strings.xml create mode 100644 components/feature/media/src/main/res/values-skr/strings.xml create mode 100644 components/feature/prompts/src/main/res/values-skr/strings.xml create mode 100644 components/feature/readerview/src/main/res/values-skr/strings.xml diff --git a/components/browser/awesomebar/src/main/res/values-skr/strings.xml b/components/browser/awesomebar/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..10bff0878f0 --- /dev/null +++ b/components/browser/awesomebar/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + تجویز کوں قبول کرو تے تبدیلی کرو + diff --git a/components/browser/engine-system/src/main/res/values-skr/strings.xml b/components/browser/engine-system/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..f5b21bf6997 --- /dev/null +++ b/components/browser/engine-system/src/main/res/values-skr/strings.xml @@ -0,0 +1,10 @@ + + + + %1$s تے ورقہ آہدے: + + %2$s ورتݨ ناں تے پاسورڈ دی ارداس کریندا پئے۔ سائٹ آہدی ہے: “%1$s” + + %1$s تہاݙے ورتݨ ناں تے پاسورڈ دی ارداس کریندا پئے۔ + diff --git a/components/browser/errorpages/src/main/res/values-skr/strings.xml b/components/browser/errorpages/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..719dc299d0b --- /dev/null +++ b/components/browser/errorpages/src/main/res/values-skr/strings.xml @@ -0,0 +1,95 @@ + + + + + ولدا کوشش کرو + + + ارداس پوری کائنی کر سڳدا + + + قابل بھروسہ کنکشن ناکام تھی ڳیا + + + قابل بھروسہ کنکشن ناکام تھی ڳیا + + + ودھایا۔۔۔ + + واپس ون٘ڄو(سفارش کیتی ویندی ہے) + + خطرے کوں قبول کرو تے جاری رکھو + + + کنکشن خراب تھی ڳیا ہائی۔ + + براؤزر کامیابی نال کنکٹ تھی ڳیا ہے، پر معلومات دی منتقلی دے دوران کنکشن وچ رکاوٹ پیدا تھئی۔ براہ کرم، ولدا کوشش کرو۔

    +
      +
    • سائٹ عارضی طور پر غیر دستیاب یا کافی مصروف تھی سڳدی ہے۔ کجھ دیر بعد وت کوشش کرو۔
    • +
    • جے تساں کوئی وی ورقہ لوڈ نہوے کر سڳدے پئے ، تاں آپݨاں ڈیوائس ڈیٹا یا وائی-فائی کنیکشن دی پڑتال کرو۔
    • +
    ]]>
    + + + کنکشن ٹائم آوٹ تھی ڳیا ہے + + + سرور ولوں غیر متوقع جواب + + + آف لائن موڈ + + + کنکشن ریسٹ تھی ڳیا + + + غیر محفوظ فائل قسم + + +
  • ویب سائٹ دے مالکاں کوں ایہ مسلے ݙسݨ کیتے رابطہ کرو۔
  • + ]]>
    + + + خراب مواد نقص + + + مواد اینکوڈ کرݨ وچ خرابی + + + پتہ کائنی لبھا + + + کوئی انٹرنیٹ کنکشن کائنی + + ولدا لوڈ کرو + + + غلط پتہ + + + پتہ ٹھیک کائنی + + + نامعلوم پروٹوکول + + + فائل کائنی لبھی + + + پراکسی سرور کنکشن دا انکار کر ݙتے + + + پراکسی سرور کائنی لبھا + + + مالویئر سائٹ مسئلہ + + + ناپسندیدہ سائٹ مسئلہ + + + نقصان دہ سائٹ مسئلہ + + + ایچ ٹی ٹی پی سائٹ تے جاری رکھو +
    diff --git a/components/browser/menu/src/main/res/values-skr/strings.xml b/components/browser/menu/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..6d62945728b --- /dev/null +++ b/components/browser/menu/src/main/res/values-skr/strings.xml @@ -0,0 +1,9 @@ + + + + مینیو + + نمایاں کیتا ڳیا + + ایڈ ــ آن + diff --git a/components/browser/menu2/src/main/res/values-skr/strings.xml b/components/browser/menu2/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..92d8c77be32 --- /dev/null +++ b/components/browser/menu2/src/main/res/values-skr/strings.xml @@ -0,0 +1,7 @@ + + + + مینیو + + نمایاں کیتا ڳیا + diff --git a/components/browser/toolbar/src/main/res/values-skr/strings.xml b/components/browser/toolbar/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..6f3cbb4b9aa --- /dev/null +++ b/components/browser/toolbar/src/main/res/values-skr/strings.xml @@ -0,0 +1,10 @@ + + + + مینیو + صاف کرو + + سائٹ ڄاݨکاری + + لوڈ تھیندا پئے + diff --git a/components/compose/awesomebar/src/main/res/values-skr/strings.xml b/components/compose/awesomebar/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..10bff0878f0 --- /dev/null +++ b/components/compose/awesomebar/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + تجویز کوں قبول کرو تے تبدیلی کرو + diff --git a/components/compose/browser-toolbar/src/main/res/values-skr/strings.xml b/components/compose/browser-toolbar/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..638b35e354b --- /dev/null +++ b/components/compose/browser-toolbar/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + صاف کرو + diff --git a/components/feature/addons/src/main/res/values-skr/strings.xml b/components/feature/addons/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..8fc465169bd --- /dev/null +++ b/components/feature/addons/src/main/res/values-skr/strings.xml @@ -0,0 +1,71 @@ + + + + ورشن + + مصنف + + چھیکڑی واری اپ ڈیٹ تھیا + + مکھ پناں + + اجازتاں بارے ٻیا سکھو + + ریٹنگ + + ترتیباں + + چالو + + بند + + نجی براؤزنگ وچ اجازت ݙیوو + + نجی براؤزنگ وچ چلاؤ + + فعال تھیا + + غیرفعال تھیا + + انسٹال تھیا + + سفارش تھئے ہوئے + + اڄݨ تائیں دستاب کائنی + + غیرفعال تھیا + + تفصیلاں + + اجازتاں + + ہٹاؤ + + شامل کرو + + منسوخ + + منسوخ + + %1$.02f/5 + + ایڈ ــ آن + + ایڈ ــ آن منیجر + + اجازت ݙیوو + + انکار کرو + + ایڈ ــ آن اپ ڈیٹاں + + نویں ایڈ ــ آن دستیاب ہے + + نویں ایڈ ــ آن دستیاب ہے + + %1$s ایڈ ــ آن + + ٻیا سِکھو + + نقص + diff --git a/components/feature/app-links/src/main/res/values-skr/strings.xml b/components/feature/app-links/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..301978ee9f6 --- /dev/null +++ b/components/feature/app-links/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + منسوخ + diff --git a/components/feature/autofill/src/main/res/values-skr/strings.xml b/components/feature/autofill/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..cd3818261b1 --- /dev/null +++ b/components/feature/autofill/src/main/res/values-skr/strings.xml @@ -0,0 +1,12 @@ + + + + + جیا + + + کو + + diff --git a/components/feature/contextmenu/src/main/res/values-skr/strings.xml b/components/feature/contextmenu/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..c0f0ae06eac --- /dev/null +++ b/components/feature/contextmenu/src/main/res/values-skr/strings.xml @@ -0,0 +1,11 @@ + + + + لنک ڈاؤن لوڈ کرو + + ڳولو + + شیئر + + ای میل + diff --git a/components/feature/customtabs/src/main/res/values-skr/strings.xml b/components/feature/customtabs/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..20dc9e0696b --- /dev/null +++ b/components/feature/customtabs/src/main/res/values-skr/strings.xml @@ -0,0 +1,4 @@ + + + لنک شیئر کرو + diff --git a/components/feature/downloads/src/main/res/values-skr/strings.xml b/components/feature/downloads/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..73d489e1d55 --- /dev/null +++ b/components/feature/downloads/src/main/res/values-skr/strings.xml @@ -0,0 +1,20 @@ + + + + ڈاؤن لوڈاں + + + ڈاؤن لوڈ + + منسوخ + + + منسوخ + + + ولدا کوشش کرو + + + بند کرو + + diff --git a/components/feature/findinpage/src/main/res/values-skr/strings.xml b/components/feature/findinpage/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..10b36bbe059 --- /dev/null +++ b/components/feature/findinpage/src/main/res/values-skr/strings.xml @@ -0,0 +1,8 @@ + + + + + %1$d/%2$d + + diff --git a/components/feature/media/src/main/res/values-skr/strings.xml b/components/feature/media/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..60af71f9d7a --- /dev/null +++ b/components/feature/media/src/main/res/values-skr/strings.xml @@ -0,0 +1,6 @@ + + + + میڈیا + + diff --git a/components/feature/prompts/src/main/res/values-skr/strings.xml b/components/feature/prompts/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..a85396f44b5 --- /dev/null +++ b/components/feature/prompts/src/main/res/values-skr/strings.xml @@ -0,0 +1,39 @@ + + + + راہوو + + چھوڑو + + ہک مہینہ چُݨو + + جنورى + + فرورى + + مارچ + + اپريل + + مئی + + جون + + جولائى + + اگست + + ستمبر + + اکتوبر + + نومبر + + دسمبر + + + ڈیٹا ولدا پٹھو + + منسوخ + + diff --git a/components/feature/readerview/src/main/res/values-skr/strings.xml b/components/feature/readerview/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..b0dc1b7bc1f --- /dev/null +++ b/components/feature/readerview/src/main/res/values-skr/strings.xml @@ -0,0 +1,12 @@ + + + + سینس سیرف + + سانس سیرف فونٹ + + سیرف + + سیرف فونٹ + + From d0701ce1cf4c6a2369275dddf277c0a76f5419aa Mon Sep 17 00:00:00 2001 From: sarah541 Date: Wed, 4 May 2022 21:27:25 -0400 Subject: [PATCH 047/160] For #12096 - Add 20px close icon whatever --- .../src/main/res/drawable/mozac_ic_close_20.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 components/ui/icons/src/main/res/drawable/mozac_ic_close_20.xml diff --git a/components/ui/icons/src/main/res/drawable/mozac_ic_close_20.xml b/components/ui/icons/src/main/res/drawable/mozac_ic_close_20.xml new file mode 100644 index 00000000000..87bba3ae9f3 --- /dev/null +++ b/components/ui/icons/src/main/res/drawable/mozac_ic_close_20.xml @@ -0,0 +1,13 @@ + + + + + From 8efce34fdefcf1a4143a7f685fc5c90810770c08 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Fri, 6 May 2022 13:01:10 +0000 Subject: [PATCH 048/160] Update GeckoView (Nightly) to 102.0.20220506095052. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index d121a9afd42..65851344d33 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220505094230" + const val version = "102.0.20220506095052" /** * GeckoView channel From da5901469ddfe44d5cd4d787733c2f1f17813641 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Sat, 7 May 2022 00:06:48 +0000 Subject: [PATCH 049/160] Import l10n. --- .../src/main/res/values-skr/strings.xml | 21 ++++++++++++ .../menu/src/main/res/values-skr/strings.xml | 4 ++- .../src/main/res/values-skr/strings.xml | 28 +++++++++++++++- .../src/main/res/values-skr/strings.xml | 4 +++ .../src/main/res/values-skr/strings.xml | 14 +++++++- .../src/main/res/values-skr/strings.xml | 7 ++++ .../src/main/res/values-skr/strings.xml | 32 +++++++++++++++++- .../src/main/res/values-skr/strings.xml | 14 ++++++++ .../src/main/res/values-skr/strings.xml | 9 +++++ .../media/src/main/res/values-skr/strings.xml | 11 +++++++ .../src/main/res/values-skr/strings.xml | 33 +++++++++++++++++++ .../pwa/src/main/res/values-skr/strings.xml | 8 +++++ .../src/main/res/values-skr/strings.xml | 14 +++++++- .../src/main/res/values-skr/strings.xml | 15 +++++++++ .../tabs/src/main/res/values-skr/strings.xml | 5 +++ .../src/main/res/values-skr/strings.xml | 5 +++ .../crash/src/main/res/values-skr/strings.xml | 15 +++++++++ 17 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 components/feature/awesomebar/src/main/res/values-skr/strings.xml create mode 100644 components/feature/pwa/src/main/res/values-skr/strings.xml create mode 100644 components/feature/sitepermissions/src/main/res/values-skr/strings.xml create mode 100644 components/feature/tabs/src/main/res/values-skr/strings.xml create mode 100644 components/feature/webnotifications/src/main/res/values-skr/strings.xml create mode 100644 components/lib/crash/src/main/res/values-skr/strings.xml diff --git a/components/browser/errorpages/src/main/res/values-skr/strings.xml b/components/browser/errorpages/src/main/res/values-skr/strings.xml index 719dc299d0b..41c0bb7af7c 100644 --- a/components/browser/errorpages/src/main/res/values-skr/strings.xml +++ b/components/browser/errorpages/src/main/res/values-skr/strings.xml @@ -7,6 +7,10 @@ ارداس پوری کائنی کر سڳدا + + ایں مسئلے بارے وادھوں معلومات فی الحال دستیاب کائنی۔

    ]]>
    + قابل بھروسہ کنکشن ناکام تھی ڳیا @@ -33,12 +37,21 @@ کنکشن ٹائم آوٹ تھی ڳیا ہے + + جڑݨ وچ ناکام ریہا + سرور ولوں غیر متوقع جواب + + ورقہ ٹھیک طرح ری ڈائریکٹ کائنی تھیندا پیا + آف لائن موڈ + + حفاظتی وجوہات کیتے پورٹ محدود کر ݙتا ڳیا ہے + کنکشن ریسٹ تھی ڳیا @@ -52,6 +65,9 @@ خراب مواد نقص + + مواد تباہ تھی ڳیا + مواد اینکوڈ کرݨ وچ خرابی @@ -60,6 +76,8 @@ کوئی انٹرنیٹ کنکشن کائنی + + اپݨے نیٹ ورک کنکشن دی پڑتال کرو یا کجھ لمحے بعد ولدا کوشش کرو۔ ولدا لوڈ کرو @@ -75,6 +93,9 @@ فائل کائنی لبھی + + فائل تائیں رسائی مسترد کر ݙتی ڳئی ہائی + پراکسی سرور کنکشن دا انکار کر ݙتے diff --git a/components/browser/menu/src/main/res/values-skr/strings.xml b/components/browser/menu/src/main/res/values-skr/strings.xml index 6d62945728b..979a43dd3fe 100644 --- a/components/browser/menu/src/main/res/values-skr/strings.xml +++ b/components/browser/menu/src/main/res/values-skr/strings.xml @@ -6,4 +6,6 @@ نمایاں کیتا ڳیا ایڈ ــ آن - + + ایڈ ــ آن منیجر + diff --git a/components/feature/addons/src/main/res/values-skr/strings.xml b/components/feature/addons/src/main/res/values-skr/strings.xml index 8fc465169bd..0868e5b55fb 100644 --- a/components/feature/addons/src/main/res/values-skr/strings.xml +++ b/components/feature/addons/src/main/res/values-skr/strings.xml @@ -1,5 +1,11 @@ + + براؤزر ٹیبز تائیں رسائی حاصل کرو + + کلپ بورڈ کنوں ڈیٹا گھنو + + کلپ بورڈ وچ ڈیٹا پاؤ ورشن @@ -44,8 +50,12 @@ شامل کرو منسوخ + + ایڈ ــ آن انسٹال کرو منسوخ + + جائزہ:%1$s %1$.02f/5 @@ -62,10 +72,26 @@ نویں ایڈ ــ آن دستیاب ہے نویں ایڈ ــ آن دستیاب ہے + + %2$s وچ %1$s شامل کرو + + 1 ایڈ ــ آن %1$s ایڈ ــ آن ٻیا سِکھو + + کامیابی نال اپ ڈیٹ تھی ڳیا + + اڄݨ تائیں کوئی اپ ڈیٹ دستاب کائنی نقص - + + چھیکڑی کوشش: + + حیثیت: + + ایں کوں مینیو وچ کھولو + + ٹھیک ہے، سمجھ آ ڳیا + diff --git a/components/feature/app-links/src/main/res/values-skr/strings.xml b/components/feature/app-links/src/main/res/values-skr/strings.xml index 301978ee9f6..d0ae37bfe3c 100644 --- a/components/feature/app-links/src/main/res/values-skr/strings.xml +++ b/components/feature/app-links/src/main/res/values-skr/strings.xml @@ -1,5 +1,9 @@ + + ۔۔۔ وچ کھولو + + کھولو منسوخ diff --git a/components/feature/autofill/src/main/res/values-skr/strings.xml b/components/feature/autofill/src/main/res/values-skr/strings.xml index cd3818261b1..7539d20a59c 100644 --- a/components/feature/autofill/src/main/res/values-skr/strings.xml +++ b/components/feature/autofill/src/main/res/values-skr/strings.xml @@ -1,6 +1,10 @@ + + پڑتال ناکام تھی ڳئی + جیا @@ -9,4 +13,12 @@ credentials in a third-part app (Also see string mozac_feature_autofill_confirmation_authenticity). --> کو - + + %1$s ڳولو + + + لاگ ان ڳولو + diff --git a/components/feature/awesomebar/src/main/res/values-skr/strings.xml b/components/feature/awesomebar/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..31dc0ce0100 --- /dev/null +++ b/components/feature/awesomebar/src/main/res/values-skr/strings.xml @@ -0,0 +1,7 @@ + + + + ٹیب تے ون٘ڄو + diff --git a/components/feature/contextmenu/src/main/res/values-skr/strings.xml b/components/feature/contextmenu/src/main/res/values-skr/strings.xml index c0f0ae06eac..ac7aefcbd8e 100644 --- a/components/feature/contextmenu/src/main/res/values-skr/strings.xml +++ b/components/feature/contextmenu/src/main/res/values-skr/strings.xml @@ -1,11 +1,41 @@ + + نویں ٹیب وچ لنک کھولو + + نجی ٹیب وچ لنک کھولو + + نویں ٹیب وچ تصویر کھولو لنک ڈاؤن لوڈ کرو + + لنک شیئر کرو + + تصویر شیئر کرو + + لنک نقل کرو + + تصویر مقام نقل کرو + + تصویر محفوظ کرو + + فائل ڈیوائس وچ محفوظ کرو + + نواں ٹیب کھل ڳیا + + نواں نجی ٹیب کھُل ڳیا + + لنک کلپ بورڈ تے نقل تھی ڳیا + + سوئچ ڳولو + + نجی ڳولݨ شیئر ای میل - + + فون کرو + diff --git a/components/feature/downloads/src/main/res/values-skr/strings.xml b/components/feature/downloads/src/main/res/values-skr/strings.xml index 73d489e1d55..3c4f81f02b7 100644 --- a/components/feature/downloads/src/main/res/values-skr/strings.xml +++ b/components/feature/downloads/src/main/res/values-skr/strings.xml @@ -8,13 +8,27 @@ منسوخ + + فائل کائنی کھول سڳا + + + ذرا روکو + + ولدا جاری کرو منسوخ + + کھولو ولدا کوشش کرو بند کرو + --> + %1$s کھولݨ وچ ناکام ریہا + + + ڈاؤن لوڈ منسوخ کرو diff --git a/components/feature/findinpage/src/main/res/values-skr/strings.xml b/components/feature/findinpage/src/main/res/values-skr/strings.xml index 10b36bbe059..80a1be88ef7 100644 --- a/components/feature/findinpage/src/main/res/values-skr/strings.xml +++ b/components/feature/findinpage/src/main/res/values-skr/strings.xml @@ -1,8 +1,17 @@ + + ورقے وچ لبھو + %1$d/%2$d + + اڳلا نتیجہ لبھو + + + پچھلا نتیجہ لبھو + diff --git a/components/feature/media/src/main/res/values-skr/strings.xml b/components/feature/media/src/main/res/values-skr/strings.xml index 60af71f9d7a..fb09bf447ff 100644 --- a/components/feature/media/src/main/res/values-skr/strings.xml +++ b/components/feature/media/src/main/res/values-skr/strings.xml @@ -3,4 +3,15 @@ میڈیا + + کیمرہ چالو ہے + + مائیکروفون چالو ہے + + + چلاؤ + + + ذرا روکو + diff --git a/components/feature/prompts/src/main/res/values-skr/strings.xml b/components/feature/prompts/src/main/res/values-skr/strings.xml index a85396f44b5..277c49cab81 100644 --- a/components/feature/prompts/src/main/res/values-skr/strings.xml +++ b/components/feature/prompts/src/main/res/values-skr/strings.xml @@ -1,5 +1,35 @@ + + ٹھیک ہے + + منسوخ + + ٹھیک کرو + + صاف کرو + + سائن ان + + ورتݨ ناں + + پاس ورڈ + + محفوظ نہ کرو + + کݙاہیں وی محفوظ نہ کرو + + محفوظ کرو + + اپ ڈیٹ نہ کرو + + اپ ڈیٹ کرو + + اجازت ݙیوو + + انکار کرو + + بھلا تہاکوں پک ہے؟ راہوو @@ -31,6 +61,9 @@ دسمبر + + لاگ ان منیج کرو + ڈیٹا ولدا پٹھو diff --git a/components/feature/pwa/src/main/res/values-skr/strings.xml b/components/feature/pwa/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..5910df2e456 --- /dev/null +++ b/components/feature/pwa/src/main/res/values-skr/strings.xml @@ -0,0 +1,8 @@ + + + + ویب سائٹ + + + تازہ کرو + diff --git a/components/feature/readerview/src/main/res/values-skr/strings.xml b/components/feature/readerview/src/main/res/values-skr/strings.xml index b0dc1b7bc1f..091972ddcc1 100644 --- a/components/feature/readerview/src/main/res/values-skr/strings.xml +++ b/components/feature/readerview/src/main/res/values-skr/strings.xml @@ -9,4 +9,16 @@ سیرف فونٹ - + + شوخ + + شوخ رنگ سکیم + + سیپیا + + سیپیا رنگ سکیم + + پھکا + + پھکا رنگ سکیم + diff --git a/components/feature/sitepermissions/src/main/res/values-skr/strings.xml b/components/feature/sitepermissions/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..907da100faf --- /dev/null +++ b/components/feature/sitepermissions/src/main/res/values-skr/strings.xml @@ -0,0 +1,15 @@ + + + + اجازت ݙیوو + + اجازت نہ ݙیوو + + ہمیشہ + + کݙاہیں نہ + + ٻیا سِکھو + diff --git a/components/feature/tabs/src/main/res/values-skr/strings.xml b/components/feature/tabs/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..52923cf7506 --- /dev/null +++ b/components/feature/tabs/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + ٹیباں + diff --git a/components/feature/webnotifications/src/main/res/values-skr/strings.xml b/components/feature/webnotifications/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..ef5f54b5822 --- /dev/null +++ b/components/feature/webnotifications/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + سائٹ نوٹیفیکیشن + diff --git a/components/lib/crash/src/main/res/values-skr/strings.xml b/components/lib/crash/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..8a35ef20c27 --- /dev/null +++ b/components/lib/crash/src/main/res/values-skr/strings.xml @@ -0,0 +1,15 @@ + + + + + بند کرو + + + کریش + + + رپورٹ کرو + + + شیئر + From 76f89d6f7238ba1224ff68c5d883f914594a4752 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sat, 7 May 2022 12:56:18 +0000 Subject: [PATCH 050/160] Update GeckoView (Nightly) to 102.0.20220507095414. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 65851344d33..9f40da3e131 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220506095052" + const val version = "102.0.20220507095414" /** * GeckoView channel From 357891a0b1352248b7651aed184399049c9f682c Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sat, 7 May 2022 18:21:47 +0000 Subject: [PATCH 051/160] Update GeckoView (Nightly) to 102.0.20220507151609. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 9f40da3e131..3b42e763bab 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220507095414" + const val version = "102.0.20220507151609" /** * GeckoView channel From bd9d1942378c26ebaac2ce966a866616c3b35ed2 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sun, 8 May 2022 13:23:02 +0000 Subject: [PATCH 052/160] Update GeckoView (Nightly) to 102.0.20220508065845. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 3b42e763bab..244bc89b6c0 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220507151609" + const val version = "102.0.20220508065845" /** * GeckoView channel From 9ad5f82205110ff535453a89545cde2e953ec8e4 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Sun, 8 May 2022 00:06:20 +0000 Subject: [PATCH 053/160] Import l10n. --- .../src/main/res/values-skr/strings.xml | 10 ++++ .../src/main/res/values-skr/strings.xml | 10 +++- .../src/main/res/values-skr/strings.xml | 7 +++ .../src/main/res/values-skr/strings.xml | 46 +++++++++++++++++++ .../src/main/res/values-skr/strings.xml | 5 ++ .../src/main/res/values-skr/strings.xml | 4 ++ .../src/main/res/values-skr/strings.xml | 2 + .../pwa/src/main/res/values-skr/strings.xml | 4 +- .../qr/src/main/res/values-skr/strings.xml | 7 +++ .../src/main/res/values-skr/strings.xml | 5 ++ .../base/src/main/res/values-skr/strings.xml | 7 +++ .../src/main/res/values-skr/strings.xml | 9 ++++ 12 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 components/compose/tabstray/src/main/res/values-skr/strings.xml create mode 100644 components/feature/qr/src/main/res/values-skr/strings.xml create mode 100644 components/support/base/src/main/res/values-skr/strings.xml create mode 100644 components/ui/tabcounter/src/main/res/values-skr/strings.xml diff --git a/components/browser/errorpages/src/main/res/values-skr/strings.xml b/components/browser/errorpages/src/main/res/values-skr/strings.xml index 41c0bb7af7c..65c1e0ef23b 100644 --- a/components/browser/errorpages/src/main/res/values-skr/strings.xml +++ b/components/browser/errorpages/src/main/res/values-skr/strings.xml @@ -43,6 +43,11 @@ سرور ولوں غیر متوقع جواب + + سائٹ نے نیٹ ورک ارداس دا غیر متوقع طریقے نال جواب ݙتا تے براؤزر جاری کائنی رہ سڳدا۔

    + ]]>
    + ورقہ ٹھیک طرح ری ڈائریکٹ کائنی تھیندا پیا @@ -111,6 +116,11 @@ نقصان دہ سائٹ مسئلہ + + فریبی سائٹ مسئلہ + + + محفوظ سائٹ دستیاب کائنی ایچ ٹی ٹی پی سائٹ تے جاری رکھو diff --git a/components/browser/toolbar/src/main/res/values-skr/strings.xml b/components/browser/toolbar/src/main/res/values-skr/strings.xml index 6f3cbb4b9aa..9a10e8b1c5e 100644 --- a/components/browser/toolbar/src/main/res/values-skr/strings.xml +++ b/components/browser/toolbar/src/main/res/values-skr/strings.xml @@ -3,8 +3,16 @@ مینیو صاف کرو + + سراغ کاری تحفظ چالو ہے + + سراغ کاری تحفظ نے سُراغ رساں کوں بلاک کر ݙتا ہے + + سراغ کاری تحفظ ایں سائٹ کیتے بند ہے سائٹ ڄاݨکاری لوڈ تھیندا پئے - + + کجھ مواد کوں آٹو پلے ترتیباں نال بلاک کر ݙتا ڳئے + diff --git a/components/compose/tabstray/src/main/res/values-skr/strings.xml b/components/compose/tabstray/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..259bf6e8432 --- /dev/null +++ b/components/compose/tabstray/src/main/res/values-skr/strings.xml @@ -0,0 +1,7 @@ + + + + 1 ٹیب کھولو۔ ٹیبز بدلن کیتے دباؤ۔ + + %1$s ٹیبز کھولو۔ ٹیبز بدلن کیتے دباؤ۔ + diff --git a/components/feature/addons/src/main/res/values-skr/strings.xml b/components/feature/addons/src/main/res/values-skr/strings.xml index 0868e5b55fb..4dea2254394 100644 --- a/components/feature/addons/src/main/res/values-skr/strings.xml +++ b/components/feature/addons/src/main/res/values-skr/strings.xml @@ -1,11 +1,49 @@ + + رازداری ترتیباں پڑھو تے تبدیل کرو + + ساری ویب سائٹس کیتے آپݨے ڈیٹا تائیں رسائی گھنو + + %1$s کیتے آپݨے ڈیٹا تائیں رسائی گھنو۔ + + %1$s ڈومین وچ آپݨی سائٹس دے ڈیٹا تے رسائی حاصل کرو + + 1 ٻئی سائٹ تے آپݨے ڈیٹا تائیں رسائی گھنو۔ + + %1$d ٻیاں سائٹاں تے آپݨے ڈیٹا تائیں رسائی گھنو + + 1 ٻئی ڈومین تے آپݨے ڈیٹا تائیں رسائی گھنو۔ + + %1$d ٻئی ڈومیناں تے آپݨے ڈیٹا تے رسائی گھنو براؤزر ٹیبز تائیں رسائی حاصل کرو + + گاہک دی طرفوں بے انت ڈیٹا ذخیرہ کرو + + نیویگیشݨ دے دوران براوئزر دی سرگرمی تائیں رسائی + + کتاب نشان پڑھو تے ترمیم کرو + + براؤزر ترتیباں پڑھو تے تبدیل کرو کلپ بورڈ کنوں ڈیٹا گھنو کلپ بورڈ وچ ڈیٹا پاؤ + + سارے کھلے ٹیباں دی عبارت پڑھو + + آپݨے مقام تائیں اپڑو + + براؤزنگ تاریخ تے اپڑو + + براؤزنگ تاریخ تے اپڑو ورشن @@ -36,6 +74,8 @@ انسٹال تھیا سفارش تھئے ہوئے + + اڄݨ تائیں سہارا تھیا کائنی اڄݨ تائیں دستاب کائنی @@ -46,6 +86,8 @@ اجازتاں ہٹاؤ + + %1$s شامل کروں؟ شامل کرو @@ -74,6 +116,10 @@ نویں ایڈ ــ آن دستیاب ہے %2$s وچ %1$s شامل کرو + + %1$s تے %2$s کوں %3$s وچ جوڑو + + اُنہاں کوں %1$s وچ شامل کرو 1 ایڈ ــ آن diff --git a/components/feature/autofill/src/main/res/values-skr/strings.xml b/components/feature/autofill/src/main/res/values-skr/strings.xml index 7539d20a59c..d1a6e982fac 100644 --- a/components/feature/autofill/src/main/res/values-skr/strings.xml +++ b/components/feature/autofill/src/main/res/values-skr/strings.xml @@ -1,6 +1,11 @@ + + %1$s اݨ لاک کرو + پڑتال ناکام تھی ڳئی diff --git a/components/feature/contextmenu/src/main/res/values-skr/strings.xml b/components/feature/contextmenu/src/main/res/values-skr/strings.xml index ac7aefcbd8e..9406bddb476 100644 --- a/components/feature/contextmenu/src/main/res/values-skr/strings.xml +++ b/components/feature/contextmenu/src/main/res/values-skr/strings.xml @@ -28,6 +28,10 @@ لنک کلپ بورڈ تے نقل تھی ڳیا سوئچ + + ای میل پتہ شیئر کرو + + ای میل پتہ نقل کرو ڳولو diff --git a/components/feature/prompts/src/main/res/values-skr/strings.xml b/components/feature/prompts/src/main/res/values-skr/strings.xml index 277c49cab81..c67abebc43e 100644 --- a/components/feature/prompts/src/main/res/values-skr/strings.xml +++ b/components/feature/prompts/src/main/res/values-skr/strings.xml @@ -24,6 +24,8 @@ اپ ڈیٹ نہ کرو اپ ڈیٹ کرو + + رنگ چُݨو اجازت ݙیوو diff --git a/components/feature/pwa/src/main/res/values-skr/strings.xml b/components/feature/pwa/src/main/res/values-skr/strings.xml index 5910df2e456..041729463dc 100644 --- a/components/feature/pwa/src/main/res/values-skr/strings.xml +++ b/components/feature/pwa/src/main/res/values-skr/strings.xml @@ -5,4 +5,6 @@ تازہ کرو - + + یوآرایل نقل تھی ڳیا۔ + diff --git a/components/feature/qr/src/main/res/values-skr/strings.xml b/components/feature/qr/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..9e579a53697 --- /dev/null +++ b/components/feature/qr/src/main/res/values-skr/strings.xml @@ -0,0 +1,7 @@ + + + + + QR سکینر + + diff --git a/components/feature/readerview/src/main/res/values-skr/strings.xml b/components/feature/readerview/src/main/res/values-skr/strings.xml index 091972ddcc1..34b1b31c0dd 100644 --- a/components/feature/readerview/src/main/res/values-skr/strings.xml +++ b/components/feature/readerview/src/main/res/values-skr/strings.xml @@ -9,6 +9,11 @@ سیرف فونٹ + + فونٹ سائز گھٹاؤ + + + فونٹ سائز ودھاؤ شوخ diff --git a/components/support/base/src/main/res/values-skr/strings.xml b/components/support/base/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..05ff45ad872 --- /dev/null +++ b/components/support/base/src/main/res/values-skr/strings.xml @@ -0,0 +1,7 @@ + + + + ترتیباں تے ون٘ڄو + + فارغ کرو + diff --git a/components/ui/tabcounter/src/main/res/values-skr/strings.xml b/components/ui/tabcounter/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..eb5a19e6362 --- /dev/null +++ b/components/ui/tabcounter/src/main/res/values-skr/strings.xml @@ -0,0 +1,9 @@ + + + + نواں ٹیب + + نویں نجی ٹیب + + ٹیب بند کرو + From d21b7cf7b5b8b113e00b8544d2fc28f72980e74c Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Mon, 9 May 2022 00:04:40 +0000 Subject: [PATCH 054/160] Import l10n. --- .../src/main/res/values-skr/strings.xml | 14 ++++++++ .../src/main/res/values-skr/strings.xml | 34 +++++++++++++++++++ .../src/main/res/values-skr/strings.xml | 9 +++++ .../src/main/res/values-skr/strings.xml | 6 ++++ .../src/main/res/values-skr/strings.xml | 1 + .../src/main/res/values-skr/strings.xml | 18 +++++++++- .../src/main/res/values-skr/strings.xml | 9 ++++- .../media/src/main/res/values-skr/strings.xml | 7 +++- .../src/main/res/values-skr/strings.xml | 5 +++ .../src/main/res/values-skr/strings.xml | 28 ++++++++++++++- .../pwa/src/main/res/values-skr/strings.xml | 2 ++ .../src/main/res/values-skr/strings.xml | 5 +++ .../crash/src/main/res/values-skr/strings.xml | 12 +++++++ .../src/main/res/values-skr/strings.xml | 5 +++ .../ktx/src/main/res/values-skr/strings.xml | 9 +++++ .../src/main/res/values-skr/strings.xml | 7 ++++ 16 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 components/feature/privatemode/src/main/res/values-skr/strings.xml create mode 100644 components/service/nimbus/src/main/res/values-skr/strings.xml create mode 100644 components/support/ktx/src/main/res/values-skr/strings.xml create mode 100644 components/support/migration/src/main/res/values-skr/strings.xml diff --git a/components/browser/errorpages/src/main/res/values-skr/strings.xml b/components/browser/errorpages/src/main/res/values-skr/strings.xml index 65c1e0ef23b..b6607477ce7 100644 --- a/components/browser/errorpages/src/main/res/values-skr/strings.xml +++ b/components/browser/errorpages/src/main/res/values-skr/strings.xml @@ -14,6 +14,14 @@ قابل بھروسہ کنکشن ناکام تھی ڳیا + + +
  • جہڑا ورقہ ݙیکھݨ چاہندے ہو، کائنی ݙکھایا ون٘ڄ سڳدا کیونجو موصول ڈیٹا دی اصلیت دی تصدیق کائنی تھی سڳی۔
  • +
  • ویب سائٹ دے مالک کوں ایں مسئلے دا ݙسݨ کیتے انہاں نال رابطہ کرو۔
  • + + ]]>
    + قابل بھروسہ کنکشن ناکام تھی ڳیا @@ -92,6 +100,12 @@ پتہ ٹھیک کائنی + + +
  • ویب پتے عمومی طور تے این٘ویں http://www.example.com/ لکھے ویندے ہن
  • +
  • یقینی بݨاؤ جو تساں فارورڈ سلیشیز (جین٘ویں /) ورتندے پئے ہو۔
  • + ]]>
    + نامعلوم پروٹوکول diff --git a/components/feature/addons/src/main/res/values-skr/strings.xml b/components/feature/addons/src/main/res/values-skr/strings.xml index 4dea2254394..a24e1a1ab4a 100644 --- a/components/feature/addons/src/main/res/values-skr/strings.xml +++ b/components/feature/addons/src/main/res/values-skr/strings.xml @@ -36,12 +36,18 @@ کلپ بورڈ کنوں ڈیٹا گھنو کلپ بورڈ وچ ڈیٹا پاؤ + + آپݨے ڈیوائس تے ڈاؤن لوڈ تھیاں فائلاں کھولو سارے کھلے ٹیباں دی عبارت پڑھو آپݨے مقام تائیں اپڑو براؤزنگ تاریخ تے اپڑو + + براؤزر دیاں پراکسی ترتیباں کوں کنٹرول کرو + + حالیہ بند تھیاں ٹیباں تے اپڑو براؤزنگ تاریخ تے اپڑو @@ -108,6 +114,12 @@ اجازت ݙیوو انکار کرو + + %1$s کیتے ہک نویں اپ ڈیٹ ہے + + %1$d کوں نویاں اجازتاں دی لوڑ ہے + + نویں اجازت ضروری ہے ایڈ ــ آن اپ ڈیٹاں @@ -120,6 +132,26 @@ %1$s تے %2$s کوں %3$s وچ جوڑو اُنہاں کوں %1$s وچ شامل کرو + + %1$s کامیابی نال انسٹال تھی ڳیا + + %1$s انسٹال تھیوݨ وچ ناکام تھیا + + %1$s کامیابی نال فعال تھی ڳیا + + %1$s فعال کرݨ وچ ناکام تھیا + + %1$s کامیابی نال غیرفعال تھی ڳیا + + %1$s غیرفعال کرݨ وچ ناکام تھیا + + %1$s کامیابی نال اݨ انسٹال تھی ڳیا + + %1$s اݨ انسٹال تھیوݨ وچ ناکام تھیا + + %1$s کامیابی نال ہٹ ڳیا + + %1$s ہٹاوݨ وچ ناکام تھیا 1 ایڈ ــ آن @@ -136,6 +168,8 @@ چھیکڑی کوشش: حیثیت: + + %2$s وچ %1$s شامل تھی ڳیا ہے ایں کوں مینیو وچ کھولو diff --git a/components/feature/autofill/src/main/res/values-skr/strings.xml b/components/feature/autofill/src/main/res/values-skr/strings.xml index d1a6e982fac..ca86bff4c0b 100644 --- a/components/feature/autofill/src/main/res/values-skr/strings.xml +++ b/components/feature/autofill/src/main/res/values-skr/strings.xml @@ -6,6 +6,15 @@ with the name of the browser application (e.g. Firefox) --> %1$s اݨ لاک کرو + + (ورتݨ ناں کوئی کائنی) + + + %1$s کیتے پاس ورڈ + پڑتال ناکام تھی ڳئی diff --git a/components/feature/contextmenu/src/main/res/values-skr/strings.xml b/components/feature/contextmenu/src/main/res/values-skr/strings.xml index 9406bddb476..b6b9b164225 100644 --- a/components/feature/contextmenu/src/main/res/values-skr/strings.xml +++ b/components/feature/contextmenu/src/main/res/values-skr/strings.xml @@ -28,10 +28,16 @@ لنک کلپ بورڈ تے نقل تھی ڳیا سوئچ + + لنک ٻاہرلی ایپ وچ کھولو ای میل پتہ شیئر کرو ای میل پتہ نقل کرو + + ای میل پتہ کلپ بورڈ تے نقل تھی ڳیا + + رابطہ وچ شامل کرو ڳولو diff --git a/components/feature/customtabs/src/main/res/values-skr/strings.xml b/components/feature/customtabs/src/main/res/values-skr/strings.xml index 20dc9e0696b..1336257ab3f 100644 --- a/components/feature/customtabs/src/main/res/values-skr/strings.xml +++ b/components/feature/customtabs/src/main/res/values-skr/strings.xml @@ -1,4 +1,5 @@ + پچھلی ایپ تے واپس ون٘ڄو لنک شیئر کرو diff --git a/components/feature/downloads/src/main/res/values-skr/strings.xml b/components/feature/downloads/src/main/res/values-skr/strings.xml index 3c4f81f02b7..98a8bd959c0 100644 --- a/components/feature/downloads/src/main/res/values-skr/strings.xml +++ b/components/feature/downloads/src/main/res/values-skr/strings.xml @@ -3,6 +3,15 @@ ڈاؤن لوڈاں + + ڈاؤن لوڈ رک ڳیا + + ڈاؤن لوڈ مکمل تھی ڳیا + + ڈاؤن لوڈ ناکام تھیا + + + ڈاؤن لوڈ(%1$s) ڈاؤن لوڈ @@ -11,6 +20,9 @@ فائل کائنی کھول سڳا + + %1$s فائلاں کھولݨ کیتے کوئی ایپ کائنی لبھی + ذرا روکو @@ -29,6 +41,10 @@ --> %1$s کھولݨ وچ ناکام ریہا + + نجی ڈاؤن لوڈاں منسوخ کروں؟ ڈاؤن لوڈ منسوخ کرو - + + نجی براؤزنگ وچ راہوو + diff --git a/components/feature/findinpage/src/main/res/values-skr/strings.xml b/components/feature/findinpage/src/main/res/values-skr/strings.xml index 80a1be88ef7..8a7e68969cc 100644 --- a/components/feature/findinpage/src/main/res/values-skr/strings.xml +++ b/components/feature/findinpage/src/main/res/values-skr/strings.xml @@ -8,10 +8,17 @@ position the user is at. The first argument is the position, the second argument is the total. --> %1$d/%2$d + + %2$d وچوں %1$d + اڳلا نتیجہ لبھو پچھلا نتیجہ لبھو - + + ورقے وچ لبھݨ کوں فارغ کرو + + diff --git a/components/feature/media/src/main/res/values-skr/strings.xml b/components/feature/media/src/main/res/values-skr/strings.xml index fb09bf447ff..1a74ae5c77c 100644 --- a/components/feature/media/src/main/res/values-skr/strings.xml +++ b/components/feature/media/src/main/res/values-skr/strings.xml @@ -8,10 +8,15 @@ مائیکروفون چالو ہے + + کیمرہ تے مائیکروفون چالو ہن + چلاؤ ذرا روکو - + + سائٹ میڈیا چلیندی پئی ہے۔ + diff --git a/components/feature/privatemode/src/main/res/values-skr/strings.xml b/components/feature/privatemode/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..971f535406e --- /dev/null +++ b/components/feature/privatemode/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + نجی براؤزنگ سیشن + diff --git a/components/feature/prompts/src/main/res/values-skr/strings.xml b/components/feature/prompts/src/main/res/values-skr/strings.xml index c67abebc43e..a11e2a30cb6 100644 --- a/components/feature/prompts/src/main/res/values-skr/strings.xml +++ b/components/feature/prompts/src/main/res/values-skr/strings.xml @@ -24,6 +24,14 @@ اپ ڈیٹ نہ کرو اپ ڈیٹ کرو + + پاس ورڈ خانہ خالی کائنی ہووݨاں چاہیدا + + لاگ ان محفوظ کرݨ کنوں قاصر + + ایہ لاگ ان محفوظ کروں؟ + + ایہ لاگ ان اپ ڈیٹ کروں؟ رنگ چُݨو @@ -66,9 +74,27 @@ لاگ ان منیج کرو + + تجویز تھئے لاگ اناں کوں ودھاؤ + + تجویز تھئے لاگ اناں کوں کٹھا کرو + + تجویز تھئے لاگ ان + + + ایں سائٹ تے ڈیٹا ولدا بھیڄوں؟ ڈیٹا ولدا پٹھو منسوخ - + + + کریڈٹ کارڈ چݨو + + تجویز تھئے کریڈٹ کارڈاں کوں ودھاؤ + + تجویز تھئے کریڈٹ کارڈاں کوں کٹھا کرو + + کریڈیٹ کارڈ منیج کرو + diff --git a/components/feature/pwa/src/main/res/values-skr/strings.xml b/components/feature/pwa/src/main/res/values-skr/strings.xml index 041729463dc..4e6126bf4ec 100644 --- a/components/feature/pwa/src/main/res/values-skr/strings.xml +++ b/components/feature/pwa/src/main/res/values-skr/strings.xml @@ -3,6 +3,8 @@ ویب سائٹ + + فل سکرین سائٹ کنٹرول تازہ کرو diff --git a/components/feature/sitepermissions/src/main/res/values-skr/strings.xml b/components/feature/sitepermissions/src/main/res/values-skr/strings.xml index 907da100faf..fcd86ff993d 100644 --- a/components/feature/sitepermissions/src/main/res/values-skr/strings.xml +++ b/components/feature/sitepermissions/src/main/res/values-skr/strings.xml @@ -1,11 +1,16 @@ + + مائیکروفون ١ اجازت ݙیوو اجازت نہ ݙیوو + + ایں سائٹ کیتے فیصلہ یاد رکھو ہمیشہ diff --git a/components/lib/crash/src/main/res/values-skr/strings.xml b/components/lib/crash/src/main/res/values-skr/strings.xml index 8a35ef20c27..9bfef06051a 100644 --- a/components/lib/crash/src/main/res/values-skr/strings.xml +++ b/components/lib/crash/src/main/res/values-skr/strings.xml @@ -1,15 +1,27 @@ + + %1$s کوں کریش رپوٹ بھیڄو + بند کرو + + %1$s ولدا شروع کرو + کریش رپورٹ کرو + + کریش رپورٹاں + + + کوئی کریش رپوٹاں جمع کائنی کرائیاں۔ + شیئر diff --git a/components/service/nimbus/src/main/res/values-skr/strings.xml b/components/service/nimbus/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..0135c35baf9 --- /dev/null +++ b/components/service/nimbus/src/main/res/values-skr/strings.xml @@ -0,0 +1,5 @@ + + + + اتھاں کوئی تجربے کائنی + diff --git a/components/support/ktx/src/main/res/values-skr/strings.xml b/components/support/ktx/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..5beaf253896 --- /dev/null +++ b/components/support/ktx/src/main/res/values-skr/strings.xml @@ -0,0 +1,9 @@ + + + + این٘دے نال فون کرو۔۔۔ + + این٘دے نال ای میل کرو۔۔۔ + این٘دے نال شیئر کرو۔۔۔ + شیئر بذریعہ + diff --git a/components/support/migration/src/main/res/values-skr/strings.xml b/components/support/migration/src/main/res/values-skr/strings.xml new file mode 100644 index 00000000000..e5f2d61441f --- /dev/null +++ b/components/support/migration/src/main/res/values-skr/strings.xml @@ -0,0 +1,7 @@ + + + + اپ ڈیٹ تھین٘دی پئی ہے + + اپ ڈیٹ مکمل + From 1f25a7058ac4c5defcbaf18fe754240770124eb1 Mon Sep 17 00:00:00 2001 From: Alexandru2909 Date: Tue, 3 May 2022 17:27:30 +0300 Subject: [PATCH 055/160] For #12060 - Add support for select address prompt request --- .../browser/engine/gecko/ext/Address.kt | 46 ++++++++ .../gecko/prompt/GeckoPromptDelegate.kt | 36 ++++++ .../gecko/prompt/GeckoPromptDelegateTest.kt | 103 ++++++++++++++++++ .../concept/engine/prompt/PromptRequest.kt | 16 +++ .../engine/prompt/PromptRequestTest.kt | 49 +++++++++ .../storage/CreditCardsAddressesStorage.kt | 8 +- .../feature/prompts/PromptFeature.kt | 2 + docs/changelog.md | 3 + 8 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/Address.kt diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/Address.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/Address.kt new file mode 100644 index 00000000000..06cd9bb0027 --- /dev/null +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/ext/Address.kt @@ -0,0 +1,46 @@ +/* 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 mozilla.components.browser.engine.gecko.ext + +import mozilla.components.concept.storage.Address +import org.mozilla.geckoview.Autocomplete + +/** + * Converts a GeckoView [Autocomplete.Address] to an Android Components [Address]. + */ +fun Autocomplete.Address.toAddress() = Address( + guid = guid ?: "", + givenName = givenName, + additionalName = additionalName, + familyName = familyName, + organization = organization, + streetAddress = streetAddress, + addressLevel3 = addressLevel3, + addressLevel2 = addressLevel2, + addressLevel1 = addressLevel1, + postalCode = postalCode, + country = country, + tel = tel, + email = email +) + +/** + * Converts an Android Components [Address] to a GeckoView [Autocomplete.Address]. + */ +fun Address.toAutocompleteAddress() = Autocomplete.Address.Builder() + .guid(guid) + .givenName(givenName) + .additionalName(additionalName) + .familyName(familyName) + .organization(organization) + .streetAddress(streetAddress) + .addressLevel3(addressLevel3) + .addressLevel2(addressLevel2) + .addressLevel1(addressLevel1) + .postalCode(postalCode) + .country(country) + .tel(tel) + .email(email) + .build() diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt index fe065d6389c..5dda762dc92 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt @@ -8,6 +8,8 @@ import android.content.Context import android.net.Uri import androidx.annotation.VisibleForTesting import mozilla.components.browser.engine.gecko.GeckoEngineSession +import mozilla.components.browser.engine.gecko.ext.toAddress +import mozilla.components.browser.engine.gecko.ext.toAutocompleteAddress import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard import mozilla.components.browser.engine.gecko.ext.toCreditCardEntry import mozilla.components.browser.engine.gecko.ext.toLoginEntry @@ -17,6 +19,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.concept.engine.prompt.ShareData +import mozilla.components.concept.storage.Address import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry @@ -224,6 +227,39 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe return geckoResult } + override fun onAddressSelect( + session: GeckoSession, + request: AutocompleteRequest + ): GeckoResult { + val geckoResult = GeckoResult() + + val onConfirm: (Address) -> Unit = { address -> + if (!request.isComplete) { + geckoResult.complete( + request.confirm( + Autocomplete.AddressSelectOption(address.toAutocompleteAddress()) + ) + ) + } + } + + val onDismiss: () -> Unit = { + request.dismissSafely(geckoResult) + } + + geckoEngineSession.notifyObservers { + onPromptRequest( + PromptRequest.SelectAddress( + addresses = request.options.map { it.value.toAddress() }, + onConfirm = onConfirm, + onDismiss = onDismiss + ) + ) + } + + return geckoResult + } + override fun onAlertPrompt( session: GeckoSession, prompt: PromptDelegate.AlertPrompt diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt index 760a17bfa38..dbdc26fb268 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt @@ -8,6 +8,7 @@ import android.net.Uri import android.os.Looper.getMainLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.engine.gecko.GeckoEngineSession +import mozilla.components.browser.engine.gecko.ext.toAutocompleteAddress import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard import mozilla.components.browser.engine.gecko.ext.toLoginEntry import mozilla.components.concept.engine.EngineSession @@ -15,6 +16,7 @@ import mozilla.components.concept.engine.prompt.Choice import mozilla.components.concept.engine.prompt.PromptRequest import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice +import mozilla.components.concept.storage.Address import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry @@ -1436,6 +1438,96 @@ class GeckoPromptDelegateTest { verify(geckoResult, never()).complete(any()) } + @Test + fun `WHEN onAddressSelect is called THEN SelectAddress prompt request must be provided with the correct callbacks`() { + val mockSession = GeckoEngineSession(runtime) + + var isOnConfirmCalled = false + var isOnDismissCalled = false + + var selectAddressPrompt: PromptRequest.SelectAddress = mock() + + val promptDelegate = spy(GeckoPromptDelegate(mockSession)) + + // Capture the SelectAddress prompt request + mockSession.register(object : EngineSession.Observer { + override fun onPromptRequest(promptRequest: PromptRequest) { + selectAddressPrompt = promptRequest as PromptRequest.SelectAddress + } + }) + + val address = Address( + guid = "1", + givenName = "Firefox", + additionalName = "-", + familyName = "-", + organization = "-", + streetAddress = "street", + addressLevel3 = "address3", + addressLevel2 = "address2", + addressLevel1 = "address1", + postalCode = "1", + country = "Country", + tel = "1", + email = "@" + ) + val addressSelectOption = + Autocomplete.AddressSelectOption(address.toAutocompleteAddress()) + + var geckoPrompt = + geckoSelectAddressPrompt(arrayOf(addressSelectOption)) + + var geckoResult = promptDelegate.onAddressSelect( + mock(), + geckoPrompt + ) + + // Verify that the onDismiss callback was called + geckoResult.accept { + isOnDismissCalled = true + } + + selectAddressPrompt.onDismiss() + shadowOf(getMainLooper()).idle() + assertTrue(isOnDismissCalled) + + // Verify that the onConfirm callback was called + geckoPrompt = + geckoSelectAddressPrompt(arrayOf(addressSelectOption)) + + geckoResult = promptDelegate.onAddressSelect( + mock(), + geckoPrompt + ) + + geckoResult.accept { + isOnConfirmCalled = true + } + + selectAddressPrompt.onConfirm(selectAddressPrompt.addresses.first()) + shadowOf(getMainLooper()).idle() + assertTrue(isOnConfirmCalled) + + // Verify that when the prompt request is already completed and onConfirm callback is called, + // then onConfirm callback is not executed + isOnConfirmCalled = false + geckoPrompt = + geckoSelectAddressPrompt(arrayOf(addressSelectOption), true) + + geckoResult = promptDelegate.onAddressSelect( + mock(), + geckoPrompt + ) + + geckoResult.accept { + isOnConfirmCalled = true + } + + selectAddressPrompt.onConfirm(selectAddressPrompt.addresses.first()) + shadowOf(getMainLooper()).idle() + assertFalse(isOnConfirmCalled) + } + private fun geckoChoicePrompt( title: String, message: String, @@ -1595,4 +1687,15 @@ class GeckoPromptDelegateTest { ReflectionUtils.setField(prompt, "options", creditCards) return prompt } + + private fun geckoSelectAddressPrompt( + addresses: Array, + isComplete: Boolean = false + ): GeckoSession.PromptDelegate.AutocompleteRequest { + val prompt: GeckoSession.PromptDelegate.AutocompleteRequest = + mock() + whenever(prompt.isComplete).thenReturn(isComplete) + ReflectionUtils.setField(prompt, "options", addresses) + return prompt + } } diff --git a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt b/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt index 68d85c512a8..941b8559867 100644 --- a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt +++ b/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt @@ -9,6 +9,7 @@ import android.net.Uri import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type +import mozilla.components.concept.storage.Address import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry @@ -130,6 +131,21 @@ sealed class PromptRequest( override val onDismiss: () -> Unit ) : PromptRequest(), Dismissible + /** + * Value type that represents a request for a select address prompt. + * + * This prompt is triggered by the user focusing on an address field. + * + * @property addresses List of addresses for the user to choose from. + * @property onConfirm Callback used to confirm the selected address. + * @property onDismiss Callback used to dismiss the address prompt. + */ + data class SelectAddress( + val addresses: List
    , + val onConfirm: (Address) -> Unit, + override val onDismiss: () -> Unit + ) : PromptRequest(), Dismissible + /** * Value type that represents a request for an alert prompt to enter a message. * @property title title of the dialog. diff --git a/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt b/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt index ef0e3a41540..9b43cab5aa1 100644 --- a/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt +++ b/components/concept/engine/src/test/java/mozilla/components/concept/engine/prompt/PromptRequestTest.kt @@ -20,6 +20,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.concept.engine.prompt.PromptRequest.TextPrompt import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type +import mozilla.components.concept.storage.Address import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry @@ -312,4 +313,52 @@ class PromptRequestTest { assertFalse(onConfirmCalled) assertNull(confirmedCreditCard) } + + @Test + fun `WHEN calling confirm or dismiss on the SelectAddress prompt request THEN the respective callback is invoked`() { + val address = Address( + guid = "1", + givenName = "Firefox", + additionalName = "-", + familyName = "-", + organization = "-", + streetAddress = "street", + addressLevel3 = "address3", + addressLevel2 = "address2", + addressLevel1 = "address1", + postalCode = "1", + country = "Country", + tel = "1", + email = "@" + ) + var onDismissCalled = false + var onConfirmCalled = false + var confirmedAddress: Address? = null + + val selectAddresPromptRequest = PromptRequest.SelectAddress( + addresses = listOf(address), + onDismiss = { + onDismissCalled = true + }, + onConfirm = { + confirmedAddress = it + onConfirmCalled = true + } + ) + + assertEquals(selectAddresPromptRequest.addresses, listOf(address)) + + selectAddresPromptRequest.onConfirm(address) + + assertTrue(onConfirmCalled) + assertFalse(onDismissCalled) + assertEquals(address, confirmedAddress) + + onConfirmCalled = false + + selectAddresPromptRequest.onDismiss() + + assertTrue(onDismissCalled) + assertFalse(onConfirmCalled) + } } diff --git a/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt b/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt index 663c03db27e..87e965567c0 100644 --- a/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt +++ b/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt @@ -328,10 +328,10 @@ data class Address( val country: String, val tel: String, val email: String, - val timeCreated: Long, - val timeLastUsed: Long?, - val timeLastModified: Long, - val timesUsed: Long + val timeCreated: Long = 0L, + val timeLastUsed: Long? = 0L, + val timeLastModified: Long = 0L, + val timesUsed: Long = 0L ) : Parcelable /** diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt index 0f1a58449bc..ee3a5d9d0eb 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt @@ -34,6 +34,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.Popup import mozilla.components.concept.engine.prompt.PromptRequest.Repost import mozilla.components.concept.engine.prompt.PromptRequest.SaveLoginPrompt +import mozilla.components.concept.engine.prompt.PromptRequest.SelectAddress import mozilla.components.concept.engine.prompt.PromptRequest.SelectCreditCard import mozilla.components.concept.engine.prompt.PromptRequest.SelectLoginPrompt import mozilla.components.concept.engine.prompt.PromptRequest.Share @@ -791,6 +792,7 @@ class PromptFeature private constructor( is SelectLoginPrompt, is SelectCreditCard, is Share -> true + is SelectAddress -> false is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs } } diff --git a/docs/changelog.md b/docs/changelog.md index 8678f08dde7..fd92b813929 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,9 @@ permalink: /changelog/ * [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt) * [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml) +* **concept-engine**: + * Added support for `SelectAddress` prompt request. See [issue #12060](https://github.com/mozilla-mobile/android-components/issues/12060) + * **feature-autofill** * 🚒 Bug fixed [issue #11893](https://github.com/mozilla-mobile/android-components/issues/11893) - Fix issue with autofilling in 3rd party applications not being immediately available after unlocking the autofill service. From 6ff14dfd8ba759e8ba21b36eb45b93b92ada4660 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Mon, 9 May 2022 13:44:47 +0000 Subject: [PATCH 056/160] Update GeckoView (Nightly) to 102.0.20220509094710. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 244bc89b6c0..a9cee1302e3 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220508065845" + const val version = "102.0.20220509094710" /** * GeckoView channel From 9667ab66455a4a70dd16541652ec564fdc9c21b2 Mon Sep 17 00:00:00 2001 From: Tarik Eshaq Date: Mon, 9 May 2022 11:27:26 -0700 Subject: [PATCH 057/160] Catches exceptions from deleteEverything in sync history storage (#12112) * Catches exceptions from deleteEverything * Adds changelog Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../components/browser/storage/sync/PlacesHistoryStorage.kt | 4 +++- docs/changelog.md | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/PlacesHistoryStorage.kt b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/PlacesHistoryStorage.kt index 54594f30178..98521f3a5b3 100644 --- a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/PlacesHistoryStorage.kt +++ b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/PlacesHistoryStorage.kt @@ -156,7 +156,9 @@ open class PlacesHistoryStorage( */ override suspend fun deleteEverything() { withContext(writeScope.coroutineContext) { - places.writer().deleteEverything() + handlePlacesExceptions("deleteEverything") { + places.writer().deleteEverything() + } } } diff --git a/docs/changelog.md b/docs/changelog.md index fd92b813929..24d6be5fceb 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -30,6 +30,9 @@ permalink: /changelog/ * Added `CreditCardValidationDelegate` which is a delegate that will check against the `CreditCardsAddressesStorage` to determine if a `CreditCard` can be persisted in storage. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838) * Refactors `CreditCard` from `concept-engine` to `CreditCardEntry` in `concept-storage` so that it can validated with the `CreditCardValidationDelegate`. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838) +* **storage-sync**: + * Fixed a low frequency crasher that might occur when the app attempts to delete all history. [#12112](https://github.com/mozilla-mobile/android-components/pull/12112) + # 101.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) From c820eda21e79c1c171a0cfbab20e5a749e6f097f Mon Sep 17 00:00:00 2001 From: Tarik Eshaq Date: Mon, 9 May 2022 12:21:45 -0700 Subject: [PATCH 058/160] Update Application services to 93.1.0 (#12120) * app-services tabs component constructor now wants a database filename * Incrment to 93.0.1 * Bumps a-s to 93.1.0 * No issue: Correct changelog entries Co-authored-by: Mark Hammond Co-authored-by: James Hugman Co-authored-by: Jonathan Almeida --- buildSrc/src/main/java/Dependencies.kt | 2 +- .../browser/storage/sync/RemoteTabsStorage.kt | 7 ++++++- .../browser/storage/sync/RemoteTabsStorageTest.kt | 3 ++- .../syncedtabs/storage/SyncedTabsStorage.kt | 2 +- .../tooling/nimbus/NimbusGradlePlugin.groovy | 2 +- docs/changelog.md | 15 ++++++++++----- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b85282dcc3e..f2a0bfb70d4 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -30,7 +30,7 @@ object Versions { const val disklrucache = "2.0.2" const val leakcanary = "2.8.1" - const val mozilla_appservices = "91.1.2" + const val mozilla_appservices = "93.1.0" const val mozilla_glean = "44.1.1" diff --git a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt index 1186bbdaa13..e70bbff3624 100644 --- a/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt +++ b/components/browser/storage-sync/src/main/java/mozilla/components/browser/storage/sync/RemoteTabsStorage.kt @@ -4,6 +4,7 @@ package mozilla.components.browser.storage.sync +import android.content.Context import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancelChildren @@ -15,16 +16,20 @@ import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.SyncableStore import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.utils.logElapsedTime +import java.io.File import mozilla.appservices.remotetabs.InternalException as RemoteTabProviderException import mozilla.appservices.remotetabs.TabsStore as RemoteTabsProvider +private const val TABS_DB_NAME = "tabs.sqlite" + /** * An interface which defines read/write methods for remote tabs data. */ open class RemoteTabsStorage( + private val context: Context, private val crashReporter: CrashReporting? = null ) : Storage, SyncableStore { - internal val api by lazy { RemoteTabsProvider() } + internal val api by lazy { RemoteTabsProvider(File(context.filesDir, TABS_DB_NAME).canonicalPath) } private val scope by lazy { CoroutineScope(Dispatchers.IO) } internal val logger = Logger("RemoteTabsStorage") diff --git a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt index 6fbe31ac985..5c3baab473d 100644 --- a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt +++ b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt @@ -13,6 +13,7 @@ import mozilla.appservices.remotetabs.RemoteTab import mozilla.components.concept.base.crash.CrashReporting import mozilla.components.support.test.any import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -36,7 +37,7 @@ class RemoteTabsStorageTest { @Before fun setup() { crashReporter = mock() - remoteTabs = spy(RemoteTabsStorage(crashReporter)) + remoteTabs = spy(RemoteTabsStorage(testContext, crashReporter)) apiMock = mock(RemoteTabsProvider::class.java) `when`(remoteTabs.api).thenReturn(apiMock) } diff --git a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt index 14f2b6e9300..321b90ae491 100644 --- a/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt +++ b/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorage.kt @@ -38,7 +38,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged class SyncedTabsStorage( private val accountManager: FxaAccountManager, private val store: BrowserStore, - private val tabsStorage: RemoteTabsStorage = RemoteTabsStorage(), + private val tabsStorage: RemoteTabsStorage, private val debounceMillis: Long = 1000L, ) : SyncedTabsProvider { private var scope: CoroutineScope? = null diff --git a/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy b/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy index 9a5d6cbda32..6943aa619f4 100644 --- a/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy +++ b/components/tooling/nimbus-gradle-plugin/src/main/groovy/mozilla/components/tooling/nimbus/NimbusGradlePlugin.groovy @@ -171,7 +171,7 @@ class NimbusPlugin implements Plugin { // a) this plugin is going to live in the AS repo (eventually) // See https://github.com/mozilla-mobile/android-components/issues/11422 for tying this // to a version that is specified in buildSrc/src/main/java/Dependencies.kt - return "92.0.0" + return "93.1.0" } // Try one or more hosts to download the given file. diff --git a/docs/changelog.md b/docs/changelog.md index 24d6be5fceb..f9d708906ea 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -30,9 +30,14 @@ permalink: /changelog/ * Added `CreditCardValidationDelegate` which is a delegate that will check against the `CreditCardsAddressesStorage` to determine if a `CreditCard` can be persisted in storage. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838) * Refactors `CreditCard` from `concept-engine` to `CreditCardEntry` in `concept-storage` so that it can validated with the `CreditCardValidationDelegate`. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838) -* **storage-sync**: +* **browser-storage-sync** + * ⚠️ **This is a breaking change**: When constructing a `RemoteTabsStorage` object you must now a `Context` which is used to determine the location of the sqlite database which is used to persist the remote tabs [#11799](https://github.com/mozilla-mobile/android-components/pull/11799). * Fixed a low frequency crasher that might occur when the app attempts to delete all history. [#12112](https://github.com/mozilla-mobile/android-components/pull/12112) + +* **feature-syncedtabs** + * ⚠️ **This is a breaking change**: When constructing a `SyncedTabsStorage`, the `tabsStorage: RemoteTabsStorage` parameter is no longer optional so must be supplied [#11799](https://github.com/mozilla-mobile/android-components/pull/11799). + # 101.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/148?closed=1) @@ -53,7 +58,7 @@ permalink: /changelog/ * Media playback is now paused when AudioManager.ACTION_AUDIO_BECOMING_NOISY is broadcast by the system. * **feature-media** - * The Play/Pause button remains displayed on the media notification + * The Play/Pause button remains displayed on the media notification. # 100.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v99.0.0...v100.0.0) @@ -101,13 +106,13 @@ permalink: /changelog/ * **lib-crash-sentry** * 🌟️️ Add `sendCaughtExceptions` config flag to `SentryService`, allowing consumers to disable submitting caught exceptions. By default it's enabled, maintaining prior behaviour. Useful in projects with high volumes of caught exceptions and multiple release channels. - + * **site-permission-feature** * 🆕 New Add to SitePermissionsFeature a property to set visibility for NotAskAgainCheckBox * **feature-search** * 🆕 Update search Engines and Search Engine Icons - + # 99.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v98.0.0...v99.0.0) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/146?closed=1) @@ -128,7 +133,7 @@ permalink: /changelog/ * 🚒 Bug fixed [issue #8567](https://github.com/mozilla-mobile/android-components/issues/8567) - Prevent crashes when trying to add to the system databases. * **concept-engine** - * 🌟️️ Add `EngineSessionStateStorage`, describing a storage of `EngineSessionState` instances. + * 🌟️️ Add `EngineSessionStateStorage`, describing a storage of `EngineSessionState` instances. * **browser-session-storage** * 🌟️️ Add `FileEngineSessionStateStorage`, an implementation of `EngineSessionStateStorage` for persisting engine state outside of the regular RecoverableBrowserState flow. From f74a3608534810b261def3b5917e18b1197b72a6 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Tue, 10 May 2022 00:04:49 +0000 Subject: [PATCH 059/160] Import l10n. --- .../errorpages/src/main/res/values-skr/strings.xml | 6 ++++++ .../addons/src/main/res/values-skr/strings.xml | 8 ++++++++ .../downloads/src/main/res/values-skr/strings.xml | 5 +++++ .../prompts/src/main/res/values-skr/strings.xml | 3 +++ .../feature/qr/src/main/res/values-skr/strings.xml | 5 ++++- .../src/main/res/values-skr/strings.xml | 12 ++++++++++++ .../migration/src/main/res/values-skr/strings.xml | 4 +++- .../tabcounter/src/main/res/values-skr/strings.xml | 2 ++ 8 files changed, 43 insertions(+), 2 deletions(-) diff --git a/components/browser/errorpages/src/main/res/values-skr/strings.xml b/components/browser/errorpages/src/main/res/values-skr/strings.xml index b6607477ce7..ea2f4430861 100644 --- a/components/browser/errorpages/src/main/res/values-skr/strings.xml +++ b/components/browser/errorpages/src/main/res/values-skr/strings.xml @@ -25,6 +25,12 @@ قابل بھروسہ کنکشن ناکام تھی ڳیا + + +
  • ایہ سرور کانفیگریشن دا کوئی مسئلہ تھی سڳدا ہے یا تھی سڳدے جو کوئی سرور کوں نقلی بݨاوݨ دی کوشش کریندا پیا ہے۔
  • +
  • جے تساں ماضی وچ ایں سرور نال جڑے ہو تاں تھی سڳدا ہے جو خرابی کجھ وقت کیتے ہووے تے تساں کجھ دیر بعد وت کوشش کر سڳدے ہو۔
  • + ]]>
    + ودھایا۔۔۔ diff --git a/components/feature/addons/src/main/res/values-skr/strings.xml b/components/feature/addons/src/main/res/values-skr/strings.xml index a24e1a1ab4a..0499d6be853 100644 --- a/components/feature/addons/src/main/res/values-skr/strings.xml +++ b/components/feature/addons/src/main/res/values-skr/strings.xml @@ -32,6 +32,8 @@ کتاب نشان پڑھو تے ترمیم کرو براؤزر ترتیباں پڑھو تے تبدیل کرو + + حالیہ براؤزنگ تاریخ۔ کوکیاں تے متعلقہ ڈیٹا صاف کرو کلپ بورڈ کنوں ڈیٹا گھنو @@ -48,6 +50,8 @@ براؤزر دیاں پراکسی ترتیباں کوں کنٹرول کرو حالیہ بند تھیاں ٹیباں تے اپڑو + + براؤزر ٹیباں لکاؤ تے ݙکھاؤ براؤزنگ تاریخ تے اپڑو @@ -94,6 +98,8 @@ ہٹاؤ %1$s شامل کروں؟ + + ایں وچ تہاݙی اجازت ضروری ہے: شامل کرو @@ -164,6 +170,8 @@ اڄݨ تائیں کوئی اپ ڈیٹ دستاب کائنی نقص + + اپڈیٹر ڄاݨکاری چھیکڑی کوشش: diff --git a/components/feature/downloads/src/main/res/values-skr/strings.xml b/components/feature/downloads/src/main/res/values-skr/strings.xml index 98a8bd959c0..3a601d1a41b 100644 --- a/components/feature/downloads/src/main/res/values-skr/strings.xml +++ b/components/feature/downloads/src/main/res/values-skr/strings.xml @@ -17,6 +17,9 @@ منسوخ + + %1$s ایہ فائل قسم ڈاؤن لوڈ کائنی کر سڳدا + فائل کائنی کھول سڳا @@ -38,6 +41,8 @@ بند کرو + + ایں کوں ورتݨ نال عمل پورا کرو --> %1$s کھولݨ وچ ناکام ریہا diff --git a/components/feature/prompts/src/main/res/values-skr/strings.xml b/components/feature/prompts/src/main/res/values-skr/strings.xml index a11e2a30cb6..748e2cc648b 100644 --- a/components/feature/prompts/src/main/res/values-skr/strings.xml +++ b/components/feature/prompts/src/main/res/values-skr/strings.xml @@ -32,6 +32,9 @@ ایہ لاگ ان محفوظ کروں؟ ایہ لاگ ان اپ ڈیٹ کروں؟ + + + عبارت ان پُٹ خانے وچ درج کرݨ کیتے لیبل رنگ چُݨو diff --git a/components/feature/qr/src/main/res/values-skr/strings.xml b/components/feature/qr/src/main/res/values-skr/strings.xml index 9e579a53697..e44642f62cd 100644 --- a/components/feature/qr/src/main/res/values-skr/strings.xml +++ b/components/feature/qr/src/main/res/values-skr/strings.xml @@ -4,4 +4,7 @@ QR سکینر - + + ڈیوائس تے کوئی کیمرہ دستیاب کائنی + + diff --git a/components/feature/sitepermissions/src/main/res/values-skr/strings.xml b/components/feature/sitepermissions/src/main/res/values-skr/strings.xml index fcd86ff993d..8b3f67573ee 100644 --- a/components/feature/sitepermissions/src/main/res/values-skr/strings.xml +++ b/components/feature/sitepermissions/src/main/res/values-skr/strings.xml @@ -1,8 +1,20 @@ + + %1$s کوں آپݨاں مائیکروفون ورتݨ دی اجازت ݙیندے ہو؟ + + %1$s کوں آپݨاں مقام ورتݨ دی اجازت ݙیندے ہو؟ + + %1$s کوں آپݨاں کیمرہ تے مائیکروفون ورتݨ دی اجازت ݙیندے ہو؟ مائیکروفون ١ + + پچھوں آلا کیمرہ + + سامھِݨے آلا کیمرہ اجازت ݙیوو diff --git a/components/support/migration/src/main/res/values-skr/strings.xml b/components/support/migration/src/main/res/values-skr/strings.xml index e5f2d61441f..a965c94a23e 100644 --- a/components/support/migration/src/main/res/values-skr/strings.xml +++ b/components/support/migration/src/main/res/values-skr/strings.xml @@ -4,4 +4,6 @@ اپ ڈیٹ تھین٘دی پئی ہے اپ ڈیٹ مکمل - + + فائرفوکس اپ ڈیٹ تھی ڳئےــ تے چنگا تھی ڳئے! + diff --git a/components/ui/tabcounter/src/main/res/values-skr/strings.xml b/components/ui/tabcounter/src/main/res/values-skr/strings.xml index eb5a19e6362..2054c5db250 100644 --- a/components/ui/tabcounter/src/main/res/values-skr/strings.xml +++ b/components/ui/tabcounter/src/main/res/values-skr/strings.xml @@ -6,4 +6,6 @@ نویں نجی ٹیب ٹیب بند کرو + + واڳی ٹیب From 6bf079ddb436800e0e08cf324de1739e5afe024c Mon Sep 17 00:00:00 2001 From: Mugurell Date: Fri, 15 Apr 2022 09:19:33 +0300 Subject: [PATCH 060/160] For #11755 - Set coroutines version to 1.6.1 --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index f2a0bfb70d4..ab8a9dc0fd5 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -8,7 +8,7 @@ // Synchronized version numbers for dependencies used by (some) modules object Versions { const val kotlin = "1.6.10" - const val coroutines = "1.5.2" + const val coroutines = "1.6.1" const val junit = "4.12" const val robolectric = "4.7.3" From d0d293c3fa7f356ea16cc182cbf0b4f989191b3a Mon Sep 17 00:00:00 2001 From: Mugurell Date: Mon, 18 Apr 2022 13:55:51 +0300 Subject: [PATCH 061/160] For #11755 - Replace TestCoroutineDispatcher with UnconfinedTestDispatcher - Refactor out all usages of TestCoroutineDispatcher and TestCoroutineScope - Refactor MainCoroutineRule to now use UnconfinedTestDispatcher by default. This dispatcher will eagerly enter all launch or async blocks being more suited to our codebase. --- .../loader/NonBlockingHttpIconLoaderTest.kt | 7 +- .../menu/WebExtensionBrowserMenuTest.kt | 4 - .../item/WebExtensionBrowserMenuItemTest.kt | 14 +- .../icons/DrawableMenuIconViewHoldersTest.kt | 5 +- .../browser/session/storage/AutoSaveTest.kt | 51 +++---- .../state/engine/EngineMiddlewareTest.kt | 34 +---- .../engine/middleware/CrashMiddlewareTest.kt | 24 ++-- .../CreateEngineSessionMiddlewareTest.kt | 33 ++--- .../EngineDelegateMiddlewareTest.kt | 114 ++++----------- .../middleware/LinkingMiddlewareTest.kt | 42 ++---- .../middleware/SuspendMiddlewareTest.kt | 20 ++- .../middleware/TabsRemovedMiddlewareTest.kt | 31 ++--- .../middleware/TrimMemoryMiddlewareTest.kt | 15 +- .../thumbnails/BrowserThumbnailsTest.kt | 17 +-- .../storage/ThumbnailStorageTest.kt | 4 +- .../accounts/push/AutoPushObserverTest.kt | 10 +- .../DefaultSupportedAddonCheckerTest.kt | 2 - .../migration/SupportedAddonsWorkerTest.kt | 3 - .../ui/AddonInstallationDialogFragmentTest.kt | 3 +- .../addons/ui/AddonsManagerAdapterTest.kt | 3 +- .../addons/ui/UnsupportedAddonsAdapterTest.kt | 5 - .../addons/update/AddonUpdaterWorkerTest.kt | 2 - .../addons/update/DefaultAddonUpdaterTest.kt | 2 - .../feature/app/links/AppLinksFeatureTest.kt | 4 +- .../feature/app/links/AppLinksUseCasesTest.kt | 62 +++++---- .../contextmenu/ContextMenuFeatureTest.kt | 35 ++--- .../AbstractCustomTabsServiceTest.kt | 26 +--- .../CustomTabsToolbarFeatureTest.kt | 24 +--- .../AbstractFetchDownloadServiceTest.kt | 36 ++--- .../downloads/DownloadMiddlewareTest.kt | 49 ++----- .../feature/downloads/DownloadsFeatureTest.kt | 29 ++-- .../share/ShareDownloadFeatureTest.kt | 12 +- .../internal/FindInPagePresenterTest.kt | 30 ++-- .../processing/TabIntentProcessorTest.kt | 3 +- .../feature/media/MediaSessionFeatureTest.kt | 11 -- .../MediaSessionFullscreenFeatureTest.kt | 2 - .../MediaSessionServiceDelegateTest.kt | 2 - .../AbstractPrivateNotificationServiceTest.kt | 23 +--- .../feature/prompts/PromptFeatureTest.kt | 25 +--- .../feature/prompts/PromptMiddlewareTest.kt | 9 -- .../pwa/feature/ManifestUpdateFeatureTest.kt | 26 +--- .../readerview/ReaderViewFeatureTest.kt | 11 -- .../RecentlyClosedMiddlewareTest.kt | 40 +++--- .../search/middleware/SearchMiddlewareTest.kt | 20 ++- .../search/region/RegionMiddlewareTest.kt | 18 ++- .../feature/session/FullScreenFeatureTest.kt | 30 +--- .../feature/session/SessionFeatureTest.kt | 18 +-- .../session/SwipeRefreshFeatureTest.kt | 23 +--- .../middleware/undo/UndoMiddlewareTest.kt | 130 +++++------------- .../SitePermissionsFeatureTest.kt | 34 ++--- .../storage/SyncedTabsStorageTest.kt | 10 +- .../feature/tabs/TabsUseCasesTest.kt | 20 +-- .../feature/tabs/WindowFeatureTest.kt | 9 +- .../tabs/tabstray/TabsTrayPresenterTest.kt | 57 +++----- .../toolbar/ContainerToolbarFeatureTest.kt | 3 +- .../feature/toolbar/ToolbarPresenterTest.kt | 72 ++++------ .../toolbar/WebExtensionToolbarFeatureTest.kt | 8 +- .../toolbar/WebExtensionToolbarTest.kt | 6 +- .../toolbar/internal/URLRendererTest.kt | 21 +-- .../components/lib/crash/CrashReporterTest.kt | 3 +- .../crash/handler/CrashHandlerServiceTest.kt | 10 +- .../lib/crash/handler/ExceptionHandlerTest.kt | 3 +- .../crash/prompt/CrashReporterActivityTest.kt | 21 +-- .../service/SendCrashReportServiceTest.kt | 3 +- .../service/SendCrashTelemetryServiceTest.kt | 3 +- .../lib/state/ext/FragmentKtTest.kt | 1 - .../lib/state/ext/StoreExtensionsKtTest.kt | 5 +- .../lib/state/helpers/AbstractBindingTest.kt | 1 - ...CreditCardsAddressesStorageDelegateTest.kt | 7 +- .../support/ktx/android/view/ViewTest.kt | 2 - .../support/locale/LocaleMiddlewareTest.kt | 31 +---- .../support/migration/AddonMigrationTest.kt | 27 +--- .../support/migration/FennecMigratorTest.kt | 19 +-- .../migration/MigrationObserverTest.kt | 6 +- .../migration/SearchEngineMigrationTest.kt | 6 +- .../support/test/rule/MainCoroutineRule.kt | 49 +++---- .../support/utils/RunWhenReadyQueueTest.kt | 13 +- .../webextensions/WebExtensionSupportTest.kt | 17 +-- docs/changelog.md | 4 + 79 files changed, 525 insertions(+), 1089 deletions(-) diff --git a/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt b/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt index 61e3de9872a..167b1f1c4b1 100644 --- a/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt +++ b/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt @@ -6,7 +6,6 @@ package mozilla.components.browser.icons.loader import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.IconRequest @@ -20,6 +19,7 @@ import mozilla.components.support.test.any import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.junit.Assert.assertEquals @@ -27,6 +27,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn @@ -41,7 +42,9 @@ import java.io.InputStream @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) class NonBlockingHttpIconLoaderTest { - val scope = TestCoroutineScope() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val scope = coroutinesTestRule.scope @Test fun `Loader will return IconLoader#Result#NoResult for a load request and respond with the result through a callback`() = runBlockingTest { diff --git a/components/browser/menu/src/test/java/mozilla/components/browser/menu/WebExtensionBrowserMenuTest.kt b/components/browser/menu/src/test/java/mozilla/components/browser/menu/WebExtensionBrowserMenuTest.kt index 81d420309a8..b493018fe42 100644 --- a/components/browser/menu/src/test/java/mozilla/components/browser/menu/WebExtensionBrowserMenuTest.kt +++ b/components/browser/menu/src/test/java/mozilla/components/browser/menu/WebExtensionBrowserMenuTest.kt @@ -46,7 +46,6 @@ class WebExtensionBrowserMenuTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher @Before fun setup() { @@ -84,8 +83,6 @@ class WebExtensionBrowserMenuTest { val adapter = BrowserMenuAdapter(testContext, items) val menu = WebExtensionBrowserMenu(adapter, store) - testDispatcher.advanceUntilIdle() - val anchor = Button(testContext) val popup = menu.show(anchor) @@ -434,7 +431,6 @@ class WebExtensionBrowserMenuTest { val menu: WebExtensionBrowserMenu = mock() menuItem.bind(menu, view) - testDispatcher.advanceUntilIdle() CollectionProcessor.withFactCollection { facts -> container.performClick() diff --git a/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt b/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt index 89e3388a13e..44c6bb6a323 100644 --- a/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt +++ b/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt @@ -40,7 +40,7 @@ class WebExtensionBrowserMenuItemTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher + private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `web extension menu item is visible by default`() { @@ -85,7 +85,7 @@ class WebExtensionBrowserMenuItemTest { val action = WebExtensionBrowserMenuItem(browserAction, {}) action.bind(mock(), view) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertFalse(view.isEnabled) } @@ -118,7 +118,7 @@ class WebExtensionBrowserMenuItemTest { val action = WebExtensionBrowserMenuItem(browserAction, {}) action.bind(mock(), view) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() val iconCaptor = argumentCaptor() verify(imageView).setImageDrawable(iconCaptor.capture()) @@ -158,7 +158,7 @@ class WebExtensionBrowserMenuItemTest { val action = WebExtensionBrowserMenuItem(browserAction, {}) action.bind(mock(), view) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(badgeView).setBadgeText(badgeText) assertEquals(View.INVISIBLE, badgeView.visibility) @@ -189,7 +189,7 @@ class WebExtensionBrowserMenuItemTest { val action = WebExtensionBrowserMenuItem(browserAction, {}) action.bind(mock(), view) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(imageView).setImageDrawable(notNull()) } @@ -225,7 +225,7 @@ class WebExtensionBrowserMenuItemTest { val menu: WebExtensionBrowserMenu = mock() item.bind(menu, view) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() container.performClick() @@ -262,7 +262,7 @@ class WebExtensionBrowserMenuItemTest { val menu: WebExtensionBrowserMenu = mock() item.bind(menu, view) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(labelView).text = "title" verify(badgeView).text = "badgeText" diff --git a/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt b/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt index 7c7ebb2dcfd..10b81637012 100644 --- a/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt +++ b/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt @@ -40,7 +40,6 @@ class DrawableMenuIconViewHoldersTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher private lateinit var parent: ConstraintLayout private lateinit var layoutInflater: LayoutInflater @@ -85,7 +84,7 @@ class DrawableMenuIconViewHoldersTest { } @Test - fun `async view holder sets icon on view`() = testDispatcher.runBlockingTest { + fun `async view holder sets icon on view`() = runBlockingTest { val holder = AsyncDrawableMenuIconViewHolder(parent, layoutInflater, Side.END) val drawable = mock() @@ -95,7 +94,7 @@ class DrawableMenuIconViewHoldersTest { } @Test - fun `async view holder uses loading icon and fallback icon`() = testDispatcher.runBlockingTest { + fun `async view holder uses loading icon and fallback icon`() = runBlockingTest { val logger = mock() val holder = AsyncDrawableMenuIconViewHolder(parent, layoutInflater, Side.END, logger) diff --git a/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt b/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt index 5f17cdabcb0..9cf08d0a616 100644 --- a/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt +++ b/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt @@ -8,10 +8,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState @@ -23,9 +21,11 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertNotSame import org.junit.Assert.assertNull import org.junit.Assert.assertSame +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.`when` @@ -40,6 +40,11 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class AutoSaveTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope + @Test fun `AutoSave - when going to background`() { runBlocking { @@ -83,16 +88,13 @@ class AutoSaveTest { val sessionStorage: SessionStorage = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val autoSave = AutoSave( store = store, sessionStorage = sessionStorage, minimumIntervalMs = 0 ).whenSessionsChange(scope) - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(autoSave.saveJob) verify(sessionStorage, never()).save(any()) @@ -103,7 +105,7 @@ class AutoSaveTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() autoSave.saveJob?.join() @@ -126,23 +128,20 @@ class AutoSaveTest { ) ) - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val autoSave = AutoSave( store = store, sessionStorage = sessionStorage, minimumIntervalMs = 0 ).whenSessionsChange(scope) - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(autoSave.saveJob) verify(sessionStorage, never()).save(any()) store.dispatch(TabListAction.RemoveTabAction("mozilla")).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() autoSave.saveJob?.join() @@ -165,23 +164,20 @@ class AutoSaveTest { val sessionStorage: SessionStorage = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val autoSave = AutoSave( store = store, sessionStorage = sessionStorage, minimumIntervalMs = 0 ).whenSessionsChange(scope) - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(autoSave.saveJob) verify(sessionStorage, never()).save(any()) store.dispatch(TabListAction.RemoveAllNormalTabsAction).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() autoSave.saveJob?.join() @@ -201,22 +197,19 @@ class AutoSaveTest { val sessionStorage: SessionStorage = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val autoSave = AutoSave( store = store, sessionStorage = sessionStorage, minimumIntervalMs = 0 ).whenSessionsChange(scope) - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(autoSave.saveJob) verify(sessionStorage, never()).save(any()) store.dispatch(TabListAction.RemoveTabAction("firefox")).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() autoSave.saveJob?.join() @@ -239,23 +232,20 @@ class AutoSaveTest { val sessionStorage: SessionStorage = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val autoSave = AutoSave( store = store, sessionStorage = sessionStorage, minimumIntervalMs = 0 ).whenSessionsChange(scope) - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(autoSave.saveJob) verify(sessionStorage, never()).save(any()) store.dispatch(TabListAction.SelectTabAction("mozilla")).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() autoSave.saveJob?.join() @@ -277,9 +267,6 @@ class AutoSaveTest { ) ) - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val autoSave = AutoSave( store = store, sessionStorage = sessionStorage, @@ -293,7 +280,7 @@ class AutoSaveTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(autoSave.saveJob) verify(sessionStorage, never()).save(any()) @@ -305,7 +292,7 @@ class AutoSaveTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() autoSave.saveJob?.join() diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineMiddlewareTest.kt index 72a690a93dc..b3ead147e76 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineMiddlewareTest.kt @@ -4,12 +4,6 @@ package mozilla.components.browser.state.engine -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.engine.middleware.TrimMemoryMiddleware import mozilla.components.browser.state.state.BrowserState @@ -19,32 +13,18 @@ import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.mockito.Mockito import org.mockito.Mockito.verify class EngineMiddlewareTest { - private lateinit var dispatcher: TestCoroutineDispatcher - private lateinit var scope: CoroutineScope - - @Before - fun setUp() { - dispatcher = TestCoroutineDispatcher() - scope = CoroutineScope(dispatcher) - - Dispatchers.setMain(dispatcher) - } - - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - scope.cancel() - - Dispatchers.resetMain() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Test fun `Dispatching CreateEngineSessionAction multiple times should only create one engine session`() { @@ -69,7 +49,7 @@ class EngineMiddlewareTest { EngineAction.CreateEngineSessionAction("mozilla") ) - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine, Mockito.times(1)).createSession(false, null) diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CrashMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CrashMiddlewareTest.kt index 159082ae65b..bf436ad1d2e 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CrashMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CrashMiddlewareTest.kt @@ -4,8 +4,6 @@ package mozilla.components.browser.state.engine.middleware -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.CrashAction import mozilla.components.browser.state.engine.EngineMiddleware import mozilla.components.browser.state.state.BrowserState @@ -16,12 +14,19 @@ import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.doReturn class CrashMiddlewareTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope + @Test fun `Crash and restore scenario`() { val engineSession1: EngineSession = mock() @@ -29,8 +34,6 @@ class CrashMiddlewareTest { val engineSession3: EngineSession = mock() val engine: Engine = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) val store = BrowserStore( middleware = EngineMiddleware.create( @@ -75,7 +78,7 @@ class CrashMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertFalse(store.state.tabs[0].engineState.crashed) assertFalse(store.state.tabs[1].engineState.crashed) @@ -88,7 +91,7 @@ class CrashMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() // Restoring unknown session store.dispatch( @@ -97,7 +100,7 @@ class CrashMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertFalse(store.state.tabs[0].engineState.crashed) assertFalse(store.state.tabs[1].engineState.crashed) @@ -110,9 +113,6 @@ class CrashMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val store = BrowserStore( middleware = EngineMiddleware.create( engine = engine, @@ -131,7 +131,7 @@ class CrashMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertTrue(store.state.tabs[0].engineState.crashed) @@ -141,7 +141,7 @@ class CrashMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertFalse(store.state.tabs[0].engineState.crashed) } diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt index fff2b379768..b8e235f9ec2 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt @@ -5,9 +5,7 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.selector.findCustomTab @@ -23,10 +21,11 @@ import mozilla.components.support.test.any import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean @@ -38,14 +37,10 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class CreateEngineSessionMiddlewareTest { - - private val dispatcher = TestCoroutineDispatcher() - private val scope = CoroutineScope(dispatcher) - - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Test fun `creates engine session if needed`() = runBlocking { @@ -63,13 +58,13 @@ class CreateEngineSessionMiddlewareTest { store.dispatch(EngineAction.CreateEngineSessionAction(tab.id)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engine, times(1)).createSession(false) assertEquals(engineSession, store.state.findTab(tab.id)?.engineState?.engineSession) store.dispatch(EngineAction.CreateEngineSessionAction(tab.id)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engine, times(1)).createSession(false) assertEquals(engineSession, store.state.findTab(tab.id)?.engineState?.engineSession) } @@ -92,7 +87,7 @@ class CreateEngineSessionMiddlewareTest { store.dispatch(EngineAction.UpdateEngineSessionStateAction(tab.id, engineSessionState)).joinBlocking() store.dispatch(EngineAction.CreateEngineSessionAction(tab.id)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engineSession).restoreState(engineSessionState) Unit @@ -111,7 +106,7 @@ class CreateEngineSessionMiddlewareTest { store.dispatch(EngineAction.CreateEngineSessionAction("invalid")).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engine, never()).createSession(anyBoolean(), any()) Unit @@ -135,7 +130,7 @@ class CreateEngineSessionMiddlewareTest { ).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engine, never()).createSession(anyBoolean(), any()) Unit @@ -159,7 +154,7 @@ class CreateEngineSessionMiddlewareTest { store.dispatch(EngineAction.CreateEngineSessionAction(tab.id, followupAction = followupAction)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engine, times(1)).createSession(false) assertEquals(engineSession, store.state.findTab(tab.id)?.engineState?.engineSession) @@ -187,7 +182,7 @@ class CreateEngineSessionMiddlewareTest { store.dispatch(EngineAction.CreateEngineSessionAction(tab.id, followupAction = followupAction)) store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engine, times(1)).createSession(false) assertEquals(engineSession, store.state.findTab(tab.id)?.engineState?.engineSession) @@ -212,7 +207,7 @@ class CreateEngineSessionMiddlewareTest { store.dispatch(EngineAction.CreateEngineSessionAction(customTab.id, followupAction = followupAction)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engine, times(1)).createSession(false) assertEquals(engineSession, store.state.findCustomTab(customTab.id)?.engineState?.engineSession) diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt index 5d9177ea14a..5ca91c52811 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt @@ -4,8 +4,6 @@ package mozilla.components.browser.state.engine.middleware -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.engine.EngineMiddleware import mozilla.components.browser.state.state.BrowserState @@ -18,8 +16,10 @@ import mozilla.components.concept.engine.EngineSession import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Rule import org.junit.Test import org.mockito.ArgumentMatchers import org.mockito.Mockito.doReturn @@ -28,15 +28,17 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify class EngineDelegateMiddlewareTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope + @Test fun `LoadUrlAction for tab without engine session`() { val engineSession: EngineSession = mock() val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -55,7 +57,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -69,9 +71,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession(private = true) - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab", private = true) val store = BrowserStore( middleware = EngineMiddleware.create( @@ -90,7 +89,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = true, contextId = null) @@ -104,9 +103,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession(contextId = "test-container") - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab", contextId = "test-container") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -125,7 +121,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = "test-container") @@ -138,9 +134,6 @@ class EngineDelegateMiddlewareTest { val engineSession: EngineSession = mock() val engine: Engine = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val store = BrowserStore( middleware = EngineMiddleware.create( engine = engine, @@ -162,7 +155,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine, never()).createSession(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyString()) @@ -175,9 +168,6 @@ class EngineDelegateMiddlewareTest { val engineSession: EngineSession = mock() val engine: Engine = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val store = BrowserStore( middleware = EngineMiddleware.create( engine = engine, @@ -199,7 +189,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine, never()).createSession(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyString()) @@ -212,9 +202,6 @@ class EngineDelegateMiddlewareTest { val engineSession: EngineSession = mock() val engine: Engine = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val store = BrowserStore( middleware = EngineMiddleware.create( engine = engine, @@ -236,7 +223,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine, never()).createSession(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyString()) @@ -252,9 +239,6 @@ class EngineDelegateMiddlewareTest { val parentEngineSession: EngineSession = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val parent = createTab("https://getpocket.com", id = "parent-tab").copy( engineState = EngineState(parentEngineSession) ) @@ -277,7 +261,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -292,9 +276,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val parent = createTab("https://getpocket.com", id = "parent-tab") val tab = createTab("https://www.mozilla.org", id = "test-tab", parent = parent) @@ -315,7 +296,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine, times(1)).createSession(private = false, contextId = null) @@ -327,9 +308,6 @@ class EngineDelegateMiddlewareTest { fun `LoadUrlAction with flags and additional headers`() { val engineSession: EngineSession = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val store = BrowserStore( middleware = EngineMiddleware.create( engine = mock(), @@ -356,7 +334,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engineSession, times(1)).loadUrl( @@ -376,9 +354,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( @@ -398,7 +373,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -411,9 +386,6 @@ class EngineDelegateMiddlewareTest { fun `LoadUrlAction for not existing tab`() { val engine: Engine = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val store = BrowserStore( middleware = EngineMiddleware.create( engine = engine, @@ -433,7 +405,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine, never()).createSession(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyString()) @@ -446,9 +418,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -469,7 +438,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -487,9 +456,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -508,7 +474,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -524,9 +490,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -544,7 +507,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -558,9 +521,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -578,7 +538,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -592,9 +552,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -613,7 +570,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -627,9 +584,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -648,7 +602,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -662,9 +616,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -683,7 +634,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -697,9 +648,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -717,7 +665,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -731,9 +679,6 @@ class EngineDelegateMiddlewareTest { val engine: Engine = mock() doReturn(engineSession).`when`(engine).createSession() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val tab = createTab("https://www.mozilla.org", id = "test-tab") val store = BrowserStore( middleware = EngineMiddleware.create( @@ -752,7 +697,7 @@ class EngineDelegateMiddlewareTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(engine).createSession(private = false, contextId = null) @@ -765,9 +710,6 @@ class EngineDelegateMiddlewareTest { val engineSession1: EngineSession = mock() val engineSession2: EngineSession = mock() - val dispatcher = TestCoroutineDispatcher() - val scope = CoroutineScope(dispatcher) - val store = BrowserStore( middleware = EngineMiddleware.create( engine = mock(), @@ -795,7 +737,7 @@ class EngineDelegateMiddlewareTest { store.dispatch(EngineAction.PurgeHistoryAction).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engineSession1).purgeHistory() verify(engineSession2).purgeHistory() diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt index fee75ad499b..aff1ce225b9 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt @@ -5,13 +5,7 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.selector.findTab @@ -23,11 +17,11 @@ import mozilla.components.support.test.any import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyString @@ -36,24 +30,10 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class LinkingMiddlewareTest { - private lateinit var dispatcher: TestCoroutineDispatcher - private lateinit var scope: CoroutineScope - - @Before - fun setUp() { - dispatcher = TestCoroutineDispatcher() - scope = CoroutineScope(dispatcher) - - Dispatchers.setMain(dispatcher) - } - - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - scope.cancel() - - Dispatchers.resetMain() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Test fun `loads URL after linking`() { @@ -68,7 +48,7 @@ class LinkingMiddlewareTest { val engineSession: EngineSession = mock() store.dispatch(EngineAction.LinkEngineSessionAction(tab.id, engineSession)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engineSession).loadUrl(tab.content.url) } @@ -92,7 +72,7 @@ class LinkingMiddlewareTest { val childEngineSession: EngineSession = mock() store.dispatch(EngineAction.LinkEngineSessionAction(child.id, childEngineSession)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(childEngineSession).loadUrl(child.content.url, parentEngineSession) } @@ -116,7 +96,7 @@ class LinkingMiddlewareTest { val childEngineSession: EngineSession = mock() store.dispatch(EngineAction.LinkEngineSessionAction(child.id, childEngineSession)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(childEngineSession).loadUrl(child.content.url) } @@ -134,7 +114,7 @@ class LinkingMiddlewareTest { val engineSession: EngineSession = mock() store.dispatch(EngineAction.LinkEngineSessionAction(tab.id, engineSession, skipLoading = true)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engineSession, never()).loadUrl(tab.content.url) } @@ -151,7 +131,7 @@ class LinkingMiddlewareTest { val engineSession: EngineSession = mock() store.dispatch(EngineAction.LinkEngineSessionAction("invalid", engineSession)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(engineSession, never()).loadUrl(anyString(), any(), any(), any()) } diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt index 263ada2098f..85a876db012 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt @@ -5,9 +5,7 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.state.BrowserState @@ -18,9 +16,10 @@ import mozilla.components.concept.engine.EngineSessionState import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.never @@ -30,13 +29,10 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class SuspendMiddlewareTest { - private val dispatcher = TestCoroutineDispatcher() - private val scope = CoroutineScope(dispatcher) - - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Test fun `suspends engine session`() = runBlocking { @@ -57,7 +53,7 @@ class SuspendMiddlewareTest { store.dispatch(EngineAction.SuspendEngineSessionAction(tab.id)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab(tab.id)?.engineState?.engineSession) assertEquals(state, store.state.findTab(tab.id)?.engineState?.engineSessionState) @@ -122,7 +118,7 @@ class SuspendMiddlewareTest { suspendStore.waitUntilIdle() killStore.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(suspendStore.state.findTab(tab.id)?.engineState?.engineSession) assertEquals(state, suspendStore.state.findTab(tab.id)?.engineState?.engineSessionState) diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt index bef7b38f1f1..e36346603e6 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt @@ -5,9 +5,7 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.EngineAction @@ -25,9 +23,10 @@ import mozilla.components.lib.state.MiddlewareContext import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.never @@ -35,14 +34,10 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class TabsRemovedMiddlewareTest { - - private val dispatcher = TestCoroutineDispatcher() - private val scope = CoroutineScope(dispatcher) - - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Test fun `closes and unlinks engine session when tab is removed`() = runBlocking { @@ -57,7 +52,7 @@ class TabsRemovedMiddlewareTest { val engineSession = linkEngineSession(store, tab.id) store.dispatch(TabListAction.RemoveTabAction(tab.id)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab(tab.id)?.engineState?.engineSession) verify(engineSession).close() @@ -82,7 +77,7 @@ class TabsRemovedMiddlewareTest { store.dispatch(TabListAction.RemoveTabsAction(listOf(tab1.id, tab2.id))).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab(tab1.id)?.engineState?.engineSession) assertNull(store.state.findTab(tab2.id)?.engineState?.engineSession) @@ -110,7 +105,7 @@ class TabsRemovedMiddlewareTest { store.dispatch(TabListAction.RemoveAllNormalTabsAction).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab(tab1.id)?.engineState?.engineSession) assertNull(store.state.findTab(tab2.id)?.engineState?.engineSession) @@ -138,7 +133,7 @@ class TabsRemovedMiddlewareTest { store.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab(tab1.id)?.engineState?.engineSession) assertNull(store.state.findTab(tab2.id)?.engineState?.engineSession) @@ -166,7 +161,7 @@ class TabsRemovedMiddlewareTest { store.dispatch(TabListAction.RemoveAllTabsAction()).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab(tab1.id)?.engineState?.engineSession) assertNull(store.state.findTab(tab2.id)?.engineState?.engineSession) @@ -189,7 +184,7 @@ class TabsRemovedMiddlewareTest { val engineSession = linkEngineSession(store, tab.id) store.dispatch(CustomTabListAction.RemoveCustomTabAction(tab.id)).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab(tab.id)?.engineState?.engineSession) verify(engineSession).close() @@ -213,7 +208,7 @@ class TabsRemovedMiddlewareTest { store.dispatch(CustomTabListAction.RemoveAllCustomTabsAction).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findCustomTab(tab1.id)?.engineState?.engineSession) assertNull(store.state.findCustomTab(tab2.id)?.engineState?.engineSession) diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TrimMemoryMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TrimMemoryMiddlewareTest.kt index 60428efc03c..04c11334312 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TrimMemoryMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TrimMemoryMiddlewareTest.kt @@ -5,8 +5,6 @@ package mozilla.components.browser.state.engine.middleware import android.content.ComponentCallbacks2 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.SystemAction import mozilla.components.browser.state.selector.findCustomTab import mozilla.components.browser.state.selector.findTab @@ -20,14 +18,21 @@ import mozilla.components.concept.engine.EngineSessionState import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.never import org.mockito.Mockito.verify class TrimMemoryMiddlewareTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope + private lateinit var engineSessionReddit: EngineSession private lateinit var engineSessionTheVerge: EngineSession private lateinit var engineSessionTwitch: EngineSession @@ -37,8 +42,6 @@ class TrimMemoryMiddlewareTest { private lateinit var engineSessionFacebook: EngineSession private lateinit var store: BrowserStore - private val dispatcher = TestCoroutineDispatcher() - private val scope = CoroutineScope(dispatcher) private lateinit var engineSessionStateReddit: EngineSessionState private lateinit var engineSessionStateTheVerge: EngineSessionState @@ -132,7 +135,7 @@ class TrimMemoryMiddlewareTest { ).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.state.findTab("theverge")!!.engineState.apply { assertNotNull(engineSession) @@ -196,7 +199,7 @@ class TrimMemoryMiddlewareTest { ).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.state.findTab("theverge")!!.engineState.apply { assertNull(engineSession) diff --git a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/BrowserThumbnailsTest.kt b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/BrowserThumbnailsTest.kt index 7509b423515..929ba0010fa 100644 --- a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/BrowserThumbnailsTest.kt +++ b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/BrowserThumbnailsTest.kt @@ -6,10 +6,6 @@ package mozilla.components.browser.thumbnails import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.createTab @@ -19,8 +15,9 @@ import mozilla.components.support.test.any import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.`when` @@ -33,7 +30,8 @@ import org.mockito.Mockito.verifyNoMoreInteractions @RunWith(AndroidJUnit4::class) class BrowserThumbnailsTest { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() private lateinit var store: BrowserStore private lateinit var engineView: EngineView @@ -42,7 +40,6 @@ class BrowserThumbnailsTest { @Before fun setup() { - Dispatchers.setMain(testDispatcher) store = spy( BrowserStore( BrowserState( @@ -57,12 +54,6 @@ class BrowserThumbnailsTest { thumbnails = BrowserThumbnails(testContext, engineView, store) } - @After - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - @Test fun `do not capture thumbnail when feature is stopped and a site finishes loading`() { thumbnails.start() diff --git a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt index 234fd9fdec8..f68009ad21c 100644 --- a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt +++ b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt @@ -8,7 +8,7 @@ import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import mozilla.components.concept.base.images.ImageLoadRequest import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock @@ -26,7 +26,7 @@ import org.mockito.Mockito.spy @RunWith(AndroidJUnit4::class) class ThumbnailStorageTest { - private val testDispatcher = TestCoroutineDispatcher() + private val testDispatcher = UnconfinedTestDispatcher() @Before @After diff --git a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt index 58576df74ad..b8c9e8596c4 100644 --- a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt +++ b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt @@ -4,11 +4,8 @@ package mozilla.components.feature.accounts.push -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.setMain import mozilla.components.concept.sync.ConstellationState import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.DeviceConstellation @@ -19,6 +16,8 @@ import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.nullable +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.`when` import org.mockito.Mockito.never @@ -27,6 +26,9 @@ import org.mockito.Mockito.verifyNoInteractions import org.mockito.stubbing.OngoingStubbing class AutoPushObserverTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val manager: FxaAccountManager = mock() private val account: OAuthAccount = mock() private val constellation: DeviceConstellation = mock() @@ -35,7 +37,6 @@ class AutoPushObserverTest { @ExperimentalCoroutinesApi @Test fun `messages are forwarded to account manager`() = runBlocking { - Dispatchers.setMain(TestCoroutineDispatcher()) val observer = AutoPushObserver(manager, mock(), "test") `when`(manager.authenticatedAccount()).thenReturn(account) @@ -71,7 +72,6 @@ class AutoPushObserverTest { @ExperimentalCoroutinesApi @Test fun `subscription changes are forwarded to account manager`() = runBlocking { - Dispatchers.setMain(TestCoroutineDispatcher()) val observer = AutoPushObserver(manager, pushFeature, "test") whenSubscribe() diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt index de88150ea61..5bc4fb42d80 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt @@ -16,7 +16,6 @@ import androidx.work.testing.WorkManagerTestInitHelper import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker.Companion.CHECKER_UNIQUE_PERIODIC_WORK_NAME import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker.Companion.WORK_TAG_PERIODIC @@ -36,7 +35,6 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class DefaultSupportedAddonCheckerTest { - @ExperimentalCoroutinesApi @get:Rule val coroutinesTestRule = MainCoroutineRule() diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt index 0d3ee4c4c5a..7490ab29855 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt @@ -10,7 +10,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.work.ListenableWorker import androidx.work.await import androidx.work.testing.TestListenableWorkerBuilder -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import mozilla.components.concept.engine.webextension.EnableSource import mozilla.components.feature.addons.Addon @@ -40,12 +39,10 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify import java.io.IOException -import java.lang.Exception @RunWith(AndroidJUnit4::class) class SupportedAddonsWorkerTest { - @ExperimentalCoroutinesApi @get:Rule val coroutinesTestRule = MainCoroutineRule() diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt index 194d2755693..ca8a3377ea5 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt @@ -16,7 +16,6 @@ import androidx.fragment.app.FragmentTransaction import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.R import mozilla.components.feature.addons.amo.AddonCollectionProvider @@ -46,7 +45,7 @@ class AddonInstallationDialogFragmentTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Test fun `build dialog`() { diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt index eb367d5cf9c..7ea0f83f913 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt @@ -16,7 +16,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.R import mozilla.components.feature.addons.amo.AddonCollectionProvider @@ -50,7 +49,7 @@ class AddonsManagerAdapterTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Test fun `createListWithSections`() { diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/UnsupportedAddonsAdapterTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/UnsupportedAddonsAdapterTest.kt index 9e958f30aea..0b3f412aa53 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/UnsupportedAddonsAdapterTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/UnsupportedAddonsAdapterTest.kt @@ -31,7 +31,6 @@ class UnsupportedAddonsAdapterTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher @Test fun `removing successfully notifies the adapter item changed`() { @@ -50,19 +49,16 @@ class UnsupportedAddonsAdapterTest { ) adapter.removeUninstalledAddon(addonOne) - testDispatcher.advanceUntilIdle() verify(unsupportedAddonsAdapterDelegate, times(1)).onUninstallSuccess() verify(adapter, times(1)).notifyDataSetChanged() assertEquals(1, adapter.itemCount) adapter.removeUninstalledAddon(addonTwo) - testDispatcher.advanceUntilIdle() verify(unsupportedAddonsAdapterDelegate, times(2)).onUninstallSuccess() verify(adapter, times(2)).notifyDataSetChanged() assertEquals(0, adapter.itemCount) adapter.removeUninstalledAddon(addonTwo) - testDispatcher.advanceUntilIdle() verify(unsupportedAddonsAdapterDelegate, times(2)).onUninstallSuccess() verify(adapter, times(2)).notifyDataSetChanged() } @@ -116,7 +112,6 @@ class UnsupportedAddonsAdapterTest { assertFalse(removeButtonOne.isEnabled) assertFalse(removeButtonTwo.isEnabled) - testDispatcher.advanceUntilIdle() verify(addonManager).uninstallAddon(any(), onSuccessCaptor.capture(), any()) onSuccessCaptor.value.invoke() assertFalse(adapter.pendingUninstall) diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt index f1322d9628d..32ea88f6a82 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt @@ -10,7 +10,6 @@ import androidx.work.await import androidx.work.testing.TestListenableWorkerBuilder import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine @@ -38,7 +37,6 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class AddonUpdaterWorkerTest { - @ExperimentalCoroutinesApi @get:Rule val coroutinesTestRule = MainCoroutineRule() diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt index 1c17497e888..b8d213e5fd1 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt @@ -19,7 +19,6 @@ import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import mozilla.components.concept.engine.webextension.DisabledFlags import mozilla.components.concept.engine.webextension.Metadata @@ -46,7 +45,6 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class DefaultAddonUpdaterTest { - @ExperimentalCoroutinesApi @get:Rule val coroutinesTestRule = MainCoroutineRule() diff --git a/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksFeatureTest.kt b/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksFeatureTest.kt index 2af798f755b..ccdf715d438 100644 --- a/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksFeatureTest.kt +++ b/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksFeatureTest.kt @@ -45,7 +45,6 @@ class AppLinksFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher private lateinit var store: BrowserStore private lateinit var mockContext: Context @@ -120,7 +119,6 @@ class AppLinksFeatureTest { val tabWithPendingAppIntent = store.state.findTab(tab.id)!! assertNotNull(tabWithPendingAppIntent.content.appIntent) - testDispatcher.advanceUntilIdle() verify(feature).handleAppIntent(tabWithPendingAppIntent, intentUrl, intent) store.waitUntilIdle() @@ -139,7 +137,7 @@ class AppLinksFeatureTest { val intent: Intent = mock() val appIntent = AppIntentState(intentUrl, intent) store.dispatch(ContentAction.UpdateAppIntentAction(tab.id, appIntent)).joinBlocking() - testDispatcher.advanceUntilIdle() + verify(feature, never()).handleAppIntent(any(), any(), any()) } diff --git a/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt b/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt index dffaafe7821..694dd0c6bd8 100644 --- a/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt +++ b/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt @@ -12,11 +12,9 @@ import android.content.pm.ActivityInfo import android.content.pm.PackageInfo import android.content.pm.ResolveInfo import android.net.Uri +import android.os.Looper.getMainLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals @@ -35,7 +33,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.robolectric.Shadows.shadowOf import java.io.File -import java.lang.NullPointerException +import java.util.concurrent.TimeUnit.MILLISECONDS @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) @@ -245,7 +243,10 @@ class AppLinksUseCasesTest { @Test fun `A URL that matches only general packages is not an app link`() { - val context = createContext(Triple(appUrl, browserPackage, ""), Triple(browserUrl, browserPackage, "")) + val context = createContext( + Triple(appUrl, browserPackage, ""), + Triple(browserUrl, browserPackage, "") + ) val subject = AppLinksUseCases(context, { true }) val redirect = subject.interceptedAppLinkRedirect(appUrl) @@ -257,7 +258,11 @@ class AppLinksUseCasesTest { @Test fun `A URL that also matches both specialized and general packages is an app link`() { - val context = createContext(Triple(appUrl, appPackage, ""), Triple(appUrl, browserPackage, ""), Triple(browserUrl, browserPackage, "")) + val context = createContext( + Triple(appUrl, appPackage, ""), + Triple(appUrl, browserPackage, ""), + Triple(browserUrl, browserPackage, "") + ) val subject = AppLinksUseCases(context, { true }) val redirect = subject.interceptedAppLinkRedirect(appUrl) @@ -343,7 +348,8 @@ class AppLinksUseCasesTest { @Test fun `A intent scheme uri with a fallback without an installed app is not an app link`() { - val uri = "intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;S.browser_fallback_url=http%3A%2F%2Fzxing.org;end" + val uri = + "intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;S.browser_fallback_url=http%3A%2F%2Fzxing.org;end" val context = createContext() val subject = AppLinksUseCases(context, { true }) @@ -428,27 +434,24 @@ class AppLinksUseCasesTest { @Test fun `AppLinksUsecases uses cache`() { - val testDispatcher = TestCoroutineDispatcher() - TestCoroutineScope(testDispatcher).launch { - val context = createContext(Triple(appUrl, appPackage, "")) - - var subject = AppLinksUseCases(context, { true }) - var redirect = subject.interceptedAppLinkRedirect(appUrl) - assertTrue(redirect.isRedirect()) - val timestamp = AppLinksUseCases.redirectCache?.cacheTimeStamp - - testDispatcher.advanceTimeBy(APP_LINKS_CACHE_INTERVAL / 2) - subject = AppLinksUseCases(context, { true }) - redirect = subject.interceptedAppLinkRedirect(appUrl) - assertTrue(redirect.isRedirect()) - assert(timestamp == AppLinksUseCases.redirectCache?.cacheTimeStamp) - - testDispatcher.advanceTimeBy(APP_LINKS_CACHE_INTERVAL / 2 + 1) - subject = AppLinksUseCases(context, { true }) - redirect = subject.interceptedAppLinkRedirect(appUrl) - assertTrue(redirect.isRedirect()) - assert(timestamp != AppLinksUseCases.redirectCache?.cacheTimeStamp) - } + val context = createContext(Triple(appUrl, appPackage, "")) + + var subject = AppLinksUseCases(context, { true }) + var redirect = subject.interceptedAppLinkRedirect(appUrl) + assertTrue(redirect.isRedirect()) + val timestamp = AppLinksUseCases.redirectCache?.cacheTimeStamp + + shadowOf(getMainLooper()).idleFor(APP_LINKS_CACHE_INTERVAL / 2, MILLISECONDS) + subject = AppLinksUseCases(context, { true }) + redirect = subject.interceptedAppLinkRedirect(appUrl) + assertTrue(redirect.isRedirect()) + assert(timestamp == AppLinksUseCases.redirectCache?.cacheTimeStamp) + + shadowOf(getMainLooper()).idleFor(APP_LINKS_CACHE_INTERVAL / 2 + 1, MILLISECONDS) + subject = AppLinksUseCases(context, { true }) + redirect = subject.interceptedAppLinkRedirect(appUrl) + assertTrue(redirect.isRedirect()) + assert(timestamp != AppLinksUseCases.redirectCache?.cacheTimeStamp) } @Test @@ -591,7 +594,8 @@ class AppLinksUseCasesTest { assertNull(result) - uri = "intent://blank#Intent;package=test;i.android.support.customtabs.extra.TOOLBAR_COLOR=2239095040;end" + uri = + "intent://blank#Intent;package=test;i.android.support.customtabs.extra.TOOLBAR_COLOR=2239095040;end" result = subject.safeParseUri(uri, 0) assertNull(result) diff --git a/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt b/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt index 3c64bebf6d1..05511500a84 100644 --- a/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt +++ b/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt @@ -9,10 +9,6 @@ import android.view.View import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.selector.findTab @@ -29,13 +25,14 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.`when` @@ -45,14 +42,14 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class ContextMenuFeatureTest { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher private lateinit var store: BrowserStore @Before fun setUp() { - Dispatchers.setMain(testDispatcher) - store = BrowserStore( BrowserState( tabs = listOf( @@ -63,12 +60,6 @@ class ContextMenuFeatureTest { ) } - @After - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - @Test fun `New HitResult for selected session will cause fragment transaction`() { val fragmentManager = mockFragmentManager() @@ -92,7 +83,7 @@ class ContextMenuFeatureTest { ) ).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(fragmentManager).beginTransaction() verify(view).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -122,7 +113,7 @@ class ContextMenuFeatureTest { ) ).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(fragmentManager, never()).beginTransaction() verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -155,7 +146,7 @@ class ContextMenuFeatureTest { feature.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(fragment).feature = feature verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -185,7 +176,7 @@ class ContextMenuFeatureTest { feature.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(fragmentManager).beginTransaction() verify(transaction).remove(fragment) @@ -219,7 +210,7 @@ class ContextMenuFeatureTest { feature.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(fragmentManager).beginTransaction() verify(transaction).remove(fragment) @@ -289,7 +280,7 @@ class ContextMenuFeatureTest { feature.onMenuCancelled("test-tab") store.waitUntilIdle() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab("test-tab")!!.content.hitResult) } @@ -330,7 +321,7 @@ class ContextMenuFeatureTest { ) store.waitUntilIdle() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNotNull(store.state.findTab("test-tab")!!.content.hitResult) assertFalse(actionInvoked) @@ -338,7 +329,7 @@ class ContextMenuFeatureTest { feature.onMenuItemSelected("test-tab", "test-id") store.waitUntilIdle() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNull(store.state.findTab("test-tab")!!.content.hitResult) assertTrue(actionInvoked) diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/AbstractCustomTabsServiceTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/AbstractCustomTabsServiceTest.kt index 3651f066055..c49dc23fc9a 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/AbstractCustomTabsServiceTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/AbstractCustomTabsServiceTest.kt @@ -12,22 +12,17 @@ import android.support.customtabs.ICustomTabsCallback import android.support.customtabs.ICustomTabsService import androidx.browser.customtabs.CustomTabsService import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.concept.engine.Engine import mozilla.components.feature.customtabs.store.CustomTabsServiceStore import mozilla.components.service.digitalassetlinks.RelationChecker import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn @@ -36,21 +31,8 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class AbstractCustomTabsServiceTest { - @ExperimentalCoroutinesApi - private val testDispatcher = TestCoroutineDispatcher() - - @ExperimentalCoroutinesApi - @Before - fun setUp() { - Dispatchers.setMain(testDispatcher) - } - - @ExperimentalCoroutinesApi - @After - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() @Test fun customTabService() { diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt index 3ad7db2d096..e2a1042c961 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt @@ -14,7 +14,7 @@ import android.widget.ImageButton import androidx.core.view.forEach import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import mozilla.components.browser.menu.BrowserMenuBuilder @@ -857,7 +857,7 @@ class CustomTabsToolbarFeatureTest { @Test fun `show title only if not empty`() { - val dispatcher = TestCoroutineDispatcher() + val dispatcher = UnconfinedTestDispatcher() Dispatchers.setMain(dispatcher) val tab = createCustomTab( @@ -898,8 +898,6 @@ class CustomTabsToolbarFeatureTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("Internet for people, not profit - Mozilla", toolbar.title) Dispatchers.resetMain() @@ -907,7 +905,7 @@ class CustomTabsToolbarFeatureTest { @Test fun `Will use URL as title if title was shown once and is now empty`() { - val dispatcher = TestCoroutineDispatcher() + val dispatcher = UnconfinedTestDispatcher() Dispatchers.setMain(dispatcher) val tab = createCustomTab( @@ -947,64 +945,48 @@ class CustomTabsToolbarFeatureTest { ContentAction.UpdateUrlAction("mozilla", "https://www.mozilla.org/en-US/firefox/") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("", toolbar.title) store.dispatch( ContentAction.UpdateTitleAction("mozilla", "Firefox - Protect your life online with privacy-first products") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("Firefox - Protect your life online with privacy-first products", toolbar.title) store.dispatch( ContentAction.UpdateUrlAction("mozilla", "https://github.com/mozilla-mobile/android-components") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("https://github.com/mozilla-mobile/android-components", toolbar.title) store.dispatch( ContentAction.UpdateTitleAction("mozilla", "Le GitHub") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("Le GitHub", toolbar.title) store.dispatch( ContentAction.UpdateUrlAction("mozilla", "https://github.com/mozilla-mobile/fenix") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("https://github.com/mozilla-mobile/fenix", toolbar.title) store.dispatch( ContentAction.UpdateTitleAction("mozilla", "") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("https://github.com/mozilla-mobile/fenix", toolbar.title) store.dispatch( ContentAction.UpdateTitleAction("mozilla", "A collection of Android libraries to build browsers or browser-like applications.") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("A collection of Android libraries to build browsers or browser-like applications.", toolbar.title) store.dispatch( ContentAction.UpdateTitleAction("mozilla", "") ).joinBlocking() - dispatcher.advanceUntilIdle() - assertEquals("https://github.com/mozilla-mobile/fenix", toolbar.title) } diff --git a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt index 14962b27365..dc6c9fc6f32 100644 --- a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt +++ b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt @@ -22,15 +22,11 @@ import androidx.core.net.toUri import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.DownloadAction import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.browser.state.state.content.DownloadState.Status.COMPLETED @@ -61,7 +57,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals @@ -99,7 +95,6 @@ import org.robolectric.shadows.ShadowNotificationManager import java.io.File import java.io.IOException import java.io.InputStream -import java.lang.IllegalArgumentException import kotlin.random.Random @RunWith(AndroidJUnit4::class) @@ -108,17 +103,19 @@ class AbstractFetchDownloadServiceTest { @Rule @JvmField val folder = TemporaryFolder() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val testDispatcher = coroutinesTestRule.testDispatcher + @Mock private lateinit var client: Client private lateinit var browserStore: BrowserStore @Mock private lateinit var broadcastManager: LocalBroadcastManager private lateinit var service: AbstractFetchDownloadService - private val testDispatcher = TestCoroutineDispatcher() private lateinit var shadowNotificationService: ShadowNotificationManager @Before fun setup() { - Dispatchers.setMain(testDispatcher) openMocks(this) browserStore = BrowserStore() service = spy(object : AbstractFetchDownloadService() { @@ -134,11 +131,6 @@ class AbstractFetchDownloadServiceTest { shadowOf(testContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) } - @After - fun afterEach() { - Dispatchers.resetMain() - } - @Test fun `begins download when started`() = runBlocking { val download = DownloadState("https://example.com/file.txt", "file.txt") @@ -691,7 +683,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DOWNLOADING) assertEquals(DOWNLOADING, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) // The additional notification is the summary one (the notification group). assertEquals(2, shadowNotificationService.size()) @@ -1074,7 +1066,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.PAUSED) assertEquals(DownloadState.Status.PAUSED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) // one of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) @@ -1106,7 +1098,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.COMPLETED) assertEquals(DownloadState.Status.COMPLETED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) assertEquals(2, shadowNotificationService.size()) } @@ -1137,7 +1129,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, FAILED) assertEquals(FAILED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) // one of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) @@ -1169,7 +1161,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.CANCELLED) assertEquals(DownloadState.Status.CANCELLED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) // The additional notification is the summary one (the notification group). assertEquals(1, shadowNotificationService.size()) @@ -1297,7 +1289,7 @@ class AbstractFetchDownloadServiceTest { verify(service).performDownload(providedDownload.capture(), anyBoolean()) // Advance the clock so that the puller posts a notification. - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) // One of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) @@ -1360,7 +1352,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(service.downloadJobs[download.id]!!, DownloadState.Status.PAUSED) // Advance the clock so that the poller posts a notification. - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) assertEquals(2, shadowNotificationService.size()) // Now simulate onTaskRemoved. @@ -1701,7 +1693,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(cancelledDownloadJobState, DownloadState.Status.CANCELLED) assertEquals(DownloadState.Status.CANCELLED, service.getDownloadJobStatus(cancelledDownloadJobState)) - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) // The additional notification is the summary one (the notification group). assertEquals(1, shadowNotificationService.size()) @@ -1720,7 +1712,7 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.COMPLETED) assertEquals(DownloadState.Status.COMPLETED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) // one of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) } diff --git a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt index cba9577202c..29c3dac1aaf 100644 --- a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt +++ b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt @@ -8,13 +8,7 @@ import android.app.DownloadManager.EXTRA_DOWNLOAD_ID import android.content.Context import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.DownloadAction import mozilla.components.browser.state.action.TabListAction @@ -32,11 +26,11 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.never @@ -48,24 +42,9 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class DownloadMiddlewareTest { - private lateinit var dispatcher: TestCoroutineDispatcher - private lateinit var scope: CoroutineScope - - @Before - fun setUp() { - dispatcher = TestCoroutineDispatcher() - scope = CoroutineScope(dispatcher) - - Dispatchers.setMain(dispatcher) - } - - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - scope.cancel() - - Dispatchers.resetMain() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `service is started when download is queued`() = runBlockingTest { @@ -266,7 +245,6 @@ class DownloadMiddlewareTest { fun `RestoreDownloadsState MUST populate the store with items in the storage`() = runBlockingTest { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() - val dispatcher = TestCoroutineDispatcher() val downloadMiddleware = DownloadMiddleware( applicationContext, AbstractFetchDownloadService::class.java, @@ -285,7 +263,7 @@ class DownloadMiddlewareTest { store.dispatch(DownloadAction.RestoreDownloadsStateAction).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() assertEquals(download, store.state.downloads.values.first()) @@ -295,7 +273,6 @@ class DownloadMiddlewareTest { fun `private downloads MUST NOT be restored`() = runBlockingTest { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() - val dispatcher = TestCoroutineDispatcher() val downloadMiddleware = DownloadMiddleware( applicationContext, AbstractFetchDownloadService::class.java, @@ -314,7 +291,7 @@ class DownloadMiddlewareTest { store.dispatch(DownloadAction.RestoreDownloadsStateAction).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() assertTrue(store.state.downloads.isEmpty()) @@ -369,7 +346,7 @@ class DownloadMiddlewareTest { actions.forEach { store.dispatch(it).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(downloadMiddleware, times(1)).removePrivateNotifications(any()) @@ -401,7 +378,7 @@ class DownloadMiddlewareTest { store.dispatch(TabListAction.RemoveTabsAction(listOf("test-tab1", "test-tab3"))).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(downloadMiddleware, times(1)).removePrivateNotifications(any()) @@ -432,7 +409,7 @@ class DownloadMiddlewareTest { store.dispatch(TabListAction.RemoveTabsAction(listOf("test-tab1", "test-tab2"))).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(downloadMiddleware, times(0)).removePrivateNotifications(any()) @@ -463,7 +440,7 @@ class DownloadMiddlewareTest { store.dispatch(TabListAction.RemoveTabAction("test-tab3")).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(downloadMiddleware, times(1)).removePrivateNotifications(any()) @@ -493,7 +470,7 @@ class DownloadMiddlewareTest { store.dispatch(TabListAction.RemoveTabAction("test-tab3")).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(downloadMiddleware, times(0)).removePrivateNotifications(any()) @@ -594,7 +571,7 @@ class DownloadMiddlewareTest { store.dispatch(ContentAction.UpdateDownloadAction(tab.id, download = download)).joinBlocking() store.dispatch(ContentAction.CancelDownloadAction(tab.id, download.id)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(downloadMiddleware, times(1)).closeDownloadResponse(any(), any()) diff --git a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt index ce9d6377952..c6ed60ac5e7 100644 --- a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt +++ b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt @@ -16,10 +16,7 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.selector.findTab @@ -39,14 +36,15 @@ import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.grantPermission import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt @@ -63,14 +61,14 @@ import org.robolectric.shadows.ShadowToast @RunWith(AndroidJUnit4::class) class DownloadsFeatureTest { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher private lateinit var store: BrowserStore @Before - @ExperimentalCoroutinesApi fun setUp() { - Dispatchers.setMain(testDispatcher) store = BrowserStore( BrowserState( @@ -80,13 +78,6 @@ class DownloadsFeatureTest { ) } - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - @Test fun `Adding a download object will request permissions if needed`() { val fragmentManager: FragmentManager = mock() @@ -110,7 +101,7 @@ class DownloadsFeatureTest { store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertTrue(requestedPermissions) verify(fragmentManager, never()).beginTransaction() @@ -137,7 +128,7 @@ class DownloadsFeatureTest { store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(fragmentManager).beginTransaction() } @@ -188,7 +179,7 @@ class DownloadsFeatureTest { store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(downloadManager).download(eq(download), anyString()) } @@ -227,7 +218,7 @@ class DownloadsFeatureTest { store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(fragmentManager, never()).beginTransaction() @@ -471,7 +462,7 @@ class DownloadsFeatureTest { store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(downloadManager).download(eq(download), anyString()) verify(feature).showDownloadNotSupportedError() diff --git a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt index ddb90a99d8f..a8f7e44c22c 100644 --- a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt +++ b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt @@ -9,7 +9,7 @@ import android.webkit.MimeTypeMap import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestScope import mozilla.components.browser.state.action.ShareInternetResourceAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ContentState @@ -60,7 +60,7 @@ class ShareDownloadFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher + private val dispatcher = coroutinesTestRule.testDispatcher @Before fun setup() { @@ -106,7 +106,7 @@ class ShareDownloadFeatureTest { shareFeature.start() store.dispatch(action).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(shareFeature).startSharing(download) verify(store).dispatch(ShareInternetResourceAction.ConsumeShareAction("123")) @@ -136,7 +136,7 @@ class ShareDownloadFeatureTest { val shareState = ShareInternetResourceState(url = "testUrl", contentType = "contentType") val downloadedFile = File("filePath") doReturn(downloadedFile).`when`(shareFeature).download(any()) - shareFeature.scope = TestCoroutineScope() + shareFeature.scope = TestScope(coroutineContext) shareFeature.startSharing(shareState) @@ -153,7 +153,7 @@ class ShareDownloadFeatureTest { val shareState = ShareInternetResourceState(url = tooLongUrl, contentType = "contentType") val downloadedFile = File("filePath") doReturn(downloadedFile).`when`(shareFeature).download(any()) - shareFeature.scope = TestCoroutineScope() + shareFeature.scope = TestScope() shareFeature.startSharing(shareState) @@ -168,7 +168,7 @@ class ShareDownloadFeatureTest { val shareState = ShareInternetResourceState(url = "data:image/png;base64,longstring", contentType = "contentType") val downloadedFile = File("filePath") doReturn(downloadedFile).`when`(shareFeature).download(any()) - shareFeature.scope = TestCoroutineScope() + shareFeature.scope = TestScope() shareFeature.startSharing(shareState) diff --git a/components/feature/findinpage/src/test/java/mozilla/components/feature/findinpage/internal/FindInPagePresenterTest.kt b/components/feature/findinpage/src/test/java/mozilla/components/feature/findinpage/internal/FindInPagePresenterTest.kt index 3e631458b24..fb4ce15a830 100644 --- a/components/feature/findinpage/src/test/java/mozilla/components/feature/findinpage/internal/FindInPagePresenterTest.kt +++ b/components/feature/findinpage/src/test/java/mozilla/components/feature/findinpage/internal/FindInPagePresenterTest.kt @@ -4,11 +4,7 @@ package mozilla.components.feature.findinpage.internal -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.state.BrowserState @@ -20,10 +16,11 @@ import mozilla.components.feature.findinpage.view.FindInPageView import mozilla.components.support.test.any import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Rule import org.junit.Test import org.mockito.Mockito import org.mockito.Mockito.`when` @@ -33,13 +30,15 @@ import org.mockito.Mockito.verify class FindInPagePresenterTest { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private lateinit var store: BrowserStore @Before @ExperimentalCoroutinesApi fun setUp() { - Dispatchers.setMain(testDispatcher) store = BrowserStore( BrowserState( tabs = listOf( @@ -50,13 +49,6 @@ class FindInPagePresenterTest { ) } - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - @Test fun `view is updated to display latest find result`() { val view: FindInPageView = mock() @@ -65,17 +57,17 @@ class FindInPagePresenterTest { val result = FindResultState(0, 2, false) store.dispatch(ContentAction.AddFindResultAction("test-tab", result)).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(view, never()).displayResult(result) presenter.bind(store.state.selectedTab!!) store.dispatch(ContentAction.AddFindResultAction("test-tab", result)).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(view).displayResult(result) val result2 = FindResultState(1, 2, true) store.dispatch(ContentAction.AddFindResultAction("test-tab", result2)).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(view).displayResult(result2) } @@ -87,12 +79,12 @@ class FindInPagePresenterTest { presenter.bind(store.state.selectedTab!!) store.dispatch(ContentAction.AddFindResultAction("test-tab", mock())).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(view, times(1)).displayResult(any()) presenter.stop() store.dispatch(ContentAction.AddFindResultAction("test-tab", mock())).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(view, times(1)).displayResult(any()) } diff --git a/components/feature/intent/src/test/java/mozilla/components/feature/intent/processing/TabIntentProcessorTest.kt b/components/feature/intent/src/test/java/mozilla/components/feature/intent/processing/TabIntentProcessorTest.kt index 44aaa4f6897..d0de3b1acf5 100644 --- a/components/feature/intent/src/test/java/mozilla/components/feature/intent/processing/TabIntentProcessorTest.kt +++ b/components/feature/intent/src/test/java/mozilla/components/feature/intent/processing/TabIntentProcessorTest.kt @@ -9,7 +9,6 @@ import android.content.Intent import android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.engine.EngineMiddleware @@ -51,7 +50,7 @@ class TabIntentProcessorTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope private lateinit var middleware: CaptureActionsMiddleware diff --git a/components/feature/media/src/test/java/mozilla/components/feature/media/MediaSessionFeatureTest.kt b/components/feature/media/src/test/java/mozilla/components/feature/media/MediaSessionFeatureTest.kt index 27e4fdf01a4..284c87d7cf6 100644 --- a/components/feature/media/src/test/java/mozilla/components/feature/media/MediaSessionFeatureTest.kt +++ b/components/feature/media/src/test/java/mozilla/components/feature/media/MediaSessionFeatureTest.kt @@ -29,7 +29,6 @@ class MediaSessionFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `feature triggers foreground service when there's is media session state`() { @@ -103,7 +102,6 @@ class MediaSessionFeatureTest { store.dispatch(MediaSessionAction.ActivatedMediaSessionAction(store.state.tabs[0].id, mock())) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, never()).startForegroundService(any()) store.dispatch( @@ -113,7 +111,6 @@ class MediaSessionFeatureTest { ) ) store.waitUntilIdle() - dispatcher.advanceUntilIdle() } @Test @@ -143,7 +140,6 @@ class MediaSessionFeatureTest { store.dispatch(MediaSessionAction.ActivatedMediaSessionAction(store.state.tabs[0].id, mock())) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, never()).startForegroundService(any()) store.dispatch( @@ -153,7 +149,6 @@ class MediaSessionFeatureTest { ) ) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, times(1)).startForegroundService(any()) store.dispatch( @@ -163,17 +158,14 @@ class MediaSessionFeatureTest { ) ) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, times(1)).startForegroundService(any()) store.dispatch(MediaSessionAction.DeactivatedMediaSessionAction(store.state.tabs[0].id)) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, times(1)).startForegroundService(any()) store.dispatch(MediaSessionAction.ActivatedMediaSessionAction(store.state.tabs[0].id, mock())) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, times(1)).startForegroundService(any()) store.dispatch( @@ -183,7 +175,6 @@ class MediaSessionFeatureTest { ) ) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, times(1)).startForegroundService(any()) store.dispatch( @@ -193,7 +184,6 @@ class MediaSessionFeatureTest { ) ) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, times(2)).startForegroundService(any()) store.dispatch( @@ -203,7 +193,6 @@ class MediaSessionFeatureTest { ) ) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockApplicationContext, times(2)).startForegroundService(any()) } } diff --git a/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt b/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt index 75ca3e1485d..fd779389904 100644 --- a/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt +++ b/components/feature/media/src/test/java/mozilla/components/feature/media/fullscreen/MediaSessionFullscreenFeatureTest.kt @@ -26,7 +26,6 @@ class MediaSessionFullscreenFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `screen orientation is updated correctly`() { @@ -63,7 +62,6 @@ class MediaSessionFullscreenFeatureTest { ) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(mockActivity).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT) } diff --git a/components/feature/media/src/test/java/mozilla/components/feature/media/service/MediaSessionServiceDelegateTest.kt b/components/feature/media/src/test/java/mozilla/components/feature/media/service/MediaSessionServiceDelegateTest.kt index 99159569bac..5cc2d6f270b 100644 --- a/components/feature/media/src/test/java/mozilla/components/feature/media/service/MediaSessionServiceDelegateTest.kt +++ b/components/feature/media/src/test/java/mozilla/components/feature/media/service/MediaSessionServiceDelegateTest.kt @@ -34,7 +34,6 @@ class MediaSessionServiceDelegateTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `media session state starts service in foreground`() { @@ -113,7 +112,6 @@ class MediaSessionServiceDelegateTest { store.dispatch(MediaSessionAction.DeactivatedMediaSessionAction(store.state.customTabs[0].id)) store.waitUntilIdle() - dispatcher.advanceUntilIdle() verify(service).stopSelf() verify(delegate).shutdown() diff --git a/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/AbstractPrivateNotificationServiceTest.kt b/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/AbstractPrivateNotificationServiceTest.kt index 33e82325064..9c8ada41976 100644 --- a/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/AbstractPrivateNotificationServiceTest.kt +++ b/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/AbstractPrivateNotificationServiceTest.kt @@ -12,11 +12,7 @@ import android.content.SharedPreferences import androidx.core.app.NotificationCompat import androidx.core.content.getSystemService import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.LocaleAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState @@ -26,10 +22,11 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt @@ -44,14 +41,15 @@ import java.util.Locale @RunWith(AndroidJUnit4::class) class AbstractPrivateNotificationServiceTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private lateinit var preferences: SharedPreferences private lateinit var notificationManager: NotificationManager - private val testDispatcher = TestCoroutineDispatcher() @Before fun setup() { - Dispatchers.setMain(testDispatcher) - preferences = mock() notificationManager = mock() val editor = mock() @@ -60,13 +58,6 @@ class AbstractPrivateNotificationServiceTest { whenever(editor.putLong(anyString(), anyLong())).thenReturn(editor) } - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - @Test fun `WHEN the service is created THEN start foreground is called`() { val service = spy(object : MockService() { @@ -113,7 +104,7 @@ class AbstractPrivateNotificationServiceTest { val mockLocale = Locale("English") service.store.dispatch(LocaleAction.UpdateLocaleAction(mockLocale)).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(service).notifyLocaleChanged() } diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt index 77724d30f91..8a7c45cefe0 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt @@ -17,11 +17,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState @@ -61,13 +57,14 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.`when` @@ -84,7 +81,8 @@ import java.util.Date @RunWith(AndroidJUnit4::class) class PromptFeatureTest { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() private lateinit var store: BrowserStore private lateinit var fragmentManager: FragmentManager @@ -99,7 +97,6 @@ class PromptFeatureTest { @Before @ExperimentalCoroutinesApi fun setUp() { - Dispatchers.setMain(testDispatcher) store = BrowserStore( BrowserState( tabs = listOf( @@ -116,13 +113,6 @@ class PromptFeatureTest { fragmentManager = mockFragmentManager() } - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - @Test fun `PromptFeature acts on the selected session by default`() { val feature = spy( @@ -136,7 +126,6 @@ class PromptFeatureTest { val promptRequest = SingleChoice(arrayOf(), {}, {}) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, promptRequest)).joinBlocking() - testDispatcher.advanceUntilIdle() verify(feature).onPromptRequested(store.state.tabs.first()) } @@ -155,7 +144,6 @@ class PromptFeatureTest { val promptRequest = SingleChoice(arrayOf(), {}, {}) store.dispatch(ContentAction.UpdatePromptRequestAction("custom-tab", promptRequest)) .joinBlocking() - testDispatcher.advanceUntilIdle() verify(feature).onPromptRequested(store.state.customTabs.first()) } @@ -172,7 +160,6 @@ class PromptFeatureTest { val promptRequest = SingleChoice(arrayOf(), {}, {}) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, promptRequest)).joinBlocking() - testDispatcher.advanceUntilIdle() feature.start() verify(feature).onPromptRequested(store.state.tabs.first()) } @@ -1449,7 +1436,6 @@ class PromptFeatureTest { val promptRequest = PromptRequest.Share(ShareData("Title", "Text", null), {}, {}, {}) store.dispatch(ContentAction.UpdatePromptRequestAction("custom-tab", promptRequest)) .joinBlocking() - testDispatcher.advanceUntilIdle() verify(feature).onPromptRequested(store.state.customTabs.first()) verify(delegate).showShareSheet( @@ -1477,7 +1463,6 @@ class PromptFeatureTest { store.dispatch(ContentAction.UpdatePromptRequestAction("custom-tab", selectCreditCardRequest)) .joinBlocking() - testDispatcher.advanceUntilIdle() verify(feature).onPromptRequested(store.state.customTabs.first()) verify(creditCardPicker).handleSelectCreditCardRequest(selectCreditCardRequest) @@ -1500,7 +1485,6 @@ class PromptFeatureTest { store.dispatch(ContentAction.UpdatePromptRequestAction("custom-tab", selectCreditCardRequest)) .joinBlocking() - testDispatcher.advanceUntilIdle() verify(feature).onPromptRequested(store.state.customTabs.first()) verify(creditCardPicker, never()).handleSelectCreditCardRequest(selectCreditCardRequest) @@ -1523,7 +1507,6 @@ class PromptFeatureTest { store.dispatch(ContentAction.UpdatePromptRequestAction("custom-tab", selectCreditCardRequest)) .joinBlocking() - testDispatcher.advanceUntilIdle() verify(feature).onPromptRequested(store.state.customTabs.first()) verify(creditCardPicker, never()).handleSelectCreditCardRequest(selectCreditCardRequest) diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptMiddlewareTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptMiddlewareTest.kt index 649947918ac..b5cc25f2e64 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptMiddlewareTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptMiddlewareTest.kt @@ -28,7 +28,6 @@ class PromptMiddlewareTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher private lateinit var store: BrowserStore @@ -56,14 +55,12 @@ class PromptMiddlewareTest { val onDeny = spy { } val popupPrompt1 = PromptRequest.Popup("https://firefox.com", onAllow = { }, onDeny = onDeny) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, popupPrompt1)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(1, tab()!!.content.promptRequests.size) assertEquals(popupPrompt1, tab()!!.content.promptRequests[0]) verify(onDeny, never()).invoke() val popupPrompt2 = PromptRequest.Popup("https://firefox.com", onAllow = { }, onDeny = onDeny) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, popupPrompt2)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(1, tab()!!.content.promptRequests.size) assertEquals(popupPrompt1, tab()!!.content.promptRequests[0]) verify(onDeny).invoke() @@ -74,14 +71,12 @@ class PromptMiddlewareTest { val onDeny = spy { } val popupPrompt = PromptRequest.Popup("https://firefox.com", onAllow = { }, onDeny = onDeny) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, popupPrompt)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(1, tab()!!.content.promptRequests.size) assertEquals(popupPrompt, tab()!!.content.promptRequests[0]) verify(onDeny, never()).invoke() val alert = PromptRequest.Alert("title", "message", false, { }, { }) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, alert)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(2, tab()!!.content.promptRequests.size) assertEquals(popupPrompt, tab()!!.content.promptRequests[0]) assertEquals(alert, tab()!!.content.promptRequests[1]) @@ -91,14 +86,12 @@ class PromptMiddlewareTest { fun `Process popup after other prompt request`() { val alert = PromptRequest.Alert("title", "message", false, { }, { }) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, alert)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(1, tab()!!.content.promptRequests.size) assertEquals(alert, tab()!!.content.promptRequests[0]) val onDeny = spy { } val popupPrompt = PromptRequest.Popup("https://firefox.com", onAllow = { }, onDeny = onDeny) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, popupPrompt)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(2, tab()!!.content.promptRequests.size) assertEquals(alert, tab()!!.content.promptRequests[0]) assertEquals(popupPrompt, tab()!!.content.promptRequests[1]) @@ -109,13 +102,11 @@ class PromptMiddlewareTest { fun `Process other prompt requests`() { val alert = PromptRequest.Alert("title", "message", false, { }, { }) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, alert)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(1, tab()!!.content.promptRequests.size) assertEquals(alert, tab()!!.content.promptRequests[0]) val beforeUnloadPrompt = PromptRequest.BeforeUnload("title", onLeave = { }, onStay = { }) store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, beforeUnloadPrompt)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(2, tab()!!.content.promptRequests.size) assertEquals(alert, tab()!!.content.promptRequests[0]) assertEquals(beforeUnloadPrompt, tab()!!.content.promptRequests[1]) diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt index 50ba0c0dab8..9d98dd03423 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt @@ -5,12 +5,8 @@ package mozilla.components.feature.pwa.feature import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.createCustomTab @@ -23,8 +19,9 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.never @@ -33,10 +30,12 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class ManifestUpdateFeatureTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private lateinit var shortcutManager: WebAppShortcutManager private lateinit var storage: ManifestStorage private lateinit var store: BrowserStore - private lateinit var dispatcher: TestCoroutineDispatcher private val sessionId = "external-app-session-id" private val baseManifest = WebAppManifest( @@ -50,9 +49,6 @@ class ManifestUpdateFeatureTest { storage = mock() shortcutManager = mock() - dispatcher = TestCoroutineDispatcher() - Dispatchers.setMain(dispatcher) - store = BrowserStore( BrowserState( customTabs = listOf( @@ -62,13 +58,6 @@ class ManifestUpdateFeatureTest { ) } - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - - Dispatchers.resetMain() - } - @Test fun `start and stop handle null session`() = runBlockingTest { val feature = ManifestUpdateFeature( @@ -83,7 +72,6 @@ class ManifestUpdateFeatureTest { feature.start() store.waitUntilIdle() - dispatcher.advanceUntilIdle() feature.stop() @@ -150,7 +138,6 @@ class ManifestUpdateFeatureTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() feature.updateJob!!.joinBlocking() runBlocking { @@ -179,7 +166,6 @@ class ManifestUpdateFeatureTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() feature.updateJob?.joinBlocking() runBlocking { @@ -215,7 +201,6 @@ class ManifestUpdateFeatureTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() feature.updateJob?.joinBlocking() runBlocking { @@ -252,7 +237,6 @@ class ManifestUpdateFeatureTest { ) ).joinBlocking() - dispatcher.advanceUntilIdle() feature.updateJob?.joinBlocking() runBlocking { diff --git a/components/feature/readerview/src/test/java/mozilla/components/feature/readerview/ReaderViewFeatureTest.kt b/components/feature/readerview/src/test/java/mozilla/components/feature/readerview/ReaderViewFeatureTest.kt index 723de6837d2..82a58c32d29 100644 --- a/components/feature/readerview/src/test/java/mozilla/components/feature/readerview/ReaderViewFeatureTest.kt +++ b/components/feature/readerview/src/test/java/mozilla/components/feature/readerview/ReaderViewFeatureTest.kt @@ -60,7 +60,6 @@ class ReaderViewFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher @Before fun setup() { @@ -194,7 +193,6 @@ class ReaderViewFeatureTest { store.dispatch(ReaderAction.UpdateReaderableCheckRequiredAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() val tabCaptor = argumentCaptor() verify(readerViewFeature).checkReaderState(tabCaptor.capture()) assertEquals(tab.id, tabCaptor.value.id) @@ -209,7 +207,6 @@ class ReaderViewFeatureTest { readerViewFeature.start() store.dispatch(ReaderAction.UpdateReaderConnectRequiredAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() val tabCaptor = argumentCaptor() verify(readerViewFeature).connectReaderViewContentScript(tabCaptor.capture()) assertEquals(tab.id, tabCaptor.value.id) @@ -232,27 +229,22 @@ class ReaderViewFeatureTest { store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking() store.dispatch(ReaderAction.UpdateReaderableAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(1, readerViewStatusChanges.size) assertEquals(Pair(true, false), readerViewStatusChanges[0]) store.dispatch(ReaderAction.UpdateReaderActiveAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(2, readerViewStatusChanges.size) assertEquals(Pair(true, true), readerViewStatusChanges[1]) store.dispatch(ReaderAction.UpdateReaderableAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() // No change -> No notification should have been sent assertEquals(2, readerViewStatusChanges.size) store.dispatch(ReaderAction.UpdateReaderActiveAction(tab.id, false)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(3, readerViewStatusChanges.size) assertEquals(Pair(true, false), readerViewStatusChanges[2]) store.dispatch(ReaderAction.UpdateReaderableAction(tab.id, false)).joinBlocking() - testDispatcher.advanceUntilIdle() assertEquals(4, readerViewStatusChanges.size) assertEquals(Pair(false, false), readerViewStatusChanges[3]) } @@ -317,7 +309,6 @@ class ReaderViewFeatureTest { store.dispatch(EngineAction.LinkEngineSessionAction(tab.id, engineSession)).joinBlocking() store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking() store.dispatch(ContentAction.UpdateBackNavigationStateAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() readerViewFeature.hideReaderView() verify(engineSession).goBack(false) @@ -496,7 +487,6 @@ class ReaderViewFeatureTest { val message = argumentCaptor() readerViewFeature.start() store.dispatch(ReaderAction.UpdateReaderConnectRequiredAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() verify(controller).registerContentMessageHandler( eq(engineSession), messageHandler.capture(), eq(READER_VIEW_ACTIVE_CONTENT_PORT) ) @@ -551,7 +541,6 @@ class ReaderViewFeatureTest { readerViewFeature.start() store.dispatch(ReaderAction.UpdateReaderConnectRequiredAction(tab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() verify(controller).registerContentMessageHandler( eq(engineSession), messageHandler.capture(), eq(READER_VIEW_ACTIVE_CONTENT_PORT) ) diff --git a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt index fa3c04a15ff..4aa635c29ea 100644 --- a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt +++ b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt @@ -5,11 +5,9 @@ package mozilla.components.feature.recentlyclosed import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.action.UndoAction @@ -25,10 +23,11 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.times @@ -41,13 +40,10 @@ class RecentlyClosedMiddlewareTest { lateinit var store: BrowserStore lateinit var engine: Engine - private val dispatcher = TestCoroutineDispatcher() - private val scope = CoroutineScope(dispatcher) - - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Before fun setup() { @@ -77,7 +73,7 @@ class RecentlyClosedMiddlewareTest { ) store.dispatch(RecentlyClosedAction.AddClosedTabsAction(listOf(closedTab))).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(storage).addTabsToCollectionWithMax( @@ -103,7 +99,7 @@ class RecentlyClosedMiddlewareTest { store.dispatch(TabListAction.RemoveTabsAction(listOf("1234", "5678"))).joinBlocking() store.dispatch(UndoAction.ClearRecoverableTabs(store.state.undoHistory.tag)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() val closedTabCaptor = argumentCaptor>() @@ -143,7 +139,7 @@ class RecentlyClosedMiddlewareTest { store.dispatch(TabListAction.RemoveTabAction("1234")).joinBlocking() store.dispatch(UndoAction.ClearRecoverableTabs(store.state.undoHistory.tag)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() val closedTabCaptor = argumentCaptor>() @@ -175,7 +171,7 @@ class RecentlyClosedMiddlewareTest { ) store.dispatch(TabListAction.RemoveTabAction("1234")).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(storage).getTabs() @@ -200,7 +196,7 @@ class RecentlyClosedMiddlewareTest { store.dispatch(TabListAction.RemoveAllNormalTabsAction).joinBlocking() store.dispatch(UndoAction.ClearRecoverableTabs(store.state.undoHistory.tag)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() val closedTabCaptor = argumentCaptor>() @@ -235,7 +231,7 @@ class RecentlyClosedMiddlewareTest { store.dispatch(TabListAction.RemoveAllTabsAction()).joinBlocking() store.dispatch(UndoAction.ClearRecoverableTabs(store.state.undoHistory.tag)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() val closedTabCaptor = argumentCaptor>() @@ -278,7 +274,7 @@ class RecentlyClosedMiddlewareTest { assertEquals(1, store.state.tabs.size) assertEquals("tab4", store.state.selectedTabId) - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() val closedTabCaptor = argumentCaptor>() @@ -321,7 +317,7 @@ class RecentlyClosedMiddlewareTest { store.waitUntilIdle() // Now wait for Middleware to process Init action and store to process action from middleware - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(storage).getTabs() @@ -343,10 +339,10 @@ class RecentlyClosedMiddlewareTest { ) store.dispatch(RecentlyClosedAction.RemoveClosedTabAction(closedTab.state)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(storage).removeTab(closedTab.state) } @@ -365,10 +361,10 @@ class RecentlyClosedMiddlewareTest { ) store.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() verify(storage).removeAllTabs() } diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt index bcfaa949ceb..174c808dea1 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt @@ -5,10 +5,8 @@ package mozilla.components.feature.search.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.cancel import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestDispatcher import mozilla.components.browser.state.action.SearchAction import mozilla.components.browser.state.search.RegionState import mozilla.components.browser.state.search.SearchEngine @@ -24,12 +22,14 @@ import mozilla.components.support.test.fakes.android.FakeSharedPreferences import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn @@ -40,21 +40,19 @@ import java.util.UUID @RunWith(AndroidJUnit4::class) class SearchMiddlewareTest { - private lateinit var dispatcher: TestCoroutineDispatcher + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private lateinit var originalLocale: Locale - private lateinit var scope: CoroutineScope @Before fun setUp() { - dispatcher = TestCoroutineDispatcher() - scope = CoroutineScope(dispatcher) originalLocale = Locale.getDefault() } @After fun tearDown() { - dispatcher.cleanupTestCoroutines() - scope.cancel() if (Locale.getDefault() != originalLocale) { Locale.setDefault(originalLocale) @@ -1584,12 +1582,12 @@ class SearchMiddlewareTest { } } -private fun wait(store: BrowserStore, dispatcher: TestCoroutineDispatcher) { +private fun wait(store: BrowserStore, dispatcher: TestDispatcher) { // First we wait for the InitAction that may still need to be processed. store.waitUntilIdle() // Now we wait for the Middleware that may need to asynchronously process an action the test dispatched - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() // Since the Middleware may have dispatched an action, we now wait for the store again. store.waitUntilIdle() diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt index 177d0e4d09f..bc93771aa97 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt @@ -5,7 +5,6 @@ package mozilla.components.feature.search.region import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher import mozilla.components.browser.state.action.InitAction import mozilla.components.browser.state.search.RegionState import mozilla.components.browser.state.store.BrowserStore @@ -15,14 +14,19 @@ import mozilla.components.support.test.fakes.FakeClock import mozilla.components.support.test.fakes.android.FakeContext import mozilla.components.support.test.fakes.android.FakeSharedPreferences import mozilla.components.support.test.libstate.ext.waitUntilIdle -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Before +import org.junit.Rule import org.junit.Test class RegionMiddlewareTest { - private lateinit var dispatcher: TestCoroutineDispatcher + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + private lateinit var locationService: FakeLocationService private lateinit var clock: FakeClock private lateinit var regionManager: RegionManager @@ -30,7 +34,6 @@ class RegionMiddlewareTest { @Before fun setUp() { clock = FakeClock() - dispatcher = TestCoroutineDispatcher() locationService = FakeLocationService() regionManager = RegionManager( context = FakeContext(), @@ -40,11 +43,6 @@ class RegionMiddlewareTest { ) } - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - } - @Test fun `Updates region on init`() { val middleware = RegionMiddleware(FakeContext(), locationService, dispatcher) @@ -76,7 +74,7 @@ class RegionMiddlewareTest { store.dispatch(InitAction).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() assertEquals(RegionState.Default, store.state.search.region) diff --git a/components/feature/session/src/test/java/mozilla/components/feature/session/FullScreenFeatureTest.kt b/components/feature/session/src/test/java/mozilla/components/feature/session/FullScreenFeatureTest.kt index b7a368b0e48..43f69071e49 100644 --- a/components/feature/session/src/test/java/mozilla/components/feature/session/FullScreenFeatureTest.kt +++ b/components/feature/session/src/test/java/mozilla/components/feature/session/FullScreenFeatureTest.kt @@ -5,11 +5,6 @@ package mozilla.components.feature.session import android.view.WindowManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState @@ -18,13 +13,13 @@ import mozilla.components.browser.state.store.BrowserStore import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.mockito.ArgumentMatchers import org.mockito.Mockito.doReturn @@ -32,20 +27,9 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify class FullScreenFeatureTest { - private val testDispatcher = TestCoroutineDispatcher() - @Before - @ExperimentalCoroutinesApi - fun setUp() { - Dispatchers.setMain(testDispatcher) - } - - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() @Test fun `Starting without tabs`() { @@ -62,8 +46,6 @@ class FullScreenFeatureTest { ) feature.start() - - testDispatcher.advanceUntilIdle() store.waitUntilIdle() assertNull(viewPort) @@ -91,8 +73,6 @@ class FullScreenFeatureTest { ) feature.start() - - testDispatcher.advanceUntilIdle() store.waitUntilIdle() assertNull(viewPort) @@ -134,8 +114,6 @@ class FullScreenFeatureTest { ) feature.start() - - testDispatcher.advanceUntilIdle() store.waitUntilIdle() assertEquals(42, viewPort) diff --git a/components/feature/session/src/test/java/mozilla/components/feature/session/SessionFeatureTest.kt b/components/feature/session/src/test/java/mozilla/components/feature/session/SessionFeatureTest.kt index 0122d10d5da..f3970b2df6e 100644 --- a/components/feature/session/src/test/java/mozilla/components/feature/session/SessionFeatureTest.kt +++ b/components/feature/session/src/test/java/mozilla/components/feature/session/SessionFeatureTest.kt @@ -5,7 +5,6 @@ package mozilla.components.feature.session import android.view.View -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.CrashAction @@ -42,8 +41,7 @@ class SessionFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) - private val testDispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Test fun `start renders selected session`() { @@ -60,7 +58,6 @@ class SessionFeatureTest { verify(view, never()).render(any()) feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSession) @@ -82,7 +79,6 @@ class SessionFeatureTest { feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSession) @@ -103,7 +99,6 @@ class SessionFeatureTest { verify(view, never()).render(any()) feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSession) @@ -126,12 +121,10 @@ class SessionFeatureTest { verify(view, never()).render(any()) feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSessionB) store.dispatch(TabListAction.SelectTabAction("A")).joinBlocking() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSessionA) } @@ -147,7 +140,6 @@ class SessionFeatureTest { verify(view, never()).render(any()) feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(store).dispatch(EngineAction.CreateEngineSessionAction("B")) } @@ -169,14 +161,12 @@ class SessionFeatureTest { verify(view, never()).render(any()) feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSessionB) feature.stop() store.dispatch(TabListAction.SelectTabAction("A")).joinBlocking() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view, never()).render(engineSessionA) } @@ -195,7 +185,6 @@ class SessionFeatureTest { feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSession) @@ -221,7 +210,6 @@ class SessionFeatureTest { feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSession) @@ -250,7 +238,6 @@ class SessionFeatureTest { feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSession) @@ -340,7 +327,6 @@ class SessionFeatureTest { verify(view, never()).render(any()) feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view).render(engineSession) @@ -366,7 +352,6 @@ class SessionFeatureTest { feature.start() store.dispatch(CrashAction.SessionCrashedAction("A")).joinBlocking() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(view, atLeastOnce()).release() middleware.assertNotDispatched(EngineAction.CreateEngineSessionAction::class) @@ -388,7 +373,6 @@ class SessionFeatureTest { assertEquals(0L, store.state.findTab("B")?.lastAccess) feature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() assertNotEquals(0L, store.state.findTab("B")?.lastAccess) diff --git a/components/feature/session/src/test/java/mozilla/components/feature/session/SwipeRefreshFeatureTest.kt b/components/feature/session/src/test/java/mozilla/components/feature/session/SwipeRefreshFeatureTest.kt index 18a49c4e1b6..384e3c0793b 100644 --- a/components/feature/session/src/test/java/mozilla/components/feature/session/SwipeRefreshFeatureTest.kt +++ b/components/feature/session/src/test/java/mozilla/components/feature/session/SwipeRefreshFeatureTest.kt @@ -9,11 +9,6 @@ import android.graphics.Bitmap import android.widget.FrameLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab import mozilla.components.browser.state.state.BrowserState @@ -26,10 +21,11 @@ import mozilla.components.concept.engine.selection.SelectionActionDelegate import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn @@ -39,8 +35,9 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class SwipeRefreshFeatureTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = TestCoroutineDispatcher() private lateinit var store: BrowserStore private lateinit var refreshFeature: SwipeRefreshFeature private val mockLayout = mock() @@ -48,7 +45,6 @@ class SwipeRefreshFeatureTest { @Before fun setup() { - Dispatchers.setMain(testDispatcher) store = BrowserStore( BrowserState( tabs = listOf( @@ -62,13 +58,6 @@ class SwipeRefreshFeatureTest { refreshFeature = SwipeRefreshFeature(store, useCase, mockLayout) } - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - @Test fun `sets the onRefreshListener and onChildScrollUpCallback`() { verify(mockLayout).setOnRefreshListener(refreshFeature) @@ -103,7 +92,6 @@ class SwipeRefreshFeatureTest { val selectedTab = store.state.findCustomTabOrSelectedTab()!! store.dispatch(ContentAction.UpdateRefreshCanceledStateAction(selectedTab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() assertFalse(selectedTab.content.refreshCanceled) @@ -112,7 +100,6 @@ class SwipeRefreshFeatureTest { @Test fun `feature clears the swipeRefreshLayout#isRefreshing when tab fishes loading or a refreshCanceled`() { refreshFeature.start() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() val selectedTab = store.state.findCustomTabOrSelectedTab()!! @@ -121,7 +108,6 @@ class SwipeRefreshFeatureTest { reset(mockLayout) store.dispatch(ContentAction.UpdateRefreshCanceledStateAction(selectedTab.id, true)).joinBlocking() - testDispatcher.advanceUntilIdle() store.waitUntilIdle() verify(mockLayout, times(2)).isRefreshing = false @@ -130,7 +116,6 @@ class SwipeRefreshFeatureTest { // As if we dispatch with loading = false, none event will be trigger. store.dispatch(ContentAction.UpdateLoadingStateAction(selectedTab.id, true)).joinBlocking() store.dispatch(ContentAction.UpdateLoadingStateAction(selectedTab.id, false)).joinBlocking() - testDispatcher.advanceUntilIdle() verify(mockLayout, times(3)).isRefreshing = false } diff --git a/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt b/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt index 318f4b770b8..87eeff658d1 100644 --- a/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt +++ b/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt @@ -4,11 +4,9 @@ package mozilla.components.feature.session.middleware.undo -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.withContext import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.action.UndoAction import mozilla.components.browser.state.selector.selectedTab @@ -17,30 +15,20 @@ import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test class UndoMiddlewareTest { - private lateinit var testDispatcher: TestCoroutineDispatcher - - @Before - fun setUp() { - testDispatcher = TestCoroutineDispatcher() - Dispatchers.setMain(testDispatcher) - } - - @After - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher @Test - fun `Undo scenario - Removing single tab`() { + fun `Undo scenario - Removing single tab`() = runBlockingTest { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -65,22 +53,14 @@ class UndoMiddlewareTest { assertEquals(1, store.state.tabs.size) assertEquals("https://getpocket.com", store.state.selectedTab!!.content.url) - testDispatcher.withDispatchingPaused { - // We need to pause the test dispatcher here to avoid it dispatching immediately. - // Otherwise we deadlock the test here when we wait for the store to complete and - // at the same time the middleware dispatches a coroutine on the dispatcher which will - // also block on the store in SessionManager.restore(). - store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() - } - - store.waitUntilIdle() + restoreRecoverableTabs(dispatcher, store) assertEquals(2, store.state.tabs.size) assertEquals("https://www.mozilla.org", store.state.selectedTab!!.content.url) } @Test - fun `Undo scenario - Removing list of tabs`() { + fun `Undo scenario - Removing list of tabs`() = runBlockingTest { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -105,22 +85,14 @@ class UndoMiddlewareTest { assertEquals(1, store.state.tabs.size) assertEquals("https://firefox.com", store.state.selectedTab!!.content.url) - testDispatcher.withDispatchingPaused { - // We need to pause the test dispatcher here to avoid it dispatching immediately. - // Otherwise we deadlock the test here when we wait for the store to complete and - // at the same time the middleware dispatches a coroutine on the dispatcher which will - // also block on the store in SessionManager.restore(). - store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() - } - - store.waitUntilIdle() + restoreRecoverableTabs(dispatcher, store) assertEquals(3, store.state.tabs.size) assertEquals("https://www.mozilla.org", store.state.selectedTab!!.content.url) } @Test - fun `Undo scenario - Removing all normal tabs`() { + fun `Undo scenario - Removing all normal tabs`() = runBlockingTest { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -145,22 +117,14 @@ class UndoMiddlewareTest { assertEquals(1, store.state.tabs.size) assertNull(store.state.selectedTab) - testDispatcher.withDispatchingPaused { - // We need to pause the test dispatcher here to avoid it dispatching immediately. - // Otherwise we deadlock the test here when we wait for the store to complete and - // at the same time the middleware dispatches a coroutine on the dispatcher which will - // also block on the store in SessionManager.restore(). - store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() - } - - store.waitUntilIdle() + restoreRecoverableTabs(dispatcher, store) assertEquals(3, store.state.tabs.size) assertEquals("https://getpocket.com", store.state.selectedTab!!.content.url) } @Test - fun `Undo scenario - Removing all tabs`() { + fun `Undo scenario - Removing all tabs`() = runBlockingTest { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -185,22 +149,14 @@ class UndoMiddlewareTest { assertEquals(0, store.state.tabs.size) assertNull(store.state.selectedTab) - testDispatcher.withDispatchingPaused { - // We need to pause the test dispatcher here to avoid it dispatching immediately. - // Otherwise we deadlock the test here when we wait for the store to complete and - // at the same time the middleware dispatches a coroutine on the dispatcher which will - // also block on the store in SessionManager.restore(). - store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() - } - - store.waitUntilIdle() + restoreRecoverableTabs(dispatcher, store) assertEquals(3, store.state.tabs.size) assertEquals("https://getpocket.com", store.state.selectedTab!!.content.url) } @Test - fun `Undo scenario - Removing all tabs non-recoverable`() { + fun `Undo scenario - Removing all tabs non-recoverable`() = runBlockingTest { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -225,13 +181,7 @@ class UndoMiddlewareTest { assertEquals(0, store.state.tabs.size) assertNull(store.state.selectedTab) - testDispatcher.withDispatchingPaused { - // We need to pause the test dispatcher here to avoid it dispatching immediately. - // Otherwise we deadlock the test here when we wait for the store to complete and - // at the same time the middleware dispatches a coroutine on the dispatcher which will - // also block on the store in SessionManager.restore(). - store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() - } + restoreRecoverableTabs(dispatcher, store) store.waitUntilIdle() @@ -239,7 +189,7 @@ class UndoMiddlewareTest { } @Test - fun `Undo History in State is written`() { + fun `Undo History in State is written`() = runBlockingTest { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -277,15 +227,7 @@ class UndoMiddlewareTest { assertEquals("https://getpocket.com", store.state.undoHistory.tabs[1].state.url) assertEquals(0, store.state.tabs.size) - testDispatcher.withDispatchingPaused { - // We need to pause the test dispatcher here to avoid it dispatching immediately. - // Otherwise we deadlock the test here when we wait for the store to complete and - // at the same time the middleware dispatches a coroutine on the dispatcher which will - // also block on the store in SessionManager.restore(). - store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() - } - - store.waitUntilIdle() + restoreRecoverableTabs(dispatcher, store) assertNull(store.state.undoHistory.selectedTabId) assertTrue(store.state.undoHistory.tabs.isEmpty()) @@ -296,13 +238,11 @@ class UndoMiddlewareTest { } @Test - fun `Undo History gets cleared after time`() { - val waitDispatcher = TestCoroutineDispatcher() - val waitScope = CoroutineScope(waitDispatcher) + fun `Undo History gets cleared after time`() = runBlockingTest { val store = BrowserStore( middleware = listOf( - UndoMiddleware(clearAfterMillis = 60000, waitScope = waitScope) + UndoMiddleware(clearAfterMillis = 60000, waitScope = coroutinesTestRule.scope) ), initialState = BrowserState( tabs = listOf( @@ -327,8 +267,7 @@ class UndoMiddlewareTest { assertEquals("https://www.mozilla.org", store.state.undoHistory.tabs[0].state.url) assertEquals("https://getpocket.com", store.state.undoHistory.tabs[1].state.url) - waitDispatcher.advanceTimeBy(70000) - waitDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.waitUntilIdle() assertNull(store.state.undoHistory.selectedTabId) @@ -336,22 +275,21 @@ class UndoMiddlewareTest { assertEquals(1, store.state.tabs.size) assertEquals("https://reddit.com/r/firefox", store.state.tabs[0].content.url) - testDispatcher.withDispatchingPaused { - // We need to pause the test dispatcher here to avoid it dispatching immediately. - // Otherwise we deadlock the test here when we wait for the store to complete and - // at the same time the middleware dispatches a coroutine on the dispatcher which will - // also block on the store in SessionManager.restore(). - store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() - } + restoreRecoverableTabs(dispatcher, store) assertEquals(1, store.state.tabs.size) assertEquals("https://reddit.com/r/firefox", store.state.tabs[0].content.url) } } -private fun TestCoroutineDispatcher.withDispatchingPaused(block: () -> Unit) { - pauseDispatcher() - block() - resumeDispatcher() - advanceUntilIdle() +private suspend fun restoreRecoverableTabs(dispatcher: TestDispatcher, store: BrowserStore) { + withContext(dispatcher) { + // We need to pause the test dispatcher here to avoid it dispatching immediately. + // Otherwise we deadlock the test here when we wait for the store to complete and + // at the same time the middleware dispatches a coroutine on the dispatcher which will + // also block on the store in SessionManager.restore(). + store.dispatch(UndoAction.RestoreRecoverableTabs).joinBlocking() + } + dispatcher.scheduler.advanceUntilIdle() + store.waitUntilIdle() } diff --git a/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt b/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt index e5fe60b0aaf..1594b88bf0d 100644 --- a/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt +++ b/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt @@ -9,14 +9,8 @@ import android.content.pm.PackageManager.PERMISSION_GRANTED import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.ContentAction.UpdatePermissionHighlightsStateAction import mozilla.components.browser.state.action.ContentAction.UpdatePermissionHighlightsStateAction.AutoPlayAudibleBlockingAction @@ -63,13 +57,14 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers @@ -98,8 +93,9 @@ class SitePermissionsFeatureTest { private lateinit var mockSitePermissionRules: SitePermissionsRules private lateinit var selectedTab: TabSessionState - private val testCoroutineDispatcher = TestCoroutineDispatcher() - private val testScope = CoroutineScope(testCoroutineDispatcher) + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val scope = coroutinesTestRule.scope companion object { const val SESSION_ID = "testSessionId" @@ -109,7 +105,6 @@ class SitePermissionsFeatureTest { @Before fun setup() { - Dispatchers.setMain(testCoroutineDispatcher) mockOnNeedToRequestPermissions = mock() mockStorage = mock() mockFragmentManager = mockFragmentManager() @@ -137,13 +132,6 @@ class SitePermissionsFeatureTest { ) } - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testCoroutineDispatcher.cleanupTestCoroutines() - } - @Test fun `GIVEN a tab load THEN stale permission indicators should be clear up and temporary permissions`() { sitePermissionFeature.start() @@ -399,7 +387,7 @@ class SitePermissionsFeatureTest { mockContentState, mockPermissionRequest, ALLOWED, - testScope + scope ) // then @@ -429,7 +417,7 @@ class SitePermissionsFeatureTest { mockContentState, mockPermissionRequest, ALLOWED, - testScope + scope ) // then @@ -443,7 +431,7 @@ class SitePermissionsFeatureTest { selectedTab.content.copy(private = true), mockPermissionRequest, ALLOWED, - testScope + scope ) // when @@ -539,7 +527,7 @@ class SitePermissionsFeatureTest { sitePermissionFeature.onContentPermissionRequested( mockPermissionRequest, URL, - testScope + scope ) } @@ -566,7 +554,7 @@ class SitePermissionsFeatureTest { sitePermissionFeature.onContentPermissionRequested( mockPermissionRequest, URL, - testScope + scope ) } @@ -594,7 +582,7 @@ class SitePermissionsFeatureTest { sitePermissionFeature.onContentPermissionRequested( mockPermissionRequest, URL, - testScope + scope ) } diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt index d5d4e51a283..29876e53d0d 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt @@ -10,10 +10,7 @@ package mozilla.components.feature.syncedtabs.storage -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.LastAccessAction import mozilla.components.browser.state.action.TabListAction @@ -36,9 +33,11 @@ import mozilla.components.service.fxa.sync.SyncReason import mozilla.components.support.test.any import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.doReturn import org.mockito.Mockito.never @@ -48,6 +47,9 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify class SyncedTabsStorageTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private lateinit var store: BrowserStore private lateinit var tabsStorage: RemoteTabsStorage private lateinit var accountManager: FxaAccountManager @@ -68,8 +70,6 @@ class SyncedTabsStorageTest { ) tabsStorage = mock() accountManager = mock() - - Dispatchers.setMain(TestCoroutineDispatcher()) } @Test diff --git a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt index 9f26f507eeb..75227b3f6ed 100644 --- a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt +++ b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt @@ -165,11 +165,11 @@ class TabsUseCasesTest { // Wait for CreateEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() // Wait for LinkEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(1, store.state.tabs.size) assertEquals("https://www.mozilla.org", store.state.tabs[0].content.url) @@ -180,11 +180,13 @@ class TabsUseCasesTest { fun `AddNewTabUseCase forwards load flags to engine`() { tabsUseCases.addTab.invoke("https://www.mozilla.org", flags = LoadUrlFlags.external(), startLoading = true) + // Wait for CreateEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() + // Wait for LinkEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(1, store.state.tabs.size) assertEquals("https://www.mozilla.org", store.state.tabs[0].content.url) @@ -265,11 +267,11 @@ class TabsUseCasesTest { // Wait for CreateEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() // Wait for LinkEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(1, store.state.tabs.size) assertEquals("https://www.mozilla.org", store.state.tabs[0].content.url) @@ -281,11 +283,13 @@ class TabsUseCasesTest { fun `AddNewPrivateTabUseCase forwards load flags to engine`() { tabsUseCases.addPrivateTab.invoke("https://www.mozilla.org", flags = LoadUrlFlags.external(), startLoading = true) + // Wait for CreateEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() + // Wait for LinkEngineSessionAction and middleware store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(1, store.state.tabs.size) assertEquals("https://www.mozilla.org", store.state.tabs[0].content.url) diff --git a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/WindowFeatureTest.kt b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/WindowFeatureTest.kt index 19fe59b55a1..3fb33400b62 100644 --- a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/WindowFeatureTest.kt +++ b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/WindowFeatureTest.kt @@ -29,7 +29,6 @@ class WindowFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher private lateinit var store: BrowserStore private lateinit var engineSession: EngineSession @@ -70,7 +69,7 @@ class WindowFeatureTest { whenever(windowRequest.url).thenReturn("https://www.firefox.com") store.dispatch(ContentAction.UpdateWindowRequestAction(tabId, windowRequest)).joinBlocking() - testDispatcher.advanceUntilIdle() + verify(addTabUseCase).invoke(url = "about:blank", selectTab = true, parentId = tabId) verify(store).dispatch(ContentAction.ConsumeWindowRequestAction(tabId)) } @@ -86,7 +85,7 @@ class WindowFeatureTest { store.dispatch(TabListAction.SelectTabAction(privateTabId)).joinBlocking() store.dispatch(ContentAction.UpdateWindowRequestAction(privateTabId, windowRequest)).joinBlocking() - testDispatcher.advanceUntilIdle() + verify(addTabUseCase).invoke(url = "about:blank", selectTab = true, parentId = privateTabId, private = true) verify(store).dispatch(ContentAction.ConsumeWindowRequestAction(privateTabId)) } @@ -101,7 +100,7 @@ class WindowFeatureTest { whenever(windowRequest.prepare()).thenReturn(engineSession) store.dispatch(ContentAction.UpdateWindowRequestAction(tabId, windowRequest)).joinBlocking() - testDispatcher.advanceUntilIdle() + verify(removeTabUseCase).invoke(tabId) verify(store).dispatch(ContentAction.ConsumeWindowRequestAction(tabId)) } @@ -116,7 +115,7 @@ class WindowFeatureTest { whenever(windowRequest.type).thenReturn(WindowRequest.Type.CLOSE) store.dispatch(ContentAction.UpdateWindowRequestAction(tabId, windowRequest)).joinBlocking() - testDispatcher.advanceUntilIdle() + verify(removeTabUseCase, never()).invoke(tabId) verify(store, never()).dispatch(ContentAction.ConsumeWindowRequestAction(tabId)) } diff --git a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/tabstray/TabsTrayPresenterTest.kt b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/tabstray/TabsTrayPresenterTest.kt index 89854ce1a0d..1f4633a19f7 100644 --- a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/tabstray/TabsTrayPresenterTest.kt +++ b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/tabstray/TabsTrayPresenterTest.kt @@ -5,11 +5,6 @@ package mozilla.components.feature.tabs.tabstray import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.TabPartition @@ -18,13 +13,13 @@ import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.tabstray.TabsTray import mozilla.components.support.test.ext.joinBlocking -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.spy @@ -32,19 +27,9 @@ import org.mockito.Mockito.verifyNoMoreInteractions @RunWith(AndroidJUnit4::class) class TabsTrayPresenterTest { - private val testDispatcher = TestCoroutineDispatcher() - - @Before - fun setUp() { - Dispatchers.setMain(testDispatcher) - } - - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `initial set of sessions will be passed to tabs tray`() { @@ -71,7 +56,7 @@ class TabsTrayPresenterTest { presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertNotNull(tabsTray.updateTabs) @@ -106,7 +91,7 @@ class TabsTrayPresenterTest { presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(2, tabsTray.updateTabs!!.size) @@ -144,17 +129,17 @@ class TabsTrayPresenterTest { presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(2, tabsTray.updateTabs!!.size) store.dispatch(TabListAction.RemoveTabAction("a")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(1, tabsTray.updateTabs!!.size) store.dispatch(TabListAction.RemoveTabAction("b")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(0, tabsTray.updateTabs!!.size) @@ -184,12 +169,12 @@ class TabsTrayPresenterTest { presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(2, tabsTray.updateTabs!!.size) store.dispatch(TabListAction.RemoveAllTabsAction()).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(0, tabsTray.updateTabs!!.size) @@ -221,13 +206,13 @@ class TabsTrayPresenterTest { ) presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(5, tabsTray.updateTabs!!.size) assertEquals("a", tabsTray.selectedTabId) store.dispatch(TabListAction.SelectTabAction("d")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() println("Selection: " + store.state.selectedTabId) assertEquals("d", tabsTray.selectedTabId) @@ -255,7 +240,7 @@ class TabsTrayPresenterTest { ) presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertTrue(tabsTray.updateTabs?.size == 1) } @@ -287,12 +272,12 @@ class TabsTrayPresenterTest { ) presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() Assert.assertFalse(closed) store.dispatch(TabListAction.RemoveAllTabsAction()).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertTrue(closed) @@ -323,17 +308,17 @@ class TabsTrayPresenterTest { ) presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() Assert.assertFalse(closed) store.dispatch(TabListAction.RemoveTabAction("a")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() Assert.assertFalse(closed) store.dispatch(TabListAction.RemoveTabAction("b")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertTrue(closed) @@ -360,7 +345,7 @@ class TabsTrayPresenterTest { ) presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertFalse(invoked) } diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ContainerToolbarFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ContainerToolbarFeatureTest.kt index f73b0a55b44..7b798fc488e 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ContainerToolbarFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ContainerToolbarFeatureTest.kt @@ -33,7 +33,6 @@ class ContainerToolbarFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher @Test fun `render a container action from browser state`() { @@ -80,7 +79,7 @@ class ContainerToolbarFeatureTest { ) val containerToolbarFeature = getContainerToolbarFeature(toolbar, store) store.dispatch(TabListAction.SelectTabAction("tab2")).joinBlocking() - testDispatcher.advanceUntilIdle() + coroutinesTestRule.testDispatcher.scheduler.advanceUntilIdle() verify(store).observeManually(any()) verify(containerToolbarFeature, times(2)).renderContainerAction(any(), any()) diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarPresenterTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarPresenterTest.kt index fa521321a86..86dc2e02de5 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarPresenterTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarPresenterTest.kt @@ -4,11 +4,6 @@ package mozilla.components.feature.toolbar -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.ContentAction.UpdatePermissionHighlightsStateAction import mozilla.components.browser.state.action.ContentAction.UpdatePermissionHighlightsStateAction.NotificationChangedAction @@ -28,8 +23,8 @@ import mozilla.components.feature.toolbar.internal.URLRenderer import mozilla.components.support.test.any import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock -import org.junit.After -import org.junit.Before +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.never import org.mockito.Mockito.spy @@ -38,20 +33,9 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions class ToolbarPresenterTest { - private val testDispatcher = TestCoroutineDispatcher() - - @Before - @ExperimentalCoroutinesApi - fun setUp() { - Dispatchers.setMain(testDispatcher) - } - - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `start with no custom tab id registers on store and renders selected tab`() { @@ -71,7 +55,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(store).observeManually(any()) @@ -100,7 +84,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(store).observeManually(any()) verify(toolbarPresenter).render(any()) @@ -129,7 +113,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar, never()).siteSecure = Toolbar.SiteSecurity.SECURE @@ -144,7 +128,7 @@ class ToolbarPresenterTest { ) ).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).siteSecure = Toolbar.SiteSecurity.SECURE } @@ -176,7 +160,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbarPresenter.renderer).start() verify(toolbarPresenter.renderer).post("https://www.mozilla.org") @@ -190,7 +174,7 @@ class ToolbarPresenterTest { store.dispatch(TabListAction.RemoveTabAction("tab1")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbarPresenter.renderer).post("") verify(toolbar).setSearchTerms("") @@ -216,7 +200,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar, never()).setSearchTerms("Hello World") @@ -227,7 +211,7 @@ class ToolbarPresenterTest { ) ).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).setSearchTerms("Hello World") } @@ -250,7 +234,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar, never()).displayProgress(75) @@ -258,7 +242,7 @@ class ToolbarPresenterTest { ContentAction.UpdateProgressAction("tab1", 75) ).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).displayProgress(75) @@ -268,7 +252,7 @@ class ToolbarPresenterTest { ContentAction.UpdateProgressAction("tab1", 90) ).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).displayProgress(90) } @@ -301,7 +285,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() store.dispatch(TabListAction.RemoveTabAction("tab2")).joinBlocking() @@ -354,7 +338,7 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbarPresenter.renderer).start() verify(toolbarPresenter.renderer).post("https://www.mozilla.org") @@ -368,7 +352,7 @@ class ToolbarPresenterTest { store.dispatch(TabListAction.SelectTabAction("tab2")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbarPresenter.renderer).post("https://www.example.org") verify(toolbar).setSearchTerms("Example") @@ -407,28 +391,28 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).siteTrackingProtection = Toolbar.SiteTrackingProtection.OFF_GLOBALLY store.dispatch(TrackingProtectionAction.ToggleAction("tab", true)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).siteTrackingProtection = Toolbar.SiteTrackingProtection.ON_NO_TRACKERS_BLOCKED store.dispatch(TrackingProtectionAction.TrackerBlockedAction("tab", mock())) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).siteTrackingProtection = Toolbar.SiteTrackingProtection.ON_TRACKERS_BLOCKED store.dispatch(TrackingProtectionAction.ToggleExclusionListAction("tab", true)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).siteTrackingProtection = Toolbar.SiteTrackingProtection.OFF_FOR_A_SITE } @@ -460,26 +444,26 @@ class ToolbarPresenterTest { toolbarPresenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).highlight = Toolbar.Highlight.NONE store.dispatch(NotificationChangedAction("tab", true)).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).highlight = Toolbar.Highlight.PERMISSIONS_CHANGED store.dispatch(TrackingProtectionAction.ToggleExclusionListAction("tab", true)) .joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar, times(2)).highlight = Toolbar.Highlight.PERMISSIONS_CHANGED store.dispatch(UpdatePermissionHighlightsStateAction.Reset("tab")).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(toolbar).highlight = Toolbar.Highlight.NONE } @@ -510,7 +494,7 @@ class ToolbarPresenterTest { presenter.start() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(presenter.renderer).post("") verify(toolbar).setSearchTerms("") diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarFeatureTest.kt index 61fb0d2cad7..f463dd07d37 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarFeatureTest.kt @@ -41,7 +41,7 @@ class WebExtensionToolbarFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher + private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `render web extension actions from browser state`() { @@ -75,7 +75,7 @@ class WebExtensionToolbarFeatureTest { ) ) val webExtToolbarFeature = getWebExtensionToolbarFeature(toolbar, store) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(store).observeManually(any()) verify(webExtToolbarFeature).renderWebExtensionActions(any(), any()) @@ -125,7 +125,7 @@ class WebExtensionToolbarFeatureTest { ) ) val webExtToolbarFeature = getWebExtensionToolbarFeature(toolbar, store) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(store).observeManually(any()) verify(webExtToolbarFeature, times(1)).renderWebExtensionActions(any(), any()) @@ -403,7 +403,7 @@ class WebExtensionToolbarFeatureTest { ) ) val webExtToolbarFeature = getWebExtensionToolbarFeature(toolbar, store) - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(store).observeManually(any()) verify(webExtToolbarFeature).renderWebExtensionActions(any(), any()) diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarTest.kt index 93b4bab19ea..6ae3267418f 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/WebExtensionToolbarTest.kt @@ -61,7 +61,7 @@ class WebExtensionToolbarTest { val action = WebExtensionToolbarAction(browserAction, iconJobDispatcher = testDispatcher) {} action.bind(view) action.iconJob?.joinBlocking() - testDispatcher.advanceUntilIdle() + testDispatcher.scheduler.advanceUntilIdle() val iconCaptor = argumentCaptor() verify(imageView).setImageDrawable(iconCaptor.capture()) @@ -95,7 +95,7 @@ class WebExtensionToolbarTest { val action = WebExtensionToolbarAction(browserAction, iconJobDispatcher = testDispatcher) {} action.bind(view) action.iconJob?.joinBlocking() - testDispatcher.advanceUntilIdle() + testDispatcher.scheduler.advanceUntilIdle() verify(imageView).setImageResource(R.drawable.mozac_ic_web_extension_default_icon) } @@ -170,7 +170,7 @@ class WebExtensionToolbarTest { assertFalse(action.iconJob?.isCancelled!!) attachListenerCaptor.value.onViewDetachedFromWindow(parent) - testDispatcher.advanceUntilIdle() + testDispatcher.scheduler.advanceUntilIdle() assertTrue(action.iconJob?.isCancelled!!) } } diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt index 7bbf2587ccf..90f2b0294fc 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt @@ -9,23 +9,20 @@ import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.concept.toolbar.Toolbar import mozilla.components.feature.toolbar.ToolbarFeature import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify @@ -33,18 +30,8 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class URLRendererTest { - @Before - @ExperimentalCoroutinesApi - fun setUp() { - // Execute main thread coroutines on same thread as caller. - Dispatchers.setMain(Dispatchers.Unconfined) - } - - @After - @ExperimentalCoroutinesApi - fun tearDown() { - Dispatchers.resetMain() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() @Test fun `Lifecycle methods start and stop job`() { diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt index 86ab0c04244..d348e8547dd 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt @@ -9,7 +9,6 @@ import android.app.PendingIntent import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.lib.crash.service.CrashReporterService import mozilla.components.lib.crash.service.CrashTelemetryService @@ -43,7 +42,7 @@ class CrashReporterTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Before fun setUp() { diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt index 21756ce858d..e830c6ab1a2 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt @@ -7,10 +7,8 @@ package mozilla.components.lib.crash.handler import android.content.ComponentName import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.lib.crash.CrashReporter import mozilla.components.support.test.any import mozilla.components.support.test.mock @@ -36,7 +34,7 @@ class CrashHandlerServiceTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope: CoroutineScope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Before fun setUp() { @@ -83,7 +81,7 @@ class CrashHandlerServiceTest { doNothing().`when`(reporter)!!.sendCrashReport(any(), any()) intent.putExtra("processType", "MAIN") - service!!.handleCrashIntent(intent, scope) + service!!.handleCrashIntent(intent, coroutinesTestRule.scope) verify(reporter)!!.onCrash(any(), any()) verify(reporter)!!.sendCrashReport(any(), any()) verify(reporter, never())!!.sendNonFatalCrashIntent(any(), any()) @@ -94,7 +92,7 @@ class CrashHandlerServiceTest { doNothing().`when`(reporter)!!.sendCrashReport(any(), any()) intent.putExtra("processType", "FOREGROUND_CHILD") - service!!.handleCrashIntent(intent, scope) + service!!.handleCrashIntent(intent, coroutinesTestRule.scope) verify(reporter)!!.onCrash(any(), any()) verify(reporter)!!.sendNonFatalCrashIntent(any(), any()) verify(reporter, never())!!.sendCrashReport(any(), any()) @@ -105,7 +103,7 @@ class CrashHandlerServiceTest { doNothing().`when`(reporter)!!.sendCrashReport(any(), any()) intent.putExtra("processType", "BACKGROUND_CHILD") - service!!.handleCrashIntent(intent, scope) + service!!.handleCrashIntent(intent, coroutinesTestRule.scope) verify(reporter)!!.onCrash(any(), any()) verify(reporter)!!.sendCrashReport(any(), any()) verify(reporter, never())!!.sendNonFatalCrashIntent(any(), any()) diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/ExceptionHandlerTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/ExceptionHandlerTest.kt index 34a34418fab..d30f919d81a 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/ExceptionHandlerTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/ExceptionHandlerTest.kt @@ -6,7 +6,6 @@ package mozilla.components.lib.crash.handler import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.CrashReporter @@ -29,7 +28,7 @@ class ExceptionHandlerTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Test fun `ExceptionHandler forwards crashes to CrashReporter`() { diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt index 2d0f3899c8c..1aeedb698b2 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt @@ -15,7 +15,7 @@ import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario.launch import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runBlockingTest import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.CrashReporter @@ -35,6 +35,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.openMocks +import kotlin.coroutines.CoroutineContext @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) @@ -42,7 +43,7 @@ class CrashReporterActivityTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Mock lateinit var service: CrashReporterService @@ -62,7 +63,7 @@ class CrashReporterActivityTest { ).install(testContext) val crash = Crash.UncaughtExceptionCrash(0, RuntimeException("Hello World"), arrayListOf()) - val scenario = launchActivityWith(crash) + val scenario = coroutineContext.launchActivityWithCrash(crash) scenario.onActivity { activity -> // When @@ -86,7 +87,7 @@ class CrashReporterActivityTest { ).install(testContext) val crash = Crash.UncaughtExceptionCrash(0, RuntimeException("Hello World"), arrayListOf()) - val scenario = launchActivityWith(crash) + val scenario = coroutineContext.launchActivityWithCrash(crash) scenario.onActivity { activity -> // When @@ -113,7 +114,7 @@ class CrashReporterActivityTest { ).install(testContext) val crash = Crash.UncaughtExceptionCrash(0, RuntimeException("Hello World"), arrayListOf()) - val scenario = launchActivityWith(crash) + val scenario = coroutineContext.launchActivityWithCrash(crash) scenario.onActivity { activity -> // Then @@ -131,7 +132,7 @@ class CrashReporterActivityTest { ).install(testContext) val crash = Crash.UncaughtExceptionCrash(0, RuntimeException("Hello World"), arrayListOf()) - val scenario = launchActivityWith(crash) + val scenario = coroutineContext.launchActivityWithCrash(crash) scenario.onActivity { activity -> // When @@ -165,7 +166,7 @@ class CrashReporterActivityTest { Crash.NativeCodeCrash.PROCESS_TYPE_MAIN, arrayListOf() ) - val scenario = launchActivityWith(crash) + val scenario = coroutineContext.launchActivityWithCrash(crash) scenario.onActivity { activity -> assertEquals(activity.restartButton.visibility, View.VISIBLE) @@ -189,7 +190,7 @@ class CrashReporterActivityTest { Crash.NativeCodeCrash.PROCESS_TYPE_BACKGROUND_CHILD, arrayListOf() ) - val scenario = launchActivityWith(crash) + val scenario = coroutineContext.launchActivityWithCrash(crash) scenario.onActivity { activity -> assertEquals(activity.restartButton.visibility, View.GONE) @@ -201,7 +202,7 @@ class CrashReporterActivityTest { * Launch activity scenario for certain [crash]. */ @ExperimentalCoroutinesApi -private fun TestCoroutineScope.launchActivityWith( +private fun CoroutineContext.launchActivityWithCrash( crash: Crash ): ActivityScenario = run { val intent = Intent(testContext, CrashReporterActivity::class.java) @@ -209,7 +210,7 @@ private fun TestCoroutineScope.launchActivityWith( launch(intent).apply { onActivity { activity -> - activity.reporterCoroutineContext = coroutineContext + activity.reporterCoroutineContext = this@run } } } diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashReportServiceTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashReportServiceTest.kt index 588333fe9bd..81b1317337b 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashReportServiceTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashReportServiceTest.kt @@ -8,7 +8,6 @@ import android.content.ComponentName import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.CrashReporter @@ -37,7 +36,7 @@ class SendCrashReportServiceTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Before fun setUp() { diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashTelemetryServiceTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashTelemetryServiceTest.kt index ff6f68f3228..7e1a6ce15de 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashTelemetryServiceTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/service/SendCrashTelemetryServiceTest.kt @@ -8,7 +8,6 @@ import android.content.ComponentName import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.CrashReporter import mozilla.components.support.test.any @@ -35,7 +34,7 @@ class SendCrashTelemetryServiceTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val scope = TestCoroutineScope(coroutinesTestRule.testDispatcher) + private val scope = coroutinesTestRule.scope @Before fun setUp() { diff --git a/components/lib/state/src/test/java/mozilla/components/lib/state/ext/FragmentKtTest.kt b/components/lib/state/src/test/java/mozilla/components/lib/state/ext/FragmentKtTest.kt index 37907b95b82..1c677bf33c7 100644 --- a/components/lib/state/src/test/java/mozilla/components/lib/state/ext/FragmentKtTest.kt +++ b/components/lib/state/src/test/java/mozilla/components/lib/state/ext/FragmentKtTest.kt @@ -13,7 +13,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.test.setMain import mozilla.components.lib.state.Store import mozilla.components.lib.state.TestAction diff --git a/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt b/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt index 0da2ef1e251..d6994c1cb0f 100644 --- a/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt +++ b/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt @@ -19,10 +19,8 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.consumeEach -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.lib.state.Store import mozilla.components.lib.state.TestAction import mozilla.components.lib.state.TestState @@ -48,7 +46,6 @@ class StoreExtensionsKtTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher @Test fun `Observer will not get registered if lifecycle is already destroyed`() { @@ -223,7 +220,7 @@ class StoreExtensionsKtTest { val flow = store.flow(owner) - val job = TestCoroutineScope(testDispatcher).launch { + val job = coroutinesTestRule.scope.launch { flow.collect { state -> receivedValue = state.counter latch.countDown() diff --git a/components/lib/state/src/test/java/mozilla/components/lib/state/helpers/AbstractBindingTest.kt b/components/lib/state/src/test/java/mozilla/components/lib/state/helpers/AbstractBindingTest.kt index 38403a80d59..3bacd920d51 100644 --- a/components/lib/state/src/test/java/mozilla/components/lib/state/helpers/AbstractBindingTest.kt +++ b/components/lib/state/src/test/java/mozilla/components/lib/state/helpers/AbstractBindingTest.kt @@ -6,7 +6,6 @@ package mozilla.components.lib.state.helpers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import mozilla.components.lib.state.Store import mozilla.components.lib.state.TestAction import mozilla.components.lib.state.TestState diff --git a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt index 4ebba7c9185..3b812988173 100644 --- a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt +++ b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt @@ -8,7 +8,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import mozilla.components.concept.storage.CreditCard import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.CreditCardNumber @@ -33,7 +34,7 @@ class GeckoCreditCardsAddressesStorageDelegateTest { private lateinit var storage: AutofillCreditCardsAddressesStorage private lateinit var securePrefs: SecureAbove22Preferences private lateinit var delegate: GeckoCreditCardsAddressesStorageDelegate - private lateinit var scope: TestCoroutineScope + private lateinit var scope: TestScope init { testContext.getDatabasePath(AUTOFILL_DB_NAME)!!.parentFile!!.mkdirs() @@ -41,7 +42,7 @@ class GeckoCreditCardsAddressesStorageDelegateTest { @Before fun before() = runBlocking { - scope = TestCoroutineScope() + scope = TestScope(UnconfinedTestDispatcher()) // forceInsecure is set in the tests because a keystore wouldn't be configured in the test environment. securePrefs = SecureAbove22Preferences(testContext, "autofill", forceInsecure = true) storage = AutofillCreditCardsAddressesStorage(testContext, lazy { securePrefs }) diff --git a/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt b/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt index 9b81d0f6cfe..5394368cdbe 100644 --- a/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt +++ b/components/support/ktx/src/test/java/mozilla/components/support/ktx/android/view/ViewTest.kt @@ -15,7 +15,6 @@ import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import mozilla.components.support.base.android.Padding @@ -45,7 +44,6 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class ViewTest { - @ExperimentalCoroutinesApi @get:Rule val coroutinesTestRule = MainCoroutineRule() diff --git a/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt b/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt index cbef194d106..f2c1c361d32 100644 --- a/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt +++ b/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt @@ -5,25 +5,20 @@ package mozilla.components.support.locale import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.LocaleAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.robolectric.testContext -import org.junit.After +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before import org.junit.Ignore +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.spy @@ -34,27 +29,15 @@ import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class LocaleMiddlewareTest { - private lateinit var dispatcher: TestCoroutineDispatcher - private lateinit var scope: CoroutineScope + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher @Before fun setUp() { - dispatcher = TestCoroutineDispatcher() - scope = CoroutineScope(dispatcher) - - Dispatchers.setMain(dispatcher) - LocaleManager.clear(testContext) } - @After - fun tearDown() { - dispatcher.cleanupTestCoroutines() - scope.cancel() - - Dispatchers.resetMain() - } - @Test @Ignore("Failing intermittently. To be fixed for https://github.com/mozilla-mobile/android-components/issues/9954") @Config(qualifiers = "en-rUS") @@ -80,7 +63,7 @@ class LocaleMiddlewareTest { store.dispatch(LocaleAction.RestoreLocaleStateAction).joinBlocking() store.waitUntilIdle() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() assertEquals(store.state.locale, currentLocale) } @@ -109,7 +92,7 @@ class LocaleMiddlewareTest { val newLocale = "es".toLocale() store.dispatch(LocaleAction.UpdateLocaleAction(newLocale)).joinBlocking() - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(localeManager).setNewLocale(testContext, locale = newLocale) } diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt index b4d36df593c..0f72302bf40 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt @@ -5,12 +5,7 @@ package mozilla.components.support.migration import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.webextension.DisabledFlags import mozilla.components.concept.engine.webextension.Metadata @@ -22,37 +17,23 @@ import mozilla.components.support.test.any import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.eq import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever -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.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito.verify -import java.lang.IllegalArgumentException @RunWith(AndroidJUnit4::class) class AddonMigrationTest { - @ExperimentalCoroutinesApi - private val testDispatcher = TestCoroutineDispatcher() - - @ExperimentalCoroutinesApi - @Before - fun setUp() { - Dispatchers.setMain(testDispatcher) - } - - @ExperimentalCoroutinesApi - @After - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() @Test fun `No addons installed`() = runBlocking { diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt index 75bbb1a38c0..90bf3bf8382 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt @@ -6,12 +6,8 @@ package mozilla.components.support.migration import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.uniffi.PlacesException import mozilla.components.browser.state.action.BrowserAction @@ -38,14 +34,15 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.middleware.CaptureActionsMiddleware import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever import org.json.JSONObject -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean @@ -60,22 +57,14 @@ import java.io.File @RunWith(AndroidJUnit4::class) class FennecMigratorTest { private lateinit var securePrefs: SecureAbove22Preferences - @ExperimentalCoroutinesApi - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() @Before @ExperimentalCoroutinesApi fun setup() { // forceInsecure is set in the tests because a keystore wouldn't be configured in the test environment. securePrefs = SecureAbove22Preferences(testContext, "test_prefs", forceInsecure = true) - Dispatchers.setMain(testDispatcher) - } - - @ExperimentalCoroutinesApi - @After - fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() } @Test diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/MigrationObserverTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/MigrationObserverTest.kt index 58ff37a692f..712a1810160 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/MigrationObserverTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/MigrationObserverTest.kt @@ -27,7 +27,7 @@ class MigrationObserverTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher + private val dispatcher = coroutinesTestRule.testDispatcher @Test fun `listener is invoked on state observation`() { @@ -42,7 +42,7 @@ class MigrationObserverTest { verify(listener).onMigrationCompleted(any()) store.dispatch(MigrationAction.Started).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(listener).onMigrationStateChanged(eq(MigrationProgress.MIGRATING), any()) } @@ -59,7 +59,7 @@ class MigrationObserverTest { observer.stop() store.dispatch(MigrationAction.Started).joinBlocking() - testDispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() verify(listener, never()).onMigrationStateChanged(any(), any()) } diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt index 520a8a6748e..6e6c3eaa430 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt @@ -8,7 +8,7 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import mozilla.components.browser.state.action.SearchAction import mozilla.components.browser.state.search.RegionState import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine @@ -208,7 +208,7 @@ private fun migrate( } private fun storeFor(language: String, country: String, region: String): BrowserStore { - val dispatcher = TestCoroutineDispatcher() + val dispatcher = UnconfinedTestDispatcher() Locale.setDefault(Locale(language, country)) @@ -228,7 +228,7 @@ private fun storeFor(language: String, country: String, region: String): Browser store.waitUntilIdle() // Now we wait for the Middleware that may need to asynchronously process an action the test dispatched - dispatcher.advanceUntilIdle() + dispatcher.scheduler.advanceUntilIdle() // Since the Middleware may have dispatched an action, we now wait for the store again. store.waitUntilIdle() diff --git a/components/support/test/src/main/java/mozilla/components/support/test/rule/MainCoroutineRule.kt b/components/support/test/src/main/java/mozilla/components/support/test/rule/MainCoroutineRule.kt index 72d91fc8c33..27fc6d0b67d 100644 --- a/components/support/test/src/main/java/mozilla/components/support/test/rule/MainCoroutineRule.kt +++ b/components/support/test/src/main/java/mozilla/components/support/test/rule/MainCoroutineRule.kt @@ -5,13 +5,14 @@ package mozilla.components.support.test.rule import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.test.setMain import mozilla.components.support.base.utils.NamedThreadFactory import org.junit.rules.TestWatcher @@ -21,45 +22,35 @@ import java.util.concurrent.Executors /** * Create single threaded dispatcher for test environment. */ -@Deprecated("Use `TestCoroutineDispatcher()` from the kotlinx-coroutines-test library", ReplaceWith("TestCoroutineDispatcher()")) +@Deprecated("Use a `TestDispatcher` from the kotlinx-coroutines-test library", ReplaceWith("UnconfinedTestDispatcher()")) fun createTestCoroutinesDispatcher(): CoroutineDispatcher = Executors.newSingleThreadExecutor( NamedThreadFactory("TestCoroutinesDispatcher") ).asCoroutineDispatcher() /** * JUnit rule to change Dispatchers.Main in coroutines. + * This assumes no other calls to `Dispatchers.setMain` are made to override the main dispatcher. + * + * @param testDispatcher [TestDispatcher] for handling all coroutines execution. + * Defaults to [UnconfinedTestDispatcher] which will eagerly enter `launch` or `async` blocks. */ @OptIn(ExperimentalCoroutinesApi::class) -class MainCoroutineRule(val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() { - override fun starting(description: Description?) { +class MainCoroutineRule(val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() { + /** + * Get a [TestScope] that integrates with `runTest` and can be passed as an argument + * to the code under test when a [CoroutineScope] is required. + * + * This will rely on [testDispatcher] for controlling entering `launch` or `async` blocks. + */ + val scope by lazy { TestScope(testDispatcher) } + + override fun starting(description: Description) { super.starting(description) Dispatchers.setMain(testDispatcher) } - override fun finished(description: Description?) { + override fun finished(description: Description) { super.finished(description) Dispatchers.resetMain() - - testDispatcher.cleanupTestCoroutines() - } - - /** - * Convenience function to access [testDispatcher]'s [TestCoroutineDispatcher.runBlockingTest], - * e.g. instead of: - * ``` - * fun testCode() = mainCoroutineRule.testDispatcher.runBlockingTest { ... } - * ``` - * - * you can run: - * ``` - * fun testCode() = mainCoroutineRule.runBlockingTest { ... } - * ``` - * - * Note: using [TestCoroutineDispatcher.runBlockingTest] is preferred over using the global - * [runBlockingTest] because new coroutines created inside it will automatically be reparented - * to the test coroutine context. - */ - fun runBlockingTest(testBlock: suspend TestCoroutineScope.() -> Unit) { - testDispatcher.runBlockingTest(testBlock) } } diff --git a/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt b/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt index 9c50aa5df07..368b7a89d8b 100644 --- a/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt +++ b/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt @@ -5,11 +5,12 @@ package mozilla.components.support.utils import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope import kotlinx.coroutines.test.runBlockingTest import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.inOrder import org.mockito.Mockito.never @@ -18,10 +19,12 @@ import org.mockito.Mockito.verify @ExperimentalCoroutinesApi class RunWhenReadyQueueTest { - private val scope = TestCoroutineScope() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val scope = coroutinesTestRule.scope @Test - fun `task should not run until ready is called`() = scope.runBlockingTest { + fun `task should not run until ready is called`() = runBlockingTest { val task = mock<() -> Unit>() val queue = RunWhenReadyQueue(scope) @@ -36,7 +39,7 @@ class RunWhenReadyQueueTest { } @Test - fun `task should run if ready was called`() = scope.runBlockingTest { + fun `task should run if ready was called`() = runBlockingTest { val task = mock<() -> Unit>() val queue = RunWhenReadyQueue(scope) queue.ready() @@ -49,7 +52,7 @@ class RunWhenReadyQueueTest { } @Test - fun `tasks should run in the order they were queued`() = scope.runBlockingTest { + fun `tasks should run in the order they were queued`() = runBlockingTest { val task1 = mock<() -> Unit>() val task2 = mock<() -> Unit>() val task3 = mock<() -> Unit>() diff --git a/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt b/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt index 1dd1440330d..fc5d62333cc 100644 --- a/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt +++ b/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt @@ -5,10 +5,6 @@ package mozilla.components.support.webextensions import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.EngineAction @@ -37,6 +33,7 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.whenever import mozilla.components.support.webextensions.WebExtensionSupport.toState import mozilla.components.support.webextensions.facts.WebExtensionFacts.Items.WEB_EXTENSIONS_INITIALIZED @@ -47,7 +44,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.never @@ -60,17 +57,11 @@ import mozilla.components.support.base.facts.Action as FactsAction @RunWith(AndroidJUnit4::class) class WebExtensionSupportTest { - private val testDispatcher = TestCoroutineDispatcher() - - @Before - fun setup() { - Dispatchers.setMain(testDispatcher) - } + @get:Rule + val coroutinesTestRule = MainCoroutineRule() @After fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() WebExtensionSupport.installedExtensions.clear() } diff --git a/docs/changelog.md b/docs/changelog.md index f9d708906ea..44c5702329d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,10 @@ permalink: /changelog/ * [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt) * [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml) +* **support-test** + * ⚠️ **This is a breaking change**: `MainCoroutineRule` constructor now takes a `TestDispatcher` instead of deprecated `TestCoroutineDispatcher`. Default is `UnconfinedTestDispatcher`. + * ⚠️ **This is a breaking change**: `MainCoroutineRule.runBlockingTest` is not available anymore since `runBlockingTest` is deprecated. + * **concept-engine**: * Added support for `SelectAddress` prompt request. See [issue #12060](https://github.com/mozilla-mobile/android-components/issues/12060) From fc8873568d758244b3ea7054dcbd3e7653ed4332 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Mon, 18 Apr 2022 20:43:16 +0300 Subject: [PATCH 062/160] For #11175 - Add a new `runTestOnMain` top level function This attempts to run the test in a new coroutine using the `TestDispatcher` previously set through `Dispatchers.setMain(..)`. --- .../components/support/test/rule/Helpers.kt | 57 +++++++++++++++++++ docs/changelog.md | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 components/support/test/src/main/java/mozilla/components/support/test/rule/Helpers.kt diff --git a/components/support/test/src/main/java/mozilla/components/support/test/rule/Helpers.kt b/components/support/test/src/main/java/mozilla/components/support/test/rule/Helpers.kt new file mode 100644 index 00000000000..cd942275f1f --- /dev/null +++ b/components/support/test/src/main/java/mozilla/components/support/test/rule/Helpers.kt @@ -0,0 +1,57 @@ +/* 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/. */ + +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // for TestMainDispatcher + +package mozilla.components.support.test.rule + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.internal.TestMainDispatcher +import kotlinx.coroutines.test.runTest +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.companionObjectInstance +import kotlin.reflect.full.memberProperties + +/** + * `coroutines.test` default timeout to use when waiting for asynchronous completions of the coroutines + * managed by a [TestCoroutineScheduler]. + */ +private const val DEFAULT_DISPATCH_TIMEOUT_MS = 60_000L + +/** + * Convenience method of executing [testBody] in a new coroutine running with the + * [TestDispatcher] previously set through [Dispatchers.setMain]. + * + * Running a test with a shared [TestDispatcher] allows + * - newly created coroutines inside it will automatically be reparented to the test coroutine context. + * - leveraging an already set strategy for entering launch / async blocks. + * - easier scheduling control. + * + * @see runTest + * @see Dispatchers.setMain + * @see TestDispatcher + */ +@OptIn(ExperimentalCoroutinesApi::class) +fun runTestOnMain( + dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, + testBody: suspend TestScope.() -> Unit +): TestResult { + val mainDispatcher = Dispatchers.Main + require(mainDispatcher is TestMainDispatcher) { + "A TestDispatcher is not available. Use MainCoroutineRule or Dispatchers.setMain to set one before calling this method" + } + + // Get the TestDispatcher set through `Dispatchers.setMain(..)`. + val companionObject = mainDispatcher::class.companionObject + val companionInstance = mainDispatcher::class.companionObjectInstance + val testDispatcher = companionObject!!.memberProperties.first().getter.call(companionInstance) as TestDispatcher + + // Delegate to the original implementation of `runTest`. Just with a previously set TestDispatcher. + runTest(testDispatcher, dispatchTimeoutMs, testBody) +} diff --git a/docs/changelog.md b/docs/changelog.md index 44c5702329d..bdd9517b9b5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -13,7 +13,7 @@ permalink: /changelog/ * **support-test** * ⚠️ **This is a breaking change**: `MainCoroutineRule` constructor now takes a `TestDispatcher` instead of deprecated `TestCoroutineDispatcher`. Default is `UnconfinedTestDispatcher`. - * ⚠️ **This is a breaking change**: `MainCoroutineRule.runBlockingTest` is not available anymore since `runBlockingTest` is deprecated. + * ⚠️ **This is a breaking change**: `MainCoroutineRule.runBlockingTest` is replaced with a `runTestOnMain` top level function. . This method is preferred over the global `runTest` because it reparents new child coroutines to the test coroutine context. * **concept-engine**: * Added support for `SelectAddress` prompt request. See [issue #12060](https://github.com/mozilla-mobile/android-components/issues/12060) From f64a19c2dc9275cce597198572955a185934b413 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Tue, 19 Apr 2022 21:53:10 +0300 Subject: [PATCH 063/160] For #11175 - Migrate runBlocking and runBlockingTest to runTest in unit tests Used `runTestOnMain` where `MainCoroutineRule` was used or needed to be used, `runTest` elsewhere. Extra effort for removing all `runBlocking` occurrences in unit tests. `kotlinx.coroutines.test.runTest` is a test specific API that seems like a more appropriate way for running tests in a coroutine than the general `kotlinx.coroutines.runBlocking` api. --- components/browser/domains/build.gradle | 1 + .../browser/domains/CustomDomainsTest.kt | 7 +- .../engine/gecko/GeckoEngineSessionTest.kt | 33 ++-- .../browser/engine/gecko/GeckoResultTest.kt | 12 +- .../GeckoSitePermissionsStorageTest.kt | 60 +++---- components/browser/engine-system/build.gradle | 1 + .../engine/system/SystemEngineSessionTest.kt | 8 +- .../engine/system/SystemEngineViewTest.kt | 16 +- .../browser/icons/BrowserIconsTest.kt | 19 ++- .../icons/extension/IconMessageHandlerTest.kt | 6 +- .../generator/DefaultIconGeneratorTest.kt | 3 +- .../loader/NonBlockingHttpIconLoaderTest.kt | 18 +-- .../item/WebExtensionBrowserMenuItemTest.kt | 4 +- .../icons/DrawableMenuIconViewHoldersTest.kt | 6 +- .../browser/session/storage/AutoSaveTest.kt | 16 +- .../FileEngineSessionStateStorageTest.kt | 10 +- .../state/engine/EngineObserverTest.kt | 18 +-- .../CreateEngineSessionMiddlewareTest.kt | 14 +- .../middleware/LinkingMiddlewareTest.kt | 8 +- .../middleware/SuspendMiddlewareTest.kt | 4 +- .../middleware/TabsRemovedMiddlewareTest.kt | 16 +- .../browser/state/store/BrowserStoreTest.kt | 4 +- components/browser/storage-sync/build.gradle | 1 + .../sync/PlacesBookmarksStorageTest.kt | 27 ++-- .../storage/sync/PlacesHistoryStorageTest.kt | 150 ++++++++---------- .../storage/sync/RemoteTabsStorageTest.kt | 8 +- .../storage/ThumbnailStorageTest.kt | 17 +- components/browser/toolbar/build.gradle | 1 + .../toolbar/AsyncFilterListenerTest.kt | 18 ++- .../browser/toolbar/edit/EditToolbarTest.kt | 10 +- components/concept/fetch/build.gradle | 1 + .../components/concept/fetch/ClientTest.kt | 6 +- .../accounts/push/AutoPushObserverTest.kt | 15 +- .../push/ConstellationObserverTest.kt | 10 +- .../accounts/push/SendTabFeatureKtTest.kt | 4 +- .../accounts/push/SendTabUseCasesTest.kt | 120 +++++++------- components/feature/accounts/build.gradle | 1 + .../FirefoxAccountsAuthFeatureTest.kt | 12 +- .../accounts/FxaWebChannelFeatureTest.kt | 6 +- .../addons/src/test/java/AddonManagerTest.kt | 34 ++-- .../addons/amo/AddonCollectionProviderTest.kt | 34 ++-- .../WebExtensionActionMenuCandidateTest.kt | 4 +- .../DefaultSupportedAddonCheckerTest.kt | 42 +++-- .../migration/SupportedAddonsWorkerTest.kt | 44 +++-- .../ui/AddonInstallationDialogFragmentTest.kt | 36 ++--- .../addons/ui/AddonsManagerAdapterTest.kt | 66 ++++---- .../addons/update/AddonUpdaterWorkerTest.kt | 60 +++---- .../addons/update/DefaultAddonUpdaterTest.kt | 102 +++++------- .../handler/FillRequestHandlerTest.kt | 7 +- .../feature/autofill/test/MockStructure.kt | 2 + components/feature/awesomebar/build.gradle | 1 + .../BookmarksStorageSuggestionProviderTest.kt | 16 +- .../ClipboardSuggestionProviderTest.kt | 51 +++--- .../CombinedHistorySuggestionProviderTest.kt | 18 ++- .../HistoryMetadataSuggestionProviderTest.kt | 22 +-- .../HistoryStorageSuggestionProviderTest.kt | 22 +-- .../provider/SearchActionProviderTest.kt | 16 +- .../SearchEngineSuggestionProviderTest.kt | 18 ++- .../provider/SearchSuggestionProviderTest.kt | 36 +++-- .../provider/SessionSuggestionProviderTest.kt | 20 +-- .../containers/ContainerMiddlewareTest.kt | 23 ++- .../feature/OriginVerifierFeatureTest.kt | 8 +- .../customtabs/verify/OriginVerifierTest.kt | 6 +- .../AbstractFetchDownloadServiceTest.kt | 108 +++++++------ .../downloads/DownloadMiddlewareTest.kt | 40 ++--- .../share/ShareDownloadFeatureTest.kt | 20 +-- .../feature/media/ext/SessionStateKtTest.kt | 30 ++-- .../notification/MediaNotificationTest.kt | 44 ++--- components/feature/privatemode/build.gradle | 1 + .../PrivateNotificationFeatureTest.kt | 16 +- .../creditcard/CreditCardSelectBarTest.kt | 4 +- .../feature/push/AutoPushFeatureKtTest.kt | 23 +-- .../feature/push/AutoPushFeatureTest.kt | 49 +++--- .../feature/push/RustPushConnectionTest.kt | 129 ++++++--------- .../feature/push/ext/CoroutineScopeKtTest.kt | 8 +- .../feature/pwa/ManifestStorageTest.kt | 32 ++-- .../feature/pwa/WebAppShortcutManagerTest.kt | 30 ++-- .../feature/pwa/WebAppUseCasesTest.kt | 4 +- .../pwa/feature/ManifestUpdateFeatureTest.kt | 39 ++--- .../pwa/intent/WebAppIntentProcessorTest.kt | 10 +- .../RecentlyClosedMiddlewareTest.kt | 22 +-- .../RecentlyClosedTabDaoTest.kt | 22 ++- .../RecentlyClosedTabsStorageTest.kt | 89 +++++------ .../search/middleware/SearchMiddlewareTest.kt | 16 +- .../search/region/RegionManagerTest.kt | 26 +-- .../search/region/RegionMiddlewareTest.kt | 8 +- .../BundledSearchEnginesStorageTest.kt | 20 +-- .../storage/CustomSearchEngineStorageTest.kt | 10 +- .../suggestions/SearchSuggestionClientTest.kt | 54 +++---- .../feature/session/HistoryDelegateTest.kt | 12 +- .../feature/session/SessionUseCasesTest.kt | 4 +- .../middleware/undo/UndoMiddlewareTest.kt | 16 +- .../OnDiskSitePermissionsStorageTest.kt | 16 +- .../SitePermissionsFeatureTest.kt | 43 +++-- ...SyncedTabsStorageSuggestionProviderTest.kt | 4 +- .../controller/DefaultControllerTest.kt | 12 +- .../interactor/DefaultInteractorTest.kt | 12 +- .../presenter/DefaultPresenterTest.kt | 24 +-- .../storage/SyncedTabsStorageTest.kt | 18 +-- .../feature/tabs/TabsUseCasesTest.kt | 4 +- .../toolbar/ToolbarAutocompleteFeatureTest.kt | 29 ++-- .../toolbar/internal/URLRendererTest.kt | 4 +- .../top/sites/DefaultTopSitesStorageTest.kt | 31 ++-- .../top/sites/PinnedSitesStorageTest.kt | 14 +- .../feature/top/sites/TopSitesUseCasesTest.kt | 10 +- .../NativeNotificationBridgeTest.kt | 24 +-- .../WebNotificationFeatureTest.kt | 14 +- .../crash/handler/CrashHandlerServiceTest.kt | 8 +- .../crash/prompt/CrashReporterActivityTest.kt | 14 +- components/lib/publicsuffixlist/build.gradle | 1 + .../publicsuffixlist/PublicSuffixListTest.kt | 18 ++- .../lib/state/ext/StoreExtensionsKtTest.kt | 18 +-- components/service/contile/build.gradle | 1 + .../contile/ContileTopSitesProviderTest.kt | 16 +- .../contile/ContileTopSitesUpdaterTest.kt | 8 +- .../ContileTopSitesUpdaterWorkerTest.kt | 8 +- .../contile/ContileTopSitesUseCasesTest.kt | 8 +- .../service/fxa/FxaAccountManagerTest.kt | 66 ++++---- .../service/fxa/FxaDeviceConstellationTest.kt | 20 +-- .../components/service/fxa/UtilsKtTest.kt | 44 ++--- .../fxa/manager/GlobalAccountManagerTest.kt | 6 +- components/service/location/build.gradle | 1 + .../service/location/LocationServiceTest.kt | 7 +- .../location/MozillaLocationServiceTest.kt | 48 +++--- .../pocket/PocketStoriesServiceTest.kt | 6 +- .../PocketRecommendationsRepositoryTest.kt | 10 +- .../stories/PocketStoriesUseCasesTest.kt | 28 ++-- .../db/PocketRecommendationsDaoTest.kt | 45 +++--- ...AutofillCreditCardsAddressesStorageTest.kt | 35 ++-- .../sync/autofill/AutofillCryptoTest.kt | 17 +- ...CreditCardsAddressesStorageDelegateTest.kt | 16 +- .../ktx/kotlinx/coroutines/UtilsKtTest.kt | 5 +- .../ktx/kotlinx/coroutines/flow/FlowKtTest.kt | 39 ++--- .../support/locale/LocaleMiddlewareTest.kt | 6 +- .../support/migration/AddonMigrationTest.kt | 18 +-- .../migration/FennecFxaMigrationTest.kt | 40 ++--- .../support/migration/FennecMigratorTest.kt | 70 ++++---- .../migration/SearchEngineMigrationTest.kt | 56 ++++--- .../support/utils/RunWhenReadyQueueTest.kt | 8 +- 139 files changed, 1592 insertions(+), 1623 deletions(-) diff --git a/components/browser/domains/build.gradle b/components/browser/domains/build.gradle index 438782bca9f..65bb01bf105 100644 --- a/components/browser/domains/build.gradle +++ b/components/browser/domains/build.gradle @@ -31,6 +31,7 @@ dependencies { testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_robolectric + testImplementation Dependencies.testing_coroutines } apply from: '../../../publish.gradle' diff --git a/components/browser/domains/src/test/java/mozilla/components/browser/domains/CustomDomainsTest.kt b/components/browser/domains/src/test/java/mozilla/components/browser/domains/CustomDomainsTest.kt index 755ee9a3a1a..3f3ec083636 100644 --- a/components/browser/domains/src/test/java/mozilla/components/browser/domains/CustomDomainsTest.kt +++ b/components/browser/domains/src/test/java/mozilla/components/browser/domains/CustomDomainsTest.kt @@ -6,7 +6,7 @@ package mozilla.components.browser.domains import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Before @@ -24,11 +24,10 @@ class CustomDomainsTest { .apply() } + @ExperimentalCoroutinesApi @Test fun customListIsEmptyByDefault() { - val domains = runBlocking { - CustomDomains.load(testContext) - } + val domains = CustomDomains.load(testContext) assertEquals(0, domains.size) } diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt index 06098fffb89..e7c43a3c990 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt @@ -12,7 +12,6 @@ import android.os.Message import android.view.WindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.engine.gecko.ext.geckoTrackingProtectionPermission import mozilla.components.browser.engine.gecko.ext.isExcludedForTrackingProtection import mozilla.components.browser.engine.gecko.permission.geckoContentPermission @@ -44,6 +43,8 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.eq import mozilla.components.support.test.expectException import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import mozilla.components.support.utils.ThreadUtils import mozilla.components.test.ReflectionUtils @@ -55,6 +56,7 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -102,6 +104,9 @@ typealias GeckoCookieBehavior = ContentBlocking.CookieBehavior @RunWith(AndroidJUnit4::class) class GeckoEngineSessionTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private lateinit var runtime: GeckoRuntime private lateinit var geckoSession: GeckoSession private lateinit var geckoSessionProvider: () -> GeckoSession @@ -774,7 +779,7 @@ class GeckoEngineSessionTest { } @Test - fun `notifies configured history delegate of title changes`() = runBlockingTest { + fun `notifies configured history delegate of title changes`() = runTestOnMain { val engineSession = GeckoEngineSession( runtime, geckoSessionProvider = geckoSessionProvider, context = coroutineContext @@ -801,7 +806,7 @@ class GeckoEngineSessionTest { } @Test - fun `does not notify configured history delegate of title changes for private sessions`() = runBlockingTest { + fun `does not notify configured history delegate of title changes for private sessions`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -833,7 +838,7 @@ class GeckoEngineSessionTest { } @Test - fun `notifies configured history delegate of preview image URL changes`() = runBlockingTest { + fun `notifies configured history delegate of preview image URL changes`() = runTestOnMain { val engineSession = GeckoEngineSession( runtime, geckoSessionProvider = geckoSessionProvider, context = coroutineContext @@ -864,7 +869,7 @@ class GeckoEngineSessionTest { } @Test - fun `does not notify configured history delegate of preview image URL changes for private sessions`() = runBlockingTest { + fun `does not notify configured history delegate of preview image URL changes for private sessions`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -896,7 +901,7 @@ class GeckoEngineSessionTest { } @Test - fun `does not notify configured history delegate for top-level visits to error pages`() = runBlockingTest { + fun `does not notify configured history delegate for top-level visits to error pages`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -919,7 +924,7 @@ class GeckoEngineSessionTest { } @Test - fun `notifies configured history delegate of visits`() = runBlockingTest { + fun `notifies configured history delegate of visits`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -938,7 +943,7 @@ class GeckoEngineSessionTest { } @Test - fun `notifies configured history delegate of reloads`() = runBlockingTest { + fun `notifies configured history delegate of reloads`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -957,7 +962,7 @@ class GeckoEngineSessionTest { } @Test - fun `checks with the delegate before trying to record a visit`() = runBlockingTest { + fun `checks with the delegate before trying to record a visit`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -985,7 +990,7 @@ class GeckoEngineSessionTest { } @Test - fun `correctly processes redirect visit flags`() = runBlockingTest { + fun `correctly processes redirect visit flags`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -1047,7 +1052,7 @@ class GeckoEngineSessionTest { } @Test - fun `does not notify configured history delegate of visits for private sessions`() = runBlockingTest { + fun `does not notify configured history delegate of visits for private sessions`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -1066,7 +1071,7 @@ class GeckoEngineSessionTest { } @Test - fun `requests visited URLs from configured history delegate`() = runBlockingTest { + fun `requests visited URLs from configured history delegate`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -1088,7 +1093,7 @@ class GeckoEngineSessionTest { } @Test - fun `does not request visited URLs from configured history delegate in private sessions`() = runBlockingTest { + fun `does not request visited URLs from configured history delegate in private sessions`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, @@ -1107,7 +1112,7 @@ class GeckoEngineSessionTest { } @Test - fun `notifies configured history delegate of state changes`() = runBlockingTest { + fun `notifies configured history delegate of state changes`() = runTestOnMain { val engineSession = GeckoEngineSession( mock(), geckoSessionProvider = geckoSessionProvider, diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoResultTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoResultTest.kt index 27e47bda766..cd56ce249aa 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoResultTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoResultTest.kt @@ -6,7 +6,7 @@ package mozilla.components.browser.engine.gecko import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.mock import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals @@ -25,18 +25,18 @@ import org.robolectric.annotation.LooperMode class GeckoResultTest { @Test - fun awaitWithResult() = runBlockingTest { + fun awaitWithResult() = runTest { val result = GeckoResult.fromValue(42).await() assertEquals(42, result) } @Test(expected = IllegalStateException::class) - fun awaitWithException() = runBlockingTest { + fun awaitWithException() = runTest { GeckoResult.fromException(IllegalStateException()).await() } @Test - fun fromResult() = runBlockingTest { + fun fromResult() = runTest { val result = launchGeckoResult { 42 } result.then { @@ -46,7 +46,7 @@ class GeckoResultTest { } @Test - fun fromException() = runBlockingTest { + fun fromException() = runTest { val result = launchGeckoResult { throw IllegalStateException() } result.then( @@ -62,7 +62,7 @@ class GeckoResultTest { } @Test - fun asCancellableOperation() = runBlockingTest { + fun asCancellableOperation() = runTest { val geckoResult: GeckoResult = mock() val op = geckoResult.asCancellableOperation() diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoSitePermissionsStorageTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoSitePermissionsStorageTest.kt index 2d87ec73602..d31deb2adc5 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoSitePermissionsStorageTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoSitePermissionsStorageTest.kt @@ -6,7 +6,7 @@ package mozilla.components.browser.engine.gecko.permission import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.permission.SitePermissions import mozilla.components.concept.engine.permission.SitePermissions.AutoplayStatus import mozilla.components.concept.engine.permission.SitePermissions.Status.ALLOWED @@ -66,7 +66,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a location permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a location permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(location = ALLOWED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_GEOLOCATION) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_GEOLOCATION, geckoPermissions, mock()) @@ -83,7 +83,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a notification permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a notification permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(notification = BLOCKED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_DESKTOP_NOTIFICATION) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_DESKTOP_NOTIFICATION, geckoPermissions, mock()) @@ -100,7 +100,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a localStorage permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a localStorage permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(localStorage = BLOCKED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_PERSISTENT_STORAGE) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_PERSISTENT_STORAGE, geckoPermissions, mock()) @@ -117,7 +117,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a crossOriginStorageAccess permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a crossOriginStorageAccess permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(crossOriginStorageAccess = BLOCKED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_STORAGE_ACCESS) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_STORAGE_ACCESS, geckoPermissions, mock()) @@ -134,7 +134,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a mediaKeySystemAccess permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a mediaKeySystemAccess permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(mediaKeySystemAccess = ALLOWED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_MEDIA_KEY_SYSTEM_ACCESS) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_MEDIA_KEY_SYSTEM_ACCESS, geckoPermissions, mock()) @@ -151,7 +151,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a autoplayInaudible permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a autoplayInaudible permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(autoplayInaudible = AutoplayStatus.ALLOWED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_AUTOPLAY_INAUDIBLE) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_AUTOPLAY_INAUDIBLE, geckoPermissions, mock()) @@ -168,7 +168,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a autoplayAudible permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a autoplayAudible permission WHEN saving THEN the permission is saved in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(autoplayAudible = AutoplayStatus.ALLOWED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE, geckoPermissions, mock()) @@ -185,7 +185,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN saving a site permission THEN the permission is saved in the gecko storage and in disk storage`() = runBlockingTest { + fun `WHEN saving a site permission THEN the permission is saved in the gecko storage and in disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(autoplayAudible = AutoplayStatus.ALLOWED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE, geckoPermissions, mock()) @@ -199,7 +199,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a temporary permission WHEN saving THEN the permission is saved in memory`() = runBlockingTest { + fun `GIVEN a temporary permission WHEN saving THEN the permission is saved in memory`() = runTest { val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE, geckoPermissions, mock()) @@ -209,7 +209,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN media type temporary permission WHEN saving THEN the permission is NOT saved in memory`() = runBlockingTest { + fun `GIVEN media type temporary permission WHEN saving THEN the permission is NOT saved in memory`() = runTest { val geckoRequest = GeckoPermissionRequest.Media("mozilla.org", emptyList(), emptyList(), mock()) assertTrue(geckoStorage.geckoTemporaryPermissions.isEmpty()) @@ -220,7 +220,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN multiple saved temporary permissions WHEN clearing all temporary permission THEN all permissions are cleared`() = runBlockingTest { + fun `GIVEN multiple saved temporary permissions WHEN clearing all temporary permission THEN all permissions are cleared`() = runTest { val geckoAutoPlayPermissions = geckoContentPermission("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE) val geckoPersistentStoragePermissions = geckoContentPermission("mozilla.org", PERMISSION_PERSISTENT_STORAGE) val geckoStorageAccessPermissions = geckoContentPermission("mozilla.org", PERMISSION_STORAGE_ACCESS) @@ -250,7 +250,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a localStorage permission WHEN updating THEN the permission is updated in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `GIVEN a localStorage permission WHEN updating THEN the permission is updated in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(location = ALLOWED) val geckoPermissions = geckoContentPermission("mozilla.org", PERMISSION_GEOLOCATION) val geckoRequest = GeckoPermissionRequest.Content("mozilla.org", PERMISSION_AUTOPLAY_AUDIBLE, geckoPermissions, mock()) @@ -264,7 +264,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN updating a permission THEN the permission is updated in the gecko storage and on the disk storage`() = runBlockingTest { + fun `WHEN updating a permission THEN the permission is updated in the gecko storage and on the disk storage`() = runTest { val sitePermissions = createNewSitePermission().copy(location = ALLOWED) doReturn(sitePermissions).`when`(geckoStorage).updateGeckoPermissionIfNeeded(sitePermissions) @@ -276,7 +276,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN updating THEN the permission is updated in the gecko storage and set to the default value on the disk storage`() = runBlockingTest { + fun `WHEN updating THEN the permission is updated in the gecko storage and set to the default value on the disk storage`() = runTest { val sitePermissions = SitePermissions( origin = "mozilla.dev", localStorage = ALLOWED, @@ -323,7 +323,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN querying the store by origin THEN the gecko and the on disk storage are queried and results are combined`() = runBlockingTest { + fun `WHEN querying the store by origin THEN the gecko and the on disk storage are queried and results are combined`() = runTest { val sitePermissions = SitePermissions( origin = "mozilla.dev", localStorage = ALLOWED, @@ -365,7 +365,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN a gecko and on disk permissions WHEN merging values THEN both should be combined into one`() = runBlockingTest { + fun `GIVEN a gecko and on disk permissions WHEN merging values THEN both should be combined into one`() = runTest { val onDiskPermissions = SitePermissions( origin = "mozilla.dev", localStorage = ALLOWED, @@ -404,7 +404,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `GIVEN permissions that are not present on the gecko storage WHEN merging THEN favor the values on disk permissions`() = runBlockingTest { + fun `GIVEN permissions that are not present on the gecko storage WHEN merging THEN favor the values on disk permissions`() = runTest { val onDiskPermissions = SitePermissions( origin = "mozilla.dev", localStorage = ALLOWED, @@ -471,7 +471,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN removing a site permissions THEN permissions should be removed from the on disk and gecko storage`() = runBlockingTest { + fun `WHEN removing a site permissions THEN permissions should be removed from the on disk and gecko storage`() = runTest { val onDiskPermissions = createNewSitePermission() doReturn(Unit).`when`(geckoStorage).removeGeckoContentPermissionBy(onDiskPermissions.origin) @@ -483,7 +483,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN removing gecko permissions THEN permissions should be set to the default values in the gecko storage`() = runBlockingTest { + fun `WHEN removing gecko permissions THEN permissions should be set to the default values in the gecko storage`() = runTest { val geckoPermissions = listOf( geckoContentPermission(type = PERMISSION_GEOLOCATION), geckoContentPermission(type = PERMISSION_DESKTOP_NOTIFICATION), @@ -511,7 +511,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN removing a temporary permissions THEN the permissions should be remove from memory`() = runBlockingTest { + fun `WHEN removing a temporary permissions THEN the permissions should be remove from memory`() = runTest { val geckoPermissions = listOf( geckoContentPermission(type = PERMISSION_GEOLOCATION), geckoContentPermission(type = PERMISSION_GEOLOCATION), @@ -539,7 +539,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN removing all THEN all permissions should be removed from the on disk and gecko storage`() = runBlockingTest { + fun `WHEN removing all THEN all permissions should be removed from the on disk and gecko storage`() = runTest { doReturn(Unit).`when`(geckoStorage).removeGeckoAllContentPermissions() @@ -550,7 +550,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN removing all gecko permissions THEN remove all permissions on gecko and clear the site permissions info`() = runBlockingTest { + fun `WHEN removing all gecko permissions THEN remove all permissions on gecko and clear the site permissions info`() = runTest { val geckoPermissions = listOf( geckoContentPermission(type = PERMISSION_GEOLOCATION), geckoContentPermission(type = PERMISSION_DESKTOP_NOTIFICATION), @@ -574,7 +574,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN querying all permission THEN the gecko and the on disk storage are queried and results are combined`() = runBlockingTest { + fun `WHEN querying all permission THEN the gecko and the on disk storage are queried and results are combined`() = runTest { val onDiskPermissions = SitePermissions( origin = "mozilla.dev", localStorage = ALLOWED, @@ -616,7 +616,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN filtering temporary permissions THEN all temporary permissions should be removed`() = runBlockingTest { + fun `WHEN filtering temporary permissions THEN all temporary permissions should be removed`() = runTest { val temporary = listOf(geckoContentPermission(type = PERMISSION_GEOLOCATION)) val geckoPermissions = listOf( @@ -637,7 +637,7 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN compering two gecko ContentPermissions THEN they are the same when host and permissions are the same`() = runBlockingTest { + fun `WHEN compering two gecko ContentPermissions THEN they are the same when host and permissions are the same`() = runTest { val location1 = geckoContentPermission(uri = "mozilla.dev", type = PERMISSION_GEOLOCATION) val location2 = geckoContentPermission(uri = "mozilla.dev", type = PERMISSION_GEOLOCATION) val notification = geckoContentPermission(uri = "mozilla.dev", type = PERMISSION_DESKTOP_NOTIFICATION) @@ -647,28 +647,28 @@ class GeckoSitePermissionsStorageTest { } @Test - fun `WHEN converting from gecko status to sitePermissions status THEN they get converted to the equivalent one`() = runBlockingTest { + fun `WHEN converting from gecko status to sitePermissions status THEN they get converted to the equivalent one`() = runTest { assertEquals(NO_DECISION, VALUE_PROMPT.toStatus()) assertEquals(BLOCKED, VALUE_DENY.toStatus()) assertEquals(ALLOWED, VALUE_ALLOW.toStatus()) } @Test - fun `WHEN converting from gecko status to autoplay sitePermissions status THEN they get converted to the equivalent one`() = runBlockingTest { + fun `WHEN converting from gecko status to autoplay sitePermissions status THEN they get converted to the equivalent one`() = runTest { assertEquals(AutoplayStatus.BLOCKED, VALUE_PROMPT.toAutoPlayStatus()) assertEquals(AutoplayStatus.BLOCKED, VALUE_DENY.toAutoPlayStatus()) assertEquals(AutoplayStatus.ALLOWED, VALUE_ALLOW.toAutoPlayStatus()) } @Test - fun `WHEN converting a sitePermissions status to gecko status THEN they get converted to the equivalent one`() = runBlockingTest { + fun `WHEN converting a sitePermissions status to gecko status THEN they get converted to the equivalent one`() = runTest { assertEquals(VALUE_PROMPT, NO_DECISION.toGeckoStatus()) assertEquals(VALUE_DENY, BLOCKED.toGeckoStatus()) assertEquals(VALUE_ALLOW, ALLOWED.toGeckoStatus()) } @Test - fun `WHEN converting from autoplay sitePermissions to gecko status THEN they get converted to the equivalent one`() = runBlockingTest { + fun `WHEN converting from autoplay sitePermissions to gecko status THEN they get converted to the equivalent one`() = runTest { assertEquals(VALUE_DENY, AutoplayStatus.BLOCKED.toGeckoStatus()) assertEquals(VALUE_ALLOW, AutoplayStatus.ALLOWED.toGeckoStatus()) } diff --git a/components/browser/engine-system/build.gradle b/components/browser/engine-system/build.gradle index 2092ff0212b..259e039b5bb 100644 --- a/components/browser/engine-system/build.gradle +++ b/components/browser/engine-system/build.gradle @@ -42,6 +42,7 @@ dependencies { testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito + testImplementation Dependencies.testing_coroutines androidTestImplementation Dependencies.androidx_test_core androidTestImplementation Dependencies.androidx_test_runner diff --git a/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineSessionTest.kt b/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineSessionTest.kt index 5e8c1a95d67..09af18f091c 100644 --- a/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineSessionTest.kt +++ b/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineSessionTest.kt @@ -15,7 +15,8 @@ import android.webkit.WebView import android.webkit.WebViewClient import android.webkit.WebViewDatabase import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.browser.engine.system.matcher.UrlMatcher import mozilla.components.browser.errorpages.ErrorType import mozilla.components.concept.engine.DefaultSettings @@ -236,8 +237,9 @@ class SystemEngineSessionTest { verify(webView).restoreState(bundle) } + @ExperimentalCoroutinesApi @Test - fun enableTrackingProtection() { + fun enableTrackingProtection() = runTest { SystemEngineView.URL_MATCHER = UrlMatcher(arrayOf("")) val engineSession = spy(SystemEngineSession(testContext)) @@ -257,7 +259,7 @@ class SystemEngineSessionTest { }) assertNull(engineSession.trackingProtectionPolicy) - runBlocking { engineSession.updateTrackingProtection() } + engineSession.updateTrackingProtection() assertEquals( EngineSession.TrackingProtectionPolicy.strict(), engineSession.trackingProtectionPolicy diff --git a/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineViewTest.kt b/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineViewTest.kt index 415c3cb10e6..b9b6e2330be 100644 --- a/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineViewTest.kt +++ b/components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineViewTest.kt @@ -30,7 +30,8 @@ import android.webkit.WebViewClient import android.webkit.WebViewDatabase import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.browser.engine.system.matcher.UrlMatcher import mozilla.components.browser.errorpages.ErrorType import mozilla.components.concept.engine.EngineSession @@ -77,6 +78,7 @@ import org.robolectric.Robolectric import org.robolectric.annotation.Config import java.io.StringReader +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class SystemEngineViewTest { @@ -285,7 +287,7 @@ class SystemEngineViewTest { } @Test - fun `WebView client notifies configured history delegate of url visits`() = runBlocking { + fun `WebView client notifies configured history delegate of url visits`() = runTest { val engineSession = SystemEngineSession(testContext) val engineView = SystemEngineView(testContext) @@ -308,7 +310,7 @@ class SystemEngineViewTest { } @Test - fun `WebView client checks with the delegate if the URI visit should be recorded`() = runBlocking { + fun `WebView client checks with the delegate if the URI visit should be recorded`() = runTest { val engineSession = SystemEngineSession(testContext) val engineView = SystemEngineView(testContext) val webView: WebView = mock() @@ -332,7 +334,7 @@ class SystemEngineViewTest { } @Test - fun `WebView client requests history from configured history delegate`() { + fun `WebView client requests history from configured history delegate`() = runTest { val engineSession = SystemEngineSession(testContext) val engineView = SystemEngineView(testContext) @@ -371,14 +373,12 @@ class SystemEngineViewTest { engineSession.settings.historyTrackingDelegate = historyDelegate val historyValueCallback: ValueCallback> = mock() - runBlocking { - engineSession.webView.webChromeClient!!.getVisitedHistory(historyValueCallback) - } + engineSession.webView.webChromeClient!!.getVisitedHistory(historyValueCallback) verify(historyValueCallback).onReceiveValue(arrayOf("https://www.mozilla.com")) } @Test - fun `WebView client notifies configured history delegate of title changes`() = runBlocking { + fun `WebView client notifies configured history delegate of title changes`() = runTest { val engineSession = SystemEngineSession(testContext) val engineView = SystemEngineView(testContext) diff --git a/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt b/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt index f946a6b375c..bce6aefdb18 100644 --- a/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt +++ b/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt @@ -10,8 +10,8 @@ import android.os.Looper.getMainLooper import android.widget.ImageView import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job -import kotlinx.coroutines.runBlocking import mozilla.components.browser.icons.generator.IconGenerator import mozilla.components.concept.engine.manifest.Size import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient @@ -20,6 +20,8 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okio.Okio @@ -28,6 +30,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertSame import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers @@ -39,9 +42,13 @@ import org.mockito.Mockito.verify import org.robolectric.Shadows.shadowOf import java.io.OutputStream +@ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class BrowserIconsTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + @Before @After fun cleanUp() { @@ -50,7 +57,7 @@ class BrowserIconsTest { } @Test - fun `Uses generator`() { + fun `Uses generator`() = runTestOnMain { val mockedIcon: Icon = mock() val generator: IconGenerator = mock() @@ -60,13 +67,13 @@ class BrowserIconsTest { val icon = BrowserIcons(testContext, httpClient = mock(), generator = generator) .loadIcon(request) - assertEquals(mockedIcon, runBlocking { icon.await() }) + assertEquals(mockedIcon, icon.await()) verify(generator).generate(testContext, request) } @Test - fun `WHEN resources are provided THEN an icon will be downloaded from one of them`() = runBlocking { + fun `WHEN resources are provided THEN an icon will be downloaded from one of them`() = runTestOnMain { val server = MockWebServer() server.enqueue( @@ -119,7 +126,7 @@ class BrowserIconsTest { } @Test - fun `WHEN icon is loaded twice THEN second load is delivered from memory cache`() = runBlocking { + fun `WHEN icon is loaded twice THEN second load is delivered from memory cache`() = runTestOnMain { val server = MockWebServer() server.enqueue( @@ -162,7 +169,7 @@ class BrowserIconsTest { } @Test - fun `WHEN icon is loaded again and not in memory cache THEN second load is delivered from disk cache`() = runBlocking { + fun `WHEN icon is loaded again and not in memory cache THEN second load is delivered from disk cache`() = runTestOnMain { val server = MockWebServer() server.enqueue( diff --git a/components/browser/icons/src/test/java/mozilla/components/browser/icons/extension/IconMessageHandlerTest.kt b/components/browser/icons/src/test/java/mozilla/components/browser/icons/extension/IconMessageHandlerTest.kt index 24c3f20a252..a2a105ecf73 100644 --- a/components/browser/icons/src/test/java/mozilla/components/browser/icons/extension/IconMessageHandlerTest.kt +++ b/components/browser/icons/src/test/java/mozilla/components/browser/icons/extension/IconMessageHandlerTest.kt @@ -7,9 +7,10 @@ package mozilla.components.browser.icons.extension import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.IconRequest @@ -34,10 +35,11 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class IconMessageHandlerTest { + @ExperimentalCoroutinesApi @OptIn(DelicateCoroutinesApi::class) @Test fun `Complex message (TheVerge) is transformed into IconRequest and loaded`() { - runBlocking { + runTest { val bitmap: Bitmap = mock() val icon = Icon(bitmap, source = Icon.Source.DOWNLOAD) val deferredIcon = GlobalScope.async { icon } diff --git a/components/browser/icons/src/test/java/mozilla/components/browser/icons/generator/DefaultIconGeneratorTest.kt b/components/browser/icons/src/test/java/mozilla/components/browser/icons/generator/DefaultIconGeneratorTest.kt index b07dc4c0e42..15a9f7824b2 100644 --- a/components/browser/icons/src/test/java/mozilla/components/browser/icons/generator/DefaultIconGeneratorTest.kt +++ b/components/browser/icons/src/test/java/mozilla/components/browser/icons/generator/DefaultIconGeneratorTest.kt @@ -6,7 +6,6 @@ package mozilla.components.browser.icons.generator import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.IconRequest import mozilla.components.support.ktx.android.util.dpToPx @@ -43,7 +42,7 @@ class DefaultIconGeneratorTest { } @Test - fun generate() = runBlocking { + fun generate() { val generator = DefaultIconGenerator() val icon = generator.generate( diff --git a/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt b/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt index 167b1f1c4b1..f89779d293f 100644 --- a/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt +++ b/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt @@ -6,7 +6,6 @@ package mozilla.components.browser.icons.loader import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.IconRequest import mozilla.components.concept.fetch.Client @@ -20,6 +19,7 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.junit.Assert.assertEquals @@ -47,7 +47,7 @@ class NonBlockingHttpIconLoaderTest { private val scope = coroutinesTestRule.scope @Test - fun `Loader will return IconLoader#Result#NoResult for a load request and respond with the result through a callback`() = runBlockingTest { + fun `Loader will return IconLoader#Result#NoResult for a load request and respond with the result through a callback`() = runTestOnMain { val clients = listOf( HttpURLConnectionClient(), OkHttpClient() @@ -100,7 +100,7 @@ class NonBlockingHttpIconLoaderTest { } @Test - fun `Loader will not perform any requests for data uris`() = runBlockingTest { + fun `Loader will not perform any requests for data uris`() = runTestOnMain { val client: Client = mock() var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null @@ -128,7 +128,7 @@ class NonBlockingHttpIconLoaderTest { } @Test - fun `Request has timeouts applied`() = runBlockingTest { + fun `Request has timeouts applied`() = runTestOnMain { val client: Client = mock() val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } doReturn( @@ -157,7 +157,7 @@ class NonBlockingHttpIconLoaderTest { } @Test - fun `NoResult is returned for non-successful requests`() = runBlockingTest { + fun `NoResult is returned for non-successful requests`() = runTestOnMain { val client: Client = mock() var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null @@ -192,7 +192,7 @@ class NonBlockingHttpIconLoaderTest { } @Test - fun `Loader will not try to load URL again that just recently failed`() = runBlockingTest { + fun `Loader will not try to load URL again that just recently failed`() = runTestOnMain { val client: Client = mock() val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } doReturn( @@ -221,7 +221,7 @@ class NonBlockingHttpIconLoaderTest { } @Test - fun `Loader will return NoResult for IOExceptions happening during fetch`() = runBlockingTest { + fun `Loader will return NoResult for IOExceptions happening during fetch`() = runTestOnMain { val client: Client = mock() doThrow(IOException("Mock")).`when`(client).fetch(any()) var callbackIconRequest: IconRequest? = null @@ -247,7 +247,7 @@ class NonBlockingHttpIconLoaderTest { } @Test - fun `Loader will return NoResult for IOExceptions happening during toIconLoaderResult`() = runBlockingTest { + fun `Loader will return NoResult for IOExceptions happening during toIconLoaderResult`() = runTestOnMain { val client: Client = mock() var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null @@ -285,7 +285,7 @@ class NonBlockingHttpIconLoaderTest { } @Test - fun `Loader will sanitize URL`() = runBlockingTest { + fun `Loader will sanitize URL`() = runTestOnMain { val client: Client = mock() val captor = argumentCaptor() val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } diff --git a/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt b/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt index 44c6bb6a323..90c9c0868fd 100644 --- a/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt +++ b/components/browser/menu/src/test/java/mozilla/components/browser/menu/item/WebExtensionBrowserMenuItemTest.kt @@ -14,7 +14,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.menu.R import mozilla.components.browser.menu.WebExtensionBrowserMenu import mozilla.components.concept.engine.webextension.Action @@ -286,7 +286,7 @@ class WebExtensionBrowserMenuItemTest { } @Test - fun `GIVEN setIcon was called, WHEN bind is called, icon setup uses the tint set`() = runBlocking { + fun `GIVEN setIcon was called, WHEN bind is called, icon setup uses the tint set`() = runTest { val webExtMenuItem = spy(WebExtensionBrowserMenuItem(mock(), mock())) val testIconTintColorResource = R.color.accent_material_dark val menu: WebExtensionBrowserMenu = mock() diff --git a/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt b/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt index 10b81637012..929ac8df0e4 100644 --- a/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt +++ b/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/adapter/icons/DrawableMenuIconViewHoldersTest.kt @@ -12,7 +12,6 @@ import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.menu2.R import mozilla.components.concept.menu.Side import mozilla.components.concept.menu.candidate.AsyncDrawableMenuIcon @@ -23,6 +22,7 @@ import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -84,7 +84,7 @@ class DrawableMenuIconViewHoldersTest { } @Test - fun `async view holder sets icon on view`() = runBlockingTest { + fun `async view holder sets icon on view`() = runTestOnMain { val holder = AsyncDrawableMenuIconViewHolder(parent, layoutInflater, Side.END) val drawable = mock() @@ -94,7 +94,7 @@ class DrawableMenuIconViewHoldersTest { } @Test - fun `async view holder uses loading icon and fallback icon`() = runBlockingTest { + fun `async view holder uses loading icon and fallback icon`() = runTestOnMain { val logger = mock() val holder = AsyncDrawableMenuIconViewHolder(parent, layoutInflater, Side.END, logger) diff --git a/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt b/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt index 9cf08d0a616..92a5614939f 100644 --- a/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt +++ b/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/AutoSaveTest.kt @@ -9,7 +9,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Job -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState @@ -22,6 +21,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertNotSame import org.junit.Assert.assertNull import org.junit.Assert.assertSame @@ -47,7 +47,7 @@ class AutoSaveTest { @Test fun `AutoSave - when going to background`() { - runBlocking { + runTestOnMain { // Keep the "owner" in scope to avoid it getting garbage collected and therefore lifecycle events // not getting propagated (See #1428). val owner = mock(LifecycleOwner::class.java) @@ -82,7 +82,7 @@ class AutoSaveTest { @Test fun `AutoSave - when tab gets added`() { - runBlocking { + runTestOnMain { val state = BrowserState() val store = BrowserStore(state) @@ -115,7 +115,7 @@ class AutoSaveTest { @Test fun `AutoSave - when tab gets removed`() { - runBlocking { + runTestOnMain { val sessionStorage: SessionStorage = mock() val store = BrowserStore( @@ -151,7 +151,7 @@ class AutoSaveTest { @Test fun `AutoSave - when all tabs get removed`() { - runBlocking { + runTestOnMain { val store = BrowserStore( BrowserState( tabs = listOf( @@ -187,7 +187,7 @@ class AutoSaveTest { @Test fun `AutoSave - when no tabs are left`() { - runBlocking { + runTestOnMain { val store = BrowserStore( BrowserState( tabs = listOf(createTab("https://www.firefox.com", id = "firefox")), @@ -219,7 +219,7 @@ class AutoSaveTest { @Test fun `AutoSave - when tab gets selected`() { - runBlocking { + runTestOnMain { val store = BrowserStore( BrowserState( tabs = listOf( @@ -255,7 +255,7 @@ class AutoSaveTest { @Test fun `AutoSave - when tab loading state changes`() { - runBlocking { + runTestOnMain { val sessionStorage: SessionStorage = mock() val store = BrowserStore( diff --git a/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/FileEngineSessionStateStorageTest.kt b/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/FileEngineSessionStateStorageTest.kt index 68383a456bf..317124d226b 100644 --- a/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/FileEngineSessionStateStorageTest.kt +++ b/components/browser/session-storage/src/test/java/mozilla/components/browser/session/storage/FileEngineSessionStateStorageTest.kt @@ -5,7 +5,7 @@ package mozilla.components.browser.session.storage import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.fakes.engine.FakeEngine import mozilla.components.support.test.fakes.engine.FakeEngineSessionState import mozilla.components.support.test.robolectric.testContext @@ -18,11 +18,11 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class FileEngineSessionStateStorageTest { @Test - fun `able to read and write engine session states`() = runBlocking { + fun `able to read and write engine session states`() = runTest { val storage = FileEngineSessionStateStorage(testContext, FakeEngine()) // reading non-existing tab - assertNull(runBlocking { storage.read("test-tab") }) + assertNull(storage.read("test-tab")) storage.write("test-tab", FakeEngineSessionState("some-engine-state")) val state = storage.read("test-tab") @@ -33,7 +33,7 @@ class FileEngineSessionStateStorageTest { } @Test - fun `able to delete specific engine session states`() = runBlocking { + fun `able to delete specific engine session states`() = runTest { val storage = FileEngineSessionStateStorage(testContext, FakeEngine()) storage.write("test-tab-1", FakeEngineSessionState("some-engine-state-1")) storage.write("test-tab-2", FakeEngineSessionState("some-engine-state-2")) @@ -63,7 +63,7 @@ class FileEngineSessionStateStorageTest { } @Test - fun `able to delete all engine states`() = runBlocking { + fun `able to delete all engine states`() = runTest { val storage = FileEngineSessionStateStorage(testContext, FakeEngine()) // already empty storage diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineObserverTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineObserverTest.kt index bfe325ca42c..9d9dca73fde 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineObserverTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/EngineObserverTest.kt @@ -9,7 +9,7 @@ import android.graphics.Bitmap import android.view.WindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Job -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.CrashAction @@ -821,7 +821,7 @@ class EngineObserverTest { } @Test - fun engineSessionObserverWithContentPermissionRequests() { + fun engineSessionObserverWithContentPermissionRequests() = runTest { val permissionRequest: PermissionRequest = mock() val store: BrowserStore = mock() val observer = EngineObserver("tab-id", store) @@ -831,14 +831,12 @@ class EngineObserverTest { ) doReturn(Job()).`when`(store).dispatch(action) - runBlockingTest { - observer.onContentPermissionRequest(permissionRequest) - verify(store).dispatch(action) - } + observer.onContentPermissionRequest(permissionRequest) + verify(store).dispatch(action) } @Test - fun engineSessionObserverWithAppPermissionRequests() { + fun engineSessionObserverWithAppPermissionRequests() = runTest { val permissionRequest: PermissionRequest = mock() val store: BrowserStore = mock() val observer = EngineObserver("tab-id", store) @@ -847,10 +845,8 @@ class EngineObserverTest { permissionRequest ) - runBlockingTest { - observer.onAppPermissionRequest(permissionRequest) - verify(store).dispatch(action) - } + observer.onAppPermissionRequest(permissionRequest) + verify(store).dispatch(action) } @Test diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt index b8e235f9ec2..1370d26e22d 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddlewareTest.kt @@ -5,7 +5,6 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.selector.findCustomTab @@ -22,6 +21,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -43,7 +43,7 @@ class CreateEngineSessionMiddlewareTest { private val scope = coroutinesTestRule.scope @Test - fun `creates engine session if needed`() = runBlocking { + fun `creates engine session if needed`() = runTestOnMain { val engine: Engine = mock() val engineSession: EngineSession = mock() whenever(engine.createSession(anyBoolean(), any())).thenReturn(engineSession) @@ -70,7 +70,7 @@ class CreateEngineSessionMiddlewareTest { } @Test - fun `restores engine session state if available`() = runBlocking { + fun `restores engine session state if available`() = runTestOnMain { val engine: Engine = mock() val engineSession: EngineSession = mock() whenever(engine.createSession(anyBoolean(), any())).thenReturn(engineSession) @@ -94,7 +94,7 @@ class CreateEngineSessionMiddlewareTest { } @Test - fun `creates no engine session if tab does not exist`() = runBlocking { + fun `creates no engine session if tab does not exist`() = runTestOnMain { val engine: Engine = mock() `when`(engine.createSession(anyBoolean(), anyString())).thenReturn(mock()) @@ -113,7 +113,7 @@ class CreateEngineSessionMiddlewareTest { } @Test - fun `creates no engine session if session does not exist`() = runBlocking { + fun `creates no engine session if session does not exist`() = runTestOnMain { val engine: Engine = mock() `when`(engine.createSession(anyBoolean(), anyString())).thenReturn(mock()) @@ -137,7 +137,7 @@ class CreateEngineSessionMiddlewareTest { } @Test - fun `dispatches follow-up action after engine session is created`() = runBlocking { + fun `dispatches follow-up action after engine session is created`() = runTestOnMain { val engine: Engine = mock() val engineSession: EngineSession = mock() whenever(engine.createSession(anyBoolean(), any())).thenReturn(engineSession) @@ -162,7 +162,7 @@ class CreateEngineSessionMiddlewareTest { } @Test - fun `dispatches follow-up action once engine session is created by pending action`() = runBlocking { + fun `dispatches follow-up action once engine session is created by pending action`() = runTestOnMain { val engine: Engine = mock() val engineSession: EngineSession = mock() whenever(engine.createSession(anyBoolean(), any())).thenReturn(engineSession) diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt index aff1ce225b9..ae15ff183ab 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt @@ -5,7 +5,6 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.selector.findTab @@ -18,6 +17,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -137,7 +137,7 @@ class LinkingMiddlewareTest { } @Test - fun `registers engine observer after linking`() = runBlocking { + fun `registers engine observer after linking`() = runTestOnMain { val tab1 = createTab("https://www.mozilla.org", id = "1") val tab2 = createTab("https://www.mozilla.org", id = "2") @@ -167,7 +167,7 @@ class LinkingMiddlewareTest { } @Test - fun `unregisters engine observer before unlinking`() = runBlocking { + fun `unregisters engine observer before unlinking`() = runTestOnMain { val tab1 = createTab("https://www.mozilla.org", id = "1") val tab2 = createTab("https://www.mozilla.org", id = "2") @@ -192,7 +192,7 @@ class LinkingMiddlewareTest { } @Test - fun `registers engine observer when tab is added with engine session`() = runBlocking { + fun `registers engine observer when tab is added with engine session`() = runTestOnMain { val engineSession: EngineSession = mock() val tab1 = createTab("https://www.mozilla.org", id = "1") val tab2 = createTab("https://www.mozilla.org", id = "2", engineSession = engineSession) diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt index 85a876db012..7e9dd9931a8 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/SuspendMiddlewareTest.kt @@ -5,7 +5,6 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.state.BrowserState @@ -17,6 +16,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Rule @@ -35,7 +35,7 @@ class SuspendMiddlewareTest { private val scope = coroutinesTestRule.scope @Test - fun `suspends engine session`() = runBlocking { + fun `suspends engine session`() = runTestOnMain { val middleware = SuspendMiddleware(scope) val tab = createTab("https://www.mozilla.org", id = "1") diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt index e36346603e6..7ab9150ea35 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TabsRemovedMiddlewareTest.kt @@ -5,7 +5,6 @@ package mozilla.components.browser.state.engine.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.EngineAction @@ -24,6 +23,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Rule @@ -40,7 +40,7 @@ class TabsRemovedMiddlewareTest { private val scope = coroutinesTestRule.scope @Test - fun `closes and unlinks engine session when tab is removed`() = runBlocking { + fun `closes and unlinks engine session when tab is removed`() = runTestOnMain { val middleware = TabsRemovedMiddleware(scope) val tab = createTab("https://www.mozilla.org", id = "1") @@ -59,7 +59,7 @@ class TabsRemovedMiddlewareTest { } @Test - fun `closes and unlinks engine session when list of tabs are removed`() = runBlocking { + fun `closes and unlinks engine session when list of tabs are removed`() = runTestOnMain { val middleware = TabsRemovedMiddleware(scope) val tab1 = createTab("https://www.mozilla.org", id = "1", private = false) @@ -88,7 +88,7 @@ class TabsRemovedMiddlewareTest { } @Test - fun `closes and unlinks engine session when all normal tabs are removed`() = runBlocking { + fun `closes and unlinks engine session when all normal tabs are removed`() = runTestOnMain { val middleware = TabsRemovedMiddleware(scope) val tab1 = createTab("https://www.mozilla.org", id = "1", private = false) @@ -116,7 +116,7 @@ class TabsRemovedMiddlewareTest { } @Test - fun `closes and unlinks engine session when all private tabs are removed`() = runBlocking { + fun `closes and unlinks engine session when all private tabs are removed`() = runTestOnMain { val middleware = TabsRemovedMiddleware(scope) val tab1 = createTab("https://www.mozilla.org", id = "1", private = true) @@ -144,7 +144,7 @@ class TabsRemovedMiddlewareTest { } @Test - fun `closes and unlinks engine session when all tabs are removed`() = runBlocking { + fun `closes and unlinks engine session when all tabs are removed`() = runTestOnMain { val middleware = TabsRemovedMiddleware(scope) val tab1 = createTab("https://www.mozilla.org", id = "1", private = true) @@ -172,7 +172,7 @@ class TabsRemovedMiddlewareTest { } @Test - fun `closes and unlinks engine session when custom tab is removed`() = runBlocking { + fun `closes and unlinks engine session when custom tab is removed`() = runTestOnMain { val middleware = TabsRemovedMiddleware(scope) val tab = createCustomTab("https://www.mozilla.org", id = "1") @@ -191,7 +191,7 @@ class TabsRemovedMiddlewareTest { } @Test - fun `closes and unlinks engine session when all custom tabs are removed`() = runBlocking { + fun `closes and unlinks engine session when all custom tabs are removed`() = runTestOnMain { val middleware = TabsRemovedMiddleware(scope) val tab1 = createCustomTab("https://www.mozilla.org", id = "1") diff --git a/components/browser/state/src/test/java/mozilla/components/browser/state/store/BrowserStoreTest.kt b/components/browser/state/src/test/java/mozilla/components/browser/state/store/BrowserStoreTest.kt index 8b294e2cd6c..20d71d417ef 100644 --- a/components/browser/state/src/test/java/mozilla/components/browser/state/store/BrowserStoreTest.kt +++ b/components/browser/state/src/test/java/mozilla/components/browser/state/store/BrowserStoreTest.kt @@ -4,7 +4,7 @@ package mozilla.components.browser.state.store -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.InitAction import mozilla.components.browser.state.action.RestoreCompleteAction @@ -50,7 +50,7 @@ class BrowserStoreTest { } @Test - fun `Adding a tab`() = runBlocking { + fun `Adding a tab`() = runTest { val store = BrowserStore() assertEquals(0, store.state.tabs.size) diff --git a/components/browser/storage-sync/build.gradle b/components/browser/storage-sync/build.gradle index 8766b3eede6..49e793c5555 100644 --- a/components/browser/storage-sync/build.gradle +++ b/components/browser/storage-sync/build.gradle @@ -46,6 +46,7 @@ dependencies { testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito + testImplementation Dependencies.testing_coroutines testImplementation Dependencies.mozilla_places testImplementation Dependencies.mozilla_remote_tabs diff --git a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesBookmarksStorageTest.kt b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesBookmarksStorageTest.kt index 4e18bb4a535..9d793cf5452 100644 --- a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesBookmarksStorageTest.kt +++ b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesBookmarksStorageTest.kt @@ -5,13 +5,15 @@ package mozilla.components.browser.storage.sync import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.uniffi.PlacesException import mozilla.components.concept.storage.BookmarkInfo import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNodeType import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -19,28 +21,33 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.util.concurrent.TimeUnit +@ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class PlacesBookmarksStorageTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private lateinit var bookmarks: PlacesBookmarksStorage @Before - fun setup() = runBlocking { + fun setup() = runTestOnMain { bookmarks = PlacesBookmarksStorage(testContext) // There's a database on disk which needs to be cleaned up between tests. bookmarks.writer.deleteEverything() } @After - fun cleanup() = runBlocking { + fun cleanup() = runTestOnMain { bookmarks.cleanup() } @Test - fun `get bookmarks tree by root, recursive or not`() = runBlocking { + fun `get bookmarks tree by root, recursive or not`() = runTestOnMain { val tree = bookmarks.getTree(BookmarkRoot.Root.id)!! assertEquals(BookmarkRoot.Root.id, tree.guid) assertNotNull(tree.children) @@ -80,7 +87,7 @@ class PlacesBookmarksStorageTest { } @Test - fun `bookmarks APIs smoke testing - basic operations`() = runBlocking { + fun `bookmarks APIs smoke testing - basic operations`() = runTestOnMain { val url = "http://www.mozilla.org" assertEquals(emptyList(), bookmarks.getBookmarksWithUrl(url)) @@ -204,7 +211,7 @@ class PlacesBookmarksStorageTest { } @Test - fun `bookmarks import v34 populated`() = runBlocking { + fun `bookmarks import v34 populated`() = runTestOnMain { val path = getTestPath("databases/history-v34.db").absolutePath // Need to import history first before we import bookmarks. @@ -244,7 +251,7 @@ class PlacesBookmarksStorageTest { } @Test - fun `bookmarks import v38 populated`() = runBlocking { + fun `bookmarks import v38 populated`() = runTestOnMain { val path = getTestPath("databases/populated-v38.db").absolutePath // Need to import history first before we import bookmarks. @@ -308,7 +315,7 @@ class PlacesBookmarksStorageTest { } @Test - fun `bookmarks import v39 populated`() = runBlocking { + fun `bookmarks import v39 populated`() = runTestOnMain { val path = getTestPath("databases/populated-v39.db").absolutePath // Need to import history first before we import bookmarks. @@ -369,7 +376,7 @@ class PlacesBookmarksStorageTest { } @Test - fun `bookmarks pinned sites read v39`() = runBlocking { + fun `bookmarks pinned sites read v39`() = runTestOnMain { val path = getTestPath("databases/pinnedSites-v39.db").absolutePath with(bookmarks.readPinnedSitesFromFennec(path)) { @@ -392,7 +399,7 @@ class PlacesBookmarksStorageTest { } @Test - fun `bookmarks pinned sites read empty v39`() = runBlocking { + fun `bookmarks pinned sites read empty v39`() = runTestOnMain { val path = getTestPath("databases/populated-v39.db").absolutePath assertEquals(0, bookmarks.readPinnedSitesFromFennec(path).size) diff --git a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesHistoryStorageTest.kt b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesHistoryStorageTest.kt index 4080f06e0bc..656e9a17423 100644 --- a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesHistoryStorageTest.kt +++ b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/PlacesHistoryStorageTest.kt @@ -5,7 +5,8 @@ package mozilla.components.browser.storage.sync import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle import mozilla.appservices.places.PlacesReaderConnection import mozilla.appservices.places.PlacesWriterConnection import mozilla.appservices.places.uniffi.PlacesException @@ -23,6 +24,8 @@ import mozilla.components.concept.sync.SyncAuthInfo import mozilla.components.concept.sync.SyncStatus import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.json.JSONObject import org.junit.After import org.junit.Assert.assertEquals @@ -32,28 +35,33 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.io.File +@ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class PlacesHistoryStorageTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private lateinit var history: PlacesHistoryStorage @Before - fun setup() = runBlocking { + fun setup() = runTestOnMain { history = PlacesHistoryStorage(testContext) // There's a database on disk which needs to be cleaned up between tests. history.deleteEverything() } @After - fun cleanup() = runBlocking { + fun cleanup() = runTestOnMain { history.cleanup() } @Test - fun `storage allows recording and querying visits of different types`() = runBlocking { + fun `storage allows recording and querying visits of different types`() = runTestOnMain { history.recordVisit("http://www.firefox.com/1", PageVisit(VisitType.LINK)) history.recordVisit("http://www.firefox.com/2", PageVisit(VisitType.RELOAD)) history.recordVisit("http://www.firefox.com/3", PageVisit(VisitType.TYPED)) @@ -141,7 +149,7 @@ class PlacesHistoryStorageTest { } @Test - fun `storage passes through recordObservation calls`() = runBlocking { + fun `storage passes through recordObservation calls`() = runTestOnMain { history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK)) history.recordObservation("http://www.mozilla.org", PageObservation(title = "Mozilla")) @@ -159,7 +167,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can be used to query top frecent site information`() = runBlocking { + fun `store can be used to query top frecent site information`() = runTestOnMain { val toAdd = listOf( "https://www.example.com/123", "https://www.example.com/123", @@ -223,7 +231,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can be used to query detailed visit information`() = runBlocking { + fun `store can be used to query detailed visit information`() = runTestOnMain { history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK)) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.RELOAD)) history.recordObservation( @@ -254,7 +262,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can be used to record and retrieve history via webview-style callbacks`() = runBlocking { + fun `store can be used to record and retrieve history via webview-style callbacks`() = runTestOnMain { // Empty. assertEquals(0, history.getVisited().size) @@ -281,7 +289,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can be used to record and retrieve history via gecko-style callbacks`() = runBlocking { + fun `store can be used to record and retrieve history via gecko-style callbacks`() = runTestOnMain { assertEquals(0, history.getVisited(listOf()).size) // Regular visits are tracked @@ -304,7 +312,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can be used to track page meta information - title and previewImageUrl changes`() = runBlocking { + fun `store can be used to track page meta information - title and previewImageUrl changes`() = runTestOnMain { // Title and previewImageUrl changes are recorded. history.recordVisit("https://www.wikipedia.org", PageVisit(VisitType.TYPED)) history.recordObservation( @@ -338,7 +346,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can provide suggestions`() = runBlocking { + fun `store can provide suggestions`() = runTestOnMain { assertEquals(0, history.getSuggestions("Mozilla", 100).size) history.recordVisit("http://www.firefox.com", PageVisit(VisitType.LINK)) @@ -388,7 +396,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can provide autocomplete suggestions`() = runBlocking { + fun `store can provide autocomplete suggestions`() = runTestOnMain { assertNull(history.getAutocompleteSuggestion("moz")) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK)) @@ -445,7 +453,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store ignores url parse exceptions during record operations`() = runBlocking { + fun `store ignores url parse exceptions during record operations`() = runTestOnMain { // These aren't valid URIs, and if we're not explicitly ignoring exceptions from the underlying // storage layer, these calls will throw. history.recordVisit("mozilla.org", PageVisit(VisitType.LINK)) @@ -453,7 +461,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can delete everything`() = runBlocking { + fun `store can delete everything`() = runTestOnMain { history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.TYPED)) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.DOWNLOAD)) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.BOOKMARK)) @@ -473,7 +481,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can delete by url`() = runBlocking { + fun `store can delete by url`() = runTestOnMain { history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.TYPED)) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.DOWNLOAD)) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.BOOKMARK)) @@ -497,7 +505,7 @@ class PlacesHistoryStorageTest { } @Test - fun `store can delete by 'since'`() = runBlocking { + fun `store can delete by 'since'`() = runTestOnMain { history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.TYPED)) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.DOWNLOAD)) history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.BOOKMARK)) @@ -508,28 +516,21 @@ class PlacesHistoryStorageTest { } @Test - fun `store can delete by 'range'`() { - runBlocking { - history.recordVisit("http://www.mozilla.org/1", PageVisit(VisitType.TYPED)) - Thread.sleep(10) - history.recordVisit("http://www.mozilla.org/2", PageVisit(VisitType.DOWNLOAD)) - Thread.sleep(10) - history.recordVisit("http://www.mozilla.org/3", PageVisit(VisitType.BOOKMARK)) - } + fun `store can delete by 'range'`() = runTestOnMain { + history.recordVisit("http://www.mozilla.org/1", PageVisit(VisitType.TYPED)) + advanceUntilIdle() + history.recordVisit("http://www.mozilla.org/2", PageVisit(VisitType.DOWNLOAD)) + advanceUntilIdle() + history.recordVisit("http://www.mozilla.org/3", PageVisit(VisitType.BOOKMARK)) - val ts = runBlocking { - val visits = history.getDetailedVisits(0, Long.MAX_VALUE) + var visits = history.getDetailedVisits(0, Long.MAX_VALUE) + assertEquals(3, visits.size) + val ts = visits[1].visitTime - assertEquals(3, visits.size) - visits[1].visitTime - } + history.deleteVisitsBetween(ts - 1, ts + 1) + + visits = history.getDetailedVisits(0, Long.MAX_VALUE) - runBlocking { - history.deleteVisitsBetween(ts - 1, ts + 1) - } - val visits = runBlocking { - history.getDetailedVisits(0, Long.MAX_VALUE) - } assertEquals(2, visits.size) assertEquals("http://www.mozilla.org/1", visits[0].url) @@ -537,41 +538,28 @@ class PlacesHistoryStorageTest { } @Test - fun `store can delete visit by 'url' and 'timestamp'`() { - runBlocking { - history.recordVisit("http://www.mozilla.org/1", PageVisit(VisitType.TYPED)) - Thread.sleep(10) - history.recordVisit("http://www.mozilla.org/2", PageVisit(VisitType.DOWNLOAD)) - Thread.sleep(10) - history.recordVisit("http://www.mozilla.org/3", PageVisit(VisitType.BOOKMARK)) - } - - val ts = runBlocking { - val visits = history.getDetailedVisits(0, Long.MAX_VALUE) + fun `store can delete visit by 'url' and 'timestamp'`() = runTestOnMain { + history.recordVisit("http://www.mozilla.org/1", PageVisit(VisitType.TYPED)) + Thread.sleep(10) + history.recordVisit("http://www.mozilla.org/2", PageVisit(VisitType.DOWNLOAD)) + Thread.sleep(10) + history.recordVisit("http://www.mozilla.org/3", PageVisit(VisitType.BOOKMARK)) - assertEquals(3, visits.size) - visits[1].visitTime - } + var visits = history.getDetailedVisits(0, Long.MAX_VALUE) + assertEquals(3, visits.size) + val ts = visits[1].visitTime - runBlocking { - history.deleteVisit("http://www.mozilla.org/4", 111) - // There are no visits for this url, delete is a no-op. - assertEquals(3, history.getDetailedVisits(0, Long.MAX_VALUE).size) - } + history.deleteVisit("http://www.mozilla.org/4", 111) + // There are no visits for this url, delete is a no-op. + assertEquals(3, history.getDetailedVisits(0, Long.MAX_VALUE).size) - runBlocking { - history.deleteVisit("http://www.mozilla.org/1", ts) - // There is no such visit for this url, delete is a no-op. - assertEquals(3, history.getDetailedVisits(0, Long.MAX_VALUE).size) - } + history.deleteVisit("http://www.mozilla.org/1", ts) + // There is no such visit for this url, delete is a no-op. + assertEquals(3, history.getDetailedVisits(0, Long.MAX_VALUE).size) - runBlocking { - history.deleteVisit("http://www.mozilla.org/2", ts) - } + history.deleteVisit("http://www.mozilla.org/2", ts) - val visits = runBlocking { - history.getDetailedVisits(0, Long.MAX_VALUE) - } + visits = history.getDetailedVisits(0, Long.MAX_VALUE) assertEquals(2, visits.size) assertEquals("http://www.mozilla.org/1", visits[0].url) @@ -579,12 +567,12 @@ class PlacesHistoryStorageTest { } @Test - fun `can run maintanence on the store`() = runBlocking { + fun `can run maintanence on the store`() = runTestOnMain { history.runMaintenance() } @Test - fun `can run prune on the store`() = runBlocking { + fun `can run prune on the store`() = runTestOnMain { // Empty. history.prune() history.recordVisit("http://www.mozilla.org/1", PageVisit(VisitType.TYPED)) @@ -602,7 +590,7 @@ class PlacesHistoryStorageTest { internal class MockingPlacesHistoryStorage(override val places: Connection) : PlacesHistoryStorage(testContext) @Test - fun `storage passes through sync calls`() = runBlocking { + fun `storage passes through sync calls`() = runTestOnMain { var passedAuthInfo: SyncAuthInfo? = null val conn = object : Connection { override fun reader(): PlacesReaderConnection { @@ -659,7 +647,7 @@ class PlacesHistoryStorageTest { } @Test - fun `storage passes through sync OK results`() = runBlocking { + fun `storage passes through sync OK results`() = runTestOnMain { val conn = object : Connection { override fun reader(): PlacesReaderConnection { fail() @@ -705,7 +693,7 @@ class PlacesHistoryStorageTest { } @Test - fun `storage passes through sync exceptions`() = runBlocking { + fun `storage passes through sync exceptions`() = runTestOnMain { // Can be any PlacesException val exception = PlacesException.UrlParseFailed("test error") val conn = object : Connection { @@ -761,7 +749,7 @@ class PlacesHistoryStorageTest { } @Test(expected = PlacesException::class) - fun `storage re-throws sync panics`() = runBlocking { + fun `storage re-throws sync panics`() = runTestOnMain { val exception = PlacesException.UnexpectedPlacesException("test panic") val conn = object : Connection { override fun reader(): PlacesReaderConnection { @@ -837,7 +825,7 @@ class PlacesHistoryStorageTest { } @Test - fun `history import v38 populated`() = runBlocking { + fun `history import v38 populated`() = runTestOnMain { val path = getTestPath("databases/populated-v38.db").absolutePath var visits = history.getDetailedVisits(0, Long.MAX_VALUE) assertEquals(0, visits.size) @@ -868,7 +856,7 @@ class PlacesHistoryStorageTest { } @Test - fun `history import v39 populated`() = runBlocking { + fun `history import v39 populated`() = runTestOnMain { val path = getTestPath("databases/populated-v39.db").absolutePath var visits = history.getDetailedVisits(0, Long.MAX_VALUE) assertEquals(0, visits.size) @@ -931,7 +919,7 @@ class PlacesHistoryStorageTest { } @Test - fun `history import v34 populated`() = runBlocking { + fun `history import v34 populated`() = runTestOnMain { val path = getTestPath("databases/history-v34.db").absolutePath var visits = history.getDetailedVisits(0, Long.MAX_VALUE) assertEquals(0, visits.size) @@ -992,7 +980,7 @@ class PlacesHistoryStorageTest { } @Test - fun `record and get latest history metadata by url`() = runBlocking { + fun `record and get latest history metadata by url`() = runTestOnMain { val metaKey = HistoryMetadataKey( url = "https://doc.rust-lang.org/std/macro.assert_eq.html", searchTerm = "rust assert_eq", @@ -1009,7 +997,7 @@ class PlacesHistoryStorageTest { } @Test - fun `get history query`() = runBlocking { + fun `get history query`() = runTestOnMain { assertEquals(0, history.queryHistoryMetadata("keystore", 1).size) val metaKey1 = HistoryMetadataKey( @@ -1087,7 +1075,7 @@ class PlacesHistoryStorageTest { } @Test - fun `get history metadata between`() = runBlocking { + fun `get history metadata between`() = runTestOnMain { assertEquals(0, history.getHistoryMetadataBetween(-1, 0).size) assertEquals(0, history.getHistoryMetadataBetween(0, Long.MAX_VALUE).size) assertEquals(0, history.getHistoryMetadataBetween(Long.MAX_VALUE, Long.MIN_VALUE).size) @@ -1140,7 +1128,7 @@ class PlacesHistoryStorageTest { } @Test - fun `get history metadata since`() = runBlocking { + fun `get history metadata since`() = runTestOnMain { val beginning = System.currentTimeMillis() assertEquals(0, history.getHistoryMetadataSince(-1).size) @@ -1198,7 +1186,7 @@ class PlacesHistoryStorageTest { } @Test - fun `delete history metadata by search term`() = runBlocking { + fun `delete history metadata by search term`() = runTestOnMain { // Able to operate against an empty db history.deleteHistoryMetadata("test") history.deleteHistoryMetadata("") @@ -1312,7 +1300,7 @@ class PlacesHistoryStorageTest { } @Test - fun `safe read from places`() = runBlocking { + fun `safe read from places`() = runTestOnMain { val result = history.handlePlacesExceptions("test", default = emptyList()) { // Can be any PlacesException error throw PlacesException.PlacesConnectionBusy("test") @@ -1344,7 +1332,7 @@ class PlacesHistoryStorageTest { } @Test - fun `delete history metadata by url`() = runBlocking { + fun `delete history metadata by url`() = runTestOnMain { // Able to operate against an empty db history.deleteHistoryMetadataForUrl("https://mozilla.org") history.deleteHistoryMetadataForUrl("") diff --git a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt index 5c3baab473d..768c56c0355 100644 --- a/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt +++ b/components/browser/storage-sync/src/test/java/mozilla/components/browser/storage/sync/RemoteTabsStorageTest.kt @@ -6,7 +6,7 @@ package mozilla.components.browser.storage.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.appservices.remotetabs.ClientRemoteTabs import mozilla.appservices.remotetabs.DeviceType import mozilla.appservices.remotetabs.RemoteTab @@ -48,7 +48,7 @@ class RemoteTabsStorageTest { } @Test - fun `store() translates tabs to rust format`() = runBlocking { + fun `store() translates tabs to rust format`() = runTest { remoteTabs.store( listOf( Tab( @@ -86,7 +86,7 @@ class RemoteTabsStorageTest { } @Test - fun `getAll() translates tabs to our format`() = runBlocking { + fun `getAll() translates tabs to our format`() = runTest { `when`(apiMock.getAll()).thenReturn( listOf( ClientRemoteTabs( @@ -137,7 +137,7 @@ class RemoteTabsStorageTest { } @Test - fun `exceptions from getAll are propagated to the crash reporter`() = runBlocking { + fun `exceptions from getAll are propagated to the crash reporter`() = runTest { val throwable = RemoteTabProviderException("test") `when`(apiMock.getAll()).thenAnswer { throw throwable } diff --git a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt index f68009ad21c..07e35e6ae58 100644 --- a/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt +++ b/components/browser/thumbnails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt @@ -7,17 +7,18 @@ package mozilla.components.browser.thumbnails.storage import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.UnconfinedTestDispatcher import mozilla.components.concept.base.images.ImageLoadRequest import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.`when` @@ -26,7 +27,9 @@ import org.mockito.Mockito.spy @RunWith(AndroidJUnit4::class) class ThumbnailStorageTest { - private val testDispatcher = UnconfinedTestDispatcher() + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val testDispatcher = coroutinesTestRule.testDispatcher @Before @After @@ -35,7 +38,7 @@ class ThumbnailStorageTest { } @Test - fun `clearThumbnails`() = runBlocking { + fun `clearThumbnails`() = runTestOnMain { val bitmap: Bitmap = mock() val thumbnailStorage = spy(ThumbnailStorage(testContext, testDispatcher)) @@ -54,7 +57,7 @@ class ThumbnailStorageTest { } @Test - fun `deleteThumbnail`() = runBlocking { + fun `deleteThumbnail`() = runTestOnMain { val request = "test-tab1" val bitmap: Bitmap = mock() val thumbnailStorage = spy(ThumbnailStorage(testContext, testDispatcher)) @@ -69,7 +72,7 @@ class ThumbnailStorageTest { } @Test - fun `saveThumbnail`() = runBlocking { + fun `saveThumbnail`() = runTestOnMain { val request = ImageLoadRequest("test-tab1", 100) val bitmap: Bitmap = mock() val thumbnailStorage = spy(ThumbnailStorage(testContext)) @@ -83,7 +86,7 @@ class ThumbnailStorageTest { } @Test - fun `loadThumbnail`() = runBlocking { + fun `loadThumbnail`() = runTestOnMain { val request = ImageLoadRequest("test-tab1", 100) val bitmap: Bitmap = mock() val thumbnailStorage = spy(ThumbnailStorage(testContext, testDispatcher)) diff --git a/components/browser/toolbar/build.gradle b/components/browser/toolbar/build.gradle index 1a4fd7d85f4..a7be0de798c 100644 --- a/components/browser/toolbar/build.gradle +++ b/components/browser/toolbar/build.gradle @@ -47,6 +47,7 @@ dependencies { testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito + testImplementation Dependencies.testing_coroutines } apply from: '../../../publish.gradle' diff --git a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/AsyncFilterListenerTest.kt b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/AsyncFilterListenerTest.kt index 22d87bc5d42..56a289b940e 100644 --- a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/AsyncFilterListenerTest.kt +++ b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/AsyncFilterListenerTest.kt @@ -4,11 +4,12 @@ package mozilla.components.browser.toolbar +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.isActive -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.toolbar.AutocompleteDelegate import mozilla.components.concept.toolbar.AutocompleteResult import mozilla.components.support.test.mock @@ -24,9 +25,10 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import java.util.concurrent.Executor +@ExperimentalCoroutinesApi // for runTest class AsyncFilterListenerTest { @Test - fun `filter listener cancels prior filter executions`() = runBlocking { + fun `filter listener cancels prior filter executions`() = runTest { val urlView: AutocompleteView = mock() val filter: suspend (String, AutocompleteDelegate) -> Unit = mock() @@ -46,7 +48,7 @@ class AsyncFilterListenerTest { } @Test - fun `filter delegate checks for cancellations before it runs, passes results to autocomplete view`() = runBlocking { + fun `filter delegate checks for cancellations before it runs, passes results to autocomplete view`() = runTest { var filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> assertEquals("test", query) delegate.applyAutocompleteResult( @@ -132,7 +134,7 @@ class AsyncFilterListenerTest { } @Test - fun `delegate discards stale results`() = runBlocking { + fun `delegate discards stale results`() = runTest { val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> assertEquals("test", query) delegate.applyAutocompleteResult( @@ -169,7 +171,7 @@ class AsyncFilterListenerTest { } @Test - fun `delegate discards stale lack of results`() = runBlocking { + fun `delegate discards stale lack of results`() = runTest { val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> assertEquals("test", query) delegate.noAutocompleteResult("test") @@ -198,7 +200,7 @@ class AsyncFilterListenerTest { } @Test - fun `delegate passes through non-stale lack of results`() = runBlocking { + fun `delegate passes through non-stale lack of results`() = runTest { val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> assertEquals("test", query) delegate.noAutocompleteResult("test") @@ -230,7 +232,7 @@ class AsyncFilterListenerTest { } @Test - fun `delegate discards results if parent scope was cancelled`() = runBlocking { + fun `delegate discards results if parent scope was cancelled`() = runTest { var preservedDelegate: AutocompleteDelegate? = null val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> @@ -291,7 +293,7 @@ class AsyncFilterListenerTest { } @Test - fun `delegate discards lack of results if parent scope was cancelled`() = runBlocking { + fun `delegate discards lack of results if parent scope was cancelled`() = runTest { var preservedDelegate: AutocompleteDelegate? = null val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> diff --git a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/edit/EditToolbarTest.kt b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/edit/EditToolbarTest.kt index 19ed8a46d12..9438360a1b8 100644 --- a/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/edit/EditToolbarTest.kt +++ b/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/edit/EditToolbarTest.kt @@ -7,7 +7,8 @@ package mozilla.components.browser.toolbar.edit import android.view.KeyEvent import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.R import mozilla.components.concept.toolbar.AutocompleteDelegate @@ -27,6 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import java.util.concurrent.CountDownLatch +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class EditToolbarTest { private fun createEditToolbar(): Pair { @@ -39,7 +41,7 @@ class EditToolbarTest { } @Test - fun `entered text is forwarded to async autocomplete filter`() { + fun `entered text is forwarded to async autocomplete filter`() = runTest { val toolbar = BrowserToolbar(testContext) toolbar.edit.views.url.onAttachedToWindow() @@ -55,9 +57,7 @@ class EditToolbarTest { // Autocomplete filter will be invoked on a worker thread. // Serialize here for the sake of tests. - runBlocking { - latch.await() - } + latch.await() assertEquals("Hello", invokedWithParams!![0]) assertTrue(invokedWithParams!![1] is AutocompleteDelegate) diff --git a/components/concept/fetch/build.gradle b/components/concept/fetch/build.gradle index 18b2f919682..1bf9a560c01 100644 --- a/components/concept/fetch/build.gradle +++ b/components/concept/fetch/build.gradle @@ -31,6 +31,7 @@ dependencies { testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver + testImplementation Dependencies.testing_coroutines testImplementation project(':support-test') } diff --git a/components/concept/fetch/src/test/java/mozilla/components/concept/fetch/ClientTest.kt b/components/concept/fetch/src/test/java/mozilla/components/concept/fetch/ClientTest.kt index d1d1304caf5..5c8c043b229 100644 --- a/components/concept/fetch/src/test/java/mozilla/components/concept/fetch/ClientTest.kt +++ b/components/concept/fetch/src/test/java/mozilla/components/concept/fetch/ClientTest.kt @@ -4,14 +4,16 @@ package mozilla.components.concept.fetch +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Test class ClientTest { + @ExperimentalCoroutinesApi @Test - fun `Async request with coroutines`() = runBlocking { + fun `Async request with coroutines`() = runTest { val client = TestClient(responseBody = Response.Body("Hello World".byteInputStream())) val request = Request("https://www.mozilla.org") diff --git a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt index b8c9e8596c4..3cda17c2311 100644 --- a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt +++ b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/AutoPushObserverTest.kt @@ -5,7 +5,6 @@ package mozilla.components.feature.accounts.push import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking import mozilla.components.concept.sync.ConstellationState import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.DeviceConstellation @@ -17,6 +16,7 @@ import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.nullable import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Rule import org.junit.Test import org.mockito.Mockito.`when` @@ -25,6 +25,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoInteractions import org.mockito.stubbing.OngoingStubbing +@ExperimentalCoroutinesApi // for runTestOnMain class AutoPushObserverTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() @@ -36,7 +37,7 @@ class AutoPushObserverTest { @ExperimentalCoroutinesApi @Test - fun `messages are forwarded to account manager`() = runBlocking { + fun `messages are forwarded to account manager`() = runTestOnMain { val observer = AutoPushObserver(manager, mock(), "test") `when`(manager.authenticatedAccount()).thenReturn(account) @@ -49,7 +50,7 @@ class AutoPushObserverTest { } @Test - fun `account manager is not invoked if no account is available`() = runBlocking { + fun `account manager is not invoked if no account is available`() = runTestOnMain { val observer = AutoPushObserver(manager, mock(), "test") observer.onMessageReceived("test", "foobar".toByteArray()) @@ -60,7 +61,7 @@ class AutoPushObserverTest { } @Test - fun `messages are not forwarded to account manager if they are for a different scope`() = runBlocking { + fun `messages are not forwarded to account manager if they are for a different scope`() = runTestOnMain { val observer = AutoPushObserver(manager, mock(), "fake") observer.onMessageReceived("test", "foobar".toByteArray()) @@ -71,7 +72,7 @@ class AutoPushObserverTest { @ExperimentalCoroutinesApi @Test - fun `subscription changes are forwarded to account manager`() = runBlocking { + fun `subscription changes are forwarded to account manager`() = runTestOnMain { val observer = AutoPushObserver(manager, pushFeature, "test") whenSubscribe() @@ -91,7 +92,7 @@ class AutoPushObserverTest { } @Test - fun `do nothing if there is no account manager`() = runBlocking { + fun `do nothing if there is no account manager`() = runTestOnMain { val observer = AutoPushObserver(manager, pushFeature, "test") whenSubscribe() @@ -103,7 +104,7 @@ class AutoPushObserverTest { } @Test - fun `subscription changes are not forwarded to account manager if they are for a different scope`() = runBlocking { + fun `subscription changes are not forwarded to account manager if they are for a different scope`() = runTestOnMain { val observer = AutoPushObserver(manager, mock(), "fake") `when`(manager.authenticatedAccount()).thenReturn(account) diff --git a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/ConstellationObserverTest.kt b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/ConstellationObserverTest.kt index 76b218e8de8..a45cba6417c 100644 --- a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/ConstellationObserverTest.kt +++ b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/ConstellationObserverTest.kt @@ -8,7 +8,7 @@ package mozilla.components.feature.accounts.push import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.concept.base.crash.CrashReporting import mozilla.components.concept.sync.ConstellationState import mozilla.components.concept.sync.Device @@ -22,6 +22,7 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.nullable import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Before import org.junit.Rule import org.junit.Test @@ -32,6 +33,7 @@ import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.stubbing.OngoingStubbing +@ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class ConstellationObserverTest { @@ -56,7 +58,7 @@ class ConstellationObserverTest { val coroutinesTestRule = MainCoroutineRule() @Test - fun `first subscribe works`() = runBlocking { + fun `first subscribe works`() = runTestOnMain { val observer = ConstellationObserver(context, push, "testScope", account, verifier, crashReporter) verifyNoInteractions(push) @@ -74,7 +76,7 @@ class ConstellationObserverTest { } @Test - fun `re-subscribe doesn't update constellation on same endpoint`() = runBlocking { + fun `re-subscribe doesn't update constellation on same endpoint`() = runTestOnMain { val observer = ConstellationObserver(context, push, "testScope", account, verifier, crashReporter) verifyNoInteractions(push) @@ -93,7 +95,7 @@ class ConstellationObserverTest { } @Test - fun `re-subscribe update constellations on same endpoint if expired`() = runBlocking { + fun `re-subscribe update constellations on same endpoint if expired`() = runTestOnMain { val observer = ConstellationObserver(context, push, "testScope", account, verifier, crashReporter) verifyNoInteractions(push) diff --git a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabFeatureKtTest.kt b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabFeatureKtTest.kt index e991c17035e..4b7fa7c135e 100644 --- a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabFeatureKtTest.kt +++ b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabFeatureKtTest.kt @@ -5,7 +5,7 @@ package mozilla.components.feature.accounts.push import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.concept.sync.DeviceConstellation import mozilla.components.concept.sync.OAuthAccount import mozilla.components.service.fxa.manager.FxaAccountManager @@ -21,7 +21,7 @@ import org.mockito.Mockito.verify @ExperimentalCoroutinesApi class SendTabFeatureKtTest { @Test - fun `feature register all observers`() = runBlockingTest { + fun `feature register all observers`() = runTest { val accountManager: FxaAccountManager = mock() SendTabFeature( diff --git a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabUseCasesTest.kt b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabUseCasesTest.kt index df075193329..6c2ac871727 100644 --- a/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabUseCasesTest.kt +++ b/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/SendTabUseCasesTest.kt @@ -5,8 +5,6 @@ package mozilla.components.feature.accounts.push import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.concept.sync.ConstellationState import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.DeviceCapability @@ -18,8 +16,11 @@ import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.support.test.any import mozilla.components.support.test.eq import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert import org.junit.Before +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.`when` import org.mockito.Mockito.never @@ -30,6 +31,9 @@ import java.util.UUID @ExperimentalCoroutinesApi class SendTabUseCasesTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val manager: FxaAccountManager = mock() private val account: OAuthAccount = mock() private val constellation: DeviceConstellation = mock() @@ -43,7 +47,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabUseCase - tab is sent to capable device`() = runBlockingTest { + fun `SendTabUseCase - tab is sent to capable device`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice() @@ -57,7 +61,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabUseCase - tabs are sent to capable device`() = runBlockingTest { + fun `SendTabUseCase - tabs are sent to capable device`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice() val tab = TabData("Title", "http://example.com") @@ -72,7 +76,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabUseCase - tabs are NOT sent to incapable devices`() = runBlockingTest { + fun `SendTabUseCase - tabs are NOT sent to incapable devices`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = mock() val tab = TabData("Title", "http://example.com") @@ -92,7 +96,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabUseCase - ONLY tabs with valid schema are sent to capable device`() = runBlockingTest { + fun `SendTabUseCase - ONLY tabs with valid schema are sent to capable device`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice() val tab = TabData("Title", "http://example.com") @@ -109,7 +113,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabUseCase - device id does not match when sending single tab`() = runBlockingTest { + fun `SendTabUseCase - device id does not match when sending single tab`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice("123") val tab = TabData("Title", "http://example.com") @@ -132,7 +136,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabUseCase - device id does not match when sending tabs`() = runBlockingTest { + fun `SendTabUseCase - device id does not match when sending tabs`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice("123") val tab = TabData("Title", "http://example.com") @@ -155,7 +159,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabToAllUseCase - tab is sent to capable devices`() = runBlockingTest { + fun `SendTabToAllUseCase - tab is sent to capable devices`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice() val device2: Device = generateDevice() @@ -172,7 +176,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabToAllUseCase - tabs is sent to capable devices`() = runBlockingTest { + fun `SendTabToAllUseCase - tabs is sent to capable devices`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice() val device2: Device = generateDevice() @@ -190,53 +194,49 @@ class SendTabUseCasesTest { } @Test - fun `SendTabToAllUseCase - tab is NOT sent to incapable devices`() { + fun `SendTabToAllUseCase - tab is NOT sent to incapable devices`() = runTestOnMain { val useCases = SendTabUseCases(manager) val tab = TabData("Mozilla", "https://mozilla.org") val device: Device = mock() val device2: Device = mock() - runBlocking { - useCases.sendToAllAsync(tab) + useCases.sendToAllAsync(tab) - verify(constellation, never()).sendCommandToDevice(any(), any()) + verify(constellation, never()).sendCommandToDevice(any(), any()) - `when`(device.id).thenReturn("123") - `when`(device2.id).thenReturn("456") - `when`(state.otherDevices).thenReturn(listOf(device, device2)) + `when`(device.id).thenReturn("123") + `when`(device2.id).thenReturn("456") + `when`(state.otherDevices).thenReturn(listOf(device, device2)) - useCases.sendToAllAsync(tab) + useCases.sendToAllAsync(tab) - verify(constellation, never()).sendCommandToDevice(any(), any()) - } + verify(constellation, never()).sendCommandToDevice(any(), any()) } @Test - fun `SendTabToAllUseCase - tabs are NOT sent to capable devices`() { + fun `SendTabToAllUseCase - tabs are NOT sent to capable devices`() = runTestOnMain { val useCases = SendTabUseCases(manager) val tab = TabData("Mozilla", "https://mozilla.org") val tab2 = TabData("Firefox", "https://firefox.com") val device: Device = mock() val device2: Device = mock() - runBlocking { - useCases.sendToAllAsync(tab) + useCases.sendToAllAsync(tab) - verify(constellation, never()).sendCommandToDevice(any(), any()) + verify(constellation, never()).sendCommandToDevice(any(), any()) - `when`(device.id).thenReturn("123") - `when`(device2.id).thenReturn("456") - `when`(state.otherDevices).thenReturn(listOf(device, device2)) + `when`(device.id).thenReturn("123") + `when`(device2.id).thenReturn("456") + `when`(state.otherDevices).thenReturn(listOf(device, device2)) - useCases.sendToAllAsync(listOf(tab, tab2)) + useCases.sendToAllAsync(listOf(tab, tab2)) - verify(constellation, never()).sendCommandToDevice(eq("123"), any()) - verify(constellation, never()).sendCommandToDevice(eq("456"), any()) - } + verify(constellation, never()).sendCommandToDevice(eq("123"), any()) + verify(constellation, never()).sendCommandToDevice(eq("456"), any()) } @Test - fun `SendTabToAllUseCase - ONLY tabs with valid schema are sent to capable devices`() = runBlockingTest { + fun `SendTabToAllUseCase - ONLY tabs with valid schema are sent to capable devices`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = generateDevice() val device2: Device = generateDevice() @@ -258,7 +258,7 @@ class SendTabUseCasesTest { } @Test - fun `SendTabUseCase - result is false if any send tab action fails`() = runBlockingTest { + fun `SendTabUseCase - result is false if any send tab action fails`() = runTestOnMain { val useCases = SendTabUseCases(manager, coroutineContext) val device: Device = mock() val tab = TabData("Title", "http://example.com") @@ -280,43 +280,39 @@ class SendTabUseCasesTest { } @Test - fun `filter devices returns capable devices`() { + fun `filter devices returns capable devices`() = runTestOnMain { var executed = false - runBlocking { - `when`(state.otherDevices).thenReturn(listOf(generateDevice(), generateDevice())) - filterSendTabDevices(manager) { _, _ -> - executed = true - } - - Assert.assertTrue(executed) + `when`(state.otherDevices).thenReturn(listOf(generateDevice(), generateDevice())) + filterSendTabDevices(manager) { _, _ -> + executed = true } + + Assert.assertTrue(executed) } @Test - fun `filter devices does NOT provide for incapable devices`() { + fun `filter devices does NOT provide for incapable devices`() = runTestOnMain { val device: Device = mock() val device2: Device = mock() - runBlocking { - `when`(device.id).thenReturn("123") - `when`(device2.id).thenReturn("456") - `when`(state.otherDevices).thenReturn(listOf(device, device2)) - - filterSendTabDevices(manager) { _, filteredDevices -> - Assert.assertTrue(filteredDevices.isEmpty()) - } - - val accountManager: FxaAccountManager = mock() - val account: OAuthAccount = mock() - val constellation: DeviceConstellation = mock() - val state: ConstellationState = mock() - `when`(accountManager.authenticatedAccount()).thenReturn(account) - `when`(account.deviceConstellation()).thenReturn(constellation) - `when`(constellation.state()).thenReturn(state) - - filterSendTabDevices(mock()) { _, _ -> - Assert.fail() - } + `when`(device.id).thenReturn("123") + `when`(device2.id).thenReturn("456") + `when`(state.otherDevices).thenReturn(listOf(device, device2)) + + filterSendTabDevices(manager) { _, filteredDevices -> + Assert.assertTrue(filteredDevices.isEmpty()) + } + + val accountManager: FxaAccountManager = mock() + val account: OAuthAccount = mock() + val constellation: DeviceConstellation = mock() + val state: ConstellationState = mock() + `when`(accountManager.authenticatedAccount()).thenReturn(account) + `when`(account.deviceConstellation()).thenReturn(constellation) + `when`(constellation.state()).thenReturn(state) + + filterSendTabDevices(mock()) { _, _ -> + Assert.fail() } } diff --git a/components/feature/accounts/build.gradle b/components/feature/accounts/build.gradle index e128933c515..c37b33099e1 100644 --- a/components/feature/accounts/build.gradle +++ b/components/feature/accounts/build.gradle @@ -45,6 +45,7 @@ dependencies { testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_robolectric + testImplementation Dependencies.testing_coroutines testImplementation project(':support-test') } diff --git a/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt b/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt index 00546b9377a..689aca6c851 100644 --- a/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt +++ b/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt @@ -8,7 +8,7 @@ import android.content.Context import android.os.Looper.getMainLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.request.RequestInterceptor import mozilla.components.concept.sync.AccountEventsObserver import mozilla.components.concept.sync.AuthFlowUrl @@ -76,7 +76,7 @@ class FirefoxAccountsAuthFeatureTest { @Config(sdk = [22]) @Test - fun `begin authentication`() = runBlocking { + fun `begin authentication`() = runTest { val manager = prepareAccountManagerForSuccessfulAuthentication( this.coroutineContext ) @@ -95,7 +95,7 @@ class FirefoxAccountsAuthFeatureTest { @Config(sdk = [22]) @Test - fun `begin pairing authentication`() = runBlocking { + fun `begin pairing authentication`() = runTest { val manager = prepareAccountManagerForSuccessfulAuthentication( this.coroutineContext ) @@ -114,7 +114,7 @@ class FirefoxAccountsAuthFeatureTest { @Config(sdk = [22]) @Test - fun `begin authentication with errors`() = runBlocking { + fun `begin authentication with errors`() = runTest { val manager = prepareAccountManagerForFailedAuthentication( this.coroutineContext ) @@ -135,7 +135,7 @@ class FirefoxAccountsAuthFeatureTest { @Config(sdk = [22]) @Test - fun `begin pairing authentication with errors`() = runBlocking { + fun `begin pairing authentication with errors`() = runTest { val manager = prepareAccountManagerForFailedAuthentication( this.coroutineContext ) @@ -155,7 +155,7 @@ class FirefoxAccountsAuthFeatureTest { } @Test - fun `auth interceptor`() = runBlocking { + fun `auth interceptor`() = runTest { val manager = mock() val redirectUrl = "https://accounts.firefox.com/oauth/success/123" val feature = FirefoxAccountsAuthFeature( diff --git a/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt b/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt index 40fe978fa12..adaaa47b051 100644 --- a/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt +++ b/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt @@ -6,7 +6,7 @@ package mozilla.components.feature.accounts import android.os.Looper.getMainLooper import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore @@ -579,7 +579,7 @@ class FxaWebChannelFeatureTest { // Receiving an oauth-login message account manager accepts the request @Test - fun `COMMAND_OAUTH_LOGIN web-channel must be processed through when the accountManager accepts the request`() = runBlocking { + fun `COMMAND_OAUTH_LOGIN web-channel must be processed through when the accountManager accepts the request`() = runTest { val accountManager: FxaAccountManager = mock() // syncConfig is null by default (is not configured) val engineSession: EngineSession = mock() val ext: WebExtension = mock() @@ -612,7 +612,7 @@ class FxaWebChannelFeatureTest { // Receiving an oauth-login message account manager refuses the request @Test - fun `COMMAND_OAUTH_LOGIN web-channel must be processed when the accountManager refuses the request`() = runBlocking { + fun `COMMAND_OAUTH_LOGIN web-channel must be processed when the accountManager refuses the request`() = runTest { val accountManager: FxaAccountManager = mock() // syncConfig is null by default (is not configured) val engineSession: EngineSession = mock() val ext: WebExtension = mock() diff --git a/components/feature/addons/src/test/java/AddonManagerTest.kt b/components/feature/addons/src/test/java/AddonManagerTest.kt index 75cdfaa807d..d93550c826e 100644 --- a/components/feature/addons/src/test/java/AddonManagerTest.kt +++ b/components/feature/addons/src/test/java/AddonManagerTest.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.WebExtensionAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.WebExtensionState @@ -30,6 +29,7 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import mozilla.components.support.webextensions.WebExtensionSupport import org.junit.After @@ -68,7 +68,7 @@ class AddonManagerTest { } @Test - fun `getAddons - queries addons from provider and updates installation state`() = runBlocking { + fun `getAddons - queries addons from provider and updates installation state`() = runTestOnMain { // Prepare addons provider val addon1 = Addon(id = "ext1") val addon2 = Addon(id = "ext2") @@ -156,7 +156,7 @@ class AddonManagerTest { } @Test - fun `getAddons - returns temporary add-ons as supported`() = runBlocking { + fun `getAddons - returns temporary add-ons as supported`() = runTestOnMain { val addonsProvider: AddonsProvider = mock() whenever(addonsProvider.getAvailableAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(listOf()) @@ -199,7 +199,7 @@ class AddonManagerTest { } @Test(expected = AddonManagerException::class) - fun `getAddons - wraps exceptions and rethrows them`() = runBlocking { + fun `getAddons - wraps exceptions and rethrows them`() = runTestOnMain { val store = BrowserStore() val engine: Engine = mock() @@ -217,7 +217,7 @@ class AddonManagerTest { } @Test - fun `getAddons - filters unneeded locales`() = runBlocking { + fun `getAddons - filters unneeded locales`() = runTestOnMain { val addon = Addon( id = "addon1", translatableName = mapOf(Addon.DEFAULT_LOCALE to "name", "invalid1" to "Name", "invalid2" to "nombre"), @@ -247,7 +247,7 @@ class AddonManagerTest { } @Test - fun `getAddons - suspends until pending actions are completed`() { + fun `getAddons - suspends until pending actions are completed`() = runTestOnMain { val addon = Addon( id = "ext1", installedState = Addon.InstalledState("ext1", "1.0", "", true) @@ -265,11 +265,9 @@ class AddonManagerTest { } val addonsProvider: AddonsProvider = mock() - runBlocking { - whenever(addonsProvider.getAvailableAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(listOf(addon)) - WebExtensionSupport.initialize(engine, store) - WebExtensionSupport.installedExtensions[addon.id] = extension - } + whenever(addonsProvider.getAvailableAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(listOf(addon)) + WebExtensionSupport.initialize(engine, store) + WebExtensionSupport.installedExtensions[addon.id] = extension val addonManager = AddonManager(store, mock(), addonsProvider, mock()) addonManager.installAddon(addon) @@ -283,10 +281,8 @@ class AddonManagerTest { getAddonsResult = addonManager.getAddons(waitForPendingActions = false) } - runBlocking { - nonSuspendingJob.join() - assertNotNull(getAddonsResult) - } + nonSuspendingJob.join() + assertNotNull(getAddonsResult) getAddonsResult = null val suspendingJob = CoroutineScope(Dispatchers.IO).launch { @@ -295,14 +291,12 @@ class AddonManagerTest { addonManager.pendingAddonActions.forEach { it.complete(Unit) } - runBlocking { - suspendingJob.join() - assertNotNull(getAddonsResult) - } + suspendingJob.join() + assertNotNull(getAddonsResult) } @Test - fun `getAddons - passes on allowCache parameter`() = runBlocking { + fun `getAddons - passes on allowCache parameter`() = runTestOnMain { val store = BrowserStore() val engine: Engine = mock() diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/amo/AddonCollectionProviderTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/amo/AddonCollectionProviderTest.kt index 9ef7128a9a5..8ecd25b40d3 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/amo/AddonCollectionProviderTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/amo/AddonCollectionProviderTest.kt @@ -6,7 +6,7 @@ package mozilla.components.feature.addons.amo import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Request import mozilla.components.concept.fetch.Response @@ -37,7 +37,7 @@ import java.util.concurrent.TimeUnit class AddonCollectionProviderTest { @Test - fun `getAvailableAddons - with a successful status response must contain add-ons`() = runBlocking { + fun `getAvailableAddons - with a successful status response must contain add-ons`() = runTest { val mockedClient = prepareClient(loadResourceAsString("/collection.json")) val provider = AddonCollectionProvider(testContext, client = mockedClient) val addons = provider.getAvailableAddons() @@ -96,7 +96,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAvailableAddons - with a successful status response must handle empty values`() = runBlocking { + fun `getAvailableAddons - with a successful status response must handle empty values`() = runTest { val client = prepareClient() val provider = AddonCollectionProvider(testContext, client = client) @@ -135,7 +135,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAvailableAddons - with a language`() = runBlocking { + fun `getAvailableAddons - with a language`() = runTest { val client = prepareClient(loadResourceAsString("/localized_collection.json")) val provider = AddonCollectionProvider(testContext, client = client) @@ -208,7 +208,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAvailableAddons - read timeout can be configured`() = runBlocking { + fun `getAvailableAddons - read timeout can be configured`() = runTest { val mockedClient = prepareClient() val provider = spy(AddonCollectionProvider(testContext, client = mockedClient)) @@ -224,7 +224,7 @@ class AddonCollectionProviderTest { } @Test(expected = IOException::class) - fun `getAvailableAddons - with unexpected status will throw exception`() = runBlocking { + fun `getAvailableAddons - with unexpected status will throw exception`() = runTest { val mockedClient = prepareClient(status = 500) val provider = AddonCollectionProvider(testContext, client = mockedClient) provider.getAvailableAddons() @@ -232,7 +232,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAvailableAddons - returns cached result if allowed and not expired`() = runBlocking { + fun `getAvailableAddons - returns cached result if allowed and not expired`() = runTest { val mockedClient = prepareClient(loadResourceAsString("/collection.json")) val provider = spy(AddonCollectionProvider(testContext, client = mockedClient)) @@ -250,7 +250,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAvailableAddons - returns cached result if allowed and fetch failed`() = runBlocking { + fun `getAvailableAddons - returns cached result if allowed and fetch failed`() = runTest { val mockedClient: Client = mock() val exception = IOException("test") val cachedAddons: List = emptyList() @@ -296,7 +296,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAvailableAddons - writes response to cache if configured`() = runBlocking { + fun `getAvailableAddons - writes response to cache if configured`() = runTest { val jsonResponse = loadResourceAsString("/collection.json") val mockedClient = prepareClient(jsonResponse) @@ -311,7 +311,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAvailableAddons - deletes unused cache files`() = runBlocking { + fun `getAvailableAddons - deletes unused cache files`() = runTest { val jsonResponse = loadResourceAsString("/collection.json") val mockedClient = prepareClient(jsonResponse) @@ -414,7 +414,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAddonIconBitmap - with a successful status will return a bitmap`() = runBlocking { + fun `getAddonIconBitmap - with a successful status will return a bitmap`() = runTest { val mockedClient = mock() val mockedResponse = mock() val stream: InputStream = javaClass.getResourceAsStream("/png/mozac.png")!!.buffered() @@ -441,7 +441,7 @@ class AddonCollectionProviderTest { } @Test - fun `getAddonIconBitmap - with an unsuccessful status will return null`() = runBlocking { + fun `getAddonIconBitmap - with an unsuccessful status will return null`() = runTest { val mockedClient = prepareClient(status = 500) val provider = AddonCollectionProvider(testContext, client = mockedClient) val addon = Addon( @@ -460,7 +460,7 @@ class AddonCollectionProviderTest { } @Test - fun `collection name can be configured`() = runBlocking { + fun `collection name can be configured`() = runTest { val mockedClient = prepareClient() val collectionName = "collection123" @@ -483,7 +483,7 @@ class AddonCollectionProviderTest { } @Test - fun `collection sort option can be specified`() = runBlocking { + fun `collection sort option can be specified`() = runTest { val mockedClient = prepareClient() val collectionName = "collection123" @@ -593,7 +593,7 @@ class AddonCollectionProviderTest { } @Test - fun `collection user can be configured`() = runBlocking { + fun `collection user can be configured`() = runTest { val mockedClient = prepareClient() val collectionUser = "user123" val collectionName = "collection123" @@ -622,7 +622,7 @@ class AddonCollectionProviderTest { } @Test - fun `default collection is used if not configured`() = runBlocking { + fun `default collection is used if not configured`() = runTest { val mockedClient = prepareClient() val provider = AddonCollectionProvider( @@ -645,7 +645,7 @@ class AddonCollectionProviderTest { } @Test - fun `cache file name is sanitized`() = runBlocking { + fun `cache file name is sanitized`() = runTest { val mockedClient = prepareClient() val collectionUser = "../../user" val collectionName = "../collection" diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/menu/WebExtensionActionMenuCandidateTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/menu/WebExtensionActionMenuCandidateTest.kt index b47baaa7a25..b3c6863544b 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/menu/WebExtensionActionMenuCandidateTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/menu/WebExtensionActionMenuCandidateTest.kt @@ -7,7 +7,7 @@ package mozilla.components.feature.addons.menu import android.graphics.Color import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.webextension.Action import mozilla.components.concept.menu.candidate.AsyncDrawableMenuIcon import mozilla.components.concept.menu.candidate.TextMenuIcon @@ -97,7 +97,7 @@ class WebExtensionActionMenuCandidateTest { } @Test - fun `create menu candidate with icon`() = runBlockingTest { + fun `create menu candidate with icon`() = runTest { var calledWith: Int = -1 val candidate = baseAction .copy( diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt index 5bc4fb42d80..54b7369ec27 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/DefaultSupportedAddonCheckerTest.kt @@ -16,13 +16,13 @@ import androidx.work.testing.WorkManagerTestInitHelper import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.runBlocking import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker.Companion.CHECKER_UNIQUE_PERIODIC_WORK_NAME import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker.Companion.WORK_TAG_PERIODIC import mozilla.components.support.base.worker.Frequency import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Before import org.junit.Rule import org.junit.Test @@ -57,51 +57,47 @@ class DefaultSupportedAddonCheckerTest { } @Test - fun `registerForChecks - schedule work for future checks`() { + fun `registerForChecks - schedule work for future checks`() = runTestOnMain { val frequency = Frequency(1, TimeUnit.DAYS) val intent = Intent() val checker = DefaultSupportedAddonsChecker(context, frequency, intent) val workId = CHECKER_UNIQUE_PERIODIC_WORK_NAME - runBlocking { - val workManger = WorkManager.getInstance(testContext) - val workData = workManger.getWorkInfosForUniqueWork(workId).await() + val workManger = WorkManager.getInstance(testContext) + val workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertTrue(workData.isEmpty()) + assertTrue(workData.isEmpty()) - checker.registerForChecks() + checker.registerForChecks() - assertExtensionIsRegisteredForChecks() - assertEquals(intent, SupportedAddonsWorker.onNotificationClickIntent) - // Cleaning work manager - workManger.cancelUniqueWork(workId) - } + assertExtensionIsRegisteredForChecks() + assertEquals(intent, SupportedAddonsWorker.onNotificationClickIntent) + // Cleaning work manager + workManger.cancelUniqueWork(workId) } @Test - fun `unregisterForChecks - will remove scheduled work for future checks`() { + fun `unregisterForChecks - will remove scheduled work for future checks`() = runTestOnMain { val frequency = Frequency(1, TimeUnit.DAYS) val checker = DefaultSupportedAddonsChecker(context, frequency) val workId = CHECKER_UNIQUE_PERIODIC_WORK_NAME - runBlocking { - val workManger = WorkManager.getInstance(testContext) - var workData = workManger.getWorkInfosForUniqueWork(workId).await() + val workManger = WorkManager.getInstance(testContext) + var workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertTrue(workData.isEmpty()) + assertTrue(workData.isEmpty()) - checker.registerForChecks() + checker.registerForChecks() - assertExtensionIsRegisteredForChecks() + assertExtensionIsRegisteredForChecks() - checker.unregisterForChecks() + checker.unregisterForChecks() - workData = workManger.getWorkInfosForUniqueWork(workId).await() + workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertEquals(WorkInfo.State.CANCELLED, workData.first().state) - } + assertEquals(WorkInfo.State.CANCELLED, workData.first().state) } private suspend fun assertExtensionIsRegisteredForChecks() { diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt index 7490ab29855..64d6052aa38 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/migration/SupportedAddonsWorkerTest.kt @@ -10,7 +10,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.work.ListenableWorker import androidx.work.await import androidx.work.testing.TestListenableWorkerBuilder -import kotlinx.coroutines.runBlocking import mozilla.components.concept.engine.webextension.EnableSource import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.AddonManager @@ -27,6 +26,7 @@ import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.After import org.junit.Assert.assertEquals @@ -57,7 +57,7 @@ class SupportedAddonsWorkerTest { } @Test - fun `doWork - will return Result_success and create a notification when a new supported add-on is found`() { + fun `doWork - will return Result_success and create a notification when a new supported add-on is found`() = runTestOnMain { val addonManager = mock() val worker = TestListenableWorkerBuilder(testContext).build() var throwable: Throwable? = null @@ -74,23 +74,21 @@ class SupportedAddonsWorkerTest { GlobalAddonDependencyProvider.initialize(addonManager, mock(), onCrash) val onErrorCaptor = argumentCaptor<((Throwable) -> Unit)>() - runBlocking { - whenever(addonManager.getAddons()).thenReturn(listOf(unsupportedAddon)) - val result = worker.startWork().await() + whenever(addonManager.getAddons()).thenReturn(listOf(unsupportedAddon)) + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.success(), result) + assertEquals(ListenableWorker.Result.success(), result) - val notificationId = SharedIdsHelper.getIdForTag(testContext, NOTIFICATION_TAG) - assertTrue(isNotificationVisible(notificationId)) - verify(addonManager).enableAddon(eq(unsupportedAddon), source = eq(EnableSource.APP_SUPPORT), onSuccess = any(), onError = onErrorCaptor.capture()) + val notificationId = SharedIdsHelper.getIdForTag(testContext, NOTIFICATION_TAG) + assertTrue(isNotificationVisible(notificationId)) + verify(addonManager).enableAddon(eq(unsupportedAddon), source = eq(EnableSource.APP_SUPPORT), onSuccess = any(), onError = onErrorCaptor.capture()) - onErrorCaptor.value.invoke(Exception()) - assertNotNull(throwable!!) - } + onErrorCaptor.value.invoke(Exception()) + assertNotNull(throwable!!) } @Test - fun `doWork - will try pass any exceptions to the crashReporter`() { + fun `doWork - will try pass any exceptions to the crashReporter`() = runTestOnMain { val addonManager = mock() val worker = TestListenableWorkerBuilder(testContext).build() var crashWasReported = false @@ -101,16 +99,14 @@ class SupportedAddonsWorkerTest { GlobalAddonDependencyProvider.initialize(addonManager, mock(), crashReporter) GlobalAddonDependencyProvider.addonManager = null - runBlocking { - val result = worker.startWork().await() + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.success(), result) - assertTrue(crashWasReported) - } + assertEquals(ListenableWorker.Result.success(), result) + assertTrue(crashWasReported) } @Test - fun `doWork - will NOT pass any IOExceptions to the crashReporter`() { + fun `doWork - will NOT pass any IOExceptions to the crashReporter`() = runTestOnMain { val addonManager = mock() val worker = TestListenableWorkerBuilder(testContext).build() var crashWasReported = false @@ -120,13 +116,11 @@ class SupportedAddonsWorkerTest { GlobalAddonDependencyProvider.initialize(addonManager, mock(), crashReporter) - runBlocking { - whenever(addonManager.getAddons()).thenThrow(AddonManagerException(IOException())) - val result = worker.startWork().await() + whenever(addonManager.getAddons()).thenThrow(AddonManagerException(IOException())) + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.success(), result) - assertFalse(crashWasReported) - } + assertEquals(ListenableWorker.Result.success(), result) + assertFalse(crashWasReported) } @Test diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt index ca8a3377ea5..7d76b891d40 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonInstallationDialogFragmentTest.kt @@ -15,7 +15,7 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Job -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.R import mozilla.components.feature.addons.amo.AddonCollectionProvider @@ -141,39 +141,35 @@ class AddonInstallationDialogFragmentTest { } @Test - fun `fetching the add-on icon successfully`() { + fun `fetching the add-on icon successfully`() = runTest { val addon = mock() val bitmap = mock() val mockedImageView = spy(ImageView(testContext)) val mockedCollectionProvider = mock() val fragment = createAddonInstallationDialogFragment(addon, mockedCollectionProvider) - runBlocking { - whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenReturn(bitmap) - assertNull(fragment.arguments?.getParcelable(KEY_ICON)) - fragment.fetchIcon(addon, mockedImageView, scope).join() - assertNotNull(fragment.arguments?.getParcelable(KEY_ICON)) - verify(mockedImageView).setImageDrawable(Mockito.any()) - } + whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenReturn(bitmap) + assertNull(fragment.arguments?.getParcelable(KEY_ICON)) + fragment.fetchIcon(addon, mockedImageView, scope).join() + assertNotNull(fragment.arguments?.getParcelable(KEY_ICON)) + verify(mockedImageView).setImageDrawable(Mockito.any()) } @Test - fun `handle errors while fetching the add-on icon`() { + fun `handle errors while fetching the add-on icon`() = runTest { val addon = mock() val mockedImageView = spy(ImageView(testContext)) val mockedCollectionProvider = mock() val fragment = createAddonInstallationDialogFragment(addon, mockedCollectionProvider) - runBlocking { - whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).then { - throw IOException("Request failed") - } - try { - fragment.fetchIcon(addon, mockedImageView, scope).join() - verify(mockedImageView).setColorFilter(Mockito.anyInt()) - } catch (e: IOException) { - fail("The exception must be handle in the adapter") - } + whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).then { + throw IOException("Request failed") + } + try { + fragment.fetchIcon(addon, mockedImageView, scope).join() + verify(mockedImageView).setColorFilter(Mockito.anyInt()) + } catch (e: IOException) { + fail("The exception must be handle in the adapter") } } diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt index 7ea0f83f913..8bca0c169c5 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/AddonsManagerAdapterTest.kt @@ -26,6 +26,7 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -115,64 +116,58 @@ class AddonsManagerAdapterTest { } @Test - fun `handle errors while fetching the add-on icon`() { + fun `handle errors while fetching the add-on icon`() = runTestOnMain { val addon = mock() val mockedImageView = spy(ImageView(testContext)) val mockedCollectionProvider = mock() val adapter = AddonsManagerAdapter(mockedCollectionProvider, mock(), emptyList()) + whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).then { + throw IOException("Request failed") + } - runBlocking { - whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).then { - throw IOException("Request failed") - } - try { - adapter.fetchIcon(addon, mockedImageView, scope).join() - verify(mockedImageView).setColorFilter(anyInt()) - } catch (e: IOException) { - fail("The exception must be handle in the adapter") - } + try { + adapter.fetchIcon(addon, mockedImageView, scope).join() + verify(mockedImageView).setColorFilter(anyInt()) + } catch (e: IOException) { + fail("The exception must be handle in the adapter") } } @Test - fun `fetching the add-on icon from cache MUST NOT animate`() { + fun `fetching the add-on icon from cache MUST NOT animate`() = runTestOnMain { val addon = mock() val bitmap = mock() val mockedImageView = spy(ImageView(testContext)) val mockedCollectionProvider = mock() val adapter = AddonsManagerAdapter(mockedCollectionProvider, mock(), emptyList()) + whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenReturn(bitmap) - runBlocking { - whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenReturn(bitmap) + adapter.fetchIcon(addon, mockedImageView, scope).join() - adapter.fetchIcon(addon, mockedImageView, scope).join() - verify(mockedImageView).setImageDrawable(any()) - } + verify(mockedImageView).setImageDrawable(any()) } @Test - fun `fetching the add-on icon uncached MUST animate`() { + fun `fetching the add-on icon uncached MUST animate`() = runTestOnMain { val addon = mock() val bitmap = mock() val mockedImageView = spy(ImageView(testContext)) val mockedCollectionProvider = mock() val adapter = spy(AddonsManagerAdapter(mockedCollectionProvider, mock(), emptyList())) - - runBlocking { - whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenAnswer { - runBlocking { - delay(1000) - } - bitmap + whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenAnswer { + runBlocking { + delay(1000) } - - adapter.fetchIcon(addon, mockedImageView, scope).join() - verify(adapter).setWithCrossFadeAnimation(mockedImageView, bitmap) + bitmap } + + adapter.fetchIcon(addon, mockedImageView, scope).join() + + verify(adapter).setWithCrossFadeAnimation(mockedImageView, bitmap) } @Test - fun `fall back to icon of installed extension`() { + fun `fall back to icon of installed extension`() = runTestOnMain { val addon = mock() val installedState = mock() val icon = mock() @@ -181,14 +176,13 @@ class AddonsManagerAdapterTest { val mockedImageView = spy(ImageView(testContext)) val mockedCollectionProvider = mock() val adapter = AddonsManagerAdapter(mockedCollectionProvider, mock(), emptyList()) + val captor = argumentCaptor() + whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenReturn(null) - runBlocking { - whenever(mockedCollectionProvider.getAddonIconBitmap(addon)).thenReturn(null) - adapter.fetchIcon(addon, mockedImageView, scope).join() - val captor = argumentCaptor() - verify(mockedImageView).setImageDrawable(captor.capture()) - assertEquals(icon, captor.value.bitmap) - } + adapter.fetchIcon(addon, mockedImageView, scope).join() + + verify(mockedImageView).setImageDrawable(captor.capture()) + assertEquals(icon, captor.value.bitmap) } @Test diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt index 32ea88f6a82..4056e122a80 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/AddonUpdaterWorkerTest.kt @@ -10,7 +10,6 @@ import androidx.work.await import androidx.work.testing.TestListenableWorkerBuilder import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.webextension.WebExtension @@ -21,6 +20,7 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import mozilla.components.support.webextensions.WebExtensionSupport import org.junit.After @@ -65,7 +65,7 @@ class AddonUpdaterWorkerTest { } @Test - fun `doWork - will return Result_success when SuccessfullyUpdated`() { + fun `doWork - will return Result_success when SuccessfullyUpdated`() = runTestOnMain { val updateAttemptStorage = mock() val addonId = "addonId" val onFinishCaptor = argumentCaptor<((AddonUpdater.Status) -> Unit)>() @@ -83,18 +83,16 @@ class AddonUpdaterWorkerTest { onFinishCaptor.value.invoke(AddonUpdater.Status.SuccessfullyUpdated) } - runBlocking { - doReturn(this).`when`(worker).attemptScope + doReturn(this).`when`(worker).attemptScope - val result = worker.startWork().await() + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.success(), result) - verify(worker).saveUpdateAttempt(addonId, AddonUpdater.Status.SuccessfullyUpdated) - } + assertEquals(ListenableWorker.Result.success(), result) + verify(worker).saveUpdateAttempt(addonId, AddonUpdater.Status.SuccessfullyUpdated) } @Test - fun `doWork - will return Result_success when NoUpdateAvailable`() { + fun `doWork - will return Result_success when NoUpdateAvailable`() = runTestOnMain { val addonId = "addonId" val onFinishCaptor = argumentCaptor<((AddonUpdater.Status) -> Unit)>() val addonManager = mock() @@ -108,15 +106,13 @@ class AddonUpdaterWorkerTest { onFinishCaptor.value.invoke(AddonUpdater.Status.NoUpdateAvailable) } - runBlocking { - val result = worker.startWork().await() + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.success(), result) - } + assertEquals(ListenableWorker.Result.success(), result) } @Test - fun `doWork - will return Result_failure when NotInstalled`() { + fun `doWork - will return Result_failure when NotInstalled`() = runTestOnMain { val addonId = "addonId" val onFinishCaptor = argumentCaptor<((AddonUpdater.Status) -> Unit)>() val addonManager = mock() @@ -130,15 +126,13 @@ class AddonUpdaterWorkerTest { onFinishCaptor.value.invoke(AddonUpdater.Status.NotInstalled) } - runBlocking { - val result = worker.startWork().await() + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.failure(), result) - } + assertEquals(ListenableWorker.Result.failure(), result) } @Test - fun `doWork - will return Result_retry when an Error happens and is recoverable`() { + fun `doWork - will return Result_retry when an Error happens and is recoverable`() = runTestOnMain { val updateAttemptStorage = mock() val addonId = "addonId" val onFinishCaptor = argumentCaptor<((AddonUpdater.Status) -> Unit)>() @@ -155,16 +149,14 @@ class AddonUpdaterWorkerTest { onFinishCaptor.value.invoke(AddonUpdater.Status.Error("error", recoverableException)) } - runBlocking { - val result = worker.startWork().await() + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.retry(), result) - updateAttemptStorage.saveOrUpdate(any()) - } + assertEquals(ListenableWorker.Result.retry(), result) + updateAttemptStorage.saveOrUpdate(any()) } @Test - fun `doWork - will return Result_success when an Error happens and is unrecoverable`() { + fun `doWork - will return Result_success when an Error happens and is unrecoverable`() = runTestOnMain { val updateAttemptStorage = mock() val addonId = "addonId" val onFinishCaptor = argumentCaptor<((AddonUpdater.Status) -> Unit)>() @@ -181,16 +173,14 @@ class AddonUpdaterWorkerTest { onFinishCaptor.value.invoke(AddonUpdater.Status.Error("error", unrecoverableException)) } - runBlocking { - val result = worker.startWork().await() + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.success(), result) - updateAttemptStorage.saveOrUpdate(any()) - } + assertEquals(ListenableWorker.Result.success(), result) + updateAttemptStorage.saveOrUpdate(any()) } @Test - fun `doWork - will try pass any exceptions to the crashReporter`() { + fun `doWork - will try pass any exceptions to the crashReporter`() = runTestOnMain { val addonId = "addonId" val onFinishCaptor = argumentCaptor<((AddonUpdater.Status) -> Unit)>() val addonManager = mock() @@ -209,12 +199,10 @@ class AddonUpdaterWorkerTest { onFinishCaptor.value.invoke(AddonUpdater.Status.Error("error", Exception())) } - runBlocking { - val result = worker.startWork().await() + val result = worker.startWork().await() - assertEquals(ListenableWorker.Result.success(), result) - assertTrue(crashWasReported) - } + assertEquals(ListenableWorker.Result.success(), result) + assertTrue(crashWasReported) } @Test diff --git a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt index b8d213e5fd1..7ff86c495ec 100644 --- a/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt +++ b/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt @@ -19,7 +19,6 @@ import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking import mozilla.components.concept.engine.webextension.DisabledFlags import mozilla.components.concept.engine.webextension.Metadata import mozilla.components.concept.engine.webextension.WebExtension @@ -31,6 +30,7 @@ import mozilla.components.support.base.worker.Frequency import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Before import org.junit.Rule @@ -57,58 +57,54 @@ class DefaultAddonUpdaterTest { } @Test - fun `registerForFutureUpdates - schedule work for future update`() { + fun `registerForFutureUpdates - schedule work for future update`() = runTestOnMain { val frequency = Frequency(1, TimeUnit.DAYS) val updater = DefaultAddonUpdater(testContext, frequency) val addonId = "addonId" val workId = updater.getUniquePeriodicWorkName(addonId) - runBlocking { - val workManger = WorkManager.getInstance(testContext) - var workData = workManger.getWorkInfosForUniqueWork(workId).await() + val workManger = WorkManager.getInstance(testContext) + var workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertTrue(workData.isEmpty()) + assertTrue(workData.isEmpty()) - updater.registerForFutureUpdates(addonId) - workData = workManger.getWorkInfosForUniqueWork(workId).await() + updater.registerForFutureUpdates(addonId) + workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertFalse(workData.isEmpty()) + assertFalse(workData.isEmpty()) - assertExtensionIsRegisteredFoUpdates(updater, addonId) + assertExtensionIsRegisteredFoUpdates(updater, addonId) - // Cleaning work manager - workManger.cancelUniqueWork(workId) - } + // Cleaning work manager + workManger.cancelUniqueWork(workId) } @Test - fun `update - schedule work for immediate update`() { + fun `update - schedule work for immediate update`() = runTestOnMain { val updater = DefaultAddonUpdater(testContext) val addonId = "addonId" val workId = updater.getUniqueImmediateWorkName(addonId) - runBlocking { - val workManger = WorkManager.getInstance(testContext) - var workData = workManger.getWorkInfosForUniqueWork(workId).await() + val workManger = WorkManager.getInstance(testContext) + var workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertTrue(workData.isEmpty()) + assertTrue(workData.isEmpty()) - updater.update(addonId) - workData = workManger.getWorkInfosForUniqueWork(workId).await() + updater.update(addonId) + workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertFalse(workData.isEmpty()) + assertFalse(workData.isEmpty()) - val work = workData.first() + val work = workData.first() - assertEquals(WorkInfo.State.ENQUEUED, work.state) - assertTrue(work.tags.contains(workId)) - assertTrue(work.tags.contains(WORK_TAG_IMMEDIATE)) + assertEquals(WorkInfo.State.ENQUEUED, work.state) + assertTrue(work.tags.contains(workId)) + assertTrue(work.tags.contains(WORK_TAG_IMMEDIATE)) - // Cleaning work manager - workManger.cancelUniqueWork(workId) - } + // Cleaning work manager + workManger.cancelUniqueWork(workId) } @Test @@ -262,7 +258,7 @@ class DefaultAddonUpdaterTest { } @Test - fun `unregisterForFutureUpdates - will remove scheduled work for future update`() { + fun `unregisterForFutureUpdates - will remove scheduled work for future update`() = runTestOnMain { val frequency = Frequency(1, TimeUnit.DAYS) val updater = DefaultAddonUpdater(testContext, frequency) updater.scope = CoroutineScope(Dispatchers.Main) @@ -273,25 +269,23 @@ class DefaultAddonUpdaterTest { val workId = updater.getUniquePeriodicWorkName(addonId) - runBlocking { - val workManger = WorkManager.getInstance(testContext) - var workData = workManger.getWorkInfosForUniqueWork(workId).await() + val workManger = WorkManager.getInstance(testContext) + var workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertTrue(workData.isEmpty()) + assertTrue(workData.isEmpty()) - updater.registerForFutureUpdates(addonId) - workData = workManger.getWorkInfosForUniqueWork(workId).await() + updater.registerForFutureUpdates(addonId) + workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertFalse(workData.isEmpty()) + assertFalse(workData.isEmpty()) - assertExtensionIsRegisteredFoUpdates(updater, addonId) + assertExtensionIsRegisteredFoUpdates(updater, addonId) - updater.unregisterForFutureUpdates(addonId) + updater.unregisterForFutureUpdates(addonId) - workData = workManger.getWorkInfosForUniqueWork(workId).await() - assertEquals(WorkInfo.State.CANCELLED, workData.first().state) - verify(updater.updateAttempStorage).remove(addonId) - } + workData = workManger.getWorkInfosForUniqueWork(workId).await() + assertEquals(WorkInfo.State.CANCELLED, workData.first().state) + verify(updater.updateAttempStorage).remove(addonId) } @Test @@ -313,7 +307,7 @@ class DefaultAddonUpdaterTest { } @Test - fun `registerForFutureUpdates - will register only unregistered extensions`() { + fun `registerForFutureUpdates - will register only unregistered extensions`() = runTestOnMain { val updater = DefaultAddonUpdater(testContext) val registeredExt: WebExtension = mock() val notRegisteredExt: WebExtension = mock() @@ -324,21 +318,17 @@ class DefaultAddonUpdaterTest { val extensions = listOf(registeredExt, notRegisteredExt) - runBlocking { - assertExtensionIsRegisteredFoUpdates(updater, "registeredExt") - } + assertExtensionIsRegisteredFoUpdates(updater, "registeredExt") updater.registerForFutureUpdates(extensions) - runBlocking { - extensions.forEach { ext -> - assertExtensionIsRegisteredFoUpdates(updater, ext.id) - } + extensions.forEach { ext -> + assertExtensionIsRegisteredFoUpdates(updater, ext.id) } } @Test - fun `registerForFutureUpdates - will not register built-in and unsupported extensions`() { + fun `registerForFutureUpdates - will not register built-in and unsupported extensions`() = runTestOnMain { val updater = DefaultAddonUpdater(testContext) val regularExt: WebExtension = mock() @@ -357,14 +347,10 @@ class DefaultAddonUpdaterTest { val extensions = listOf(regularExt, builtInExt, unsupportedExt) updater.registerForFutureUpdates(extensions) - runBlocking { - assertExtensionIsRegisteredFoUpdates(updater, regularExt.id) - } + assertExtensionIsRegisteredFoUpdates(updater, regularExt.id) - runBlocking { - assertExtensionIsNotRegisteredFoUpdates(updater, builtInExt.id) - assertExtensionIsNotRegisteredFoUpdates(updater, unsupportedExt.id) - } + assertExtensionIsNotRegisteredFoUpdates(updater, builtInExt.id) + assertExtensionIsNotRegisteredFoUpdates(updater, unsupportedExt.id) } private suspend fun assertExtensionIsRegisteredFoUpdates(updater: DefaultAddonUpdater, extId: String) { diff --git a/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt b/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt index a153d3f0895..21228b564bf 100644 --- a/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt +++ b/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt @@ -4,7 +4,8 @@ package mozilla.components.feature.autofill.handler -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginsStorage import mozilla.components.feature.autofill.AutofillConfiguration @@ -34,6 +35,7 @@ import org.mockito.Mockito.doReturn import org.robolectric.RobolectricTestRunner import java.util.UUID +@ExperimentalCoroutinesApi // for createTestCase @RunWith(RobolectricTestRunner::class) internal class FillRequestHandlerTest { @Test @@ -182,13 +184,14 @@ internal class FillRequestHandlerTest { } } +@ExperimentalCoroutinesApi private fun FillRequestHandlerTest.createTestCase( filename: String, packageName: String, logins: Map, assertThat: (B?) -> Unit, canVerifyRelationship: Boolean = true -) = runBlocking { +) = runTest { val structure = createMockStructure(filename, packageName) val storage: LoginsStorage = mock() diff --git a/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt b/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt index 7d5ba35962c..0e349ca56d6 100644 --- a/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt +++ b/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt @@ -5,11 +5,13 @@ package mozilla.components.feature.autofill.test import android.view.autofill.AutofillId +import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.feature.autofill.handler.FillRequestHandlerTest import mozilla.components.feature.autofill.structure.AutofillNodeNavigator import mozilla.components.feature.autofill.structure.RawStructure import java.io.File +@ExperimentalCoroutinesApi internal fun FillRequestHandlerTest.createMockStructure(filename: String, packageName: String): RawStructure { val classLoader = javaClass.classLoader ?: throw RuntimeException("No class loader") val resource = classLoader.getResource(filename) ?: throw RuntimeException("Resource not found") diff --git a/components/feature/awesomebar/build.gradle b/components/feature/awesomebar/build.gradle index 4a2cced89d0..27a45be29c0 100644 --- a/components/feature/awesomebar/build.gradle +++ b/components/feature/awesomebar/build.gradle @@ -49,6 +49,7 @@ dependencies { testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver + testImplementation Dependencies.testing_coroutines } apply from: '../../../publish.gradle' diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProviderTest.kt index 6062e3d82ff..34143ac0dce 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProviderTest.kt @@ -5,7 +5,8 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.BookmarkInfo import mozilla.components.concept.storage.BookmarkNode @@ -25,6 +26,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import java.util.UUID +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class BookmarksStorageSuggestionProviderTest { @@ -36,7 +38,7 @@ class BookmarksStorageSuggestionProviderTest { ) @Test - fun `Provider returns empty list when text is empty`() = runBlocking { + fun `Provider returns empty list when text is empty`() = runTest { val provider = BookmarksStorageSuggestionProvider(mock(), mock()) val suggestions = provider.onInputChanged("") @@ -44,7 +46,7 @@ class BookmarksStorageSuggestionProviderTest { } @Test - fun `Provider returns suggestions from configured bookmarks storage`() = runBlocking { + fun `Provider returns suggestions from configured bookmarks storage`() = runTest { val provider = BookmarksStorageSuggestionProvider(bookmarks, mock()) val id = bookmarks.addItem("Mobile", newItem.url!!, newItem.title!!, null) @@ -61,7 +63,7 @@ class BookmarksStorageSuggestionProviderTest { } @Test - fun `Provider does not return duplicate suggestions`() = runBlocking { + fun `Provider does not return duplicate suggestions`() = runTest { val provider = BookmarksStorageSuggestionProvider(bookmarks, mock()) for (i in 1..20) { @@ -73,7 +75,7 @@ class BookmarksStorageSuggestionProviderTest { } @Test - fun `Provider limits number of returned unique suggestions`() = runBlocking { + fun `Provider limits number of returned unique suggestions`() = runTest { val provider = BookmarksStorageSuggestionProvider(bookmarks, mock()) for (i in 1..100) { @@ -90,7 +92,7 @@ class BookmarksStorageSuggestionProviderTest { } @Test - fun `provider calls speculative connect for URL of first suggestion`() = runBlocking { + fun `provider calls speculative connect for URL of first suggestion`() = runTest { val engine: Engine = mock() val provider = BookmarksStorageSuggestionProvider(bookmarks, mock(), engine = engine) @@ -107,7 +109,7 @@ class BookmarksStorageSuggestionProviderTest { } @Test - fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runBlocking { + fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runTest { val engine: Engine = mock() val provider = BookmarksStorageSuggestionProvider(bookmarks, mock(), engine = engine, showEditSuggestion = false) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/ClipboardSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/ClipboardSuggestionProviderTest.kt index ea559c3e8b9..1ead5cffb8d 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/ClipboardSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/ClipboardSuggestionProviderTest.kt @@ -9,7 +9,7 @@ import android.content.ClipboardManager import android.content.Context import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.concept.awesomebar.AwesomeBar import mozilla.components.concept.engine.Engine import mozilla.components.feature.session.SessionUseCases @@ -17,10 +17,13 @@ import mozilla.components.support.test.any import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString @@ -28,14 +31,18 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class ClipboardSuggestionProviderTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val clipboardManager: ClipboardManager get() = testContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @Test - fun `provider returns empty list by default`() = runBlocking { + fun `provider returns empty list by default`() = runTestOnMain { clipboardManager.clearPrimaryClip() val provider = ClipboardSuggestionProvider(testContext, mock()) @@ -47,7 +54,7 @@ class ClipboardSuggestionProviderTest { } @Test - fun `provider returns empty list for non plain text clip`() { + fun `provider returns empty list for non plain text clip`() = runTestOnMain { clipboardManager.setPrimaryClip( ClipData.newHtmlText( "Label", @@ -60,7 +67,7 @@ class ClipboardSuggestionProviderTest { } @Test - fun `provider should return suggestion if clipboard contains url`() { + fun `provider should return suggestion if clipboard contains url`() = runTestOnMain { assertClipboardYieldsUrl( "https://www.mozilla.org", "https://www.mozilla.org" @@ -106,11 +113,11 @@ class ClipboardSuggestionProviderTest { } @Test - fun `provider return suggestion on input start`() { + fun `provider return suggestion on input start`() = runTestOnMain { clipboardManager.setPrimaryClip(ClipData.newPlainText("Test label", "https://www.mozilla.org")) val provider = ClipboardSuggestionProvider(testContext, mock()) - val suggestions = runBlocking { provider.onInputStarted() } + val suggestions = provider.onInputStarted() assertEquals(1, suggestions.size) @@ -121,14 +128,14 @@ class ClipboardSuggestionProviderTest { } @Test - fun `provider should return no suggestions if clipboard does not contain a url`() { + fun `provider should return no suggestions if clipboard does not contain a url`() = runTestOnMain { assertClipboardYieldsNothing("Hello World") assertClipboardYieldsNothing("Is this mozilla org") } @Test - fun `provider should allow customization of title and icon on suggestion`() { + fun `provider should allow customization of title and icon on suggestion`() = runTestOnMain { clipboardManager.setPrimaryClip(ClipData.newPlainText("Test label", "http://mozilla.org")) val bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888) val provider = ClipboardSuggestionProvider( @@ -139,21 +146,19 @@ class ClipboardSuggestionProviderTest { requireEmptyText = false ) - val suggestion = runBlocking { + val suggestion = run { provider.onInputStarted() val suggestions = provider.onInputChanged("Hello") suggestions.firstOrNull() } - runBlocking { - assertEquals(bitmap, suggestion?.icon) - assertEquals("My test title", suggestion?.title) - } + assertEquals(bitmap, suggestion?.icon) + assertEquals("My test title", suggestion?.title) } @Test - fun `clicking suggestion loads url`() = runBlocking { + fun `clicking suggestion loads url`() = runTestOnMain { clipboardManager.setPrimaryClip( ClipData.newPlainText( "Label", @@ -180,7 +185,7 @@ class ClipboardSuggestionProviderTest { } @Test - fun `provider returns empty list for non-empty text if empty text required`() = runBlocking { + fun `provider returns empty list for non-empty text if empty text required`() = runTestOnMain { clipboardManager.setPrimaryClip( ClipData.newPlainText( "Label", @@ -194,15 +199,15 @@ class ClipboardSuggestionProviderTest { } @Test - fun `provider calls speculative connect for URL of suggestion`() { + fun `provider calls speculative connect for URL of suggestion`() = runTestOnMain { val engine: Engine = mock() val provider = ClipboardSuggestionProvider(testContext, mock(), engine = engine) - var suggestions = runBlocking { provider.onInputStarted() } + var suggestions = provider.onInputStarted() assertTrue(suggestions.isEmpty()) verify(engine, never()).speculativeConnect(anyString()) clipboardManager.setPrimaryClip(ClipData.newPlainText("Test label", "https://www.mozilla.org")) - suggestions = runBlocking { provider.onInputStarted() } + suggestions = provider.onInputStarted() assertEquals(1, suggestions.size) verify(engine, times(1)).speculativeConnect(eq("https://www.mozilla.org")) @@ -211,7 +216,7 @@ class ClipboardSuggestionProviderTest { assertEquals("https://www.mozilla.org", suggestion.description) } - private fun assertClipboardYieldsUrl(text: String, url: String) { + private suspend fun assertClipboardYieldsUrl(text: String, url: String) { val suggestion = getSuggestionWithClipboard(text) assertNotNull(suggestion) @@ -219,22 +224,22 @@ class ClipboardSuggestionProviderTest { assertEquals(url, suggestion!!.description) } - private fun assertClipboardYieldsNothing(text: String) { + private suspend fun assertClipboardYieldsNothing(text: String) { val suggestion = getSuggestionWithClipboard(text) assertNull(suggestion) } - private fun getSuggestionWithClipboard(text: String): AwesomeBar.Suggestion? { + private suspend fun getSuggestionWithClipboard(text: String): AwesomeBar.Suggestion? { clipboardManager.setPrimaryClip(ClipData.newPlainText("Test label", text)) return getSuggestion() } - private fun getSuggestion(): AwesomeBar.Suggestion? = runBlocking { + private suspend fun getSuggestion(): AwesomeBar.Suggestion? { val provider = ClipboardSuggestionProvider(testContext, mock(), requireEmptyText = false) provider.onInputStarted() val suggestions = provider.onInputChanged("Hello") - suggestions.firstOrNull() + return suggestions.firstOrNull() } } diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt index 5508a1acd32..8e3f9a782d7 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt @@ -5,7 +5,8 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.storage.DocumentType import mozilla.components.concept.storage.HistoryMetadata import mozilla.components.concept.storage.HistoryMetadataKey @@ -22,6 +23,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.anyInt import org.mockito.Mockito.doReturn +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class CombinedHistorySuggestionProviderTest { @@ -36,7 +38,7 @@ class CombinedHistorySuggestionProviderTest { ) @Test - fun `GIVEN history items exists WHEN onInputChanged is called with empty text THEN return empty suggestions list`() = runBlocking { + fun `GIVEN history items exists WHEN onInputChanged is called with empty text THEN return empty suggestions list`() = runTest { val metadata: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(metadata).queryHistoryMetadata(eq("moz"), anyInt()) val history: HistoryStorage = mock() @@ -48,7 +50,7 @@ class CombinedHistorySuggestionProviderTest { } @Test - fun `GIVEN more suggestions asked than metadata items exist WHEN user changes input THEN return a combined list of suggestions`() = runBlocking { + fun `GIVEN more suggestions asked than metadata items exist WHEN user changes input THEN return a combined list of suggestions`() = runTest { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) val history: HistoryStorage = mock() @@ -63,7 +65,7 @@ class CombinedHistorySuggestionProviderTest { } @Test - fun `GIVEN fewer suggestions asked than metadata items exist WHEN user changes input THEN return suggestions only based on metadata items`() = runBlocking { + fun `GIVEN fewer suggestions asked than metadata items exist WHEN user changes input THEN return suggestions only based on metadata items`() = runTest { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) val history: HistoryStorage = mock() @@ -77,7 +79,7 @@ class CombinedHistorySuggestionProviderTest { } @Test - fun `GIVEN only storage history items exist WHEN user changes input THEN return suggestions only based on storage items`() = runBlocking { + fun `GIVEN only storage history items exist WHEN user changes input THEN return suggestions only based on storage items`() = runTest { val metadata: HistoryMetadataStorage = mock() doReturn(emptyList()).`when`(metadata).queryHistoryMetadata(eq("moz"), anyInt()) val history: HistoryStorage = mock() @@ -91,7 +93,7 @@ class CombinedHistorySuggestionProviderTest { } @Test - fun `GIVEN duplicated metadata and storage entries WHEN user changes input THEN return distinct suggestions`() = runBlocking { + fun `GIVEN duplicated metadata and storage entries WHEN user changes input THEN return distinct suggestions`() = runTest { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) val history: HistoryStorage = mock() @@ -105,7 +107,7 @@ class CombinedHistorySuggestionProviderTest { } @Test - fun `GIVEN a combined list of suggestions WHEN history results exist THEN urls are deduped and scores are adjusted`() = runBlocking { + fun `GIVEN a combined list of suggestions WHEN history results exist THEN urls are deduped and scores are adjusted`() = runTest { val metadataEntry1 = HistoryMetadata( key = HistoryMetadataKey("https://www.mozilla.com", null, null), title = "mozilla", @@ -161,7 +163,7 @@ class CombinedHistorySuggestionProviderTest { } @Test - fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runBlocking { + fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runTest { val metadata: HistoryMetadataStorage = mock() doReturn(emptyList()).`when`(metadata).queryHistoryMetadata(eq("moz"), anyInt()) val history: HistoryStorage = mock() diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProviderTest.kt index 0359457ee54..5790c6b04f7 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProviderTest.kt @@ -4,7 +4,8 @@ package mozilla.components.feature.awesomebar.provider -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.DocumentType import mozilla.components.concept.storage.HistoryMetadata @@ -30,6 +31,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest class HistoryMetadataSuggestionProviderTest { private val historyEntry = HistoryMetadata( key = HistoryMetadataKey("http://www.mozilla.com", null, null), @@ -47,7 +49,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `provider returns empty list when text is empty`() = runBlocking { + fun `provider returns empty list when text is empty`() = runTest { val provider = HistoryMetadataSuggestionProvider(mock(), mock()) val suggestions = provider.onInputChanged("") @@ -55,7 +57,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `provider returns suggestions from configured history storage`() = runBlocking { + fun `provider returns suggestions from configured history storage`() = runTest { val storage: HistoryMetadataStorage = mock() whenever(storage.queryHistoryMetadata("moz", DEFAULT_METADATA_SUGGESTION_LIMIT)).thenReturn(listOf(historyEntry)) @@ -68,7 +70,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `provider limits number of returned suggestions to 5 by default`() = runBlocking { + fun `provider limits number of returned suggestions to 5 by default`() = runTest { val storage: HistoryMetadataStorage = mock() doReturn(emptyList()).`when`(storage).queryHistoryMetadata(anyString(), anyInt()) val provider = HistoryMetadataSuggestionProvider(storage, mock()) @@ -80,7 +82,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `provider allows lowering the number of returned suggestions beneath the default`() = runBlocking { + fun `provider allows lowering the number of returned suggestions beneath the default`() = runTest { val storage: HistoryMetadataStorage = mock() doReturn(emptyList()).`when`(storage).queryHistoryMetadata(anyString(), anyInt()) val provider = HistoryMetadataSuggestionProvider( @@ -94,7 +96,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `provider allows increasing the number of returned suggestions above the default`() = runBlocking { + fun `provider allows increasing the number of returned suggestions above the default`() = runTest { val storage: HistoryMetadataStorage = mock() doReturn(emptyList()).`when`(storage).queryHistoryMetadata(anyString(), anyInt()) val provider = HistoryMetadataSuggestionProvider( @@ -108,7 +110,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `provider only as suggestions pages on which users actually spent some time`() = runBlocking { + fun `provider only as suggestions pages on which users actually spent some time`() = runTest { val storage: HistoryMetadataStorage = mock() val historyEntries = mutableListOf().apply { add(historyEntry) @@ -122,7 +124,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `provider calls speculative connect for URL of highest scored suggestion`() = runBlocking { + fun `provider calls speculative connect for URL of highest scored suggestion`() = runTest { val storage: HistoryMetadataStorage = mock() val engine: Engine = mock() val provider = HistoryMetadataSuggestionProvider(storage, mock(), engine = engine) @@ -139,7 +141,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `fact is emitted when suggestion is clicked`() = runBlocking { + fun `fact is emitted when suggestion is clicked`() = runTest { val storage: HistoryMetadataStorage = mock() val engine: Engine = mock() val provider = HistoryMetadataSuggestionProvider(storage, mock(), engine = engine) @@ -173,7 +175,7 @@ class HistoryMetadataSuggestionProviderTest { } @Test - fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runBlocking { + fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runTest { val storage: HistoryMetadataStorage = mock() whenever(storage.queryHistoryMetadata("moz", DEFAULT_METADATA_SUGGESTION_LIMIT)).thenReturn(listOf(historyEntry)) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt index b82105a4592..f95328c87ee 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt @@ -5,7 +5,8 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage import mozilla.components.concept.storage.SearchResult @@ -30,6 +31,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class HistoryStorageSuggestionProviderTest { @@ -39,7 +41,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `Provider returns empty list when text is empty`() = runBlocking { + fun `Provider returns empty list when text is empty`() = runTest { val provider = HistoryStorageSuggestionProvider(mock(), mock()) val suggestions = provider.onInputChanged("") @@ -47,7 +49,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `Provider returns suggestions from configured history storage`() = runBlocking { + fun `Provider returns suggestions from configured history storage`() = runTest { val history: HistoryStorage = mock() Mockito.doReturn(listOf(SearchResult("id", "http://www.mozilla.com/", 10))).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) val provider = HistoryStorageSuggestionProvider(history, mock()) @@ -58,7 +60,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runBlocking { + fun `WHEN provider is set to not show edit suggestions THEN edit suggestion is set to null`() = runTest { val history: HistoryStorage = mock() Mockito.doReturn(listOf(SearchResult("id", "http://www.mozilla.com/", 10))).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) val provider = HistoryStorageSuggestionProvider(history, mock(), showEditSuggestion = false) @@ -70,7 +72,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `Provider limits number of returned suggestions to a max of 20 by default`() = runBlocking { + fun `Provider limits number of returned suggestions to a max of 20 by default`() = runTest { val history: HistoryStorage = mock() Mockito.doReturn( (1..100).map { @@ -84,7 +86,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `Provider allows lowering the number of returned suggestions beneath the default`() = runBlocking { + fun `Provider allows lowering the number of returned suggestions beneath the default`() = runTest { val history: HistoryStorage = mock() Mockito.doReturn( (1..50).map { @@ -101,7 +103,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `Provider allows increasing the number of returned suggestions above the default`() = runBlocking { + fun `Provider allows increasing the number of returned suggestions above the default`() = runTest { val history: HistoryStorage = mock() Mockito.doReturn( (1..50).map { @@ -118,7 +120,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `Provider dedupes suggestions`() = runBlocking { + fun `Provider dedupes suggestions`() = runTest { val storage: HistoryStorage = mock() val provider = HistoryStorageSuggestionProvider(storage, mock()) @@ -157,7 +159,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `provider calls speculative connect for URL of highest scored suggestion`() = runBlocking { + fun `provider calls speculative connect for URL of highest scored suggestion`() = runTest { val history: HistoryStorage = mock() val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) @@ -175,7 +177,7 @@ class HistoryStorageSuggestionProviderTest { } @Test - fun `fact is emitted when suggestion is clicked`() = runBlocking { + fun `fact is emitted when suggestion is clicked`() = runTest { val history: HistoryStorage = mock() val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchActionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchActionProviderTest.kt index da2a6d556fd..7a93753d540 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchActionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchActionProviderTest.kt @@ -4,36 +4,38 @@ package mozilla.components.feature.awesomebar.provider -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.mock import org.junit.Assert.assertEquals import org.junit.Test +@ExperimentalCoroutinesApi // for runTest class SearchActionProviderTest { @Test - fun `provider returns no suggestion for empty text`() { + fun `provider returns no suggestion for empty text`() = runTest { val provider = SearchActionProvider(mock(), mock()) - val suggestions = runBlocking { provider.onInputChanged("") } + val suggestions = provider.onInputChanged("") assertEquals(0, suggestions.size) } @Test - fun `provider returns no suggestion for blank text`() { + fun `provider returns no suggestion for blank text`() = runTest { val provider = SearchActionProvider(mock(), mock()) - val suggestions = runBlocking { provider.onInputChanged(" ") } + val suggestions = provider.onInputChanged(" ") assertEquals(0, suggestions.size) } @Test - fun `provider returns suggestion matching input`() { + fun `provider returns suggestion matching input`() = runTest { val provider = SearchActionProvider( store = mock(), searchEngine = mock(), searchUseCase = mock() ) - val suggestions = runBlocking { provider.onInputChanged("firefox") } + val suggestions = provider.onInputChanged("firefox") assertEquals(1, suggestions.size) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchEngineSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchEngineSuggestionProviderTest.kt index 0f628ee1677..557fef99ea0 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchEngineSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchEngineSuggestionProviderTest.kt @@ -6,7 +6,8 @@ package mozilla.components.feature.awesomebar.provider import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.feature.search.ext.createSearchEngine import mozilla.components.support.test.mock import mozilla.components.support.test.whenever @@ -16,6 +17,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class SearchEngineSuggestionProviderTest { private lateinit var defaultProvider: SearchEngineSuggestionProvider @@ -43,21 +45,21 @@ class SearchEngineSuggestionProviderTest { } @Test - fun `Provider returns empty list when text is empty`() = runBlocking { + fun `Provider returns empty list when text is empty`() = runTest { val suggestions = defaultProvider.onInputChanged("") assertTrue(suggestions.isEmpty()) } @Test - fun `Provider returns empty list when text is blank`() = runBlocking { + fun `Provider returns empty list when text is blank`() = runTest { val suggestions = defaultProvider.onInputChanged(" ") assertTrue(suggestions.isEmpty()) } @Test - fun `Provider returns empty list when text is shorter than charactersThreshold`() = runBlocking { + fun `Provider returns empty list when text is shorter than charactersThreshold`() = runTest { val provider = SearchEngineSuggestionProvider( testContext, engineList, mock(), 1, "description", mock(), charactersThreshold = 3 ) @@ -68,7 +70,7 @@ class SearchEngineSuggestionProviderTest { } @Test - fun `Provider returns empty list when list does not contain engines with typed text`() = runBlocking { + fun `Provider returns empty list when list does not contain engines with typed text`() = runTest { val suggestions = defaultProvider.onInputChanged("x") @@ -76,7 +78,7 @@ class SearchEngineSuggestionProviderTest { } @Test - fun `Provider returns a match when list contains the typed engine`() = runBlocking { + fun `Provider returns a match when list contains the typed engine`() = runTest { val suggestions = defaultProvider.onInputChanged("am") @@ -84,7 +86,7 @@ class SearchEngineSuggestionProviderTest { } @Test - fun `Provider returns empty list when the engine list is empty`() = runBlocking { + fun `Provider returns empty list when the engine list is empty`() = runTest { val providerEmpty = SearchEngineSuggestionProvider( testContext, emptyList(), mock(), 1, "description", mock() ) @@ -95,7 +97,7 @@ class SearchEngineSuggestionProviderTest { } @Test - fun `Provider limits number of returned suggestions to maxSuggestions`() = runBlocking { + fun `Provider limits number of returned suggestions to maxSuggestions`() = runTest { // this should match to both engines in list val suggestions = defaultProvider.onInputChanged("n") diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchSuggestionProviderTest.kt index 8c5afc9582d..ec25b82ac12 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SearchSuggestionProviderTest.kt @@ -6,7 +6,8 @@ package mozilla.components.feature.awesomebar.provider import androidx.core.graphics.drawable.toBitmap import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.Engine import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Request @@ -39,11 +40,12 @@ import java.io.IOException private const val GOOGLE_MOCK_RESPONSE = "[\"firefox\",[\"firefox\",\"firefox for mac\",\"firefox quantum\",\"firefox update\",\"firefox esr\",\"firefox focus\",\"firefox addons\",\"firefox extensions\",\"firefox nightly\",\"firefox clear cache\"]]" private const val GOOGLE_MOCK_RESPONSE_WITH_DUPLICATES = "[\"firefox\",[\"firefox\",\"firefox\",\"firefox for mac\",\"firefox quantum\",\"firefox update\",\"firefox esr\",\"firefox esr\",\"firefox focus\",\"firefox addons\",\"firefox extensions\",\"firefox nightly\",\"firefox clear cache\"]]" +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class SearchSuggestionProviderTest { @Test fun `Provider returns suggestion with chips based on search engine suggestion`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -101,7 +103,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider returns multiple suggestions in MULTIPLE mode`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -163,7 +165,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider returns multiple suggestions with limit`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -205,7 +207,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider returns chips with limit`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -242,7 +244,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider should use engine icon by default`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -269,7 +271,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider should use icon parameter when available`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -301,7 +303,7 @@ class SearchSuggestionProviderTest { } @Test - fun `Provider returns empty list if text is empty`() = runBlocking { + fun `Provider returns empty list if text is empty`() = runTest { val provider = SearchSuggestionProvider(mock(), mock(), mock()) val suggestions = provider.onInputChanged("") @@ -310,7 +312,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider should return default suggestion for search engine that cannot provide suggestion`() = - runBlocking { + runTest { val searchEngine = createSearchEngine( name = "Test", url = "https://localhost/?q={searchTerms}", @@ -329,7 +331,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider doesn't fail if fetch returns HTTP error`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setResponseCode(404).setBody("error")) server.start() @@ -361,7 +363,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider doesn't fail if fetch throws exception`() { - runBlocking { + runTest { val searchEngine = createSearchEngine( name = "Test", url = "https://localhost/?q={searchTerms}", @@ -394,7 +396,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider returns distinct multiple suggestions`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE_WITH_DUPLICATES)) server.start() @@ -437,7 +439,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider returns multiple suggestions with limit and no description`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -481,7 +483,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider calls speculativeConnect for URL of highest scored suggestion in MULTIPLE mode`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -518,7 +520,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider calls speculativeConnect for URL of highest scored chip in SINGLE mode`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() @@ -555,7 +557,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider filters exact match from multiple suggestions`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE_WITH_DUPLICATES)) server.start() @@ -597,7 +599,7 @@ class SearchSuggestionProviderTest { @Test fun `Provider filters chips with exact match`() { - runBlocking { + runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody(GOOGLE_MOCK_RESPONSE)) server.start() diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProviderTest.kt index 63baaee88f4..f54d0057dac 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProviderTest.kt @@ -5,7 +5,8 @@ package mozilla.components.feature.awesomebar.provider import android.content.res.Resources -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.createTab @@ -20,9 +21,10 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest class SessionSuggestionProviderTest { @Test - fun `Provider returns empty list when text is empty`() = runBlocking { + fun `Provider returns empty list when text is empty`() = runTest { val resources: Resources = mock() `when`(resources.getString(anyInt())).thenReturn("Switch to tab") @@ -33,7 +35,7 @@ class SessionSuggestionProviderTest { } @Test - fun `Provider returns Sessions with matching URLs`() = runBlocking { + fun `Provider returns Sessions with matching URLs`() = runTest { val store = BrowserStore() val tab1 = createTab("https://www.mozilla.org") @@ -77,7 +79,7 @@ class SessionSuggestionProviderTest { } @Test - fun `Provider returns Sessions with matching titles`() = runBlocking { + fun `Provider returns Sessions with matching titles`() = runTest { val tab1 = createTab("https://allizom.org", title = "Internet for people, not profit — Mozilla") val tab2 = createTab("https://getpocket.com", title = "Pocket: My List") val tab3 = createTab("https://firefox.com", title = "Download Firefox — Free Web Browser") @@ -113,7 +115,7 @@ class SessionSuggestionProviderTest { } @Test - fun `Provider only returns non-private Sessions`() = runBlocking { + fun `Provider only returns non-private Sessions`() = runTest { val tab = createTab("https://www.mozilla.org") val privateTab1 = createTab("https://mozilla.org/firefox", private = true) val privateTab2 = createTab("https://mozilla.org/projects", private = true) @@ -136,7 +138,7 @@ class SessionSuggestionProviderTest { } @Test - fun `Clicking suggestion invokes SelectTabUseCase`() = runBlocking { + fun `Clicking suggestion invokes SelectTabUseCase`() = runTest { val resources: Resources = mock() `when`(resources.getString(anyInt())).thenReturn("Switch to tab") @@ -164,7 +166,7 @@ class SessionSuggestionProviderTest { } @Test - fun `When excludeSelectedSession is true provider should not include the selected session`() = runBlocking { + fun `When excludeSelectedSession is true provider should not include the selected session`() = runTest { val store = BrowserStore( BrowserState( tabs = listOf( @@ -189,7 +191,7 @@ class SessionSuggestionProviderTest { } @Test - fun `When excludeSelectedSession is false provider should include the selected session`() = runBlocking { + fun `When excludeSelectedSession is false provider should include the selected session`() = runTest { val store = BrowserStore( BrowserState( tabs = listOf( @@ -214,7 +216,7 @@ class SessionSuggestionProviderTest { } @Test - fun `Uses title for chip title when available, but falls back to URL`() = runBlocking { + fun `Uses title for chip title when available, but falls back to URL`() = runTest { val store = BrowserStore( BrowserState( tabs = listOf( diff --git a/components/feature/containers/src/test/java/mozilla/components/feature/containers/ContainerMiddlewareTest.kt b/components/feature/containers/src/test/java/mozilla/components/feature/containers/ContainerMiddlewareTest.kt index 71d97afcc83..a3c1c5796cb 100644 --- a/components/feature/containers/src/test/java/mozilla/components/feature/containers/ContainerMiddlewareTest.kt +++ b/components/feature/containers/src/test/java/mozilla/components/feature/containers/ContainerMiddlewareTest.kt @@ -7,7 +7,6 @@ package mozilla.components.feature.containers import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.state.action.ContainerAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ContainerState @@ -16,8 +15,11 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify @@ -25,6 +27,9 @@ import org.mockito.Mockito.verify @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) class ContainerMiddlewareTest { + @get:Rule + val mainCoroutineRule = MainCoroutineRule() + // Test container private val container = ContainerState( contextId = "contextId", @@ -35,7 +40,7 @@ class ContainerMiddlewareTest { @Test fun `container storage stores the provided container on add container action`() = - runBlockingTest { + runTestOnMain { val storage = mockStorage() val middleware = ContainerMiddleware(testContext, coroutineContext, containerStorage = storage) val store = BrowserStore( @@ -43,6 +48,9 @@ class ContainerMiddlewareTest { middleware = listOf(middleware) ) + store.waitUntilIdle() // wait to consume InitAction + store.waitUntilIdle() // wait to consume AddContainersAction + store.dispatch(ContainerAction.AddContainerAction(container)).joinBlocking() verify(storage).addContainer( @@ -55,7 +63,7 @@ class ContainerMiddlewareTest { @Test fun `fetch the containers from the container storage and load into browser state on initialize container state action`() = - runBlockingTest { + runTestOnMain { val storage = mockStorage(listOf(container)) val middleware = ContainerMiddleware(testContext, coroutineContext, containerStorage = storage) val store = BrowserStore( @@ -63,8 +71,8 @@ class ContainerMiddlewareTest { middleware = listOf(middleware) ) - // Wait until init action is processed - store.waitUntilIdle() + store.waitUntilIdle() // wait to consume InitAction + store.waitUntilIdle() // wait to consume AddContainersAction verify(storage).getContainers() assertEquals(container, store.state.containers["contextId"]) @@ -72,7 +80,7 @@ class ContainerMiddlewareTest { @Test fun `container storage removes the provided container on remove container action`() = - runBlockingTest { + runTestOnMain { val storage = mockStorage() val middleware = ContainerMiddleware(testContext, coroutineContext, containerStorage = storage) val store = BrowserStore( @@ -84,6 +92,9 @@ class ContainerMiddlewareTest { middleware = listOf(middleware) ) + store.waitUntilIdle() // wait to consume InitAction + store.waitUntilIdle() // wait to consume AddContainersAction + store.dispatch(ContainerAction.RemoveContainerAction(container.contextId)) .joinBlocking() diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/OriginVerifierFeatureTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/OriginVerifierFeatureTest.kt index 9f359e41125..87c26780af9 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/OriginVerifierFeatureTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/OriginVerifierFeatureTest.kt @@ -10,7 +10,7 @@ import androidx.browser.customtabs.CustomTabsSessionToken import androidx.core.net.toUri import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.feature.customtabs.store.CustomTabState import mozilla.components.feature.customtabs.store.CustomTabsServiceStore import mozilla.components.feature.customtabs.store.OriginRelationPair @@ -37,14 +37,14 @@ import org.mockito.Mockito.verify class OriginVerifierFeatureTest { @Test - fun `verify fails if no creatorPackageName is saved`() = runBlockingTest { + fun `verify fails if no creatorPackageName is saved`() = runTest { val feature = OriginVerifierFeature(mock(), mock(), mock()) assertFalse(feature.verify(CustomTabState(), mock(), RELATION_HANDLE_ALL_URLS, mock())) } @Test - fun `verify returns existing relationship`() = runBlockingTest { + fun `verify returns existing relationship`() = runTest { val feature = OriginVerifierFeature(mock(), mock(), mock()) val origin = "https://example.com".toUri() val state = CustomTabState( @@ -61,7 +61,7 @@ class OriginVerifierFeatureTest { } @Test - fun `verify checks new relationships`() = runBlockingTest { + fun `verify checks new relationships`() = runTest { val store: CustomTabsServiceStore = mock() val verifier: OriginVerifier = mock() val feature = spy(OriginVerifierFeature(mock(), mock()) { store.dispatch(it) }) diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/verify/OriginVerifierTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/verify/OriginVerifierTest.kt index e0a604deb37..ca6dd8055b7 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/verify/OriginVerifierTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/verify/OriginVerifierTest.kt @@ -10,7 +10,7 @@ import androidx.browser.customtabs.CustomTabsService.RELATION_USE_AS_ORIGIN import androidx.core.net.toUri import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.fetch.Response import mozilla.components.service.digitalassetlinks.AssetDescriptor import mozilla.components.service.digitalassetlinks.Relation @@ -49,14 +49,14 @@ class OriginVerifierTest { } @Test - fun `only HTTPS allowed`() = runBlocking { + fun `only HTTPS allowed`() = runTest { val verifier = buildVerifier(RELATION_HANDLE_ALL_URLS) assertFalse(verifier.verifyOrigin("LOL".toUri())) assertFalse(verifier.verifyOrigin("http://www.android.com".toUri())) } @Test - fun verifyOrigin() = runBlocking { + fun verifyOrigin() = runTest { val verifier = buildVerifier(RELATION_USE_AS_ORIGIN) doReturn(true).`when`(checker).checkRelationship( AssetDescriptor.Web("https://www.example.com"), diff --git a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt index dc6c9fc6f32..68aece8291c 100644 --- a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt +++ b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/AbstractFetchDownloadServiceTest.kt @@ -26,7 +26,9 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.DownloadAction import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.browser.state.state.content.DownloadState.Status.COMPLETED @@ -103,9 +105,14 @@ class AbstractFetchDownloadServiceTest { @Rule @JvmField val folder = TemporaryFolder() + // We need different scopes and schedulers because: + // - the service will continuously try to update the download notification using MainScope() + // - if using the same scope in tests the test won't end + // - need a way to advance main dispatcher used by the service. @get:Rule val coroutinesTestRule = MainCoroutineRule() - private val testDispatcher = coroutinesTestRule.testDispatcher + private val mainDispatcher = coroutinesTestRule.testDispatcher + private val testsDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler()) @Mock private lateinit var client: Client private lateinit var browserStore: BrowserStore @@ -132,7 +139,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `begins download when started`() = runBlocking { + fun `begins download when started`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val response = Response( "https://example.com/file.txt", @@ -161,7 +168,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `WHEN a download intent is received THEN handleDownloadIntent must be called`() = runBlocking { + fun `WHEN a download intent is received THEN handleDownloadIntent must be called`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val downloadIntent = Intent("ACTION_DOWNLOAD") @@ -179,7 +186,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `WHEN an intent does not provide an action THEN handleDownloadIntent must be called`() = runBlocking { + fun `WHEN an intent does not provide an action THEN handleDownloadIntent must be called`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val downloadIntent = Intent() @@ -197,7 +204,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `WHEN a remove download intent is received THEN handleRemoveDownloadIntent must be called`() = runBlocking { + fun `WHEN a remove download intent is received THEN handleRemoveDownloadIntent must be called`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val downloadIntent = Intent(ACTION_REMOVE_PRIVATE_DOWNLOAD) @@ -255,7 +262,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `service redelivers if no download extra is passed `() = runBlocking { + fun `service redelivers if no download extra is passed `() = runTest(testsDispatcher) { val downloadIntent = Intent("ACTION_DOWNLOAD") val intentCode = service.onStartCommand(downloadIntent, 0, 0) @@ -264,7 +271,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `verifyDownload sets the download to failed if it is not complete`() = runBlocking { + fun `verifyDownload sets the download to failed if it is not complete`() = runTest(testsDispatcher) { val downloadState = DownloadState( url = "mozilla.org/mozilla.txt", contentLength = 50L, @@ -289,7 +296,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `verifyDownload does NOT set the download to failed if it is paused`() = runBlocking { + fun `verifyDownload does NOT set the download to failed if it is paused`() = runTest(testsDispatcher) { val downloadState = DownloadState( url = "mozilla.org/mozilla.txt", contentLength = 50L, @@ -314,7 +321,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `verifyDownload does NOT set the download to failed if it is complete`() = runBlocking { + fun `verifyDownload does NOT set the download to failed if it is complete`() = runTest(testsDispatcher) { val downloadState = DownloadState( url = "mozilla.org/mozilla.txt", contentLength = 50L, @@ -339,7 +346,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `verifyDownload does NOT set the download to failed if it is cancelled`() = runBlocking { + fun `verifyDownload does NOT set the download to failed if it is cancelled`() = runTest(testsDispatcher) { val downloadState = DownloadState( url = "mozilla.org/mozilla.txt", contentLength = 50L, @@ -364,7 +371,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `verifyDownload does NOT set the download to failed if it is status COMPLETED`() = runBlocking { + fun `verifyDownload does NOT set the download to failed if it is status COMPLETED`() = runTest(testsDispatcher) { val downloadState = DownloadState( url = "mozilla.org/mozilla.txt", contentLength = 50L, @@ -414,7 +421,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `broadcastReceiver handles ACTION_PAUSE`() = runBlocking { + fun `broadcastReceiver handles ACTION_PAUSE`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val response = Response( "https://example.com/file.txt", @@ -453,7 +460,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `broadcastReceiver handles ACTION_CANCEL`() = runBlocking { + fun `broadcastReceiver handles ACTION_CANCEL`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val response = Response( "https://example.com/file.txt", @@ -490,7 +497,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `broadcastReceiver handles ACTION_RESUME`() = runBlocking { + fun `broadcastReceiver handles ACTION_RESUME`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val downloadResponse = Response( @@ -553,7 +560,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `broadcastReceiver handles ACTION_TRY_AGAIN`() = runBlocking { + fun `broadcastReceiver handles ACTION_TRY_AGAIN`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val response = Response( "https://example.com/file.txt", @@ -604,7 +611,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `download fails on a bad network response`() = runBlocking { + fun `download fails on a bad network response`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val response = Response( "https://example.com/file.txt", @@ -683,14 +690,15 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DOWNLOADING) assertEquals(DOWNLOADING, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() // The additional notification is the summary one (the notification group). assertEquals(2, shadowNotificationService.size()) } @Test - fun `onStartCommand must change status of INITIATED downloads to DOWNLOADING`() = runBlocking { + fun `onStartCommand must change status of INITIATED downloads to DOWNLOADING`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt", status = INITIATED) val downloadIntent = Intent("ACTION_DOWNLOAD") @@ -707,7 +715,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `onStartCommand must change the status only for INITIATED downloads`() = runBlocking { + fun `onStartCommand must change the status only for INITIATED downloads`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt", status = FAILED) val downloadIntent = Intent("ACTION_DOWNLOAD") @@ -721,7 +729,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `onStartCommand sets the notification foreground`() = runBlocking { + fun `onStartCommand sets the notification foreground`() = runTest(testsDispatcher) { val download = DownloadState("https://example.com/file.txt", "file.txt") val downloadIntent = Intent("ACTION_DOWNLOAD") @@ -736,7 +744,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `sets the notification foreground in devices that support notification group`() = runBlocking { + fun `sets the notification foreground in devices that support notification group`() = runTest(testsDispatcher) { val download = DownloadState( id = "1", url = "https://example.com/file.txt", fileName = "file.txt", status = DOWNLOADING @@ -840,7 +848,7 @@ class AbstractFetchDownloadServiceTest { @Test @Config(sdk = [Build.VERSION_CODES.M]) - fun `updateNotificationGroup will do nothing on devices that do not support notificaiton groups`() = runBlocking { + fun `updateNotificationGroup will do nothing on devices that do not support notificaiton groups`() = runTest(testsDispatcher) { val download = DownloadState( id = "1", url = "https://example.com/file.txt", fileName = "file.txt", status = DOWNLOADING @@ -1066,7 +1074,8 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.PAUSED) assertEquals(DownloadState.Status.PAUSED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() // one of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) @@ -1098,7 +1107,8 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.COMPLETED) assertEquals(DownloadState.Status.COMPLETED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() assertEquals(2, shadowNotificationService.size()) } @@ -1129,7 +1139,8 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, FAILED) assertEquals(FAILED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() // one of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) @@ -1161,14 +1172,15 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.CANCELLED) assertEquals(DownloadState.Status.CANCELLED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() // The additional notification is the summary one (the notification group). assertEquals(1, shadowNotificationService.size()) } @Test - fun `job status is set to failed when an Exception is thrown while performDownload`() = runBlocking { + fun `job status is set to failed when an Exception is thrown while performDownload`() = runTest(testsDispatcher) { doThrow(IOException()).`when`(client).fetch(any()) val download = DownloadState("https://example.com/file.txt", "file.txt") @@ -1187,7 +1199,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `WHEN a download is from a private session the request must be private`() = runBlocking { + fun `WHEN a download is from a private session the request must be private`() = runTest(testsDispatcher) { val response = Response( "https://example.com/file.txt", 200, @@ -1289,7 +1301,8 @@ class AbstractFetchDownloadServiceTest { verify(service).performDownload(providedDownload.capture(), anyBoolean()) // Advance the clock so that the puller posts a notification. - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() // One of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) @@ -1352,7 +1365,8 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(service.downloadJobs[download.id]!!, DownloadState.Status.PAUSED) // Advance the clock so that the poller posts a notification. - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() assertEquals(2, shadowNotificationService.size()) // Now simulate onTaskRemoved. @@ -1362,7 +1376,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `clearAllDownloadsNotificationsAndJobs cancels all running jobs and remove all notifications`() = runBlocking { + fun `clearAllDownloadsNotificationsAndJobs cancels all running jobs and remove all notifications`() = runTest(testsDispatcher) { val download = DownloadState( id = "1", url = "https://example.com/file.txt", fileName = "file.txt", status = DOWNLOADING @@ -1402,7 +1416,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `onDestroy will remove all download notifications, jobs and will call unregisterNotificationActionsReceiver`() = runBlocking { + fun `onDestroy will remove all download notifications, jobs and will call unregisterNotificationActionsReceiver`() = runTest(testsDispatcher) { val service = spy(object : AbstractFetchDownloadService() { override val httpClient = client override val store = browserStore @@ -1438,7 +1452,7 @@ class AbstractFetchDownloadServiceTest { @Test @Config(sdk = [Build.VERSION_CODES.P], shadows = [ShadowFileProvider::class]) - fun `WHEN a download is completed and the scoped storage is not used it MUST be added manually to the download system database`() = runBlockingTest { + fun `WHEN a download is completed and the scoped storage is not used it MUST be added manually to the download system database`() = runTest(testsDispatcher) { val download = DownloadState( url = "http://www.mozilla.org", fileName = "example.apk", @@ -1468,7 +1482,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `WHEN a download is completed and the scoped storage is used addToDownloadSystemDatabaseCompat MUST NOT be called`() = runBlockingTest { + fun `WHEN a download is completed and the scoped storage is used addToDownloadSystemDatabaseCompat MUST NOT be called`() = runTest(testsDispatcher) { val download = DownloadState( url = "http://www.mozilla.org", fileName = "example.apk", @@ -1548,7 +1562,7 @@ class AbstractFetchDownloadServiceTest { @Test @Config(sdk = [Build.VERSION_CODES.P], shadows = [ShadowFileProvider::class]) @Suppress("Deprecation") - fun `do not pass non-http(s) url to addCompletedDownload`() = runBlockingTest { + fun `do not pass non-http(s) url to addCompletedDownload`() = runTest(testsDispatcher) { val download = DownloadState( url = "blob:moz-extension://d5ea9baa-64c9-4c3d-bb38-49308c47997c/", fileName = "example.apk", @@ -1574,7 +1588,7 @@ class AbstractFetchDownloadServiceTest { @Config(sdk = [Build.VERSION_CODES.P], shadows = [ShadowFileProvider::class]) @Suppress("Deprecation") fun `GIVEN a download that throws an exception WHEN adding to the system database THEN handle the exception`() = - runBlockingTest { + runTest(testsDispatcher) { val download = DownloadState( url = "url", fileName = "example.apk", @@ -1608,7 +1622,7 @@ class AbstractFetchDownloadServiceTest { @Test @Config(sdk = [Build.VERSION_CODES.P], shadows = [ShadowFileProvider::class]) @Suppress("Deprecation") - fun `pass http(s) url to addCompletedDownload`() = runBlockingTest { + fun `pass http(s) url to addCompletedDownload`() = runTest(testsDispatcher) { val download = DownloadState( url = "https://mozilla.com", fileName = "example.apk", @@ -1633,7 +1647,7 @@ class AbstractFetchDownloadServiceTest { @Test @Config(sdk = [Build.VERSION_CODES.P], shadows = [ShadowFileProvider::class]) @Suppress("Deprecation") - fun `always call addCompletedDownload with a not empty or null mimeType`() = runBlockingTest { + fun `always call addCompletedDownload with a not empty or null mimeType`() = runTest(testsDispatcher) { val service = spy(object : AbstractFetchDownloadService() { override val httpClient = client override val store = browserStore @@ -1693,7 +1707,8 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(cancelledDownloadJobState, DownloadState.Status.CANCELLED) assertEquals(DownloadState.Status.CANCELLED, service.getDownloadJobStatus(cancelledDownloadJobState)) - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() // The additional notification is the summary one (the notification group). assertEquals(1, shadowNotificationService.size()) @@ -1712,13 +1727,14 @@ class AbstractFetchDownloadServiceTest { service.setDownloadJobStatus(downloadJobState, DownloadState.Status.COMPLETED) assertEquals(DownloadState.Status.COMPLETED, service.getDownloadJobStatus(downloadJobState)) - testDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.advanceTimeBy(PROGRESS_UPDATE_INTERVAL) + mainDispatcher.scheduler.runCurrent() // one of the notifications it is the group notification only for devices the support it assertEquals(2, shadowNotificationService.size()) } @Test - fun `createDirectoryIfNeeded - MUST create directory when it does not exists`() = runBlocking { + fun `createDirectoryIfNeeded - MUST create directory when it does not exists`() = runTest(testsDispatcher) { val download = DownloadState(destinationDirectory = Environment.DIRECTORY_DOWNLOADS, url = "") val file = File(download.directoryPath) @@ -1790,7 +1806,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `copyInChunks must alter download currentBytesCopied`() = runBlocking { + fun `copyInChunks must alter download currentBytesCopied`() = runTest(testsDispatcher) { val downloadJobState = DownloadJobState(state = mock(), status = DOWNLOADING) val inputStream = mock() @@ -1805,7 +1821,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `copyInChunks - must return ERROR_IN_STREAM_CLOSED when inStream is closed`() = runBlocking { + fun `copyInChunks - must return ERROR_IN_STREAM_CLOSED when inStream is closed`() = runTest(testsDispatcher) { val downloadJobState = DownloadJobState(state = mock(), status = DOWNLOADING) val inputStream = mock() @@ -1822,7 +1838,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `copyInChunks - must throw when inStream is closed and download was performed using http client`() = runBlocking { + fun `copyInChunks - must throw when inStream is closed and download was performed using http client`() = runTest(testsDispatcher) { val downloadJobState = DownloadJobState(state = mock(), status = DOWNLOADING) val inputStream = mock() var exceptionWasThrown = false @@ -1844,7 +1860,7 @@ class AbstractFetchDownloadServiceTest { } @Test - fun `copyInChunks - must return COMPLETED when finish copying bytes`() = runBlocking { + fun `copyInChunks - must return COMPLETED when finish copying bytes`() = runTest(testsDispatcher) { val downloadJobState = DownloadJobState(state = mock(), status = DOWNLOADING) val inputStream = mock() diff --git a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt index 29c3dac1aaf..66b2acbe51b 100644 --- a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt +++ b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadMiddlewareTest.kt @@ -8,7 +8,6 @@ import android.app.DownloadManager.EXTRA_DOWNLOAD_ID import android.content.Context import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.DownloadAction import mozilla.components.browser.state.action.TabListAction @@ -27,6 +26,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -47,7 +47,7 @@ class DownloadMiddlewareTest { private val dispatcher = coroutinesTestRule.testDispatcher @Test - fun `service is started when download is queued`() = runBlockingTest { + fun `service is started when download is queued`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -83,7 +83,7 @@ class DownloadMiddlewareTest { } @Test - fun `saveDownload do not store private downloads`() = runBlockingTest { + fun `saveDownload do not store private downloads`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -106,7 +106,7 @@ class DownloadMiddlewareTest { } @Test - fun `restarted downloads MUST not be passed to the downloadStorage`() = runBlockingTest { + fun `restarted downloads MUST not be passed to the downloadStorage`() = runTestOnMain { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() val downloadMiddleware = DownloadMiddleware( @@ -132,7 +132,7 @@ class DownloadMiddlewareTest { } @Test - fun `previously added downloads MUST be ignored`() = runBlockingTest { + fun `previously added downloads MUST be ignored`() = runTestOnMain { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() val download = DownloadState("https://mozilla.org/download") @@ -155,7 +155,7 @@ class DownloadMiddlewareTest { } @Test - fun `RemoveDownloadAction MUST remove from the storage`() = runBlockingTest { + fun `RemoveDownloadAction MUST remove from the storage`() = runTestOnMain { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() val downloadMiddleware = DownloadMiddleware( @@ -178,7 +178,7 @@ class DownloadMiddlewareTest { } @Test - fun `RemoveAllDownloadsAction MUST remove all downloads from the storage`() = runBlockingTest { + fun `RemoveAllDownloadsAction MUST remove all downloads from the storage`() = runTestOnMain { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() val downloadMiddleware = DownloadMiddleware( @@ -201,7 +201,7 @@ class DownloadMiddlewareTest { } @Test - fun `UpdateDownloadAction MUST update the storage when changes are needed`() = runBlockingTest { + fun `UpdateDownloadAction MUST update the storage when changes are needed`() = runTestOnMain { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() val downloadMiddleware = DownloadMiddleware( @@ -242,7 +242,7 @@ class DownloadMiddlewareTest { } @Test - fun `RestoreDownloadsState MUST populate the store with items in the storage`() = runBlockingTest { + fun `RestoreDownloadsState MUST populate the store with items in the storage`() = runTestOnMain { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() val downloadMiddleware = DownloadMiddleware( @@ -270,7 +270,7 @@ class DownloadMiddlewareTest { } @Test - fun `private downloads MUST NOT be restored`() = runBlockingTest { + fun `private downloads MUST NOT be restored`() = runTestOnMain { val applicationContext: Context = mock() val downloadStorage: DownloadStorage = mock() val downloadMiddleware = DownloadMiddleware( @@ -298,7 +298,7 @@ class DownloadMiddlewareTest { } @Test - fun `sendDownloadIntent MUST call startForegroundService WHEN downloads are NOT COMPLETED, CANCELLED and FAILED`() = runBlockingTest { + fun `sendDownloadIntent MUST call startForegroundService WHEN downloads are NOT COMPLETED, CANCELLED and FAILED`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -326,7 +326,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN RemoveAllTabsAction and RemoveAllPrivateTabsAction are received THEN removePrivateNotifications must be called`() = runBlockingTest { + fun `WHEN RemoveAllTabsAction and RemoveAllPrivateTabsAction are received THEN removePrivateNotifications must be called`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -355,7 +355,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN RemoveTabsAction is received AND there is no private tabs THEN removePrivateNotifications MUST be called`() = runBlockingTest { + fun `WHEN RemoveTabsAction is received AND there is no private tabs THEN removePrivateNotifications MUST be called`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -386,7 +386,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN RemoveTabsAction is received AND there is a private tab THEN removePrivateNotifications MUST NOT be called`() = runBlockingTest { + fun `WHEN RemoveTabsAction is received AND there is a private tab THEN removePrivateNotifications MUST NOT be called`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -417,7 +417,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN RemoveTabAction is received AND there is no private tabs THEN removePrivateNotifications MUST be called`() = runBlockingTest { + fun `WHEN RemoveTabAction is received AND there is no private tabs THEN removePrivateNotifications MUST be called`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -447,7 +447,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN RemoveTabAction is received AND there is a private tab THEN removePrivateNotifications MUST NOT be called`() = runBlockingTest { + fun `WHEN RemoveTabAction is received AND there is a private tab THEN removePrivateNotifications MUST NOT be called`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -477,7 +477,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN removeStatusBarNotification is called THEN an ACTION_REMOVE_PRIVATE_DOWNLOAD intent must be created`() = runBlockingTest { + fun `WHEN removeStatusBarNotification is called THEN an ACTION_REMOVE_PRIVATE_DOWNLOAD intent must be created`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -497,7 +497,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN removePrivateNotifications is called THEN removeStatusBarNotification will be called only for private download`() = runBlockingTest { + fun `WHEN removePrivateNotifications is called THEN removeStatusBarNotification will be called only for private download`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -522,7 +522,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN removePrivateNotifications is called THEN removeStatusBarNotification will be called for all private downloads`() = runBlockingTest { + fun `WHEN removePrivateNotifications is called THEN removeStatusBarNotification will be called for all private downloads`() = runTestOnMain { val applicationContext: Context = mock() val downloadMiddleware = spy( DownloadMiddleware( @@ -548,7 +548,7 @@ class DownloadMiddlewareTest { } @Test - fun `WHEN an action for canceling a download response is received THEN a download response must be canceled`() = runBlockingTest { + fun `WHEN an action for canceling a download response is received THEN a download response must be canceled`() = runTestOnMain { val response = mock() val download = DownloadState(id = "downloadID", url = "example.com/5MB.zip", response = response) val applicationContext: Context = mock() diff --git a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt index a8f7e44c22c..3cd81912174 100644 --- a/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt +++ b/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/share/ShareDownloadFeatureTest.kt @@ -8,8 +8,6 @@ import android.content.Context import android.webkit.MimeTypeMap import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestScope import mozilla.components.browser.state.action.ShareInternetResourceAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ContentState @@ -27,6 +25,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -61,6 +60,7 @@ class ShareDownloadFeatureTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() private val dispatcher = coroutinesTestRule.testDispatcher + private val scope = coroutinesTestRule.scope @Before fun setup() { @@ -75,7 +75,7 @@ class ShareDownloadFeatureTest { } @Test - fun `cleanupCache should automatically be called when this class is initialized`() = runBlocking { + fun `cleanupCache should automatically be called when this class is initialized`() = runTestOnMain { val cacheDir = File(context.cacheDir, cacheDirName).also { dir -> dir.mkdirs() File(dir, "leftoverFile").also { file -> @@ -113,7 +113,7 @@ class ShareDownloadFeatureTest { } @Test - fun `cleanupCache should delete all files from the cache directory`() = runBlocking { + fun `cleanupCache should delete all files from the cache directory`() = runTestOnMain { val shareFeature = spy(ShareDownloadFeature(context, mock(), mock(), null, Dispatchers.Main)) val testDir = File(context.cacheDir, cacheDirName).also { dir -> dir.mkdirs() @@ -131,12 +131,12 @@ class ShareDownloadFeatureTest { } @Test - fun `startSharing() will download and then share the selected download`() = runBlocking { + fun `startSharing() will download and then share the selected download`() = runTestOnMain { val shareFeature = spy(ShareDownloadFeature(context, mock(), mock(), null)) val shareState = ShareInternetResourceState(url = "testUrl", contentType = "contentType") val downloadedFile = File("filePath") doReturn(downloadedFile).`when`(shareFeature).download(any()) - shareFeature.scope = TestScope(coroutineContext) + shareFeature.scope = scope shareFeature.startSharing(shareState) @@ -146,14 +146,14 @@ class ShareDownloadFeatureTest { } @Test - fun `startSharing() will not use a too long HTTP url as message`() = runBlocking { + fun `startSharing() will not use a too long HTTP url as message`() = runTestOnMain { val shareFeature = spy(ShareDownloadFeature(context, mock(), mock(), null)) val maxSizeUrl = "a".repeat(CHARACTERS_IN_SHARE_TEXT_LIMIT) val tooLongUrl = maxSizeUrl + 'x' val shareState = ShareInternetResourceState(url = tooLongUrl, contentType = "contentType") val downloadedFile = File("filePath") doReturn(downloadedFile).`when`(shareFeature).download(any()) - shareFeature.scope = TestScope() + shareFeature.scope = scope shareFeature.startSharing(shareState) @@ -163,12 +163,12 @@ class ShareDownloadFeatureTest { } @Test - fun `startSharing() will use an empty String as message for data URLs`() = runBlocking { + fun `startSharing() will use an empty String as message for data URLs`() = runTestOnMain { val shareFeature = spy(ShareDownloadFeature(context, mock(), mock(), null)) val shareState = ShareInternetResourceState(url = "data:image/png;base64,longstring", contentType = "contentType") val downloadedFile = File("filePath") doReturn(downloadedFile).`when`(shareFeature).download(any()) - shareFeature.scope = TestScope() + shareFeature.scope = scope shareFeature.startSharing(shareState) diff --git a/components/feature/media/src/test/java/mozilla/components/feature/media/ext/SessionStateKtTest.kt b/components/feature/media/src/test/java/mozilla/components/feature/media/ext/SessionStateKtTest.kt index 4feb74f7151..e1815065e79 100644 --- a/components/feature/media/src/test/java/mozilla/components/feature/media/ext/SessionStateKtTest.kt +++ b/components/feature/media/src/test/java/mozilla/components/feature/media/ext/SessionStateKtTest.kt @@ -2,7 +2,7 @@ package mozilla.components.feature.media.ext import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.ContentState import mozilla.components.browser.state.state.SessionState import mozilla.components.support.test.mock @@ -19,39 +19,33 @@ class SessionStateKtTest { private val getArtworkNull: (suspend () -> Bitmap?) = { null } @Test - fun `getNonPrivateIcon returns null when in private mode`() { + fun `getNonPrivateIcon returns null when in private mode`() = runTest { val sessionState: SessionState = mock() val contentState: ContentState = mock() whenever(sessionState.content).thenReturn(contentState) whenever(contentState.private).thenReturn(true) - var result: Bitmap? - runBlocking { - result = sessionState.getNonPrivateIcon(getArtwork) - } + val result = sessionState.getNonPrivateIcon(getArtwork) assertEquals(result, null) } @Test - fun `getNonPrivateIcon returns bitmap when not in private mode`() { + fun `getNonPrivateIcon returns bitmap when not in private mode`() = runTest { val sessionState: SessionState = mock() val contentState: ContentState = mock() whenever(sessionState.content).thenReturn(contentState) whenever(contentState.private).thenReturn(false) - var result: Bitmap? - runBlocking { - result = sessionState.getNonPrivateIcon(getArtwork) - } + val result = sessionState.getNonPrivateIcon(getArtwork) assertEquals(result, bitmap) } @Test - fun `getNonPrivateIcon returns content icon when not in private mode`() { + fun `getNonPrivateIcon returns content icon when not in private mode`() = runTest { val sessionState: SessionState = mock() val contentState: ContentState = mock() val icon: Bitmap = mock() @@ -60,16 +54,13 @@ class SessionStateKtTest { whenever(contentState.private).thenReturn(false) whenever(contentState.icon).thenReturn(icon) - var result: Bitmap? - runBlocking { - result = sessionState.getNonPrivateIcon(null) - } + val result = sessionState.getNonPrivateIcon(null) assertEquals(result, icon) } @Test - fun `getNonPrivateIcon returns content icon when getArtwork return null`() { + fun `getNonPrivateIcon returns content icon when getArtwork return null`() = runTest { val sessionState: SessionState = mock() val contentState: ContentState = mock() val icon: Bitmap = mock() @@ -78,10 +69,7 @@ class SessionStateKtTest { whenever(contentState.private).thenReturn(false) whenever(contentState.icon).thenReturn(icon) - var result: Bitmap? - runBlocking { - result = sessionState.getNonPrivateIcon(getArtworkNull) - } + val result = sessionState.getNonPrivateIcon(getArtworkNull) assertEquals(result, icon) } diff --git a/components/feature/media/src/test/java/mozilla/components/feature/media/notification/MediaNotificationTest.kt b/components/feature/media/src/test/java/mozilla/components/feature/media/notification/MediaNotificationTest.kt index 94a7d6ecfd7..e3cc1a9dc6a 100644 --- a/components/feature/media/src/test/java/mozilla/components/feature/media/notification/MediaNotificationTest.kt +++ b/components/feature/media/src/test/java/mozilla/components/feature/media/notification/MediaNotificationTest.kt @@ -10,7 +10,7 @@ import android.content.Intent import android.content.pm.PackageManager import androidx.core.app.NotificationCompat import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.MediaSessionState import mozilla.components.browser.state.state.createTab @@ -52,7 +52,7 @@ class MediaNotificationTest { } @Test - fun `media session notification for playing state`() { + fun `media session notification for playing state`() = runTest { val state = BrowserState( tabs = listOf( createTab( @@ -62,9 +62,7 @@ class MediaNotificationTest { ) ) - val notification = runBlocking { - MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) - } + val notification = MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) assertEquals("https://www.mozilla.org", notification.text) assertEquals("Mozilla", notification.title) @@ -72,7 +70,7 @@ class MediaNotificationTest { } @Test - fun `media session notification for paused state`() { + fun `media session notification for paused state`() = runTest { val state = BrowserState( tabs = listOf( createTab( @@ -82,9 +80,7 @@ class MediaNotificationTest { ) ) - val notification = runBlocking { - MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) - } + val notification = MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) assertEquals("https://www.mozilla.org", notification.text) assertEquals("Mozilla", notification.title) @@ -92,7 +88,7 @@ class MediaNotificationTest { } @Test - fun `media session notification for stopped state`() { + fun `media session notification for stopped state`() = runTest { val state = BrowserState( tabs = listOf( createTab( @@ -102,16 +98,14 @@ class MediaNotificationTest { ) ) - val notification = runBlocking { - MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) - } + val notification = MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) assertEquals("", notification.text) assertEquals("", notification.title) } @Test - fun `media session notification for playing state in private mode`() { + fun `media session notification for playing state in private mode`() = runTest { val state = BrowserState( tabs = listOf( createTab( @@ -121,9 +115,7 @@ class MediaNotificationTest { ) ) - val notification = runBlocking { - MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) - } + val notification = MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) assertEquals("", notification.text) assertEquals("A site is playing media", notification.title) @@ -131,7 +123,7 @@ class MediaNotificationTest { } @Test - fun `media session notification for paused state in private mode`() { + fun `media session notification for paused state in private mode`() = runTest { val state = BrowserState( tabs = listOf( createTab( @@ -141,9 +133,7 @@ class MediaNotificationTest { ) ) - val notification = runBlocking { - MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) - } + val notification = MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) assertEquals("", notification.text) assertEquals("A site is playing media", notification.title) @@ -151,7 +141,7 @@ class MediaNotificationTest { } @Test - fun `media session notification with metadata in non private mode`() { + fun `media session notification with metadata in non private mode`() = runTest { val mediaSessionState: MediaSessionState = mock() val metadata: MediaSession.Metadata = mock() whenever(mediaSessionState.metadata).thenReturn(metadata) @@ -167,9 +157,7 @@ class MediaNotificationTest { ) ) - val notification = runBlocking { - MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) - } + val notification = MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) assertEquals("https://www.mozilla.org", notification.text) assertEquals("test title", notification.title) @@ -177,7 +165,7 @@ class MediaNotificationTest { } @Test - fun `media session notification with metadata in private mode`() { + fun `media session notification with metadata in private mode`() = runTest { val mediaSessionState: MediaSessionState = mock() val metadata: MediaSession.Metadata = mock() whenever(mediaSessionState.metadata).thenReturn(metadata) @@ -193,9 +181,7 @@ class MediaNotificationTest { ) ) - val notification = runBlocking { - MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) - } + val notification = MediaNotification(context, AbstractMediaSessionService::class.java).create(state.tabs[0], mock()) assertEquals("", notification.text) assertEquals("A site is playing media", notification.title) diff --git a/components/feature/privatemode/build.gradle b/components/feature/privatemode/build.gradle index 844adfc551d..7c801f6f055 100644 --- a/components/feature/privatemode/build.gradle +++ b/components/feature/privatemode/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation Dependencies.kotlin_stdlib testImplementation project(':support-test') + testImplementation project(':support-test-libstate') testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit diff --git a/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/PrivateNotificationFeatureTest.kt b/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/PrivateNotificationFeatureTest.kt index 2b0af6069e0..c1cdcc74563 100644 --- a/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/PrivateNotificationFeatureTest.kt +++ b/components/feature/privatemode/src/test/java/mozilla/components/feature/privatemode/notification/PrivateNotificationFeatureTest.kt @@ -4,7 +4,9 @@ import android.content.Context import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.createCustomTab @@ -15,6 +17,7 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -49,13 +52,14 @@ class PrivateNotificationFeatureTest { } @Test - fun `service should be started if pre-existing private session is present`() = runBlocking { + fun `service should be started if pre-existing private session is present`() = runTest(StandardTestDispatcher()) { val privateSession = createTab("https://firefox.com", private = true) val intent = argumentCaptor() store.dispatch(TabListAction.AddTabAction(privateSession)).join() feature.start() + runCurrent() verify(context, times(1)).startService(intent.capture()) val expected = Intent(testContext, AbstractPrivateNotificationService::class.java) @@ -64,7 +68,7 @@ class PrivateNotificationFeatureTest { } @Test - fun `service should be started when private session is added`() = runBlocking { + fun `service should be started when private session is added`() = runTestOnMain { val privateSession = createTab("https://firefox.com", private = true) feature.start() @@ -76,7 +80,7 @@ class PrivateNotificationFeatureTest { } @Test - fun `service should not be started multiple times`() = runBlocking { + fun `service should not be started multiple times`() = runTestOnMain { val privateSession1 = createTab("https://firefox.com", private = true) val privateSession2 = createTab("https://mozilla.org", private = true) @@ -90,7 +94,7 @@ class PrivateNotificationFeatureTest { } @Test - fun `notification service should not be started when normal sessions are added`() = runBlocking { + fun `notification service should not be started when normal sessions are added`() = runTestOnMain { val normalSession = createTab("https://firefox.com") val customSession = createCustomTab("https://firefox.com") @@ -106,7 +110,7 @@ class PrivateNotificationFeatureTest { } @Test - fun `notification service should not be started when custom sessions are added`() = runBlocking { + fun `notification service should not be started when custom sessions are added`() = runTestOnMain { val privateCustomSession = createCustomTab("https://firefox.com").let { it.copy(content = it.content.copy(private = true)) } diff --git a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt index ed695b2285a..df004e822ec 100644 --- a/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt +++ b/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSelectBarTest.kt @@ -9,7 +9,7 @@ import androidx.appcompat.widget.AppCompatTextView import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.feature.prompts.R import mozilla.components.feature.prompts.concept.SelectablePromptView @@ -81,7 +81,7 @@ class CreditCardSelectBarTest { } @Test - fun `GIVEN a listener WHEN a credit card is selected THEN onOptionSelect is called`() = runBlocking { + fun `GIVEN a listener WHEN a credit card is selected THEN onOptionSelect is called`() = runTest { val listener: SelectablePromptView.Listener = mock() creditCardSelectBar.listener = listener diff --git a/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureKtTest.kt b/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureKtTest.kt index a8d8a72409a..6615bb4fd3d 100644 --- a/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureKtTest.kt +++ b/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureKtTest.kt @@ -4,11 +4,11 @@ package mozilla.components.feature.push -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import kotlinx.coroutines.plus -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.appservices.push.PushException.CommunicationException import mozilla.appservices.push.PushException.CommunicationServerException import mozilla.appservices.push.PushException.CryptoException @@ -32,28 +32,29 @@ class AutoPushFeatureKtTest { assertEquals(ServiceType.FCM, config.serviceType) } + @OptIn(DelicateCoroutinesApi::class) @Test - fun `exception handler handles exceptions`() = runBlockingTest { + fun `exception handler handles exceptions`() = runTest { var invoked = false - val scope = CoroutineScope(coroutineContext) + exceptionHandler { invoked = true } + val handler = exceptionHandler { invoked = true } - scope.launch { throw PushError.MalformedMessage("test") } + GlobalScope.launch(handler) { throw PushError.MalformedMessage("test") }.join() assertFalse(invoked) - scope.launch { throw GeneralException("test") } + GlobalScope.launch(handler) { throw GeneralException("test") }.join() assertFalse(invoked) - scope.launch { throw CryptoException("test") } + GlobalScope.launch(handler) { throw CryptoException("test") }.join() assertFalse(invoked) - scope.launch { throw CommunicationException("test") } + GlobalScope.launch(handler) { throw CommunicationException("test") }.join() assertFalse(invoked) - scope.launch { throw CommunicationServerException("test") } + GlobalScope.launch(handler) { throw CommunicationServerException("test") }.join() assertFalse(invoked) // An exception where we should invoke our callback. - scope.launch { throw MissingRegistrationTokenException("") } + GlobalScope.launch(handler) { throw MissingRegistrationTokenException("") }.join() assertTrue(invoked) } } diff --git a/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureTest.kt b/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureTest.kt index d8a3c4c156e..9d171273921 100644 --- a/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureTest.kt +++ b/components/feature/push/src/test/java/mozilla/components/feature/push/AutoPushFeatureTest.kt @@ -10,7 +10,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest import mozilla.appservices.push.PushException.GeneralException import mozilla.appservices.push.PushException.MissingRegistrationTokenException import mozilla.components.concept.base.crash.CrashReporting @@ -25,6 +24,8 @@ import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.nullable import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.After import org.junit.Assert.assertEquals @@ -33,13 +34,12 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString -import org.mockito.ArgumentMatchers.nullable import org.mockito.Mockito.never import org.mockito.Mockito.spy -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @@ -47,6 +47,9 @@ import org.mockito.Mockito.verifyNoMoreInteractions @RunWith(AndroidJUnit4::class) class AutoPushFeatureTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private var lastVerified: Long get() = preference(testContext).getLong(LAST_VERIFIED, System.currentTimeMillis()) set(value) = preference(testContext).edit().putLong(LAST_VERIFIED, value).apply() @@ -77,14 +80,14 @@ class AutoPushFeatureTest { } @Test - fun `updateToken not called if no token in prefs`() = runBlockingTest { + fun `updateToken not called if no token in prefs`() = runTestOnMain { AutoPushFeature(testContext, mock(), mock(), coroutineContext, connection) verify(connection, never()).updateToken(anyString()) } @Test - fun `updateToken called if token is in prefs`() = runBlockingTest { + fun `updateToken called if token is in prefs`() = runTestOnMain { preference(testContext).edit().putString(PREF_TOKEN, "token").apply() val feature = AutoPushFeature( @@ -97,7 +100,7 @@ class AutoPushFeatureTest { } @Test - fun `shutdown stops service and unsubscribes all`() = runBlockingTest { + fun `shutdown stops service and unsubscribes all`() = runTestOnMain { val service: PushService = mock() whenever(connection.isInitialized()).thenReturn(true) @@ -109,7 +112,7 @@ class AutoPushFeatureTest { } @Test - fun `onNewToken updates connection and saves pref`() = runBlockingTest { + fun `onNewToken updates connection and saves pref`() = runTestOnMain { val feature = AutoPushFeature(testContext, mock(), mock(), coroutineContext, connection) whenever(connection.subscribe(anyString(), nullable())).thenReturn(mock()) @@ -124,7 +127,7 @@ class AutoPushFeatureTest { } @Test - fun `onMessageReceived decrypts message and notifies observers`() = runBlockingTest { + fun `onMessageReceived decrypts message and notifies observers`() = runTestOnMain { val encryptedMessage: EncryptedPushMessage = mock() val owner: LifecycleOwner = mock() val lifecycle: Lifecycle = mock() @@ -150,7 +153,7 @@ class AutoPushFeatureTest { } @Test - fun `subscribe calls native layer and notifies observers`() = runBlockingTest { + fun `subscribe calls native layer and notifies observers`() = runTestOnMain { val connection: PushConnection = mock() val subscription: AutoPushSubscription = mock() var invoked = false @@ -174,7 +177,7 @@ class AutoPushFeatureTest { } @Test - fun `subscribe invokes error callback`() = runBlockingTest { + fun `subscribe invokes error callback`() = runTestOnMain { val connection: PushConnection = mock() val subscription: AutoPushSubscription = mock() var invoked = false @@ -213,7 +216,7 @@ class AutoPushFeatureTest { } @Test - fun `unsubscribe calls native layer and notifies observers`() = runBlockingTest { + fun `unsubscribe calls native layer and notifies observers`() = runTestOnMain { val connection: PushConnection = mock() var invoked = false var errorInvoked = false @@ -266,7 +269,7 @@ class AutoPushFeatureTest { } @Test - fun `unsubscribe invokes error callback on native exception`() = runBlockingTest { + fun `unsubscribe invokes error callback on native exception`() = runTestOnMain { val feature = AutoPushFeature(testContext, mock(), mock(), coroutineContext, connection) var invoked = false var errorInvoked = false @@ -288,7 +291,7 @@ class AutoPushFeatureTest { } @Test - fun `getSubscription returns null when there is no subscription`() = runBlockingTest { + fun `getSubscription returns null when there is no subscription`() = runTestOnMain { val feature = AutoPushFeature(testContext, mock(), mock(), coroutineContext, connection) var invoked = false @@ -305,7 +308,7 @@ class AutoPushFeatureTest { } @Test - fun `getSubscription invokes subscribe when there is a subscription`() = runBlockingTest { + fun `getSubscription invokes subscribe when there is a subscription`() = runTestOnMain { val connection = TestPushConnection(true) val feature = AutoPushFeature(testContext, mock(), mock(), coroutineContext, connection) var invoked = false @@ -321,7 +324,7 @@ class AutoPushFeatureTest { } @Test - fun `forceRegistrationRenewal deletes pref and calls service`() = runBlockingTest { + fun `forceRegistrationRenewal deletes pref and calls service`() = runTestOnMain { val service: PushService = mock() val feature = AutoPushFeature(testContext, service, mock(), coroutineContext, mock()) @@ -335,7 +338,7 @@ class AutoPushFeatureTest { } @Test - fun `verifyActiveSubscriptions notifies observers`() = runBlockingTest { + fun `verifyActiveSubscriptions notifies observers`() = runTestOnMain { val connection: PushConnection = spy(TestPushConnection(true)) val owner: LifecycleOwner = mock() val lifecycle: Lifecycle = mock() @@ -365,7 +368,7 @@ class AutoPushFeatureTest { } @Test - fun `initialize executes verifyActiveSubscriptions after interval`() = runBlockingTest { + fun `initialize executes verifyActiveSubscriptions after interval`() = runTestOnMain { val feature = spy( AutoPushFeature( context = testContext, @@ -388,7 +391,7 @@ class AutoPushFeatureTest { } @Test - fun `initialize does not execute verifyActiveSubscription before interval`() = runBlockingTest { + fun `initialize does not execute verifyActiveSubscription before interval`() = runTestOnMain { val feature = spy( AutoPushFeature( context = testContext, @@ -413,7 +416,7 @@ class AutoPushFeatureTest { } @Test - fun `new FCM token executes verifyActiveSubscription`() = runBlockingTest { + fun `new FCM token executes verifyActiveSubscription`() = runTestOnMain { val feature = spy( AutoPushFeature( context = testContext, @@ -435,7 +438,7 @@ class AutoPushFeatureTest { } @Test - fun `verification doesn't happen until we've got the token`() = runBlockingTest { + fun `verification doesn't happen until we've got the token`() = runTestOnMain { val feature = spy( AutoPushFeature( context = testContext, @@ -452,7 +455,7 @@ class AutoPushFeatureTest { } @Test - fun `crash reporter is notified of errors`() = runBlockingTest { + fun `crash reporter is notified of errors`() = runTestOnMain { val native: PushConnection = TestPushConnection(true) val crashReporter: CrashReporting = mock() val feature = AutoPushFeature( @@ -470,7 +473,7 @@ class AutoPushFeatureTest { } @Test - fun `non-fatal errors are ignored`() = runBlockingTest { + fun `non-fatal errors are ignored`() = runTestOnMain { val crashReporter: CrashReporting = mock() val feature = AutoPushFeature( context = testContext, @@ -489,7 +492,7 @@ class AutoPushFeatureTest { } @Test - fun `only fatal errors are reported`() = runBlockingTest { + fun `only fatal errors are reported`() = runTestOnMain { val crashReporter: CrashReporting = mock() val feature = AutoPushFeature( context = testContext, diff --git a/components/feature/push/src/test/java/mozilla/components/feature/push/RustPushConnectionTest.kt b/components/feature/push/src/test/java/mozilla/components/feature/push/RustPushConnectionTest.kt index ca684173552..b349a7e4678 100644 --- a/components/feature/push/src/test/java/mozilla/components/feature/push/RustPushConnectionTest.kt +++ b/components/feature/push/src/test/java/mozilla/components/feature/push/RustPushConnectionTest.kt @@ -5,7 +5,8 @@ package mozilla.components.feature.push import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.appservices.push.DispatchInfo import mozilla.appservices.push.KeyInfo import mozilla.appservices.push.PushManager @@ -30,48 +31,43 @@ import org.mockito.Mockito.anyString import org.mockito.Mockito.never import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class RustPushConnectionTest { @Ignore("Requires push-forUnitTests; seems unnecessary to introduce it for this one test.") @Test - fun `new token initializes API`() { + fun `new token initializes API`() = runTest { val connection = createConnection() assertNull(connection.api) - runBlocking { - connection.updateToken("token") - } + connection.updateToken("token") assertNotNull(connection.api) } @Test - fun `new token calls update if API is already initialized`() { + fun `new token calls update if API is already initialized`() = runTest { val connection = createConnection() val api: PushManager = mock() connection.api = api - runBlocking { - connection.updateToken("123") - } + connection.updateToken("123") verify(api, never()).subscribe(any(), any(), any()) verify(api).update(anyString()) } @Test(expected = IllegalStateException::class) - fun `subscribe throws if API is not initialized first`() { + fun `subscribe throws if API is not initialized first`() = runTest { val connection = createConnection() - runBlocking { - connection.subscribe("123") - } + connection.subscribe("123") } @Test - fun `subscribe calls Rust API`() { + fun `subscribe calls Rust API`() = runTest { val connection = createConnection() val api: PushManager = mock() val response = SubscriptionResponse( @@ -89,64 +85,54 @@ class RustPushConnectionTest { `when`(api.subscribe(anyString(), anyString(), nullable())).thenReturn(response) - runBlocking { - val sub = connection.subscribe("123") + val sub = connection.subscribe("123") - assertEquals("123", sub.scope) - assertEquals("auth", sub.authKey) - assertEquals("p256dh", sub.publicKey) - assertEquals("https://foo", sub.endpoint) - } + assertEquals("123", sub.scope) + assertEquals("auth", sub.authKey) + assertEquals("p256dh", sub.publicKey) + assertEquals("https://foo", sub.endpoint) verify(api).subscribe(anyString(), anyString(), nullable()) } @Test(expected = IllegalStateException::class) - fun `unsubscribe throws if API is not initialized first`() { + fun `unsubscribe throws if API is not initialized first`() = runTest { val connection = createConnection() - runBlocking { - connection.unsubscribe("123") - } + connection.unsubscribe("123") } @Test - fun `unsubscribe calls Rust API`() { + fun `unsubscribe calls Rust API`() = runTest { val connection = createConnection() val api: PushManager = mock() connection.api = api - runBlocking { - connection.unsubscribe("123") - } + connection.unsubscribe("123") verify(api).unsubscribe(anyString()) } @Test(expected = IllegalStateException::class) - fun `unsubscribeAll throws if API is not initialized first`() { + fun `unsubscribeAll throws if API is not initialized first`() = runTest { val connection = createConnection() - runBlocking { - connection.unsubscribeAll() - } + connection.unsubscribeAll() } @Test - fun `unsubscribeAll calls Rust API`() { + fun `unsubscribeAll calls Rust API`() = runTest { val connection = createConnection() val api: PushManager = mock() connection.api = api - runBlocking { - connection.unsubscribeAll() - } + connection.unsubscribeAll() verify(api).unsubscribeAll() } @Test - fun `containsSubscription returns true if a subscription exists`() { + fun `containsSubscription returns true if a subscription exists`() = runTest { val connection = createConnection() val api: PushManager = mock() connection.api = api @@ -155,122 +141,99 @@ class RustPushConnectionTest { .thenReturn(mock()) .thenReturn(null) - runBlocking { - assertTrue(connection.containsSubscription("validSubscription")) - - assertFalse(connection.containsSubscription("invalidSubscription")) - } + assertTrue(connection.containsSubscription("validSubscription")) + assertFalse(connection.containsSubscription("invalidSubscription")) } @Test(expected = IllegalStateException::class) - fun `verifyConnection throws if API is not initialized first`() { + fun `verifyConnection throws if API is not initialized first`() = runTest { val connection = createConnection() - runBlocking { - connection.verifyConnection() - } + connection.verifyConnection() } @Test - fun `verifyConnection calls Rust API`() { + fun `verifyConnection calls Rust API`() = runTest { val connection = createConnection() val api: PushManager = mock() connection.api = api - runBlocking { - connection.verifyConnection() - } + connection.verifyConnection() verify(api).verifyConnection() } @Test(expected = IllegalStateException::class) - fun `decrypt throws if API is not initialized first`() { + fun `decrypt throws if API is not initialized first`() = runTest { val connection = createConnection() - runBlocking { - connection.decryptMessage("123", "plain text") - } + connection.decryptMessage("123", "plain text") } @Test - fun `decrypt calls Rust API`() { + fun `decrypt calls Rust API`() = runTest { val connection = createConnection() val api: PushManager = mock() val dispatchInfo: DispatchInfo = mock() connection.api = api - runBlocking { - connection.decryptMessage("123", "body") - } + connection.decryptMessage("123", "body") verify(api, never()).decrypt(anyString(), anyString(), eq(""), eq(""), eq("")) `when`(api.dispatchInfoForChid(anyString())).thenReturn(dispatchInfo) `when`(dispatchInfo.scope).thenReturn("test") - runBlocking { - connection.decryptMessage("123", "body") - } + connection.decryptMessage("123", "body") verify(api).decrypt(anyString(), anyString(), eq(""), eq(""), eq("")) - runBlocking { - connection.decryptMessage("123", "body", "enc", "salt", "key") - } + connection.decryptMessage("123", "body", "enc", "salt", "key") verify(api).decrypt(anyString(), anyString(), eq("enc"), eq("salt"), eq("key")) } @Test - fun `empty body decrypts nothing`() { + fun `empty body decrypts nothing`() = runTest { val connection = createConnection() val api: PushManager = mock() val dispatchInfo: DispatchInfo = mock() connection.api = api - runBlocking { - connection.decryptMessage("123", null) - } + connection.decryptMessage("123", null) verify(api, never()).decrypt(anyString(), anyString(), eq(""), eq(""), eq("")) `when`(api.dispatchInfoForChid(anyString())).thenReturn(dispatchInfo) `when`(dispatchInfo.scope).thenReturn("test") - runBlocking { - val (scope, message) = connection.decryptMessage("123", null)!! - assertEquals("test", scope) - assertNull(message) - } + val (scope, message) = connection.decryptMessage("123", null)!! + assertEquals("test", scope) + assertNull(message) verify(api, never()).decrypt(anyString(), nullable(), eq(""), eq(""), eq("")) } @Test(expected = IllegalStateException::class) - fun `close throws if API is not initialized first`() { + fun `close throws if API is not initialized first`() = runTest { val connection = createConnection() - runBlocking { - connection.close() - } + connection.close() } @Test - fun `close calls Rust API`() { + fun `close calls Rust API`() = runTest { val connection = createConnection() val api: PushManager = mock() connection.api = api - runBlocking { - connection.close() - } + connection.close() verify(api).close() } @Test - fun `initialized is true when api is not null`() { + fun `initialized is true when api is not null`() = runTest { val connection = createConnection() assertFalse(connection.isInitialized()) diff --git a/components/feature/push/src/test/java/mozilla/components/feature/push/ext/CoroutineScopeKtTest.kt b/components/feature/push/src/test/java/mozilla/components/feature/push/ext/CoroutineScopeKtTest.kt index 7bb5dd3babf..9a4e560c180 100644 --- a/components/feature/push/src/test/java/mozilla/components/feature/push/ext/CoroutineScopeKtTest.kt +++ b/components/feature/push/src/test/java/mozilla/components/feature/push/ext/CoroutineScopeKtTest.kt @@ -8,7 +8,7 @@ package mozilla.components.feature.push.ext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.appservices.push.InternalException import mozilla.appservices.push.PushException.AlreadyRegisteredException import mozilla.appservices.push.PushException.CommunicationException @@ -28,7 +28,7 @@ import org.junit.Test class CoroutineScopeKtTest { @Test(expected = InternalException::class) - fun `launchAndTry throws on unrecoverable Rust exceptions`() = runBlockingTest { + fun `launchAndTry throws on unrecoverable Rust exceptions`() = runTest { CoroutineScope(coroutineContext).launchAndTry( errorBlock = { throw InternalException("unit test") }, block = { throw MissingRegistrationTokenException("") } @@ -36,7 +36,7 @@ class CoroutineScopeKtTest { } @Test(expected = ArithmeticException::class) - fun `launchAndTry throws original exception`() = runBlockingTest { + fun `launchAndTry throws original exception`() = runTest { CoroutineScope(coroutineContext).launchAndTry( errorBlock = { throw InternalException("unit test") }, block = { throw ArithmeticException() } @@ -44,7 +44,7 @@ class CoroutineScopeKtTest { } @Test - fun `launchAndTry should NOT throw on recoverable Rust exceptions`() = runBlockingTest { + fun `launchAndTry should NOT throw on recoverable Rust exceptions`() = runTest { CoroutineScope(coroutineContext).launchAndTry( { throw CryptoException("should not fail test") }, { assert(true) } diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt index 7b4d36383ea..2bf5509d711 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt @@ -6,7 +6,7 @@ package mozilla.components.feature.pwa import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.manifest.WebAppManifest import mozilla.components.feature.pwa.db.ManifestDao import mozilla.components.feature.pwa.db.ManifestEntity @@ -50,14 +50,14 @@ class ManifestStorageTest { ) @Test - fun `load returns null if entry does not exist`() = runBlocking { + fun `load returns null if entry does not exist`() = runTest { val storage = spy(ManifestStorage(testContext)) mockDatabase(storage) assertNull(storage.loadManifest("https://example.com")) } @Test - fun `load returns valid manifest`() = runBlocking { + fun `load returns valid manifest`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) @@ -69,7 +69,7 @@ class ManifestStorageTest { } @Test - fun `save saves the manifest as JSON`() = runBlocking { + fun `save saves the manifest as JSON`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) @@ -79,7 +79,7 @@ class ManifestStorageTest { } @Test - fun `update replaces the manifest as JSON`() = runBlocking { + fun `update replaces the manifest as JSON`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) val existing = ManifestEntity(firefoxManifest, currentTime = 0) @@ -92,7 +92,7 @@ class ManifestStorageTest { } @Test - fun `update does not replace non-existed manifest`() = runBlocking { + fun `update does not replace non-existed manifest`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) @@ -104,7 +104,7 @@ class ManifestStorageTest { } @Test - fun `remove deletes saved manifests`() = runBlocking { + fun `remove deletes saved manifests`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) @@ -114,7 +114,7 @@ class ManifestStorageTest { } @Test - fun `loading manifests by scope returns list of manifests`() = runBlocking { + fun `loading manifests by scope returns list of manifests`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) val manifest1 = WebAppManifest(name = "Mozilla1", startUrl = "https://mozilla.org", scope = "https://mozilla.org/pwa/1/") @@ -131,7 +131,7 @@ class ManifestStorageTest { } @Test - fun `loading manifests with share targets returns list of manifests`() = runBlocking { + fun `loading manifests with share targets returns list of manifests`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) val manifest1 = WebAppManifest( @@ -158,7 +158,7 @@ class ManifestStorageTest { } @Test - fun `updateManifestUsedAt updates usedAt to current timestamp`() = runBlocking { + fun `updateManifestUsedAt updates usedAt to current timestamp`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) val manifest = WebAppManifest(name = "Mozilla", startUrl = "https://mozilla.org") @@ -178,7 +178,7 @@ class ManifestStorageTest { } @Test - fun `has recent manifest returns false if no manifest is found`() = runBlocking { + fun `has recent manifest returns false if no manifest is found`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) val timeout = ManifestStorage.ACTIVE_THRESHOLD_MS @@ -192,7 +192,7 @@ class ManifestStorageTest { } @Test - fun `has recent manifest returns true if one or more manifests have been found`() = runBlocking { + fun `has recent manifest returns true if one or more manifests have been found`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) val timeout = ManifestStorage.ACTIVE_THRESHOLD_MS @@ -211,7 +211,7 @@ class ManifestStorageTest { } @Test - fun `recently used manifest count`() = runBlocking { + fun `recently used manifest count`() = runTest { val testThreshold = 1000 * 60 * 24L val storage = spy(ManifestStorage(testContext, activeThresholdMs = testThreshold)) val dao = mockDatabase(storage) @@ -241,7 +241,7 @@ class ManifestStorageTest { } @Test - fun `warmUpScopes populates cache of already installed web app scopes`() = runBlocking { + fun `warmUpScopes populates cache of already installed web app scopes`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) @@ -264,7 +264,7 @@ class ManifestStorageTest { } @Test - fun `getInstalledScope returns cached scope for an url`() = runBlocking { + fun `getInstalledScope returns cached scope for an url`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) @@ -282,7 +282,7 @@ class ManifestStorageTest { } @Test - fun `getStartUrlForInstalledScope returns cached start url for a currently installed scope`() = runBlocking { + fun `getStartUrlForInstalledScope returns cached start url for a currently installed scope`() = runTest { val storage = spy(ManifestStorage(testContext)) val dao = mockDatabase(storage) diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppShortcutManagerTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppShortcutManagerTest.kt index 8983fd1d4d5..c75ffa47502 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppShortcutManagerTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppShortcutManagerTest.kt @@ -14,7 +14,7 @@ import androidx.core.graphics.drawable.IconCompat import androidx.core.net.toUri import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.state.state.SecurityInfoState import mozilla.components.browser.state.state.SessionState @@ -78,7 +78,7 @@ class WebAppShortcutManagerTest { fun teardown() = setSdkInt(0) @Test - fun `requestPinShortcut no-op if pinning unsupported`() = runBlockingTest { + fun `requestPinShortcut no-op if pinning unsupported`() = runTest { val manifest = baseManifest.copy( display = WebAppManifest.DisplayMode.STANDALONE, icons = listOf( @@ -103,7 +103,7 @@ class WebAppShortcutManagerTest { } @Test - fun `requestPinShortcut won't make a PWA icon if the session is not installable`() = runBlockingTest { + fun `requestPinShortcut won't make a PWA icon if the session is not installable`() = runTest { setSdkInt(Build.VERSION_CODES.O) val manifest = baseManifest.copy( display = WebAppManifest.DisplayMode.STANDALONE, @@ -120,7 +120,7 @@ class WebAppShortcutManagerTest { } @Test - fun `requestPinShortcut pins PWA shortcut`() = runBlockingTest { + fun `requestPinShortcut pins PWA shortcut`() = runTest { setSdkInt(Build.VERSION_CODES.O) val manifest = baseManifest.copy( @@ -145,7 +145,7 @@ class WebAppShortcutManagerTest { } @Test - fun `requestPinShortcut pins basic shortcut`() = runBlockingTest { + fun `requestPinShortcut pins basic shortcut`() = runTest { setSdkInt(Build.VERSION_CODES.O) val session = buildInstallableSession() @@ -160,7 +160,7 @@ class WebAppShortcutManagerTest { } @Test - fun `buildBasicShortcut uses manifest short name as label by default`() = runBlockingTest { + fun `buildBasicShortcut uses manifest short name as label by default`() = runTest { setSdkInt(Build.VERSION_CODES.O) val session = createTab("https://www.mozilla.org", title = "Internet for people, not profit — Mozilla").let { @@ -181,7 +181,7 @@ class WebAppShortcutManagerTest { } @Test - fun `buildBasicShortcut uses manifest name as label by default`() = runBlockingTest { + fun `buildBasicShortcut uses manifest name as label by default`() = runTest { setSdkInt(Build.VERSION_CODES.O) val session = createTab("https://www.mozilla.org", title = "Internet for people, not profit — Mozilla").let { @@ -201,7 +201,7 @@ class WebAppShortcutManagerTest { } @Test - fun `buildBasicShortcut uses session title as label if there is no manifest`() = runBlockingTest { + fun `buildBasicShortcut uses session title as label if there is no manifest`() = runTest { setSdkInt(Build.VERSION_CODES.O) val expectedTitle = "Internet for people, not profit — Mozilla" @@ -214,7 +214,7 @@ class WebAppShortcutManagerTest { } @Test - fun `buildBasicShortcut can create a shortcut with a custom name`() = runBlockingTest { + fun `buildBasicShortcut can create a shortcut with a custom name`() = runTest { setSdkInt(Build.VERSION_CODES.O) val title = "Internet for people, not profit — Mozilla" @@ -228,7 +228,7 @@ class WebAppShortcutManagerTest { } @Test - fun `updateShortcuts no-op`() = runBlockingTest { + fun `updateShortcuts no-op`() = runTest { val manifests = listOf(baseManifest) doReturn(null).`when`(manager).buildWebAppShortcut(context, manifests[0]) @@ -242,7 +242,7 @@ class WebAppShortcutManagerTest { } @Test - fun `updateShortcuts updates list of existing shortcuts`() = runBlockingTest { + fun `updateShortcuts updates list of existing shortcuts`() = runTest { setSdkInt(Build.VERSION_CODES.N_MR1) val manifests = listOf(baseManifest) val shortcutCompat: ShortcutInfoCompat = mock() @@ -255,7 +255,7 @@ class WebAppShortcutManagerTest { } @Test - fun `buildWebAppShortcut builds shortcut and saves manifest`() = runBlockingTest { + fun `buildWebAppShortcut builds shortcut and saves manifest`() = runTest { doReturn(mock()).`when`(manager).buildIconFromManifest(baseManifest) val shortcut = manager.buildWebAppShortcut(context, baseManifest)!! @@ -271,7 +271,7 @@ class WebAppShortcutManagerTest { } @Test - fun `buildWebAppShortcut builds shortcut with short name`() = runBlockingTest { + fun `buildWebAppShortcut builds shortcut with short name`() = runTest { val manifest = WebAppManifest(name = "Demo Demo", shortName = "DD", startUrl = "https://example.com") doReturn(mock()).`when`(manager).buildIconFromManifest(manifest) @@ -303,7 +303,7 @@ class WebAppShortcutManagerTest { } @Test - fun `checking unknown url returns uninstalled state`() = runBlockingTest { + fun `checking unknown url returns uninstalled state`() = runTest { setSdkInt(Build.VERSION_CODES.N_MR1) val url = "https://mozilla.org" @@ -318,7 +318,7 @@ class WebAppShortcutManagerTest { } @Test - fun `checking a known url returns installed state`() = runBlockingTest { + fun `checking a known url returns installed state`() = runTest { setSdkInt(Build.VERSION_CODES.N_MR1) val url = "https://mozilla.org/pwa/" diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppUseCasesTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppUseCasesTest.kt index 1b979c78b51..b0ca535f958 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppUseCasesTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppUseCasesTest.kt @@ -6,7 +6,7 @@ package mozilla.components.feature.pwa import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.SecurityInfoState import mozilla.components.browser.state.state.TabSessionState @@ -109,7 +109,7 @@ class WebAppUseCasesTest { } @Test - fun `getInstallState returns Installed if manifest exists`() = runBlockingTest { + fun `getInstallState returns Installed if manifest exists`() = runTest { val httpClient: Client = mock() val storage: ManifestStorage = mock() val shortcutManager = WebAppShortcutManager(testContext, httpClient, storage) diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt index 9d98dd03423..0624f872390 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/feature/ManifestUpdateFeatureTest.kt @@ -5,8 +5,6 @@ package mozilla.components.feature.pwa.feature import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.createCustomTab @@ -20,6 +18,7 @@ import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Before import org.junit.Rule import org.junit.Test @@ -59,7 +58,7 @@ class ManifestUpdateFeatureTest { } @Test - fun `start and stop handle null session`() = runBlockingTest { + fun `start and stop handle null session`() = runTestOnMain { val feature = ManifestUpdateFeature( testContext, store, @@ -80,7 +79,7 @@ class ManifestUpdateFeatureTest { } @Test - fun `Last usage is updated when feature is started`() { + fun `Last usage is updated when feature is started`() = runTestOnMain { val feature = ManifestUpdateFeature( testContext, store, @@ -102,13 +101,11 @@ class ManifestUpdateFeatureTest { feature.updateUsageJob!!.joinBlocking() - runBlocking { - verify(storage).updateManifestUsedAt(baseManifest) - } + verify(storage).updateManifestUsedAt(baseManifest) } @Test - fun `updateStoredManifest is called when the manifest changes`() { + fun `updateStoredManifest is called when the manifest changes`() = runTestOnMain { val feature = ManifestUpdateFeature( testContext, store, @@ -140,13 +137,11 @@ class ManifestUpdateFeatureTest { feature.updateJob!!.joinBlocking() - runBlocking { - verify(storage).updateManifest(newManifest) - } + verify(storage).updateManifest(newManifest) } @Test - fun `updateStoredManifest is not called when the manifest is the same`() { + fun `updateStoredManifest is not called when the manifest is the same`() = runTestOnMain { val feature = ManifestUpdateFeature( testContext, store, @@ -168,13 +163,11 @@ class ManifestUpdateFeatureTest { feature.updateJob?.joinBlocking() - runBlocking { - verify(storage, never()).updateManifest(any()) - } + verify(storage, never()).updateManifest(any()) } @Test - fun `updateStoredManifest is not called when the manifest is removed`() { + fun `updateStoredManifest is not called when the manifest is removed`() = runTestOnMain { val feature = ManifestUpdateFeature( testContext, store, @@ -203,13 +196,11 @@ class ManifestUpdateFeatureTest { feature.updateJob?.joinBlocking() - runBlocking { - verify(storage, never()).updateManifest(any()) - } + verify(storage, never()).updateManifest(any()) } @Test - fun `updateStoredManifest is not called when the manifest has a different start URL`() { + fun `updateStoredManifest is not called when the manifest has a different start URL`() = runTestOnMain { val feature = ManifestUpdateFeature( testContext, store, @@ -239,13 +230,11 @@ class ManifestUpdateFeatureTest { feature.updateJob?.joinBlocking() - runBlocking { - verify(storage, never()).updateManifest(any()) - } + verify(storage, never()).updateManifest(any()) } @Test - fun `updateStoredManifest updates storage and shortcut`() = runBlockingTest { + fun `updateStoredManifest updates storage and shortcut`() = runTestOnMain { val feature = ManifestUpdateFeature(testContext, store, shortcutManager, storage, sessionId, baseManifest) val manifest = baseManifest.copy(shortName = "Moz") @@ -256,7 +245,7 @@ class ManifestUpdateFeatureTest { } @Test - fun `start updates last web app usage`() = runBlockingTest { + fun `start updates last web app usage`() = runTestOnMain { val feature = ManifestUpdateFeature(testContext, store, shortcutManager, storage, sessionId, baseManifest) feature.start() diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt index e81466b4b88..958d048e94a 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt @@ -9,7 +9,7 @@ import android.content.Intent.ACTION_VIEW import androidx.core.net.toUri import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.CustomTabConfig import mozilla.components.browser.state.state.ExternalAppType import mozilla.components.browser.state.state.SessionState @@ -50,7 +50,7 @@ class WebAppIntentProcessorTest { } @Test - fun `process returns false if no manifest is in storage`() = runBlockingTest { + fun `process returns false if no manifest is in storage`() = runTest { val storage: ManifestStorage = mock() val processor = WebAppIntentProcessor(mock(), mock(), mock(), storage) @@ -60,7 +60,7 @@ class WebAppIntentProcessorTest { } @Test - fun `process adds session ID and manifest to intent`() = runBlockingTest { + fun `process adds session ID and manifest to intent`() = runTest { val store = BrowserStore() val storage: ManifestStorage = mock() @@ -97,7 +97,7 @@ class WebAppIntentProcessorTest { } @Test - fun `process adds custom tab config`() = runBlockingTest { + fun `process adds custom tab config`() = runTest { val intent = Intent(ACTION_VIEW_PWA, "https://mozilla.com".toUri()) val storage: ManifestStorage = mock() @@ -129,7 +129,7 @@ class WebAppIntentProcessorTest { } @Test - fun `url override is applied to session if present`() = runBlockingTest { + fun `url override is applied to session if present`() = runTest { val store = BrowserStore() val storage: ManifestStorage = mock() diff --git a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt index 4aa635c29ea..3c14781e21f 100644 --- a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt +++ b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedMiddlewareTest.kt @@ -7,7 +7,6 @@ package mozilla.components.feature.recentlyclosed import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.action.UndoAction @@ -24,6 +23,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Before @@ -63,7 +63,7 @@ class RecentlyClosedMiddlewareTest { ) @Test - fun `closed tab storage stores the provided tab on add tab action`() = runBlocking { + fun `closed tab storage stores the provided tab on add tab action`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -82,7 +82,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `closed tab storage adds normal tabs removed with TabListAction`() = runBlocking { + fun `closed tab storage adds normal tabs removed with TabListAction`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -123,7 +123,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `closed tab storage adds a normal tab removed with TabListAction`() = runBlocking { + fun `closed tab storage adds a normal tab removed with TabListAction`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -157,7 +157,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `closed tab storage does not add a private tab removed with TabListAction`() = runBlocking { + fun `closed tab storage does not add a private tab removed with TabListAction`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -179,7 +179,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `closed tab storage adds all normals tab removed with TabListAction RemoveAllNormalTabsAction`() = runBlocking { + fun `closed tab storage adds all normals tab removed with TabListAction RemoveAllNormalTabsAction`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -214,7 +214,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `closed tab storage adds all normal tabs and no private tabs removed with TabListAction RemoveAllTabsAction`() = runBlocking { + fun `closed tab storage adds all normal tabs and no private tabs removed with TabListAction RemoveAllTabsAction`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -249,7 +249,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `closed tabs storage adds tabs closed one after the other without clear actions in between`() = runBlocking { + fun `closed tabs storage adds tabs closed one after the other without clear actions in between`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -307,7 +307,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `fetch the tabs from the recently closed storage and load into browser state on initialize tab state action`() = runBlocking { + fun `fetch the tabs from the recently closed storage and load into browser state on initialize tab state action`() = runTestOnMain { val storage = mockStorage(tabs = listOf(closedTab.state)) val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -325,7 +325,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `recently closed storage removes the provided tab on remove tab action`() = runBlocking { + fun `recently closed storage removes the provided tab on remove tab action`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) @@ -348,7 +348,7 @@ class RecentlyClosedMiddlewareTest { } @Test - fun `recently closed storage removes all tabs on remove all tabs action`() = runBlocking { + fun `recently closed storage removes all tabs on remove all tabs action`() = runTestOnMain { val storage = mockStorage() val middleware = RecentlyClosedMiddleware(lazy { storage }, 5, scope) val store = BrowserStore( diff --git a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabDaoTest.kt b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabDaoTest.kt index 418c3371a8d..05bcc323382 100644 --- a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabDaoTest.kt +++ b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabDaoTest.kt @@ -8,21 +8,27 @@ import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking import mozilla.components.feature.recentlyclosed.db.RecentlyClosedTabDao import mozilla.components.feature.recentlyclosed.db.RecentlyClosedTabEntity import mozilla.components.feature.recentlyclosed.db.RecentlyClosedTabsDatabase +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.util.UUID +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class RecentlyClosedTabDaoTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val context: Context get() = ApplicationProvider.getApplicationContext() @@ -31,13 +37,15 @@ class RecentlyClosedTabDaoTest { @Before fun setUp() { - database = - Room.inMemoryDatabaseBuilder(context, RecentlyClosedTabsDatabase::class.java).build() + database = Room + .inMemoryDatabaseBuilder(context, RecentlyClosedTabsDatabase::class.java) + .allowMainThreadQueries() + .build() tabDao = database.recentlyClosedTabDao() } @Test - fun testAddingTabs() = runBlocking(Dispatchers.IO) { + fun testAddingTabs() = runTestOnMain { val tab1 = RecentlyClosedTabEntity( title = "RecentlyClosedTab One", url = "https://www.mozilla.org", @@ -65,7 +73,7 @@ class RecentlyClosedTabDaoTest { } @Test - fun testRemovingTab() = runBlocking(Dispatchers.IO) { + fun testRemovingTab() = runTestOnMain { val tab1 = RecentlyClosedTabEntity( title = "RecentlyClosedTab One", url = "https://www.mozilla.org", @@ -94,7 +102,7 @@ class RecentlyClosedTabDaoTest { } @Test - fun testRemovingAllTabs() = runBlocking(Dispatchers.IO) { + fun testRemovingAllTabs() = runTestOnMain { RecentlyClosedTabEntity( title = "RecentlyClosedTab One", url = "https://www.mozilla.org", diff --git a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabsStorageTest.kt b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabsStorageTest.kt index ba8a38b3eab..260f15a0132 100644 --- a/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabsStorageTest.kt +++ b/components/feature/recentlyclosed/src/test/java/mozilla/components/feature/recentlyclosed/RecentlyClosedTabsStorageTest.kt @@ -6,9 +6,8 @@ package mozilla.components.feature.recentlyclosed import androidx.room.Room import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.state.recover.RecoverableTab import mozilla.components.browser.state.state.recover.TabState import mozilla.components.concept.base.crash.CrashReporting @@ -18,19 +17,24 @@ import mozilla.components.feature.recentlyclosed.db.RecentlyClosedTabsDatabase import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.fail import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify import java.io.IOException -import java.lang.Exception -import java.lang.IllegalStateException +@ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class RecentlyClosedTabsStorageTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private lateinit var storage: RecentlyClosedTabsStorage private lateinit var engineStateStorage: TestEngineSessionStateStorage private lateinit var database: RecentlyClosedTabsDatabase @@ -68,6 +72,7 @@ class RecentlyClosedTabsStorageTest { crashReporting = mock() database = Room .inMemoryDatabaseBuilder(testContext, RecentlyClosedTabsDatabase::class.java) + .allowMainThreadQueries() .build() engineStateStorage = TestEngineSessionStateStorage() @@ -87,7 +92,7 @@ class RecentlyClosedTabsStorageTest { } @Test - fun testAddingTabsWithMax() { + fun testAddingTabsWithMax() = runTestOnMain { // Test tab val t1 = System.currentTimeMillis() val closedTab = RecoverableTab( @@ -112,10 +117,8 @@ class RecentlyClosedTabsStorageTest { ) ) - val tabs = runBlocking(Dispatchers.IO) { - storage.addTabsToCollectionWithMax(listOf(closedTab, secondClosedTab), 1) - storage.getTabs().first() - } + storage.addTabsToCollectionWithMax(listOf(closedTab, secondClosedTab), 1) + val tabs = storage.getTabs().first() assertEquals(1, engineStateStorage.data.size) assertEquals(engineState2, engineStateStorage.data["second-tab"]) @@ -137,10 +140,8 @@ class RecentlyClosedTabsStorageTest { ) ) - val newTabs = runBlocking(Dispatchers.IO) { - storage.addTabsToCollectionWithMax(listOf(thirdClosedTab), 1) - storage.getTabs().first() - } + storage.addTabsToCollectionWithMax(listOf(thirdClosedTab), 1) + val newTabs = storage.getTabs().first() assertEquals(1, engineStateStorage.data.size) assertEquals(engineState3, engineStateStorage.data["third-tab"]) @@ -152,7 +153,7 @@ class RecentlyClosedTabsStorageTest { } @Test - fun testAllowAddingSameTabTwice() { + fun testAllowAddingSameTabTwice() = runTestOnMain { // Test tab val engineState: EngineSessionState = mock() val closedTab = RecoverableTab( @@ -166,11 +167,9 @@ class RecentlyClosedTabsStorageTest { ) val updatedTab = closedTab.copy(state = closedTab.state.copy(title = "updated")) - val tabs = runBlocking(Dispatchers.IO) { - storage.addTabsToCollectionWithMax(listOf(closedTab), 2) - storage.addTabsToCollectionWithMax(listOf(updatedTab), 2) - storage.getTabs().first() - } + storage.addTabsToCollectionWithMax(listOf(closedTab), 2) + storage.addTabsToCollectionWithMax(listOf(updatedTab), 2) + val tabs = storage.getTabs().first() assertEquals(1, engineStateStorage.data.size) assertEquals(engineState, engineStateStorage.data["first-tab"]) @@ -182,7 +181,7 @@ class RecentlyClosedTabsStorageTest { } @Test - fun testRemovingAllTabs() { + fun testRemovingAllTabs() = runTestOnMain { // Test tab val t1 = System.currentTimeMillis() val closedTab = RecoverableTab( @@ -206,10 +205,8 @@ class RecentlyClosedTabsStorageTest { ) ) - val tabs = runBlocking(Dispatchers.IO) { - storage.addTabsToCollectionWithMax(listOf(closedTab, secondClosedTab), 2) - storage.getTabs().first() - } + storage.addTabsToCollectionWithMax(listOf(closedTab, secondClosedTab), 2) + val tabs = storage.getTabs().first() assertEquals(2, engineStateStorage.data.size) assertEquals(2, tabs.size) @@ -220,17 +217,15 @@ class RecentlyClosedTabsStorageTest { assertEquals(secondClosedTab.state.title, tabs[1].title) assertEquals(secondClosedTab.state.lastAccess, tabs[1].lastAccess) - val newTabs = runBlocking(Dispatchers.IO) { - storage.removeAllTabs() - storage.getTabs().first() - } + storage.removeAllTabs() + val newTabs = storage.getTabs().first() assertEquals(0, engineStateStorage.data.size) assertEquals(0, newTabs.size) } @Test - fun testRemovingOneTab() { + fun testRemovingOneTab() = runTestOnMain { // Test tab val engineState1: EngineSessionState = mock() val t1 = System.currentTimeMillis() @@ -256,11 +251,9 @@ class RecentlyClosedTabsStorageTest { ) ) - val tabs = runBlocking(Dispatchers.IO) { - storage.addTabState(closedTab) - storage.addTabState(secondClosedTab) - storage.getTabs().first() - } + storage.addTabState(closedTab) + storage.addTabState(secondClosedTab) + val tabs = storage.getTabs().first() assertEquals(2, engineStateStorage.data.size) assertEquals(2, tabs.size) @@ -271,10 +264,8 @@ class RecentlyClosedTabsStorageTest { assertEquals(secondClosedTab.state.title, tabs[1].title) assertEquals(secondClosedTab.state.lastAccess, tabs[1].lastAccess) - val newTabs = runBlocking(Dispatchers.IO) { - storage.removeTab(tabs[0]) - storage.getTabs().first() - } + storage.removeTab(tabs[0]) + val newTabs = storage.getTabs().first() assertEquals(1, engineStateStorage.data.size) assertEquals(engineState2, engineStateStorage.data["second-tab"]) @@ -285,7 +276,7 @@ class RecentlyClosedTabsStorageTest { } @Test - fun testAddingTabWithEngineStateStorageFailure() { + fun testAddingTabWithEngineStateStorageFailure() = runTestOnMain { // 'fail' in tab's id will cause test engine session storage to fail on writing engineSessionState. val closedTab = RecoverableTab( engineSessionState = mock(), @@ -297,10 +288,8 @@ class RecentlyClosedTabsStorageTest { ) ) - val tabs = runBlocking(Dispatchers.IO) { - storage.addTabState(closedTab) - storage.getTabs().first() - } + storage.addTabState(closedTab) + val tabs = storage.getTabs().first() // if it's empty, we know state write failed assertEquals(0, engineStateStorage.data.size) // but the tab was still written into the database. @@ -313,7 +302,7 @@ class RecentlyClosedTabsStorageTest { } @Test - fun testStorageFailuresAreCaught() { + fun testStorageFailuresAreCaught() = runTestOnMain { val engineState: EngineSessionState = mock() val closedTab = RecoverableTab( engineSessionState = engineState, @@ -324,13 +313,11 @@ class RecentlyClosedTabsStorageTest { lastAccess = System.currentTimeMillis() ) ) - runBlocking(Dispatchers.IO) { - try { - storage.addTabsToCollectionWithMax(listOf(closedTab), 2) - verify(crashReporting).submitCaughtException(any()) - } catch (e: Exception) { - fail("Thrown exception was not caught") - } + try { + storage.addTabsToCollectionWithMax(listOf(closedTab), 2) + verify(crashReporting).submitCaughtException(any()) + } catch (e: Exception) { + fail("Thrown exception was not caught") } } } diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt index 174c808dea1..e17d01298d2 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/middleware/SearchMiddlewareTest.kt @@ -5,7 +5,6 @@ package mozilla.components.feature.search.middleware import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestDispatcher import mozilla.components.browser.state.action.SearchAction import mozilla.components.browser.state.search.RegionState @@ -23,6 +22,7 @@ import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -750,9 +750,9 @@ class SearchMiddlewareTest { } @Test - fun `Loads additional search engine and honors user choice`() { + fun `Loads additional search engine and honors user choice`() = runTestOnMain { val metadataStorage = SearchMetadataStorage(testContext, lazy { FakeSharedPreferences() }) - runBlocking { metadataStorage.setAdditionalSearchEngines(listOf("reddit")) } + metadataStorage.setAdditionalSearchEngines(listOf("reddit")) val searchMiddleware = SearchMiddleware( testContext, @@ -799,7 +799,7 @@ class SearchMiddlewareTest { } @Test - fun `Loads custom search engines`() { + fun `Loads custom search engines`() = runTestOnMain { val searchEngine = SearchEngine( id = "test-search", name = "Test Engine", @@ -810,7 +810,7 @@ class SearchMiddlewareTest { ) val storage = CustomSearchEngineStorage(testContext, dispatcher) - runBlocking { storage.saveSearchEngine(searchEngine) } + storage.saveSearchEngine(searchEngine) val store = BrowserStore( middleware = listOf( @@ -833,9 +833,9 @@ class SearchMiddlewareTest { } @Test - fun `Loads default search engine ID`() { + fun `Loads default search engine ID`() = runTestOnMain { val storage = SearchMetadataStorage(testContext) - runBlocking { storage.setUserSelectedSearchEngine("test-id", null) } + storage.setUserSelectedSearchEngine("test-id", null) val middleware = SearchMiddleware( testContext, @@ -1071,7 +1071,7 @@ class SearchMiddlewareTest { @Test fun `Custom search engines - Create, Update, Delete`() { - runBlocking { + runTestOnMain { val storage: SearchMiddleware.CustomStorage = mock() doReturn(emptyList()).`when`(storage).loadSearchEngineList() diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionManagerTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionManagerTest.kt index 34b4345437b..3eb227c53d6 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionManagerTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionManagerTest.kt @@ -4,7 +4,7 @@ package mozilla.components.feature.search.region -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.service.location.LocationService import mozilla.components.support.test.fakes.FakeClock import mozilla.components.support.test.fakes.android.FakeContext @@ -28,7 +28,7 @@ class RegionManagerTest { } @Test - fun `First update`() { + fun `First update`() = runTest { val locationService = FakeLocationService( region = LocationService.Region("DE", "Germany") ) @@ -40,14 +40,14 @@ class RegionManagerTest { preferences = lazy { FakeSharedPreferences() } ) - val updatedRegion = runBlocking { regionManager.update() } + val updatedRegion = regionManager.update() assertNotNull(updatedRegion!!) assertEquals("DE", updatedRegion.current) assertEquals("DE", updatedRegion.home) } @Test - fun `Updating to new home region`() { + fun `Updating to new home region`() = runTest { val clock = FakeClock() val locationService = FakeLocationService( @@ -61,12 +61,12 @@ class RegionManagerTest { preferences = lazy { FakeSharedPreferences() } ) - runBlocking { regionManager.update() } + regionManager.update() locationService.region = LocationService.Region("FR", "France") // Should not be updated since the "home" region didn't change - assertNull(runBlocking { regionManager.update() }) + assertNull(regionManager.update()) assertEquals("DE", regionManager.region()?.home) assertEquals("FR", regionManager.region()?.current) @@ -74,14 +74,14 @@ class RegionManagerTest { clock.advanceBy(60L * 60L * 24L * 7L * 1000L) // Still not updated because we switch after two weeks - assertNull(runBlocking { regionManager.update() }) + assertNull(regionManager.update()) assertEquals("DE", regionManager.region()?.home) assertEquals("FR", regionManager.region()?.current) // Let's move the clock 8 more days into the future clock.advanceBy(60L * 60L * 24L * 8L * 1000L) - val updatedRegion = (runBlocking { regionManager.update() }) + val updatedRegion = (regionManager.update()) assertNotNull(updatedRegion!!) assertEquals("FR", updatedRegion.home) assertEquals("FR", updatedRegion.current) @@ -90,7 +90,7 @@ class RegionManagerTest { } @Test - fun `Switching back to home region after staying in different region shortly`() { + fun `Switching back to home region after staying in different region shortly`() = runTest { val clock = FakeClock() val locationService = FakeLocationService( @@ -104,7 +104,7 @@ class RegionManagerTest { preferences = lazy { FakeSharedPreferences() } ) - runBlocking { regionManager.update() } + regionManager.update() // Let's jump one week into the future! clock.advanceBy(60L * 60L * 24L * 7L * 1000L) @@ -112,7 +112,7 @@ class RegionManagerTest { locationService.region = LocationService.Region("FR", "France") // Should not be updated since the "home" region didn't change - assertNull(runBlocking { regionManager.update() }) + assertNull(regionManager.update()) assertEquals("DE", regionManager.region()?.home) assertEquals("FR", regionManager.region()?.current) @@ -120,7 +120,7 @@ class RegionManagerTest { clock.advanceBy(60L * 60L * 24L * 1000L) locationService.region = LocationService.Region("DE", "Germany") - assertNull(runBlocking { regionManager.update() }) + assertNull(regionManager.update()) assertEquals("DE", regionManager.region()?.home) assertEquals("DE", regionManager.region()?.current) @@ -131,7 +131,7 @@ class RegionManagerTest { // The "home" region should not have changed since we haven't been in the other region the // whole time. - assertNull(runBlocking { regionManager.update() }) + assertNull(regionManager.update()) assertEquals("DE", regionManager.region()?.home) assertEquals("FR", regionManager.region()?.current) } diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt index bc93771aa97..9cc22a78978 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/region/RegionMiddlewareTest.kt @@ -4,7 +4,6 @@ package mozilla.components.feature.search.region -import kotlinx.coroutines.runBlocking import mozilla.components.browser.state.action.InitAction import mozilla.components.browser.state.search.RegionState import mozilla.components.browser.state.store.BrowserStore @@ -15,6 +14,7 @@ import mozilla.components.support.test.fakes.android.FakeContext import mozilla.components.support.test.fakes.android.FakeSharedPreferences import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Before @@ -83,12 +83,12 @@ class RegionMiddlewareTest { } @Test - fun `Dispatches cached home region and update later`() { + fun `Dispatches cached home region and update later`() = runTestOnMain { val middleware = RegionMiddleware(FakeContext(), locationService, dispatcher) middleware.regionManager = regionManager locationService.region = LocationService.Region("FR", "France") - runBlocking { regionManager.update() } + regionManager.update() val store = BrowserStore( middleware = listOf(middleware) @@ -102,7 +102,7 @@ class RegionMiddlewareTest { assertEquals("FR", store.state.search.region!!.current) locationService.region = LocationService.Region("DE", "Germany") - runBlocking { regionManager.update() } + regionManager.update() store.dispatch(InitAction).joinBlocking() middleware.updateJob?.joinBlocking() diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/storage/BundledSearchEnginesStorageTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/storage/BundledSearchEnginesStorageTest.kt index e56ec6d4bc4..ec766fb7bf1 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/storage/BundledSearchEnginesStorageTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/storage/BundledSearchEnginesStorageTest.kt @@ -5,7 +5,7 @@ package mozilla.components.feature.search.storage import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.search.RegionState import mozilla.components.browser.state.search.SearchEngine import mozilla.components.support.test.robolectric.testContext @@ -20,7 +20,7 @@ import java.util.Locale @RunWith(AndroidJUnit4::class) class BundledSearchEnginesStorageTest { @Test - fun `Load search engines for en-US from assets`() = runBlocking { + fun `Load search engines for en-US from assets`() = runTest { val storage = BundledSearchEnginesStorage(testContext) val engines = storage.load(RegionState("US", "US"), Locale("en", "US")) @@ -30,7 +30,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Load search engines for all known locales without region`() = runBlocking { + fun `Load search engines for all known locales without region`() = runTest { val storage = BundledSearchEnginesStorage(testContext) val locales = Locale.getAvailableLocales() assertTrue(locales.isNotEmpty()) @@ -43,7 +43,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Load search engines for de-DE with global US region override`() = runBlocking { + fun `Load search engines for de-DE with global US region override`() = runTest { // Without region run { val storage = BundledSearchEnginesStorage(testContext) @@ -67,7 +67,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Load search engines for en-US with local RU region override`() = runBlocking { + fun `Load search engines for en-US with local RU region override`() = runTest { // Without region run { val storage = BundledSearchEnginesStorage(testContext) @@ -92,7 +92,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Load search engines for zh-CN_CN locale with searchDefault override`() = runBlocking { + fun `Load search engines for zh-CN_CN locale with searchDefault override`() = runTest { val storage = BundledSearchEnginesStorage(testContext) val engines = storage.load(RegionState("CN", "CN"), Locale("zh", "CN")) val searchEngines = engines.list @@ -112,7 +112,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Load search engines for ru_RU locale with engines not in searchOrder`() = runBlocking { + fun `Load search engines for ru_RU locale with engines not in searchOrder`() = runTest { val storage = BundledSearchEnginesStorage(testContext) val engines = storage.load(RegionState("RU", "RU"), Locale("ru", "RU")) val searchEngines = engines.list @@ -129,7 +129,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Load search engines for trs locale with non-google initial engines and no default`() = runBlocking { + fun `Load search engines for trs locale with non-google initial engines and no default`() = runTest { val storage = BundledSearchEnginesStorage(testContext) val engines = storage.load(RegionState.Default, Locale("trs", "")) val searchEngines = engines.list @@ -149,7 +149,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Load search engines for locale not in configuration`() = runBlocking { + fun `Load search engines for locale not in configuration`() = runTest { val storage = BundledSearchEnginesStorage(testContext) val engines = storage.load(RegionState.Default, Locale("xx", "XX")) val searchEngines = engines.list @@ -175,7 +175,7 @@ class BundledSearchEnginesStorageTest { } @Test - fun `Verify values of Google search engine`() = runBlocking { + fun `Verify values of Google search engine`() = runTest { val storage = BundledSearchEnginesStorage(testContext) val engines = storage.load(RegionState("US", "US"), Locale("en", "US")) diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/storage/CustomSearchEngineStorageTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/storage/CustomSearchEngineStorageTest.kt index a063830ac30..2feee8eecc2 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/storage/CustomSearchEngineStorageTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/storage/CustomSearchEngineStorageTest.kt @@ -5,7 +5,7 @@ package mozilla.components.feature.search.storage import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.search.SearchEngine import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext @@ -18,7 +18,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class CustomSearchEngineStorageTest { @Test - fun `saveSearchEngine successfully saves`() = runBlockingTest { + fun `saveSearchEngine successfully saves`() = runTest { val searchEngine = SearchEngine( id = "id1", name = "example", @@ -33,7 +33,7 @@ class CustomSearchEngineStorageTest { } @Test - fun `loadSearchEngine successfully loads after saving`() = runBlockingTest { + fun `loadSearchEngine successfully loads after saving`() = runTest { val searchEngine = SearchEngine( id = "id1", name = "example", @@ -54,7 +54,7 @@ class CustomSearchEngineStorageTest { @Test @Ignore("https://github.com/mozilla-mobile/android-components/issues/8124") - fun `loadSearchEngineList successfully loads after saving`() = runBlockingTest { + fun `loadSearchEngineList successfully loads after saving`() = runTest { val searchEngine = SearchEngine( id = "id1", name = "example", @@ -87,7 +87,7 @@ class CustomSearchEngineStorageTest { } @Test - fun `removeSearchEngine successfully deletes`() = runBlockingTest { + fun `removeSearchEngine successfully deletes`() = runTest { val searchEngine = SearchEngine( id = "id1", name = "example", diff --git a/components/feature/search/src/test/java/mozilla/components/feature/search/suggestions/SearchSuggestionClientTest.kt b/components/feature/search/src/test/java/mozilla/components/feature/search/suggestions/SearchSuggestionClientTest.kt index ed9678908f0..0586aad4d47 100644 --- a/components/feature/search/src/test/java/mozilla/components/feature/search/suggestions/SearchSuggestionClientTest.kt +++ b/components/feature/search/src/test/java/mozilla/components/feature/search/suggestions/SearchSuggestionClientTest.kt @@ -5,7 +5,7 @@ package mozilla.components.feature.search.suggestions import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.SearchState import mozilla.components.browser.state.store.BrowserStore @@ -33,19 +33,17 @@ class SearchSuggestionClientTest { ) @Test - fun `Get a list of results based on the Google search engine`() { + fun `Get a list of results based on the Google search engine`() = runTest { val client = SearchSuggestionClient(searchEngine, GOOGLE_MOCK_RESPONSE) + val expectedResults = listOf("firefox", "firefox for mac", "firefox quantum", "firefox update", "firefox esr", "firefox focus", "firefox addons", "firefox extensions", "firefox nightly", "firefox clear cache") - runBlocking { - val results = client.getSuggestions("firefox") - val expectedResults = listOf("firefox", "firefox for mac", "firefox quantum", "firefox update", "firefox esr", "firefox focus", "firefox addons", "firefox extensions", "firefox nightly", "firefox clear cache") + val results = client.getSuggestions("firefox") - assertEquals(expectedResults, results) - } + assertEquals(expectedResults, results) } @Test - fun `Get a list of results based on a non google search engine`() { + fun `Get a list of results based on a non google search engine`() = runTest { val qwant = createSearchEngine( name = "Qwant", url = "https://localhost?q={searchTerms}", @@ -53,51 +51,43 @@ class SearchSuggestionClientTest { icon = mock() ) val client = SearchSuggestionClient(qwant, QWANT_MOCK_RESPONSE) + val expectedResults = listOf("firefox (video game)", "firefox addons", "firefox", "firefox quantum", "firefox focus") - runBlocking { - val results = client.getSuggestions("firefox") - val expectedResults = listOf("firefox (video game)", "firefox addons", "firefox", "firefox quantum", "firefox focus") + val results = client.getSuggestions("firefox") - assertEquals(expectedResults, results) - } + assertEquals(expectedResults, results) } @Test(expected = SearchSuggestionClient.ResponseParserException::class) - fun `Check that a bad response will throw a parser exception`() { + fun `Check that a bad response will throw a parser exception`() = runTest { val client = SearchSuggestionClient(searchEngine, SERVER_ERROR_RESPONSE) - runBlocking { - client.getSuggestions("firefox") - } + client.getSuggestions("firefox") } @Test(expected = SearchSuggestionClient.FetchException::class) - fun `Check that an exception in the suggestionFetcher will re-throw an IOException`() { + fun `Check that an exception in the suggestionFetcher will re-throw an IOException`() = runTest { val client = SearchSuggestionClient(searchEngine) { throw IOException() } - runBlocking { - client.getSuggestions("firefox") - } + client.getSuggestions("firefox") } @Test - fun `Check that a search engine without a suggestURI will return an empty suggestion list`() { + fun `Check that a search engine without a suggestURI will return an empty suggestion list`() = runTest { val searchEngine = createSearchEngine( name = "Test", url = "https://localhost?q={searchTerms}", icon = mock() ) - val client = SearchSuggestionClient(searchEngine) { "no-op" } - runBlocking { - val results = client.getSuggestions("firefox") - assertEquals(emptyList(), results) - } + val results = client.getSuggestions("firefox") + + assertEquals(emptyList(), results) } @Test - fun `Default search engine is used if search engine manager provided`() { + fun `Default search engine is used if search engine manager provided`() = runTest { val store = BrowserStore( BrowserState( search = SearchState( @@ -111,12 +101,10 @@ class SearchSuggestionClientTest { store, GOOGLE_MOCK_RESPONSE ) + val expectedResults = listOf("firefox", "firefox for mac", "firefox quantum", "firefox update", "firefox esr", "firefox focus", "firefox addons", "firefox extensions", "firefox nightly", "firefox clear cache") - runBlocking { - val results = client.getSuggestions("firefox") - val expectedResults = listOf("firefox", "firefox for mac", "firefox quantum", "firefox update", "firefox esr", "firefox focus", "firefox addons", "firefox extensions", "firefox nightly", "firefox clear cache") + val results = client.getSuggestions("firefox") - assertEquals(expectedResults, results) - } + assertEquals(expectedResults, results) } } diff --git a/components/feature/session/src/test/java/mozilla/components/feature/session/HistoryDelegateTest.kt b/components/feature/session/src/test/java/mozilla/components/feature/session/HistoryDelegateTest.kt index 9d2d584492e..c0687803833 100644 --- a/components/feature/session/src/test/java/mozilla/components/feature/session/HistoryDelegateTest.kt +++ b/components/feature/session/src/test/java/mozilla/components/feature/session/HistoryDelegateTest.kt @@ -5,7 +5,7 @@ package mozilla.components.feature.session import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.storage.FrecencyThresholdOption import mozilla.components.concept.storage.HistoryAutocompleteResult import mozilla.components.concept.storage.HistoryStorage @@ -28,7 +28,7 @@ import org.mockito.Mockito.verify class HistoryDelegateTest { @Test - fun `history delegate passes through onVisited calls`() = runBlocking { + fun `history delegate passes through onVisited calls`() = runTest { val storage = mock() val delegate = HistoryDelegate(lazy { storage }) @@ -43,7 +43,7 @@ class HistoryDelegateTest { } @Test - fun `history delegate passes through onTitleChanged calls`() = runBlocking { + fun `history delegate passes through onTitleChanged calls`() = runTest { val storage = mock() val delegate = HistoryDelegate(lazy { storage }) @@ -52,7 +52,7 @@ class HistoryDelegateTest { } @Test - fun `history delegate passes through onPreviewImageChange calls`() = runBlocking { + fun `history delegate passes through onPreviewImageChange calls`() = runTest { val storage = mock() val delegate = HistoryDelegate(lazy { storage }) @@ -65,7 +65,7 @@ class HistoryDelegateTest { } @Test - fun `history delegate passes through getVisited calls`() = runBlocking { + fun `history delegate passes through getVisited calls`() = runTest { val storage = TestHistoryStorage() val delegate = HistoryDelegate(lazy { storage }) @@ -84,7 +84,7 @@ class HistoryDelegateTest { } @Test - fun `history delegate checks with storage canAddUriCalled`() = runBlocking { + fun `history delegate checks with storage canAddUriCalled`() = runTest { val storage = TestHistoryStorage() val delegate = HistoryDelegate(lazy { storage }) diff --git a/components/feature/session/src/test/java/mozilla/components/feature/session/SessionUseCasesTest.kt b/components/feature/session/src/test/java/mozilla/components/feature/session/SessionUseCasesTest.kt index d73e3fc0f2f..e34c936a73a 100644 --- a/components/feature/session/src/test/java/mozilla/components/feature/session/SessionUseCasesTest.kt +++ b/components/feature/session/src/test/java/mozilla/components/feature/session/SessionUseCasesTest.kt @@ -4,7 +4,7 @@ package mozilla.components.feature.session -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.CrashAction import mozilla.components.browser.state.action.EngineAction @@ -249,7 +249,7 @@ class SessionUseCasesTest { } @Test - fun stopLoading() = runBlocking { + fun stopLoading() = runTest { useCases.stopLoading() store.waitUntilIdle() verify(engineSession).stopLoading() diff --git a/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt b/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt index 87eeff658d1..495372ff13b 100644 --- a/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt +++ b/components/feature/session/src/test/java/mozilla/components/feature/session/middleware/undo/UndoMiddlewareTest.kt @@ -5,7 +5,6 @@ package mozilla.components.feature.session.middleware.undo import kotlinx.coroutines.test.TestDispatcher -import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.withContext import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.action.UndoAction @@ -16,6 +15,7 @@ import mozilla.components.browser.state.store.BrowserStore import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -28,7 +28,7 @@ class UndoMiddlewareTest { private val dispatcher = coroutinesTestRule.testDispatcher @Test - fun `Undo scenario - Removing single tab`() = runBlockingTest { + fun `Undo scenario - Removing single tab`() = runTestOnMain { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -60,7 +60,7 @@ class UndoMiddlewareTest { } @Test - fun `Undo scenario - Removing list of tabs`() = runBlockingTest { + fun `Undo scenario - Removing list of tabs`() = runTestOnMain { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -92,7 +92,7 @@ class UndoMiddlewareTest { } @Test - fun `Undo scenario - Removing all normal tabs`() = runBlockingTest { + fun `Undo scenario - Removing all normal tabs`() = runTestOnMain { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -124,7 +124,7 @@ class UndoMiddlewareTest { } @Test - fun `Undo scenario - Removing all tabs`() = runBlockingTest { + fun `Undo scenario - Removing all tabs`() = runTestOnMain { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -156,7 +156,7 @@ class UndoMiddlewareTest { } @Test - fun `Undo scenario - Removing all tabs non-recoverable`() = runBlockingTest { + fun `Undo scenario - Removing all tabs non-recoverable`() = runTestOnMain { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -189,7 +189,7 @@ class UndoMiddlewareTest { } @Test - fun `Undo History in State is written`() = runBlockingTest { + fun `Undo History in State is written`() = runTestOnMain { val store = BrowserStore( middleware = listOf( UndoMiddleware(clearAfterMillis = 60000) @@ -238,7 +238,7 @@ class UndoMiddlewareTest { } @Test - fun `Undo History gets cleared after time`() = runBlockingTest { + fun `Undo History gets cleared after time`() = runTestOnMain { val store = BrowserStore( middleware = listOf( diff --git a/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorageTest.kt b/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorageTest.kt index cad65c5e575..1bbdf6694c7 100644 --- a/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorageTest.kt +++ b/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorageTest.kt @@ -10,7 +10,7 @@ import androidx.room.DatabaseConfiguration import androidx.room.InvalidationTracker import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.DataCleanable import mozilla.components.concept.engine.Engine.BrowsingData import mozilla.components.concept.engine.permission.SitePermissions @@ -60,7 +60,7 @@ class OnDiskSitePermissionsStorageTest { } @Test - fun `save a new SitePermission`() = runBlockingTest { + fun `save a new SitePermission`() = runTest { val sitePermissions = createNewSitePermission() storage.save(sitePermissions) @@ -69,7 +69,7 @@ class OnDiskSitePermissionsStorageTest { } @Test - fun `update a SitePermission`() = runBlockingTest { + fun `update a SitePermission`() = runTest { val sitePermissions = createNewSitePermission() storage.update(sitePermissions) @@ -80,14 +80,14 @@ class OnDiskSitePermissionsStorageTest { } @Test - fun `find a SitePermissions by origin`() = runBlockingTest { + fun `find a SitePermissions by origin`() = runTest { storage.findSitePermissionsBy("mozilla.org") verify(mockDAO).getSitePermissionsBy("mozilla.org") } @Test - fun `find all sitePermissions grouped by permission`() = runBlockingTest { + fun `find all sitePermissions grouped by permission`() = runTest { doReturn(dummySitePermissionEntitiesList()) .`when`(mockDAO).getSitePermissions() @@ -106,7 +106,7 @@ class OnDiskSitePermissionsStorageTest { } @Test - fun `remove a SitePermissions`() = runBlockingTest { + fun `remove a SitePermissions`() = runTest { val sitePermissions = createNewSitePermission() storage.remove(sitePermissions) @@ -118,7 +118,7 @@ class OnDiskSitePermissionsStorageTest { } @Test - fun `remove all SitePermissions`() = runBlockingTest { + fun `remove all SitePermissions`() = runTest { storage.removeAll() shadowOf(getMainLooper()).idle() @@ -127,7 +127,7 @@ class OnDiskSitePermissionsStorageTest { } @Test - fun `get all SitePermissions paged`() = runBlockingTest { + fun `get all SitePermissions paged`() = runTest { val mockDataSource: DataSource = mock() doReturn(object : DataSource.Factory() { diff --git a/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt b/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt index 1594b88bf0d..3751f0876f5 100644 --- a/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt +++ b/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt @@ -9,8 +9,6 @@ import android.content.pm.PackageManager.PERMISSION_GRANTED import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.ContentAction.UpdatePermissionHighlightsStateAction import mozilla.components.browser.state.action.ContentAction.UpdatePermissionHighlightsStateAction.AutoPlayAudibleBlockingAction @@ -58,6 +56,7 @@ import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -367,7 +366,7 @@ class SitePermissionsFeatureTest { } @Test - fun `GIVEN a new permissionRequest WHEN storeSitePermissions() THEN save(permissionRequest) is called`() = runBlockingTest { + fun `GIVEN a new permissionRequest WHEN storeSitePermissions() THEN save(permissionRequest) is called`() = runTestOnMain { // given val sitePermissions = SitePermissions(origin = "origin", savedAt = 0) doReturn(null).`when`(mockStorage).findSitePermissionsBy(ArgumentMatchers.anyString(), anyBoolean()) @@ -400,7 +399,7 @@ class SitePermissionsFeatureTest { } @Test - fun `GIVEN an already saved permissionRequest WHEN storeSitePermissions() THEN update(permissionRequest) is called`() = runBlockingTest { + fun `GIVEN an already saved permissionRequest WHEN storeSitePermissions() THEN update(permissionRequest) is called`() = runTestOnMain { // given val sitePermissions = SitePermissions(origin = "origin", savedAt = 0) doReturn(sitePermissions).`when`(mockStorage) @@ -425,7 +424,7 @@ class SitePermissionsFeatureTest { } @Test - fun `GIVEN a permissionRequest WITH a private tab WHEN storeSitePermissions() THEN save or update MUST NOT BE called`() = runBlockingTest { + fun `GIVEN a permissionRequest WITH a private tab WHEN storeSitePermissions() THEN save or update MUST NOT BE called`() = runTestOnMain { // then sitePermissionFeature.storeSitePermissions( selectedTab.content.copy(private = true), @@ -500,7 +499,7 @@ class SitePermissionsFeatureTest { doNothing().`when`(mockPermissionRequest).reject() // when - runBlockingTest { + runTestOnMain { sitePermissionFeature.onContentPermissionRequested(mockPermissionRequest, URL) } @@ -510,7 +509,7 @@ class SitePermissionsFeatureTest { } @Test - fun `GIVEN location permissionRequest and shouldApplyRules is true WHEN onContentPermissionRequested() THEN handleRuledFlow is called`() = runBlockingTest { + fun `GIVEN location permissionRequest and shouldApplyRules is true WHEN onContentPermissionRequested() THEN handleRuledFlow is called`() = runTestOnMain { // given val mockPermissionRequest: PermissionRequest = mock { whenever(permissions).thenReturn(listOf(ContentGeoLocation(id = "permission"))) @@ -523,7 +522,7 @@ class SitePermissionsFeatureTest { .handleRuledFlow(mockPermissionRequest, URL) // when - runBlockingTest { + runTestOnMain { sitePermissionFeature.onContentPermissionRequested( mockPermissionRequest, URL, @@ -537,7 +536,7 @@ class SitePermissionsFeatureTest { } @Test - fun `GIVEN location permissionRequest and shouldApplyRules is false WHEN onContentPermissionRequested() THEN handleNoRuledFlow is called`() = runBlockingTest { + fun `GIVEN location permissionRequest and shouldApplyRules is false WHEN onContentPermissionRequested() THEN handleNoRuledFlow is called`() = runTestOnMain { // given val mockPermissionRequest: PermissionRequest = mock { whenever(permissions).thenReturn(listOf(ContentGeoLocation(id = "permission"))) @@ -550,7 +549,7 @@ class SitePermissionsFeatureTest { .handleNoRuledFlow(sitePermissions, mockPermissionRequest, URL) // when - runBlockingTest { + runTestOnMain { sitePermissionFeature.onContentPermissionRequested( mockPermissionRequest, URL, @@ -564,7 +563,7 @@ class SitePermissionsFeatureTest { } @Test - fun `GIVEN autoplay permissionRequest and shouldApplyRules is false WHEN onContentPermissionRequested() THEN handleNoRuledFlow is called`() = runBlockingTest { + fun `GIVEN autoplay permissionRequest and shouldApplyRules is false WHEN onContentPermissionRequested() THEN handleNoRuledFlow is called`() = runTestOnMain { // given val mockPermissionRequest: PermissionRequest = mock { whenever(permissions).thenReturn(listOf(ContentAutoPlayInaudible(id = "permission"))) @@ -578,7 +577,7 @@ class SitePermissionsFeatureTest { .handleNoRuledFlow(sitePermissions, mockPermissionRequest, URL) // when - runBlockingTest { + runTestOnMain { sitePermissionFeature.onContentPermissionRequested( mockPermissionRequest, URL, @@ -592,7 +591,7 @@ class SitePermissionsFeatureTest { } @Test - fun `GIVEN shouldShowPrompt with isForAutoplay false AND null permissionFromStorage THEN return true`() = runBlockingTest { + fun `GIVEN shouldShowPrompt with isForAutoplay false AND null permissionFromStorage THEN return true`() = runTestOnMain { // given val mockPermissionRequest: PermissionRequest = mock { whenever(permissions).thenReturn(listOf(Permission.ContentGeoLocation(id = "permission"))) @@ -1042,7 +1041,7 @@ class SitePermissionsFeatureTest { } @Test - fun `is SitePermission granted in the storage`() = runBlockingTest { + fun `is SitePermission granted in the storage`() = runTestOnMain { val sitePermissionsList = listOf( ContentGeoLocation(), ContentNotification(), @@ -1078,7 +1077,7 @@ class SitePermissionsFeatureTest { } @Test - fun `is SitePermission blocked in the storage`() = runBlockingTest { + fun `is SitePermission blocked in the storage`() = runTestOnMain { val sitePermissionsList = listOf( ContentGeoLocation(), ContentNotification(), @@ -1169,7 +1168,7 @@ class SitePermissionsFeatureTest { } @Test - fun `getInitialSitePermissions - WHEN sitePermissionsRules is present the function MUST use the sitePermissionsRules values to create a SitePermissions object`() = runBlockingTest { + fun `getInitialSitePermissions - WHEN sitePermissionsRules is present the function MUST use the sitePermissionsRules values to create a SitePermissions object`() = runTestOnMain { val rules = SitePermissionsRules( location = SitePermissionsRules.Action.BLOCKED, @@ -1200,7 +1199,7 @@ class SitePermissionsFeatureTest { } @Test - fun `any media request must be rejected WHEN system permissions are not granted first`() = runBlocking() { + fun `any media request must be rejected WHEN system permissions are not granted first`() = runTestOnMain { val permissions = listOf( ContentVideoCapture("", "back camera"), ContentVideoCamera("", "front camera"), @@ -1228,12 +1227,10 @@ class SitePermissionsFeatureTest { mockStorage = mock() - runBlocking { - val prompt = sitePermissionFeature - .onContentPermissionRequested(permissionRequest, URL) - assertNull(prompt) - assertFalse(grantWasCalled) - } + val prompt = sitePermissionFeature + .onContentPermissionRequested(permissionRequest, URL) + assertNull(prompt) + assertFalse(grantWasCalled) } Unit diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProviderTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProviderTest.kt index f162e9a6f33..54525b62934 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProviderTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProviderTest.kt @@ -6,7 +6,7 @@ package mozilla.components.feature.syncedtabs import android.graphics.drawable.Drawable import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.storage.sync.SyncedDeviceTabs import mozilla.components.browser.storage.sync.Tab import mozilla.components.browser.storage.sync.TabEntry @@ -38,7 +38,7 @@ class SyncedTabsStorageSuggestionProviderTest { } @Test - fun `matches remote tabs`() = runBlocking { + fun `matches remote tabs`() = runTest { val provider = SyncedTabsStorageSuggestionProvider(syncedTabs, mock(), mock(), indicatorIcon) val deviceTabs1 = SyncedDeviceTabs( Device( diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt index cd0f704ed0b..c25f4ff1844 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/controller/DefaultControllerTest.kt @@ -5,7 +5,6 @@ package mozilla.components.feature.syncedtabs.controller import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.storage.sync.SyncedDeviceTabs import mozilla.components.concept.sync.ConstellationState import mozilla.components.concept.sync.DeviceConstellation @@ -18,6 +17,7 @@ import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.sync.SyncReason import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -36,7 +36,7 @@ class DefaultControllerTest { val coroutinesTestRule = MainCoroutineRule() @Test - fun `update view only when no account available`() = runBlockingTest { + fun `update view only when no account available`() = runTestOnMain { val controller = DefaultController( storage, accountManager, @@ -52,7 +52,7 @@ class DefaultControllerTest { } @Test - fun `notify if there are no other devices synced`() = runBlockingTest { + fun `notify if there are no other devices synced`() = runTestOnMain { val controller = DefaultController( storage, accountManager, @@ -76,7 +76,7 @@ class DefaultControllerTest { } @Test - fun `notify if there are no tabs from other devices to sync`() = runBlockingTest { + fun `notify if there are no tabs from other devices to sync`() = runTestOnMain { val controller = DefaultController( storage, accountManager, @@ -101,7 +101,7 @@ class DefaultControllerTest { } @Test - fun `display synced tabs`() = runBlockingTest { + fun `display synced tabs`() = runTestOnMain { val controller = DefaultController( storage, accountManager, @@ -129,7 +129,7 @@ class DefaultControllerTest { } @Test - fun `WHEN syncAccount is called THEN view is loading, devices are refreshed, and sync started`() = runBlockingTest { + fun `WHEN syncAccount is called THEN view is loading, devices are refreshed, and sync started`() = runTestOnMain { val controller = DefaultController( storage, accountManager, diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/interactor/DefaultInteractorTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/interactor/DefaultInteractorTest.kt index a2ca4201249..ebe17b42a97 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/interactor/DefaultInteractorTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/interactor/DefaultInteractorTest.kt @@ -4,7 +4,7 @@ package mozilla.components.feature.syncedtabs.interactor -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.storage.sync.SyncedDeviceTabs import mozilla.components.feature.syncedtabs.controller.SyncedTabsController import mozilla.components.feature.syncedtabs.view.SyncedTabsView @@ -21,7 +21,7 @@ class DefaultInteractorTest { private val controller: SyncedTabsController = mock() @Test - fun start() = runBlockingTest { + fun start() = runTest { val view = TestSyncedTabsView() val feature = DefaultInteractor( @@ -37,7 +37,7 @@ class DefaultInteractorTest { } @Test - fun stop() = runBlockingTest { + fun stop() = runTest { val view = TestSyncedTabsView() val feature = DefaultInteractor( @@ -57,7 +57,7 @@ class DefaultInteractorTest { } @Test - fun `onTabClicked invokes callback`() = runBlockingTest { + fun `onTabClicked invokes callback`() = runTest { var invoked = false val feature = DefaultInteractor( controller, @@ -72,7 +72,7 @@ class DefaultInteractorTest { } @Test - fun `onRefresh does not update devices when there is no constellation`() = runBlockingTest { + fun `onRefresh does not update devices when there is no constellation`() = runTest { val feature = DefaultInteractor( controller, view @@ -84,7 +84,7 @@ class DefaultInteractorTest { } @Test - fun `onRefresh updates devices when there is a constellation`() = runBlockingTest { + fun `onRefresh updates devices when there is a constellation`() = runTest { val feature = DefaultInteractor( controller, view diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt index 3be69de6ed4..b43a89555f8 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/presenter/DefaultPresenterTest.kt @@ -8,7 +8,7 @@ import android.content.Context import android.os.Looper.getMainLooper import androidx.lifecycle.LifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.feature.syncedtabs.controller.SyncedTabsController import mozilla.components.feature.syncedtabs.view.SyncedTabsView import mozilla.components.feature.syncedtabs.view.SyncedTabsView.ErrorType @@ -35,7 +35,7 @@ class DefaultPresenterTest { private val prefs = testContext.getSharedPreferences(SYNC_ENGINES_KEY, Context.MODE_PRIVATE) @Test - fun `start returns when there is no profile`() = runBlockingTest { + fun `start returns when there is no profile`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -50,7 +50,7 @@ class DefaultPresenterTest { } @Test - fun `start returns if sync engine is not enabled`() = runBlockingTest { + fun `start returns if sync engine is not enabled`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -87,7 +87,7 @@ class DefaultPresenterTest { } @Test - fun `start invokes syncTabs - account profile is absent`() = runBlockingTest { + fun `start invokes syncTabs - account profile is absent`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -107,7 +107,7 @@ class DefaultPresenterTest { } @Test - fun `start invokes syncTabs - account profile is present`() = runBlockingTest { + fun `start invokes syncTabs - account profile is present`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -127,7 +127,7 @@ class DefaultPresenterTest { } @Test - fun `notify on logout`() = runBlockingTest { + fun `notify on logout`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -143,7 +143,7 @@ class DefaultPresenterTest { } @Test - fun `notify on authenticated`() = runBlockingTest { + fun `notify on authenticated`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -159,7 +159,7 @@ class DefaultPresenterTest { } @Test - fun `notify on authentication problems`() = runBlockingTest { + fun `notify on authentication problems`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -175,7 +175,7 @@ class DefaultPresenterTest { } @Test - fun `sync tabs on idle status - tabs sync enabled`() = runBlockingTest { + fun `sync tabs on idle status - tabs sync enabled`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -191,7 +191,7 @@ class DefaultPresenterTest { } @Test - fun `sync tabs on idle status - tabs sync disabled`() = runBlockingTest { + fun `sync tabs on idle status - tabs sync disabled`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -208,7 +208,7 @@ class DefaultPresenterTest { } @Test - fun `show loading state on started status`() = runBlockingTest { + fun `show loading state on started status`() = runTest { val presenter = DefaultPresenter( context, controller, @@ -223,7 +223,7 @@ class DefaultPresenterTest { } @Test - fun `notify on error`() = runBlockingTest { + fun `notify on error`() = runTest { val presenter = DefaultPresenter( context, controller, diff --git a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt index 29876e53d0d..f7316fcc60e 100644 --- a/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt +++ b/components/feature/syncedtabs/src/test/java/mozilla/components/feature/syncedtabs/storage/SyncedTabsStorageTest.kt @@ -10,7 +10,6 @@ package mozilla.components.feature.syncedtabs.storage -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.LastAccessAction import mozilla.components.browser.state.action.TabListAction @@ -34,6 +33,7 @@ import mozilla.components.support.test.any import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Before @@ -73,7 +73,7 @@ class SyncedTabsStorageTest { } @Test - fun `listens to browser store changes, stores state changes, and calls onStoreComplete`() = runBlockingTest { + fun `listens to browser store changes, stores state changes, and calls onStoreComplete`() = runTestOnMain { val feature = SyncedTabsStorage( accountManager, store, @@ -98,7 +98,7 @@ class SyncedTabsStorageTest { } @Test - fun `stops listening to browser store changes on stop()`() = runBlockingTest { + fun `stops listening to browser store changes on stop()`() = runTestOnMain { val feature = SyncedTabsStorage( accountManager, store, @@ -124,7 +124,7 @@ class SyncedTabsStorageTest { } @Test - fun `getSyncedTabs matches tabs with FxA devices`() = runBlockingTest { + fun `getSyncedTabs matches tabs with FxA devices`() = runTestOnMain { val feature = spy( SyncedTabsStorage( accountManager, @@ -171,7 +171,7 @@ class SyncedTabsStorageTest { } @Test - fun `getSyncedTabs returns empty list if syncClients() is null`() = runBlockingTest { + fun `getSyncedTabs returns empty list if syncClients() is null`() = runTestOnMain { val feature = spy( SyncedTabsStorage( accountManager, @@ -245,7 +245,7 @@ class SyncedTabsStorageTest { } @Test - fun `tabs are stored when loaded`() = runBlockingTest { + fun `tabs are stored when loaded`() = runTestOnMain { val store = BrowserStore( BrowserState( tabs = listOf( @@ -282,7 +282,7 @@ class SyncedTabsStorageTest { } @Test - fun `only loaded tabs are stored on load`() = runBlockingTest { + fun `only loaded tabs are stored on load`() = runTestOnMain { val store = BrowserStore( BrowserState( tabs = listOf( @@ -312,7 +312,7 @@ class SyncedTabsStorageTest { } @Test - fun `tabs are stored when selected tab changes`() = runBlockingTest { + fun `tabs are stored when selected tab changes`() = runTestOnMain { val store = BrowserStore( BrowserState( tabs = listOf( @@ -343,7 +343,7 @@ class SyncedTabsStorageTest { } @Test - fun `tabs are stored when lastAccessed is changed for any tab`() = runBlockingTest { + fun `tabs are stored when lastAccessed is changed for any tab`() = runTestOnMain { val store = BrowserStore( BrowserState( tabs = listOf( diff --git a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt index 75227b3f6ed..d54600ad1d0 100644 --- a/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt +++ b/components/feature/tabs/src/test/java/mozilla/components/feature/tabs/TabsUseCasesTest.kt @@ -4,7 +4,7 @@ package mozilla.components.feature.tabs -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.session.storage.SessionStorage import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.TabListAction @@ -349,7 +349,7 @@ class TabsUseCasesTest { } @Test - fun `RestoreUseCase - filters based on tab timeout`() = runBlocking { + fun `RestoreUseCase - filters based on tab timeout`() = runTest { val useCases = TabsUseCases(BrowserStore()) val now = System.currentTimeMillis() diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt index 543dbacf78e..366bc2e51ac 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt @@ -5,7 +5,7 @@ package mozilla.components.feature.toolbar import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.domains.Domain import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainList @@ -142,7 +142,7 @@ class ToolbarAutocompleteFeatureTest { } @Test - fun `feature can be used without providers`() { + fun `feature can be used without providers`() = runTest { val toolbar = TestToolbar() ToolbarAutocompleteFeature(toolbar) @@ -150,9 +150,8 @@ class ToolbarAutocompleteFeatureTest { assertNotNull(toolbar.autocompleteFilter) val autocompleteDelegate: AutocompleteDelegate = mock() - runBlocking { - toolbar.autocompleteFilter!!("moz", autocompleteDelegate) - } + toolbar.autocompleteFilter!!("moz", autocompleteDelegate) + verify(autocompleteDelegate, never()).applyAutocompleteResult(any(), any()) verify(autocompleteDelegate, times(1)).noAutocompleteResult("moz") } @@ -282,7 +281,7 @@ class ToolbarAutocompleteFeatureTest { } @Test - fun `feature triggers speculative connect for results if engine provided`() { + fun `feature triggers speculative connect for results if engine provided`() = runTest { val toolbar = TestToolbar() val engine: Engine = mock() var feature = ToolbarAutocompleteFeature(toolbar, engine) @@ -296,9 +295,7 @@ class ToolbarAutocompleteFeatureTest { domains.testDomains(listOf(Domain.create("https://www.mozilla.org"))) feature.addDomainProvider(domains) - runBlocking { - toolbar.autocompleteFilter!!.invoke("mo", autocompleteDelegate) - } + toolbar.autocompleteFilter!!.invoke("mo", autocompleteDelegate) val callbackCaptor = argumentCaptor<() -> Unit>() verify(autocompleteDelegate, times(1)).applyAutocompleteResult(any(), callbackCaptor.capture()) @@ -308,19 +305,17 @@ class ToolbarAutocompleteFeatureTest { } @Suppress("SameParameterValue") - private fun verifyNoAutocompleteResult(toolbar: TestToolbar, autocompleteDelegate: AutocompleteDelegate, query: String) { - runBlocking { - toolbar.autocompleteFilter!!(query, autocompleteDelegate) - } + private fun verifyNoAutocompleteResult(toolbar: TestToolbar, autocompleteDelegate: AutocompleteDelegate, query: String) = runTest { + toolbar.autocompleteFilter!!(query, autocompleteDelegate) + verify(autocompleteDelegate, never()).applyAutocompleteResult(any(), any()) verify(autocompleteDelegate, times(1)).noAutocompleteResult(query) reset(autocompleteDelegate) } - private fun verifyAutocompleteResult(toolbar: TestToolbar, autocompleteDelegate: AutocompleteDelegate, query: String, result: AutocompleteResult) { - runBlocking { - toolbar.autocompleteFilter!!.invoke(query, autocompleteDelegate) - } + private fun verifyAutocompleteResult(toolbar: TestToolbar, autocompleteDelegate: AutocompleteDelegate, query: String, result: AutocompleteResult) = runTest { + toolbar.autocompleteFilter!!.invoke(query, autocompleteDelegate) + verify(autocompleteDelegate, times(1)).applyAutocompleteResult(eq(result), any()) verify(autocompleteDelegate, never()).noAutocompleteResult(query) reset(autocompleteDelegate) diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt index 90f2b0294fc..2aef8ff9cdc 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt @@ -9,7 +9,6 @@ import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking import mozilla.components.concept.toolbar.Toolbar import mozilla.components.feature.toolbar.ToolbarFeature import mozilla.components.lib.publicsuffixlist.PublicSuffixList @@ -17,6 +16,7 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -52,7 +52,7 @@ class URLRendererTest { @Test fun `Render with configuration`() { - runBlocking { + runTestOnMain { val configuration = ToolbarFeature.UrlRenderConfiguration( publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined), registrableDomainColor = Color.RED, diff --git a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt index 801bce380e2..d44f32d10ed 100644 --- a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt +++ b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/DefaultTopSitesStorageTest.kt @@ -6,16 +6,18 @@ package mozilla.components.feature.top.sites import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.storage.FrecencyThresholdOption import mozilla.components.concept.storage.TopFrecentSiteInfo import mozilla.components.feature.top.sites.ext.toTopSite import mozilla.components.support.test.any import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyInt @@ -26,12 +28,15 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class DefaultTopSitesStorageTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val pinnedSitesStorage: PinnedSiteStorage = mock() private val historyStorage: PlacesHistoryStorage = mock() private val topSitesProvider: TopSitesProvider = mock() @Test - fun `default top sites are added to pinned site storage on init`() = runBlockingTest { + fun `default top sites are added to pinned site storage on init`() = runTestOnMain { val defaultTopSites = listOf( Pair("Mozilla", "https://mozilla.com"), Pair("Firefox", "https://firefox.com") @@ -48,7 +53,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `addPinnedSite`() = runBlockingTest { + fun `addPinnedSite`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -66,7 +71,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `removeTopSite`() = runBlockingTest { + fun `removeTopSite`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -111,7 +116,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `updateTopSite`() = runBlockingTest { + fun `updateTopSite`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -154,7 +159,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `GIVEN frecencyConfig and providerConfig are null WHEN getTopSites is called THEN only default and pinned sites are returned`() = runBlockingTest { + fun `GIVEN frecencyConfig and providerConfig are null WHEN getTopSites is called THEN only default and pinned sites are returned`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -210,7 +215,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `GIVEN providerConfig is specified WHEN getTopSites is called THEN default, pinned and provided top sites are returned`() = runBlockingTest { + fun `GIVEN providerConfig is specified WHEN getTopSites is called THEN default, pinned and provided top sites are returned`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -331,7 +336,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `GIVEN providerConfig with maxThreshold is specified WHEN getTopSites is called THEN the correct number of provided top sites are returned`() = runBlockingTest { + fun `GIVEN providerConfig with maxThreshold is specified WHEN getTopSites is called THEN the correct number of provided top sites are returned`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -473,7 +478,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `GIVEN frecencyConfig and providerConfig are specified WHEN getTopSites is called THEN default, pinned, provided and frecent top sites are returned`() = runBlockingTest { + fun `GIVEN frecencyConfig and providerConfig are specified WHEN getTopSites is called THEN default, pinned, provided and frecent top sites are returned`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -581,7 +586,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `getTopSites returns pinned and frecent sites when frecencyConfig is specified`() = runBlockingTest { + fun `getTopSites returns pinned and frecent sites when frecencyConfig is specified`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -698,7 +703,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `getTopSites filters out frecent sites that already exist in pinned sites`() = runBlockingTest { + fun `getTopSites filters out frecent sites that already exist in pinned sites`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -766,7 +771,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `GIVEN providerFilter is set WHEN getTopSites is called THEN the provided top sites are filtered`() = runBlockingTest { + fun `GIVEN providerFilter is set WHEN getTopSites is called THEN the provided top sites are filtered`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, @@ -850,7 +855,7 @@ class DefaultTopSitesStorageTest { } @Test - fun `GIVEN frecent top sites exist as a pinned or provided site WHEN top sites are retrieved THEN filters out frecent sites that already exist in pinned or provided sites`() = runBlockingTest { + fun `GIVEN frecent top sites exist as a pinned or provided site WHEN top sites are retrieved THEN filters out frecent sites that already exist in pinned or provided sites`() = runTestOnMain { val defaultTopSitesStorage = DefaultTopSitesStorage( pinnedSitesStorage = pinnedSitesStorage, historyStorage = historyStorage, diff --git a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/PinnedSitesStorageTest.kt b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/PinnedSitesStorageTest.kt index d0bdd52faf5..c0d556c7ef4 100644 --- a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/PinnedSitesStorageTest.kt +++ b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/PinnedSitesStorageTest.kt @@ -4,7 +4,8 @@ package mozilla.components.feature.top.sites -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.feature.top.sites.db.PinnedSiteDao import mozilla.components.feature.top.sites.db.PinnedSiteEntity import mozilla.components.feature.top.sites.db.TopSiteDatabase @@ -15,10 +16,11 @@ import org.junit.Test import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest class PinnedSitesStorageTest { @Test - fun addAllDefaultSites() = runBlocking { + fun addAllDefaultSites() = runTest { val storage = PinnedSiteStorage(mock()) val dao = mockDao(storage) @@ -47,7 +49,7 @@ class PinnedSitesStorageTest { } @Test - fun addPinnedSite() = runBlocking { + fun addPinnedSite() = runTest { val storage = PinnedSiteStorage(mock()) val dao = mockDao(storage) @@ -66,7 +68,7 @@ class PinnedSitesStorageTest { } @Test - fun removePinnedSite() = runBlocking { + fun removePinnedSite() = runTest { val storage = PinnedSiteStorage(mock()) val dao = mockDao(storage) @@ -78,7 +80,7 @@ class PinnedSitesStorageTest { } @Test - fun getPinnedSites() = runBlocking { + fun getPinnedSites() = runTest { val storage = PinnedSiteStorage(mock()) val dao = mockDao(storage) @@ -113,7 +115,7 @@ class PinnedSitesStorageTest { } @Test - fun updatePinnedSite() = runBlocking { + fun updatePinnedSite() = runTest { val storage = PinnedSiteStorage(mock()) val dao = mockDao(storage) diff --git a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/TopSitesUseCasesTest.kt b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/TopSitesUseCasesTest.kt index 66866bdce14..92f79fec7d1 100644 --- a/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/TopSitesUseCasesTest.kt +++ b/components/feature/top-sites/src/test/java/mozilla/components/feature/top/sites/TopSitesUseCasesTest.kt @@ -5,17 +5,19 @@ package mozilla.components.feature.top.sites import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.mock import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class TopSitesUseCasesTest { @Test - fun `AddPinnedSiteUseCase`() = runBlocking { + fun `AddPinnedSiteUseCase`() = runTest { val topSitesStorage: TopSitesStorage = mock() val useCases = TopSitesUseCases(topSitesStorage) @@ -28,7 +30,7 @@ class TopSitesUseCasesTest { } @Test - fun `RemoveTopSiteUseCase`() = runBlocking { + fun `RemoveTopSiteUseCase`() = runTest { val topSitesStorage: TopSitesStorage = mock() val topSite: TopSite = mock() val useCases = TopSitesUseCases(topSitesStorage) @@ -39,7 +41,7 @@ class TopSitesUseCasesTest { } @Test - fun `UpdateTopSiteUseCase`() = runBlocking { + fun `UpdateTopSiteUseCase`() = runTest { val topSitesStorage: TopSitesStorage = mock() val topSite: TopSite = mock() val useCases = TopSitesUseCases(topSitesStorage) diff --git a/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/NativeNotificationBridgeTest.kt b/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/NativeNotificationBridgeTest.kt index 98d75314452..bfd6aaec4fc 100644 --- a/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/NativeNotificationBridgeTest.kt +++ b/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/NativeNotificationBridgeTest.kt @@ -5,14 +5,18 @@ package mozilla.components.feature.webnotifications import android.app.Notification +import android.app.Notification.BigTextStyle import android.app.Notification.EXTRA_SUB_TEXT import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.IconRequest +import mozilla.components.browser.icons.IconRequest.Resource +import mozilla.components.browser.icons.IconRequest.Resource.Type.MANIFEST_ICON +import mozilla.components.browser.icons.IconRequest.Size.DEFAULT import mozilla.components.concept.engine.webnotifications.WebNotification import mozilla.components.support.test.any import mozilla.components.support.test.mock @@ -34,8 +38,8 @@ private const val TEST_TEXT = "test text" private const val TEST_URL = "mozilla.org" private const val TEST_CHANNEL = "testChannel" +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) -@ExperimentalCoroutinesApi class NativeNotificationBridgeTest { private val blankNotification = WebNotification( TEST_TITLE, TEST_TAG, TEST_TEXT, TEST_URL, null, null, @@ -55,7 +59,7 @@ class NativeNotificationBridgeTest { } @Test - fun `create blank notification`() = runBlockingTest { + fun `create blank notification`() = runTest { val notification = bridge.convertToAndroidNotification( blankNotification, testContext, @@ -73,7 +77,7 @@ class NativeNotificationBridgeTest { } @Test - fun `set when`() = runBlockingTest { + fun `set when`() = runTest { val notification = bridge.convertToAndroidNotification( blankNotification.copy(timestamp = 1234567890), testContext, @@ -86,7 +90,7 @@ class NativeNotificationBridgeTest { } @Test - fun `icon is loaded from BrowserIcons`() = runBlockingTest { + fun `icon is loaded from BrowserIcons`() = runTest { bridge.convertToAndroidNotification( blankNotification.copy(sourceUrl = "https://example.com", iconUrl = "https://example.com/large.png"), testContext, @@ -98,11 +102,11 @@ class NativeNotificationBridgeTest { verify(icons).loadIcon( IconRequest( url = "https://example.com", - size = IconRequest.Size.DEFAULT, + size = DEFAULT, resources = listOf( - IconRequest.Resource( + Resource( url = "https://example.com/large.png", - type = IconRequest.Resource.Type.MANIFEST_ICON + type = MANIFEST_ICON ) ), isPrivate = true @@ -111,7 +115,7 @@ class NativeNotificationBridgeTest { } @Test - fun `android notification sets BigTextStyle`() = runBlockingTest { + fun `android notification sets BigTextStyle`() = runTest { val notification = bridge.convertToAndroidNotification( blankNotification.copy(iconUrl = "https://example.com/large.png"), testContext, @@ -120,7 +124,7 @@ class NativeNotificationBridgeTest { 0 ) - val expectedStyle = Notification.BigTextStyle().javaClass.name + val expectedStyle = BigTextStyle().javaClass.name assertEquals(expectedStyle, notification.extras.getString(Notification.EXTRA_TEMPLATE)) val noBodyNotification = bridge.convertToAndroidNotification( diff --git a/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/WebNotificationFeatureTest.kt b/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/WebNotificationFeatureTest.kt index 6d3888eff68..6a7bd9d19d2 100644 --- a/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/WebNotificationFeatureTest.kt +++ b/components/feature/webnotifications/src/test/java/mozilla/components/feature/webnotifications/WebNotificationFeatureTest.kt @@ -8,7 +8,6 @@ import android.app.NotificationManager import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.Icon.Source @@ -21,7 +20,10 @@ import mozilla.components.support.test.any import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.`when` @@ -34,6 +36,10 @@ import org.mockito.Mockito.verify @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) class WebNotificationFeatureTest { + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val context = spy(testContext) private val browserIcons: BrowserIcons = mock() private val icon: Icon = mock() @@ -84,7 +90,7 @@ class WebNotificationFeatureTest { } @Test - fun `engine notifies to show notification`() = runBlockingTest { + fun `engine notifies to show notification`() = runTestOnMain { val notification = testNotification.copy(sourceUrl = "https://mozilla.org:443") val feature = WebNotificationFeature( context, @@ -105,7 +111,7 @@ class WebNotificationFeatureTest { } @Test - fun `notification ignored if permissions are not allowed`() = runBlockingTest { + fun `notification ignored if permissions are not allowed`() = runTestOnMain { val notification = testNotification.copy(sourceUrl = "https://mozilla.org:443") val feature = WebNotificationFeature( context, @@ -134,7 +140,7 @@ class WebNotificationFeatureTest { } @Test - fun `notifications always allowed for web extensions`() = runBlockingTest { + fun `notifications always allowed for web extensions`() = runTestOnMain { val webExtensionNotification = WebNotification( "Mozilla", "mozilla.org", diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt index e830c6ab1a2..1005503cf70 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/handler/CrashHandlerServiceTest.kt @@ -8,12 +8,12 @@ import android.content.ComponentName import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking import mozilla.components.lib.crash.CrashReporter import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.After import org.junit.Before import org.junit.Rule @@ -77,7 +77,7 @@ class CrashHandlerServiceTest { } @Test - fun `CrashHandlerService forwards main process native code crash to crash reporter`() = runBlocking { + fun `CrashHandlerService forwards main process native code crash to crash reporter`() = runTestOnMain { doNothing().`when`(reporter)!!.sendCrashReport(any(), any()) intent.putExtra("processType", "MAIN") @@ -88,7 +88,7 @@ class CrashHandlerServiceTest { } @Test - fun `CrashHandlerService forwards foreground child process native code crash to crash reporter`() = runBlocking { + fun `CrashHandlerService forwards foreground child process native code crash to crash reporter`() = runTestOnMain { doNothing().`when`(reporter)!!.sendCrashReport(any(), any()) intent.putExtra("processType", "FOREGROUND_CHILD") @@ -99,7 +99,7 @@ class CrashHandlerServiceTest { } @Test - fun `CrashHandlerService forwards background child process native code crash to crash reporter`() = runBlocking { + fun `CrashHandlerService forwards background child process native code crash to crash reporter`() = runTestOnMain { doNothing().`when`(reporter)!!.sendCrashReport(any(), any()) intent.putExtra("processType", "BACKGROUND_CHILD") diff --git a/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt b/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt index 1aeedb698b2..f5e5a120ae6 100644 --- a/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt +++ b/components/lib/crash/src/test/java/mozilla/components/lib/crash/prompt/CrashReporterActivityTest.kt @@ -16,7 +16,6 @@ import androidx.test.core.app.ActivityScenario.launch import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.CrashReporter import mozilla.components.lib.crash.prompt.CrashReporterActivity.Companion.PREFERENCE_KEY_SEND_REPORT @@ -25,6 +24,7 @@ import mozilla.components.lib.crash.service.CrashReporterService import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -54,7 +54,7 @@ class CrashReporterActivityTest { } @Test - fun `Pressing close button sends report`() = runBlockingTest { + fun `Pressing close button sends report`() = runTestOnMain { CrashReporter( context = testContext, shouldPrompt = CrashReporter.Prompt.ALWAYS, @@ -78,7 +78,7 @@ class CrashReporterActivityTest { } @Test - fun `Pressing restart button sends report`() = runBlockingTest { + fun `Pressing restart button sends report`() = runTestOnMain { CrashReporter( context = testContext, shouldPrompt = CrashReporter.Prompt.ALWAYS, @@ -102,7 +102,7 @@ class CrashReporterActivityTest { } @Test - fun `Custom message is set on CrashReporterActivity`() = runBlockingTest { + fun `Custom message is set on CrashReporterActivity`() = runTestOnMain { CrashReporter( context = testContext, shouldPrompt = CrashReporter.Prompt.ALWAYS, @@ -123,7 +123,7 @@ class CrashReporterActivityTest { } @Test - fun `Sending crash report saves checkbox state`() = runBlockingTest { + fun `Sending crash report saves checkbox state`() = runTestOnMain { CrashReporter( context = testContext, shouldPrompt = CrashReporter.Prompt.ALWAYS, @@ -150,7 +150,7 @@ class CrashReporterActivityTest { } @Test - fun `Restart button visible for main process crash`() = runBlockingTest { + fun `Restart button visible for main process crash`() = runTestOnMain { CrashReporter( context = testContext, shouldPrompt = CrashReporter.Prompt.ALWAYS, @@ -174,7 +174,7 @@ class CrashReporterActivityTest { } @Test - fun `Restart button hidden for background child process crash`() = runBlockingTest { + fun `Restart button hidden for background child process crash`() = runTestOnMain { CrashReporter( context = testContext, shouldPrompt = CrashReporter.Prompt.ALWAYS, diff --git a/components/lib/publicsuffixlist/build.gradle b/components/lib/publicsuffixlist/build.gradle index fb42f52063b..26fcff09be6 100644 --- a/components/lib/publicsuffixlist/build.gradle +++ b/components/lib/publicsuffixlist/build.gradle @@ -39,6 +39,7 @@ dependencies { testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito + testImplementation Dependencies.testing_coroutines } apply from: '../../../publish.gradle' diff --git a/components/lib/publicsuffixlist/src/test/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListTest.kt b/components/lib/publicsuffixlist/src/test/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListTest.kt index dcb624551a8..9526509aa70 100644 --- a/components/lib/publicsuffixlist/src/test/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListTest.kt +++ b/components/lib/publicsuffixlist/src/test/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListTest.kt @@ -5,7 +5,8 @@ package mozilla.components.lib.publicsuffixlist import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -14,6 +15,7 @@ import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class PublicSuffixListTest { @@ -21,7 +23,7 @@ class PublicSuffixListTest { get() = PublicSuffixList(testContext) @Test - fun `Verify getPublicSuffixPlusOne for known domains`() = runBlocking { + fun `Verify getPublicSuffixPlusOne for known domains`() = runTest { assertEquals( "mozilla.org", publicSuffixList.getPublicSuffixPlusOne("www.mozilla.org").await() @@ -69,7 +71,7 @@ class PublicSuffixListTest { } @Test - fun `Verify getPublicSuffix for known domains`() = runBlocking { + fun `Verify getPublicSuffix for known domains`() = runTest { assertEquals( "org", publicSuffixList.getPublicSuffix("www.mozilla.org").await() @@ -117,7 +119,7 @@ class PublicSuffixListTest { } @Test - fun `Verify stripPublicSuffix for known domains`() = runBlocking { + fun `Verify stripPublicSuffix for known domains`() = runTest { assertEquals( "www.mozilla", publicSuffixList.stripPublicSuffix("www.mozilla.org").await() @@ -169,7 +171,7 @@ class PublicSuffixListTest { * https://raw.githubusercontent.com/publicsuffix/list/master/tests/test_psl.txt */ @Test - fun `Verify getPublicSuffixPlusOne against official test data`() = runBlocking { + fun `Verify getPublicSuffixPlusOne against official test data`() = runTest { // empty input assertNull(publicSuffixList.getPublicSuffixPlusOne("").await()) @@ -410,7 +412,7 @@ class PublicSuffixListTest { } @Test - fun `Accessing with and without prefetch`() = runBlocking { + fun `Accessing with and without prefetch`() = runTest { run { val publicSuffixList = PublicSuffixList(testContext) assertEquals("org", publicSuffixList.getPublicSuffix("mozilla.org").await()) @@ -425,7 +427,7 @@ class PublicSuffixListTest { } @Test - fun `Verify isPublicSuffix with known and unknown suffixes`() = runBlocking { + fun `Verify isPublicSuffix with known and unknown suffixes`() = runTest { assertTrue(publicSuffixList.isPublicSuffix("org").await()) assertTrue(publicSuffixList.isPublicSuffix("com").await()) assertTrue(publicSuffixList.isPublicSuffix("us").await()) @@ -454,7 +456,7 @@ class PublicSuffixListTest { * https://github.com/google/guava/blob/master/guava-tests/test/com/google/common/net/InternetDomainNameTest.java */ @Test - fun `Verify getPublicSuffix can handle obscure and invalid input`() = runBlocking { + fun `Verify getPublicSuffix can handle obscure and invalid input`() = runTest { assertEquals("cOM", publicSuffixList.getPublicSuffix("f-_-o.cOM").await()) assertEquals("com", publicSuffixList.getPublicSuffix("f11-1.com").await()) assertNull(publicSuffixList.getPublicSuffix("www").await()) diff --git a/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt b/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt index d6994c1cb0f..9551d8b7034 100644 --- a/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt +++ b/components/lib/state/src/test/java/mozilla/components/lib/state/ext/StoreExtensionsKtTest.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import mozilla.components.lib.state.Store import mozilla.components.lib.state.TestAction import mozilla.components.lib.state.TestState @@ -28,6 +27,7 @@ import mozilla.components.lib.state.reducer import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -48,7 +48,7 @@ class StoreExtensionsKtTest { val coroutinesTestRule = MainCoroutineRule() @Test - fun `Observer will not get registered if lifecycle is already destroyed`() { + fun `Observer will not get registered if lifecycle is already destroyed`() = runTestOnMain { val owner = MockedLifecycleOwner(Lifecycle.State.DESTROYED) val store = Store( @@ -138,7 +138,7 @@ class StoreExtensionsKtTest { @Test @Synchronized @ExperimentalCoroutinesApi // Channel - fun `Reading state updates from channel`() { + fun `Reading state updates from channel`() = runTestOnMain { val owner = MockedLifecycleOwner(Lifecycle.State.INITIALIZED) val store = Store( @@ -183,7 +183,7 @@ class StoreExtensionsKtTest { assertEquals(26, receivedValue) latch = CountDownLatch(1) - runBlocking { job.cancelAndJoin() } + job.cancelAndJoin() assertTrue(channel.isClosedForReceive) store.dispatch(TestAction.IncrementAction).joinBlocking() @@ -207,7 +207,7 @@ class StoreExtensionsKtTest { @Test @Synchronized @ExperimentalCoroutinesApi - fun `Reading state updates from Flow with lifecycle owner`() { + fun `Reading state updates from Flow with lifecycle owner`() = runTestOnMain { val owner = MockedLifecycleOwner(Lifecycle.State.INITIALIZED) val store = Store( @@ -253,7 +253,7 @@ class StoreExtensionsKtTest { assertEquals(26, receivedValue) latch = CountDownLatch(1) - runBlocking { job.cancelAndJoin() } + job.cancelAndJoin() // Receiving nothing anymore since coroutine is cancelled store.dispatch(TestAction.IncrementAction).joinBlocking() @@ -312,7 +312,7 @@ class StoreExtensionsKtTest { @Test @Synchronized @ExperimentalCoroutinesApi - fun `Reading state updates from Flow without lifecycle owner`() { + fun `Reading state updates from Flow without lifecycle owner`() = runTestOnMain { val store = Store( TestState(counter = 23), ::reducer @@ -351,7 +351,7 @@ class StoreExtensionsKtTest { latch = CountDownLatch(1) - runBlocking { job.cancelAndJoin() } + job.cancelAndJoin() // Receiving nothing anymore since coroutine is cancelled store.dispatch(TestAction.IncrementAction).joinBlocking() @@ -512,7 +512,7 @@ class StoreExtensionsKtTest { } @Test - fun `Observer bound to view will not get notified about state changes until the view is attached`() { + fun `Observer bound to view will not get notified about state changes until the view is attached`() = runTestOnMain { val activity = Robolectric.buildActivity(Activity::class.java).create().get() val view = View(testContext) diff --git a/components/service/contile/build.gradle b/components/service/contile/build.gradle index 7c8d3c4af74..22329cd4edf 100644 --- a/components/service/contile/build.gradle +++ b/components/service/contile/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation Dependencies.androidx_work_testing testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito + testImplementation Dependencies.testing_coroutines testImplementation project(':support-test') } diff --git a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesProviderTest.kt b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesProviderTest.kt index 620e574c554..23ab7c62bb0 100644 --- a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesProviderTest.kt +++ b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesProviderTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.contile import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Response import mozilla.components.support.test.any @@ -27,11 +28,12 @@ import java.io.File import java.io.IOException import java.util.Date +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class ContileTopSitesProviderTest { @Test - fun `GIVEN a successful status response WHEN top sites are fetched THEN response should contain top sites`() = runBlocking { + fun `GIVEN a successful status response WHEN top sites are fetched THEN response should contain top sites`() = runTest { val client = prepareClient() val provider = ContileTopSitesProvider(testContext, client) val topSites = provider.getTopSites() @@ -56,7 +58,7 @@ class ContileTopSitesProviderTest { } @Test(expected = IOException::class) - fun `GIVEN a 500 status response WHEN top sites are fetched THEN throw an exception`() = runBlocking { + fun `GIVEN a 500 status response WHEN top sites are fetched THEN throw an exception`() = runTest { val client = prepareClient(status = 500) val provider = ContileTopSitesProvider(testContext, client) provider.getTopSites() @@ -64,7 +66,7 @@ class ContileTopSitesProviderTest { } @Test - fun `GIVEN a cache configuration is allowed and not expired WHEN top sites are fetched THEN read from the disk cache`() = runBlocking { + fun `GIVEN a cache configuration is allowed and not expired WHEN top sites are fetched THEN read from the disk cache`() = runTest { val client = prepareClient() val provider = spy(ContileTopSitesProvider(testContext, client)) @@ -83,7 +85,7 @@ class ContileTopSitesProviderTest { } @Test - fun `GIVEN a cache configuration is allowed WHEN top sites are fetched THEN write response to cache`() = runBlocking { + fun `GIVEN a cache configuration is allowed WHEN top sites are fetched THEN write response to cache`() = runTest { val jsonResponse = loadResourceAsString("/contile/contile.json") val client = prepareClient(jsonResponse) val provider = spy(ContileTopSitesProvider(testContext, client)) @@ -175,7 +177,7 @@ class ContileTopSitesProviderTest { } @Test - fun `GIVEN cache is not expired WHEN top sites are refreshed THEN do nothing`() = runBlocking { + fun `GIVEN cache is not expired WHEN top sites are refreshed THEN do nothing`() = runTest { val provider = spy( ContileTopSitesProvider( testContext, @@ -192,7 +194,7 @@ class ContileTopSitesProviderTest { } @Test - fun `GIVEN cache is expired WHEN top sites are refreshed THEN fetch and write new response to cache`() = runBlocking { + fun `GIVEN cache is expired WHEN top sites are refreshed THEN fetch and write new response to cache`() = runTest { val jsonResponse = loadResourceAsString("/contile/contile.json") val provider = spy( ContileTopSitesProvider( diff --git a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterTest.kt b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterTest.kt index de3fb5c745b..799ad712498 100644 --- a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterTest.kt +++ b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterTest.kt @@ -10,7 +10,8 @@ import androidx.work.WorkInfo import androidx.work.WorkManager import androidx.work.await import androidx.work.testing.WorkManagerTestInitHelper -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.service.contile.ContileTopSitesUpdater.Companion.PERIODIC_WORK_TAG import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext @@ -23,6 +24,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class ContileTopSitesUpdaterTest { @@ -40,7 +42,7 @@ class ContileTopSitesUpdaterTest { } @Test - fun `WHEN periodic work is started THEN work is queued`() = runBlocking { + fun `WHEN periodic work is started THEN work is queued`() = runTest { val updater = ContileTopSitesUpdater(testContext, provider = mock()) val workManager = WorkManager.getInstance(testContext) var workInfo = workManager.getWorkInfosForUniqueWork(PERIODIC_WORK_TAG).await() @@ -62,7 +64,7 @@ class ContileTopSitesUpdaterTest { } @Test - fun `GIVEN periodic work is started WHEN period work is stopped THEN no work is queued`() = runBlocking { + fun `GIVEN periodic work is started WHEN period work is stopped THEN no work is queued`() = runTest { val updater = ContileTopSitesUpdater(testContext, provider = mock()) val workManager = WorkManager.getInstance(testContext) var workInfo = workManager.getWorkInfosForUniqueWork(PERIODIC_WORK_TAG).await() diff --git a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterWorkerTest.kt b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterWorkerTest.kt index 9757ae50ddd..e9cafd31e81 100644 --- a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterWorkerTest.kt +++ b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUpdaterWorkerTest.kt @@ -8,7 +8,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.work.ListenableWorker import androidx.work.await import androidx.work.testing.TestListenableWorkerBuilder -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.whenever @@ -20,6 +21,7 @@ import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.spy import java.io.IOException +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class ContileTopSitesUpdaterWorkerTest { @@ -29,7 +31,7 @@ class ContileTopSitesUpdaterWorkerTest { } @Test - fun `WHEN worker does successful work THEN return a success result`() = runBlocking { + fun `WHEN worker does successful work THEN return a success result`() = runTest { val provider: ContileTopSitesProvider = mock() val worker = spy( TestListenableWorkerBuilder(testContext) @@ -46,7 +48,7 @@ class ContileTopSitesUpdaterWorkerTest { } @Test - fun `WHEN worker does unsuccessful work THEN return a failure result`() = runBlocking { + fun `WHEN worker does unsuccessful work THEN return a failure result`() = runTest { val provider: ContileTopSitesProvider = mock() val worker = spy( TestListenableWorkerBuilder(testContext) diff --git a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUseCasesTest.kt b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUseCasesTest.kt index bab461359e4..306cd50de74 100644 --- a/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUseCasesTest.kt +++ b/components/service/contile/src/test/java/mozilla/components/service/contile/ContileTopSitesUseCasesTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.contile import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.whenever @@ -15,11 +16,12 @@ import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mockito.verify import java.io.IOException +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class ContileTopSitesUseCasesTest { @Test - fun `WHEN refresh contile top site use case is called THEN call the provider to fetch top sites bypassing the cache`() = runBlocking { + fun `WHEN refresh contile top site use case is called THEN call the provider to fetch top sites bypassing the cache`() = runTest { val provider: ContileTopSitesProvider = mock() ContileTopSitesUseCases.initialize(provider) @@ -34,7 +36,7 @@ class ContileTopSitesUseCasesTest { } @Test(expected = IOException::class) - fun `GIVEN the provider fails to fetch contile top sites WHEN refresh contile top site use case is called THEN an exception is thrown`() = runBlocking { + fun `GIVEN the provider fails to fetch contile top sites WHEN refresh contile top site use case is called THEN an exception is thrown`() = runTest { val provider: ContileTopSitesProvider = mock() val throwable = IOException("test") diff --git a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaAccountManagerTest.kt b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaAccountManagerTest.kt index c3f76552230..7a8fd1b286e 100644 --- a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaAccountManagerTest.kt +++ b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaAccountManagerTest.kt @@ -7,9 +7,10 @@ package mozilla.components.service.fxa import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.base.crash.CrashReporting import mozilla.components.concept.sync.AccessTokenInfo import mozilla.components.concept.sync.AccountEventsObserver @@ -112,6 +113,7 @@ internal open class TestableFxaAccountManager( const val EXPECTED_AUTH_STATE = "goodAuthState" const val UNEXPECTED_AUTH_STATE = "badAuthState" +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class FxaAccountManagerTest { @@ -185,7 +187,7 @@ class FxaAccountManagerTest { } @Test - fun `migrating an account via copyAccountAsync - creating a new session token`() = runBlocking { + fun `migrating an account via copyAccountAsync - creating a new session token`() = runTest { // We'll test three scenarios: // - hitting a network issue during migration // - hitting an auth issue during migration (bad credentials) @@ -257,7 +259,7 @@ class FxaAccountManagerTest { } @Test - fun `migrating an account via migrateAccountAsync - reusing existing session token`() = runBlocking { + fun `migrating an account via migrateAccountAsync - reusing existing session token`() = runTest { // We'll test three scenarios: // - hitting a network issue during migration // - hitting an auth issue during migration (bad credentials) @@ -329,7 +331,7 @@ class FxaAccountManagerTest { } @Test - fun `migrating an account via migrateAccountAsync - retry scenario`() = runBlocking { + fun `migrating an account via migrateAccountAsync - retry scenario`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile("testUid", "test@example.com", null, "Test Profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -388,7 +390,7 @@ class FxaAccountManagerTest { } @Test - fun `restored account has an in-flight migration, retries and fails`() = runBlocking { + fun `restored account has an in-flight migration, retries and fails`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile("testUid", "test@example.com", null, "Test Profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -425,7 +427,7 @@ class FxaAccountManagerTest { } @Test - fun `restored account has an in-flight migration, retries and succeeds`() = runBlocking { + fun `restored account has an in-flight migration, retries and succeeds`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile("testUid", "test@example.com", null, "Test Profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -459,7 +461,7 @@ class FxaAccountManagerTest { } @Test - fun `restored account state persistence`() = runBlocking { + fun `restored account state persistence`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile("testUid", "test@example.com", null, "Test Profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -491,7 +493,7 @@ class FxaAccountManagerTest { } @Test - fun `restored account state persistence, finalizeDevice hit an intermittent error`() = runBlocking { + fun `restored account state persistence, finalizeDevice hit an intermittent error`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile("testUid", "test@example.com", null, "Test Profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -526,7 +528,7 @@ class FxaAccountManagerTest { } @Test - fun `restored account state persistence, hit an auth error`() = runBlocking { + fun `restored account state persistence, hit an auth error`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile("testUid", "test@example.com", null, "Test Profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -561,7 +563,7 @@ class FxaAccountManagerTest { } @Test(expected = FxaPanicException::class) - fun `restored account state persistence, hit an fxa panic which is re-thrown`() = runBlocking { + fun `restored account state persistence, hit an fxa panic which is re-thrown`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile("testUid", "test@example.com", null, "Test Profile") val constellation: DeviceConstellation = mock() @@ -594,7 +596,7 @@ class FxaAccountManagerTest { } @Test - fun `newly authenticated account state persistence`() = runBlocking { + fun `newly authenticated account state persistence`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile(uid = "testUID", avatar = null, email = "test@example.com", displayName = "test profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -636,7 +638,7 @@ class FxaAccountManagerTest { } @Test - fun `auth state verification while finishing authentication`() = runBlocking { + fun `auth state verification while finishing authentication`() = runTest { val accountStorage: AccountStorage = mock() val profile = Profile(uid = "testUID", avatar = null, email = "test@example.com", displayName = "test profile") val constellation: DeviceConstellation = mockDeviceConstellation() @@ -793,7 +795,7 @@ class FxaAccountManagerTest { } @Test - fun `error reading persisted account`() = runBlocking { + fun `error reading persisted account`() = runTest { val accountStorage = mock() val readException = FxaNetworkException("pretend we failed to fetch the account") `when`(accountStorage.read()).thenThrow(readException) @@ -827,7 +829,7 @@ class FxaAccountManagerTest { } @Test - fun `no persisted account`() = runBlocking { + fun `no persisted account`() = runTest { val accountStorage = mock() // There's no account at the start. `when`(accountStorage.read()).thenReturn(null) @@ -856,7 +858,7 @@ class FxaAccountManagerTest { } @Test - fun `with persisted account and profile`() = runBlocking { + fun `with persisted account and profile`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -924,7 +926,7 @@ class FxaAccountManagerTest { } @Test - fun `happy authentication and profile flow`() = runBlocking { + fun `happy authentication and profile flow`() = runTest { val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() `when`(mockAccount.deviceConstellation()).thenReturn(constellation) @@ -968,7 +970,7 @@ class FxaAccountManagerTest { } @Test(expected = FxaPanicException::class) - fun `fxa panic during initDevice flow`() = runBlocking { + fun `fxa panic during initDevice flow`() = runTest { val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() `when`(mockAccount.deviceConstellation()).thenReturn(constellation) @@ -995,7 +997,7 @@ class FxaAccountManagerTest { } @Test(expected = FxaPanicException::class) - fun `fxa panic during pairing flow`() = runBlocking { + fun `fxa panic during pairing flow`() = runTest { val mockAccount: OAuthAccount = mock() `when`(mockAccount.deviceConstellation()).thenReturn(mock()) val profile = Profile(uid = "testUID", avatar = null, email = "test@example.com", displayName = "test profile") @@ -1023,7 +1025,7 @@ class FxaAccountManagerTest { } @Test - fun `happy pairing authentication and profile flow`() = runBlocking { + fun `happy pairing authentication and profile flow`() = runTest { val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() `when`(mockAccount.deviceConstellation()).thenReturn(constellation) @@ -1059,7 +1061,7 @@ class FxaAccountManagerTest { } @Test - fun `repeated unfinished authentication attempts succeed`() = runBlocking { + fun `repeated unfinished authentication attempts succeed`() = runTest { val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() `when`(mockAccount.deviceConstellation()).thenReturn(constellation) @@ -1102,7 +1104,7 @@ class FxaAccountManagerTest { } @Test - fun `unhappy authentication flow`() = runBlocking { + fun `unhappy authentication flow`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -1149,7 +1151,7 @@ class FxaAccountManagerTest { } @Test - fun `unhappy pairing authentication flow`() = runBlocking { + fun `unhappy pairing authentication flow`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -1196,7 +1198,7 @@ class FxaAccountManagerTest { } @Test - fun `authentication issues are propagated via AccountObserver`() = runBlocking { + fun `authentication issues are propagated via AccountObserver`() = runTest { val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() `when`(mockAccount.deviceConstellation()).thenReturn(constellation) @@ -1249,7 +1251,7 @@ class FxaAccountManagerTest { } @Test - fun `authentication issues are recoverable via checkAuthorizationState`() = runBlocking { + fun `authentication issues are recoverable via checkAuthorizationState`() = runTest { val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() `when`(mockAccount.deviceConstellation()).thenReturn(constellation) @@ -1295,7 +1297,7 @@ class FxaAccountManagerTest { } @Test - fun `authentication recovery flow has a circuit breaker`() = runBlocking { + fun `authentication recovery flow has a circuit breaker`() = runTest { val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() `when`(mockAccount.deviceConstellation()).thenReturn(constellation) @@ -1384,7 +1386,7 @@ class FxaAccountManagerTest { } @Test - fun `unhappy profile fetching flow`() = runBlocking { + fun `unhappy profile fetching flow`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -1448,7 +1450,7 @@ class FxaAccountManagerTest { } @Test - fun `profile fetching flow hit an unrecoverable auth problem`() = runBlocking { + fun `profile fetching flow hit an unrecoverable auth problem`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -1507,7 +1509,7 @@ class FxaAccountManagerTest { } @Test - fun `profile fetching flow hit an unrecoverable auth problem for which we can't determine a recovery state`() = runBlocking { + fun `profile fetching flow hit an unrecoverable auth problem for which we can't determine a recovery state`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -1566,7 +1568,7 @@ class FxaAccountManagerTest { } @Test - fun `profile fetching flow hit a recoverable auth problem`() = runBlocking { + fun `profile fetching flow hit a recoverable auth problem`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -1645,7 +1647,7 @@ class FxaAccountManagerTest { } @Test(expected = FxaPanicException::class) - fun `profile fetching flow hit an fxa panic, which is re-thrown`() = runBlocking { + fun `profile fetching flow hit an fxa panic, which is re-thrown`() = runTest { val accountStorage = mock() val mockAccount: OAuthAccount = mock() val constellation: DeviceConstellation = mock() @@ -1797,9 +1799,7 @@ class FxaAccountManagerTest { manager.register(accountObserver) - runBlocking(coroutineContext) { - manager.start() - } + manager.start() return manager } diff --git a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaDeviceConstellationTest.kt b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaDeviceConstellationTest.kt index 5e91700fb04..40febb74a3e 100644 --- a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaDeviceConstellationTest.kt +++ b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/FxaDeviceConstellationTest.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.plus -import kotlinx.coroutines.runBlocking import mozilla.appservices.fxaclient.FxaException import mozilla.appservices.fxaclient.IncomingDeviceCommand import mozilla.appservices.fxaclient.SendTabPayload @@ -34,6 +33,7 @@ import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -75,7 +75,7 @@ class FxaDeviceConstellationTest { } @Test - fun `finalize device`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `finalize device`() = runTestOnMain { fun expectedFinalizeAction(authType: AuthType): FxaDeviceConstellation.DeviceFinalizeAction = when (authType) { AuthType.Existing -> FxaDeviceConstellation.DeviceFinalizeAction.EnsureCapabilities AuthType.Signin -> FxaDeviceConstellation.DeviceFinalizeAction.Initialize @@ -117,7 +117,7 @@ class FxaDeviceConstellationTest { @Test @ExperimentalCoroutinesApi - fun `updating device name`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `updating device name`() = runTestOnMain { val currentDevice = testDevice("currentTestDevice", true) `when`(account.getDevices()).thenReturn(arrayOf(currentDevice)) @@ -158,7 +158,7 @@ class FxaDeviceConstellationTest { @Test @ExperimentalCoroutinesApi - fun `set device push subscription`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `set device push subscription`() = runTestOnMain { val subscription = DevicePushSubscription("http://endpoint.com", "pk", "auth key") constellation.setDevicePushSubscription(subscription) @@ -167,7 +167,7 @@ class FxaDeviceConstellationTest { @Test @ExperimentalCoroutinesApi - fun `process raw device command`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `process raw device command`() = runTestOnMain { // No commands, no observer. `when`(account.handlePushMessage("raw events payload")).thenReturn(emptyArray()) assertTrue(constellation.processRawEvent("raw events payload")) @@ -205,7 +205,7 @@ class FxaDeviceConstellationTest { } @Test - fun `send command to device`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `send command to device`() = runTestOnMain { `when`(account.gatherTelemetry()).thenReturn("{}") assertTrue( constellation.sendCommandToDevice( @@ -217,7 +217,7 @@ class FxaDeviceConstellationTest { } @Test - fun `send command to device will report exceptions`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `send command to device will report exceptions`() = runTestOnMain { val exception = FxaException.Other("") val exceptionCaptor = argumentCaptor() doAnswer { throw exception }.`when`(account).sendSingleTab(any(), any(), any()) @@ -232,7 +232,7 @@ class FxaDeviceConstellationTest { } @Test - fun `send command to device won't report network exceptions`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `send command to device won't report network exceptions`() = runTestOnMain { val exception = FxaException.Network("timeout!") doAnswer { throw exception }.`when`(account).sendSingleTab(any(), any(), any()) @@ -247,7 +247,7 @@ class FxaDeviceConstellationTest { @Test @ExperimentalCoroutinesApi - fun `refreshing constellation`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `refreshing constellation`() = runTestOnMain { // No devices, no observers. `when`(account.getDevices()).thenReturn(emptyArray()) @@ -332,7 +332,7 @@ class FxaDeviceConstellationTest { @Test @ExperimentalCoroutinesApi - fun `polling for commands triggers observers`() = runBlocking(coroutinesTestRule.testDispatcher) { + fun `polling for commands triggers observers`() = runTestOnMain { // No commands, no observers. `when`(account.gatherTelemetry()).thenReturn("{}") `when`(account.pollDeviceCommands()).thenReturn(emptyArray()) diff --git a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/UtilsKtTest.kt b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/UtilsKtTest.kt index 9b11d99491c..cdb4f0d585a 100644 --- a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/UtilsKtTest.kt +++ b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/UtilsKtTest.kt @@ -4,7 +4,8 @@ package mozilla.components.service.fxa -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.sync.AuthFlowUrl import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.ServiceResult @@ -25,9 +26,10 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoInteractions +@ExperimentalCoroutinesApi // for runTest class UtilsKtTest { @Test - fun `handleFxaExceptions form 1 returns correct data back`() = runBlocking { + fun `handleFxaExceptions form 1 returns correct data back`() = runTest { assertEquals( 1, handleFxaExceptions( @@ -52,7 +54,7 @@ class UtilsKtTest { } @Test - fun `handleFxaExceptions form 1 does not swallow non-panics`() = runBlocking { + fun `handleFxaExceptions form 1 does not swallow non-panics`() = runTest { val accountManager: FxaAccountManager = mock() GlobalAccountManager.setInstance(accountManager) @@ -113,7 +115,7 @@ class UtilsKtTest { } @Test(expected = IllegalStateException::class) - fun `handleFxaExceptions form 1 re-throws non-fxa exceptions`() = runBlocking { + fun `handleFxaExceptions form 1 re-throws non-fxa exceptions`() = runTest { handleFxaExceptions( mock(), "test op", { @@ -124,7 +126,7 @@ class UtilsKtTest { } @Test(expected = FxaPanicException::class) - fun `handleFxaExceptions form 1 re-throws fxa panic exceptions`() = runBlocking { + fun `handleFxaExceptions form 1 re-throws fxa panic exceptions`() = runTest { handleFxaExceptions( mock(), "test op", { @@ -135,7 +137,7 @@ class UtilsKtTest { } @Test - fun `handleFxaExceptions form 2 works`() = runBlocking { + fun `handleFxaExceptions form 2 works`() = runTest { val accountManager: FxaAccountManager = mock() GlobalAccountManager.setInstance(accountManager) @@ -173,7 +175,7 @@ class UtilsKtTest { } @Test(expected = IllegalStateException::class) - fun `handleFxaExceptions form 2 re-throws non-fxa exceptions`() = runBlocking { + fun `handleFxaExceptions form 2 re-throws non-fxa exceptions`() = runTest { val accountManager: FxaAccountManager = mock() GlobalAccountManager.setInstance(accountManager) @@ -184,7 +186,7 @@ class UtilsKtTest { } @Test(expected = FxaPanicException::class) - fun `handleFxaExceptions form 2 re-throws fxa panic exceptions`() = runBlocking { + fun `handleFxaExceptions form 2 re-throws fxa panic exceptions`() = runTest { val accountManager: FxaAccountManager = mock() GlobalAccountManager.setInstance(accountManager) @@ -196,7 +198,7 @@ class UtilsKtTest { } @Test - fun `handleFxaExceptions form 3 works`() = runBlocking { + fun `handleFxaExceptions form 3 works`() = runTest { val accountManager: FxaAccountManager = mock() GlobalAccountManager.setInstance(accountManager) @@ -238,7 +240,7 @@ class UtilsKtTest { } @Test(expected = IllegalStateException::class) - fun `handleFxaExceptions form 3 re-throws non-fxa exceptions`() = runBlocking { + fun `handleFxaExceptions form 3 re-throws non-fxa exceptions`() = runTest { val accountManager: FxaAccountManager = mock() GlobalAccountManager.setInstance(accountManager) @@ -249,7 +251,7 @@ class UtilsKtTest { } @Test(expected = FxaPanicException::class) - fun `handleFxaExceptions form 3 re-throws fxa panic exceptions`() = runBlocking { + fun `handleFxaExceptions form 3 re-throws fxa panic exceptions`() = runTest { val accountManager: FxaAccountManager = mock() GlobalAccountManager.setInstance(accountManager) @@ -260,7 +262,7 @@ class UtilsKtTest { } @Test - fun `withRetries immediate success`() = runBlocking { + fun `withRetries immediate success`() = runTest { when (val res = withRetries(mock(), 3) { true }) { is Result.Success -> assertTrue(res.value) is Result.Failure -> fail() @@ -277,7 +279,7 @@ class UtilsKtTest { } @Test - fun `withRetries immediate failure`() = runBlocking { + fun `withRetries immediate failure`() = runTest { when (withRetries(mock(), 3) { false }) { is Result.Success -> fail() is Result.Failure -> {} @@ -289,7 +291,7 @@ class UtilsKtTest { } @Test - fun `withRetries eventual success`() = runBlocking { + fun `withRetries eventual success`() = runTest { val eventual = SucceedOn(2, true) when (val res = withRetries(mock(), 5) { eventual.nullFailure() }) { is Result.Success -> { @@ -309,7 +311,7 @@ class UtilsKtTest { } @Test - fun `withRetries eventual failure`() = runBlocking { + fun `withRetries eventual failure`() = runTest { val eventual = SucceedOn(6, true) when (withRetries(mock(), 5) { eventual.nullFailure() }) { is Result.Success -> fail() @@ -327,7 +329,7 @@ class UtilsKtTest { } @Test - fun `withServiceRetries immediate success`() = runBlocking { + fun `withServiceRetries immediate success`() = runTest { when (withServiceRetries(mock(), 3, suspend { ServiceResult.Ok })) { is ServiceResult.Ok -> {} else -> fail() @@ -335,7 +337,7 @@ class UtilsKtTest { } @Test - fun `withServiceRetries generic failure keeps retrying`() = runBlocking { + fun `withServiceRetries generic failure keeps retrying`() = runTest { // keeps retrying on generic error val eventual = SucceedOn(0, ServiceResult.Ok, ServiceResult.OtherError) when (withServiceRetries(mock(), 3) { eventual.reifiedFailure() }) { @@ -347,7 +349,7 @@ class UtilsKtTest { } @Test - fun `withServiceRetries auth failure short circuit`() = runBlocking { + fun `withServiceRetries auth failure short circuit`() = runTest { // keeps retrying on generic error val eventual = SucceedOn(0, ServiceResult.Ok, ServiceResult.AuthError) when (withServiceRetries(mock(), 3) { eventual.reifiedFailure() }) { @@ -359,7 +361,7 @@ class UtilsKtTest { } @Test - fun `withServiceRetries eventual success`() = runBlocking { + fun `withServiceRetries eventual success`() = runTest { val eventual = SucceedOn(3, ServiceResult.Ok, ServiceResult.OtherError) when (withServiceRetries(mock(), 5) { eventual.reifiedFailure() }) { is ServiceResult.Ok -> { @@ -370,7 +372,7 @@ class UtilsKtTest { } @Test - fun `as auth flow pairing`() = runBlocking { + fun `as auth flow pairing`() = runTest { val account: OAuthAccount = mock() val authFlowUrl: AuthFlowUrl = mock() `when`(account.beginPairingFlow(eq("http://pairing.url"), eq(emptySet()), anyString())).thenReturn(authFlowUrl) @@ -379,7 +381,7 @@ class UtilsKtTest { } @Test - fun `as auth flow regular`() = runBlocking { + fun `as auth flow regular`() = runTest { val account: OAuthAccount = mock() val authFlowUrl: AuthFlowUrl = mock() `when`(account.beginOAuthFlow(eq(emptySet()), anyString())).thenReturn(authFlowUrl) diff --git a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/manager/GlobalAccountManagerTest.kt b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/manager/GlobalAccountManagerTest.kt index 28fe72f8321..7d9b64fc0c4 100644 --- a/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/manager/GlobalAccountManagerTest.kt +++ b/components/service/firefox-accounts/src/test/java/mozilla/components/service/fxa/manager/GlobalAccountManagerTest.kt @@ -4,14 +4,16 @@ package mozilla.components.service.fxa.manager -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.support.test.mock import org.junit.Test import org.mockito.Mockito class GlobalAccountManagerTest { + @ExperimentalCoroutinesApi @Test - fun `GlobalAccountManager authError processing`() = runBlocking { + fun `GlobalAccountManager authError processing`() = runTest { val manager: FxaAccountManager = mock() GlobalAccountManager.setInstance(manager) diff --git a/components/service/location/build.gradle b/components/service/location/build.gradle index 442f25d19f5..2b27a4f40e0 100644 --- a/components/service/location/build.gradle +++ b/components/service/location/build.gradle @@ -35,6 +35,7 @@ dependencies { testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver + testImplementation Dependencies.testing_coroutines testImplementation project(':lib-fetch-httpurlconnection') } diff --git a/components/service/location/src/test/java/mozilla/components/service/location/LocationServiceTest.kt b/components/service/location/src/test/java/mozilla/components/service/location/LocationServiceTest.kt index 4477196ffd9..eca31d268f9 100644 --- a/components/service/location/src/test/java/mozilla/components/service/location/LocationServiceTest.kt +++ b/components/service/location/src/test/java/mozilla/components/service/location/LocationServiceTest.kt @@ -5,13 +5,16 @@ package mozilla.components.service.location import junit.framework.TestCase.assertNull -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Test class LocationServiceTest { + @ExperimentalCoroutinesApi @Test fun `dummy implementation returns null`() { - runBlocking { + runTest(UnconfinedTestDispatcher()) { assertNull(LocationService.dummy().fetchRegion(false)) assertNull(LocationService.dummy().fetchRegion(true)) assertNull(LocationService.dummy().fetchRegion(false)) diff --git a/components/service/location/src/test/java/mozilla/components/service/location/MozillaLocationServiceTest.kt b/components/service/location/src/test/java/mozilla/components/service/location/MozillaLocationServiceTest.kt index 05c555e0336..d95c64c7c24 100644 --- a/components/service/location/src/test/java/mozilla/components/service/location/MozillaLocationServiceTest.kt +++ b/components/service/location/src/test/java/mozilla/components/service/location/MozillaLocationServiceTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.location import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.MutableHeaders import mozilla.components.concept.fetch.Request @@ -30,6 +31,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import java.io.IOException +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class MozillaLocationServiceTest { @Before @@ -39,7 +41,7 @@ class MozillaLocationServiceTest { } @Test - fun `WHEN calling fetchRegion AND the service returns a region THEN a Region object is returned`() { + fun `WHEN calling fetchRegion AND the service returns a region THEN a Region object is returned`() = runTest { val server = MockWebServer() server.enqueue(MockResponse().setBody("{\"country_name\": \"Germany\", \"country_code\": \"DE\"}")) @@ -53,7 +55,7 @@ class MozillaLocationServiceTest { serviceUrl = server.url("/").toString() ) - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNotNull(region!!) @@ -69,18 +71,18 @@ class MozillaLocationServiceTest { } @Test - fun `WHEN client throws IOException THEN the returned region is null`() { + fun `WHEN client throws IOException THEN the returned region is null`() = runTest { val client: Client = mock() doThrow(IOException()).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNull(region) } @Test - fun `WHEN fetching region THEN request is sent to the location service`() { + fun `WHEN fetching region THEN request is sent to the location service`() = runTest { val client: Client = mock() val response = Response( url = "http://example.org", @@ -91,7 +93,7 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNotNull(region!!) @@ -106,7 +108,7 @@ class MozillaLocationServiceTest { } @Test - fun `WHEN fetching region AND service returns 404 THEN region is null`() { + fun `WHEN fetching region AND service returns 404 THEN region is null`() = runTest { val client: Client = mock() val response = Response( url = "http://example.org", @@ -117,13 +119,13 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNull(region) } @Test - fun `WHEN fetching region AND service returns 500 THEN region is null`() { + fun `WHEN fetching region AND service returns 500 THEN region is null`() = runTest { val client: Client = mock() val response = Response( url = "http://example.org", @@ -134,13 +136,13 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNull(region) } @Test - fun `WHEN fetching region AND service returns broken JSON THEN region is null`() { + fun `WHEN fetching region AND service returns broken JSON THEN region is null`() = runTest { val client: Client = mock() val response = Response( url = "http://example.org", @@ -151,13 +153,13 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNull(region) } @Test - fun `WHEN fetching region AND service returns empty JSON object THEN region is null`() { + fun `WHEN fetching region AND service returns empty JSON object THEN region is null`() = runTest { val client: Client = mock() val response = Response( url = "http://example.org", @@ -168,13 +170,13 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNull(region) } @Test - fun `WHEN fetching region AND service returns incomplete JSON THEN region is null`() { + fun `WHEN fetching region AND service returns incomplete JSON THEN region is null`() = runTest { val client: Client = mock() val response = Response( url = "http://example.org", @@ -185,13 +187,13 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNull(region) } @Test - fun `WHEN fetching region for the second time THEN region is read from cache`() { + fun `WHEN fetching region for the second time THEN region is read from cache`() = runTest { run { val client: Client = mock() val response = Response( @@ -203,7 +205,7 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNotNull(region!!) @@ -217,7 +219,7 @@ class MozillaLocationServiceTest { val client: Client = mock() val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNotNull(region!!) @@ -229,7 +231,7 @@ class MozillaLocationServiceTest { } @Test - fun `WHEN fetching region for the second time and setting readFromCache = false THEN request is sent again`() { + fun `WHEN fetching region for the second time and setting readFromCache = false THEN request is sent again`() = runTest { run { val client: Client = mock() val response = Response( @@ -241,7 +243,7 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion() } + val region = service.fetchRegion() assertNotNull(region!!) @@ -262,7 +264,7 @@ class MozillaLocationServiceTest { doReturn(response).`when`(client).fetch(any()) val service = MozillaLocationService(testContext, client, apiKey = "test") - val region = runBlocking { service.fetchRegion(readFromCache = false) } + val region = service.fetchRegion(readFromCache = false) assertNotNull(region!!) diff --git a/components/service/pocket/src/test/java/mozilla/components/service/pocket/PocketStoriesServiceTest.kt b/components/service/pocket/src/test/java/mozilla/components/service/pocket/PocketStoriesServiceTest.kt index 0ae775d39de..b3980f45f97 100644 --- a/components/service/pocket/src/test/java/mozilla/components/service/pocket/PocketStoriesServiceTest.kt +++ b/components/service/pocket/src/test/java/mozilla/components/service/pocket/PocketStoriesServiceTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.pocket import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.service.pocket.helpers.assertConstructorsVisibility import mozilla.components.support.test.any import mozilla.components.support.test.mock @@ -43,8 +44,9 @@ class PocketStoriesServiceTest { verify(service.scheduler).stopPeriodicRefreshes(any()) } + @ExperimentalCoroutinesApi @Test - fun `GIVEN PocketStoriesService WHEN getStories THEN getStoriesUsecase should return`() = runBlocking { + fun `GIVEN PocketStoriesService WHEN getStories THEN getStoriesUsecase should return`() = runTest { val stories = listOf(mock()) whenever(service.getStoriesUsecase.invoke()).thenReturn(stories) diff --git a/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketRecommendationsRepositoryTest.kt b/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketRecommendationsRepositoryTest.kt index 3a080b17114..ae505c443d5 100644 --- a/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketRecommendationsRepositoryTest.kt +++ b/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketRecommendationsRepositoryTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.pocket.stories import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.service.pocket.helpers.PocketTestResources import mozilla.components.service.pocket.stories.db.PocketRecommendationsDao import mozilla.components.service.pocket.stories.ext.toPartialTimeShownUpdate @@ -21,6 +22,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class PocketRecommendationsRepositoryTest { @@ -35,7 +37,7 @@ class PocketRecommendationsRepositoryTest { @Test fun `GIVEN PocketRecommendationsRepository WHEN getPocketRecommendedStories is called THEN return db entities mapped to domain type`() { - runBlocking { + runTest { val dbStory = PocketTestResources.dbExpectedPocketStory `when`(dao.getPocketStories()).thenReturn(listOf(dbStory)) @@ -49,7 +51,7 @@ class PocketRecommendationsRepositoryTest { @Test fun `GIVEN PocketRecommendationsRepository WHEN addAllPocketApiStories is called THEN persist the received story to db`() { - runBlocking { + runTest { val apiStories = PocketTestResources.apiExpectedPocketStoriesRecommendations val apiStoriesMappedForDb = apiStories.map { it.toPocketLocalStory() } @@ -61,7 +63,7 @@ class PocketRecommendationsRepositoryTest { @Test fun `GIVEN PocketRecommendationsRepository WHEN updateShownPocketRecommendedStories should persist the received story to db`() { - runBlocking { + runTest { val clientStories = listOf(PocketTestResources.clientExpectedPocketStory) val clientStoriesPartialUpdate = clientStories.map { it.toPartialTimeShownUpdate() } diff --git a/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketStoriesUseCasesTest.kt b/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketStoriesUseCasesTest.kt index aa6bf16089b..38ef1ae1b3a 100644 --- a/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketStoriesUseCasesTest.kt +++ b/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/PocketStoriesUseCasesTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.pocket.stories import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.fetch.Client import mozilla.components.service.pocket.PocketRecommendedStory import mozilla.components.service.pocket.api.PocketEndpoint @@ -31,6 +32,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import kotlin.reflect.KVisibility +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class PocketStoriesUseCasesTest { private val usecases = spy(PocketStoriesUseCases()) @@ -95,7 +97,7 @@ class PocketStoriesUseCasesTest { } @Test - fun `GIVEN PocketStoriesUseCases WHEN RefreshPocketStories is called THEN download stories from API and return early if unsuccessful response`() { + fun `GIVEN PocketStoriesUseCases WHEN RefreshPocketStories is called THEN download stories from API and return early if unsuccessful response`() = runTest { PocketStoriesUseCases.initialize(mock()) val refreshUsecase = spy( usecases.RefreshPocketStories(testContext) @@ -103,39 +105,31 @@ class PocketStoriesUseCasesTest { val successfulResponse = getSuccessfulPocketStories() doReturn(successfulResponse).`when`(pocketEndoint).getRecommendedStories() - val result = runBlocking { - refreshUsecase.invoke() - } + val result = refreshUsecase.invoke() assertTrue(result) verify(pocketEndoint).getRecommendedStories() - runBlocking { - verify(pocketRepo).addAllPocketApiStories((successfulResponse as PocketResponse.Success).data) - } + verify(pocketRepo).addAllPocketApiStories((successfulResponse as PocketResponse.Success).data) } @Test - fun `GIVEN PocketStoriesUseCases WHEN RefreshPocketStories is called THEN download stories from API and save a successful response locally`() { + fun `GIVEN PocketStoriesUseCases WHEN RefreshPocketStories is called THEN download stories from API and save a successful response locally`() = runTest { PocketStoriesUseCases.initialize(mock()) val refreshUsecase = spy(usecases.RefreshPocketStories(testContext)) val successfulResponse = getFailedPocketStories() doReturn(successfulResponse).`when`(pocketEndoint).getRecommendedStories() - val result = runBlocking { - refreshUsecase.invoke() - } + val result = refreshUsecase.invoke() assertFalse(result) verify(pocketEndoint).getRecommendedStories() - runBlocking { - verify(pocketRepo, never()).addAllPocketApiStories(any()) - } + verify(pocketRepo, never()).addAllPocketApiStories(any()) } @Test fun `GIVEN PocketStoriesUseCases WHEN GetPocketStories is called THEN delegate the repository to return locally stored stories`() = - runBlocking { + runTest { val getStoriesUsecase = spy(usecases.GetPocketStories(testContext)) doReturn(emptyList()).`when`(pocketRepo) @@ -153,7 +147,7 @@ class PocketStoriesUseCasesTest { } @Test - fun `GIVEN PocketStoriesUseCases WHEN UpdateStoriesTimesShown is called THEN delegate the repository to update the stories shown`() = runBlocking { + fun `GIVEN PocketStoriesUseCases WHEN UpdateStoriesTimesShown is called THEN delegate the repository to update the stories shown`() = runTest { val updateStoriesTimesShown = usecases.UpdateStoriesTimesShown(testContext) val updatedStories: List = mock() diff --git a/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/db/PocketRecommendationsDaoTest.kt b/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/db/PocketRecommendationsDaoTest.kt index 0ad7a8d1a0d..6a9995a24ac 100644 --- a/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/db/PocketRecommendationsDaoTest.kt +++ b/components/service/pocket/src/test/java/mozilla/components/service/pocket/stories/db/PocketRecommendationsDaoTest.kt @@ -10,8 +10,7 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.service.pocket.helpers.PocketTestResources import org.junit.After import org.junit.Assert.assertEquals @@ -52,7 +51,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN an empty table WHEN a story is inserted and then queried THEN return the same story`() = runBlockingTest { + fun `GIVEN an empty table WHEN a story is inserted and then queried THEN return the same story`() = runTest { val story = PocketTestResources.dbExpectedPocketStory dao.insertPocketStories(listOf(story)) @@ -62,7 +61,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN a story already persisted WHEN another story with identical url is tried to be inserted THEN add that to the table`() = runBlockingTest { + fun `GIVEN a story already persisted WHEN another story with identical url is tried to be inserted THEN add that to the table`() = runTest { val story = PocketTestResources.dbExpectedPocketStory val newStory = story.copy( url = "updated" + story.url @@ -76,7 +75,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN a story with the same url exists WHEN another story with updated title is tried to be inserted THEN don't update the table`() = runBlockingTest { + fun `GIVEN a story with the same url exists WHEN another story with updated title is tried to be inserted THEN don't update the table`() = runTest { val story = PocketTestResources.dbExpectedPocketStory val updatedStory = story.copy( title = "updated" + story.title @@ -91,7 +90,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN a story with the same url exists WHEN another story with updated imageUrl is tried to be inserted THEN don't update the table`() = runBlockingTest { + fun `GIVEN a story with the same url exists WHEN another story with updated imageUrl is tried to be inserted THEN don't update the table`() = runTest { val story = PocketTestResources.dbExpectedPocketStory val updatedStory = story.copy( imageUrl = "updated" + story.imageUrl @@ -105,7 +104,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN a story with the same url exists WHEN another story with updated publisher is tried to be inserted THEN don't update the table`() = runBlockingTest { + fun `GIVEN a story with the same url exists WHEN another story with updated publisher is tried to be inserted THEN don't update the table`() = runTest { val story = PocketTestResources.dbExpectedPocketStory val updatedStory = story.copy( publisher = "updated" + story.publisher @@ -119,7 +118,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN a story with the same url exists WHEN another story with updated category is tried to be inserted THEN don't update the table`() = runBlockingTest { + fun `GIVEN a story with the same url exists WHEN another story with updated category is tried to be inserted THEN don't update the table`() = runTest { val story = PocketTestResources.dbExpectedPocketStory val updatedStory = story.copy( category = "updated" + story.category @@ -133,7 +132,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN a story with the same url exists WHEN another story with updated timeToRead is tried to be inserted THEN don't update the table`() = runBlockingTest { + fun `GIVEN a story with the same url exists WHEN another story with updated timeToRead is tried to be inserted THEN don't update the table`() = runTest { val story = PocketTestResources.dbExpectedPocketStory val updatedStory = story.copy( timesShown = story.timesShown * 2 @@ -147,7 +146,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN a story with the same url exists WHEN another story with updated timesShown is tried to be inserted THEN don't update the table`() = runBlockingTest { + fun `GIVEN a story with the same url exists WHEN another story with updated timesShown is tried to be inserted THEN don't update the table`() = runTest { val story = PocketTestResources.dbExpectedPocketStory val updatedStory = story.copy( timesShown = story.timesShown * 2 @@ -161,7 +160,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to delete some THEN remove them from the table`() = runBlockingTest { + fun `GIVEN stories already persisted WHEN asked to delete some THEN remove them from the table`() = runTest { val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") val story3 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "3") @@ -175,7 +174,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to delete one not present in the table THEN don't update the table`() = runBlockingTest { + fun `GIVEN stories already persisted WHEN asked to delete one not present in the table THEN don't update the table`() = runTest { val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") val story3 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "3") @@ -189,7 +188,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to update timesShown for one THEN update only that story`() = runBlockingTest { + fun `GIVEN stories already persisted WHEN asked to update timesShown for one THEN update only that story`() = runTest { val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy( url = story1.url + "2", @@ -222,7 +221,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to update timesShown for one not present in the table THEN don't update the table`() = runBlockingTest { + fun `GIVEN stories already persisted WHEN asked to update timesShown for one not present in the table THEN don't update the table`() = runTest { val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy( url = story1.url + "2", @@ -246,7 +245,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN remove from table all stories not found in the new list`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN remove from table all stories not found in the new list`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -261,7 +260,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN update stories with new urls`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN update stories with new urls`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -278,7 +277,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN update stories with new image urls`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN update stories with new image urls`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -294,7 +293,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only title changed`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only title changed`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -310,7 +309,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only publisher changed`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only publisher changed`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -326,7 +325,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only category changed`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only category changed`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -342,7 +341,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only timeToRead changed`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only timeToRead changed`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -358,7 +357,7 @@ class PocketRecommendationsDaoTest { } @Test - fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only timesShown changed`() = runBlocking { + fun `GIVEN stories already persisted WHEN asked to clean and insert new ones THEN don't update story if only timesShown changed`() = runTest { setupDatabseForTransactions() val story1 = PocketTestResources.dbExpectedPocketStory val story2 = PocketTestResources.dbExpectedPocketStory.copy(url = story1.url + "2") @@ -375,7 +374,7 @@ class PocketRecommendationsDaoTest { /** * Sets an executor to be used for database transactions. - * Needs to be used along with "runBlockingTest" to ensure waiting for transactions to finish but not hang tests. + * Needs to be used along with "runTest" to ensure waiting for transactions to finish but not hang tests. */ private fun setupDatabseForTransactions() { database = Room diff --git a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCreditCardsAddressesStorageTest.kt b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCreditCardsAddressesStorageTest.kt index 310d9297a4d..39eaaa3d170 100644 --- a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCreditCardsAddressesStorageTest.kt +++ b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCreditCardsAddressesStorageTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.sync.autofill import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.storage.CreditCard import mozilla.components.concept.storage.CreditCardNumber import mozilla.components.concept.storage.NewCreditCardFields @@ -24,25 +25,27 @@ import org.junit.runner.RunWith import org.mockito.Mockito.spy import org.mockito.Mockito.verify +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class AutofillCreditCardsAddressesStorageTest { + private lateinit var storage: AutofillCreditCardsAddressesStorage private lateinit var securePrefs: SecureAbove22Preferences @Before - fun setup() = runBlocking { + fun setup() { // forceInsecure is set in the tests because a keystore wouldn't be configured in the test environment. securePrefs = SecureAbove22Preferences(testContext, "autofill", forceInsecure = true) storage = AutofillCreditCardsAddressesStorage(testContext, lazy { securePrefs }) } @After - fun cleanup() = runBlocking { + fun cleanup() { storage.close() } @Test - fun `add credit card`() = runBlocking { + fun `add credit card`() = runTest { val plaintextNumber = CreditCardNumber.Plaintext("4111111111111111") val creditCardFields = NewCreditCardFields( billingName = "Jon Doe", @@ -72,7 +75,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `get credit card`() = runBlocking { + fun `get credit card`() = runTest { val plaintextNumber = CreditCardNumber.Plaintext("5500000000000004") val creditCardFields = NewCreditCardFields( billingName = "Jon Doe", @@ -88,12 +91,12 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `GIVEN a non-existent credit card guid WHEN getCreditCard is called THEN null is returned`() = runBlocking { + fun `GIVEN a non-existent credit card guid WHEN getCreditCard is called THEN null is returned`() = runTest { assertNull(storage.getCreditCard("guid")) } @Test - fun `get all credit cards`() = runBlocking { + fun `get all credit cards`() = runTest { val plaintextNumber1 = CreditCardNumber.Plaintext("5500000000000004") val creditCardFields1 = NewCreditCardFields( billingName = "Jane Fields", @@ -141,7 +144,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `update credit card`() = runBlocking { + fun `update credit card`() = runTest { val creditCardFields = NewCreditCardFields( billingName = "Jon Doe", plaintextCardNumber = CreditCardNumber.Plaintext("4111111111111111"), @@ -199,7 +202,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `delete credit card`() = runBlocking { + fun `delete credit card`() = runTest { val creditCardFields = NewCreditCardFields( billingName = "Jon Doe", plaintextCardNumber = CreditCardNumber.Plaintext("30000000000004"), @@ -219,7 +222,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `add address`() = runBlocking { + fun `add address`() = runTest { val addressFields = UpdatableAddressFields( givenName = "John", additionalName = "", @@ -253,7 +256,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `get address`() = runBlocking { + fun `get address`() = runTest { val addressFields = UpdatableAddressFields( givenName = "John", additionalName = "", @@ -274,12 +277,12 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `GIVEN a non-existent address guid WHEN getAddress is called THEN null is returned`() = runBlocking { + fun `GIVEN a non-existent address guid WHEN getAddress is called THEN null is returned`() = runTest { assertNull(storage.getAddress("guid")) } @Test - fun `get all addresses`() = runBlocking { + fun `get all addresses`() = runTest { val addressFields1 = UpdatableAddressFields( givenName = "John", additionalName = "", @@ -337,7 +340,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `update address`() = runBlocking { + fun `update address`() = runTest { val addressFields = UpdatableAddressFields( givenName = "John", additionalName = "", @@ -389,7 +392,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `delete address`() = runBlocking { + fun `delete address`() = runTest { val addressFields = UpdatableAddressFields( givenName = "John", additionalName = "", @@ -415,7 +418,7 @@ class AutofillCreditCardsAddressesStorageTest { } @Test - fun `WHEN warmUp method is called THEN the database connection is established`(): Unit = runBlocking { + fun `WHEN warmUp method is called THEN the database connection is established`(): Unit = runTest { val storageSpy = spy(storage) storageSpy.warmUp() diff --git a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCryptoTest.kt b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCryptoTest.kt index be7e8ab92e3..64bb0e139c3 100644 --- a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCryptoTest.kt +++ b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/AutofillCryptoTest.kt @@ -5,7 +5,8 @@ package mozilla.components.service.sync.autofill import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.concept.storage.CreditCardNumber import mozilla.components.concept.storage.KeyGenerationReason import mozilla.components.concept.storage.ManagedKey @@ -21,8 +22,10 @@ import org.junit.runner.RunWith import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoInteractions +@ExperimentalCoroutinesApi // for runTest @RunWith(AndroidJUnit4::class) class AutofillCryptoTest { + private lateinit var securePrefs: SecureAbove22Preferences @Before @@ -32,7 +35,7 @@ class AutofillCryptoTest { } @Test - fun `get key - new`() = runBlocking { + fun `get key - new`() = runTest { val storage = mock() val crypto = AutofillCrypto(testContext, securePrefs, storage) val key = crypto.getOrGenerateKey() @@ -47,7 +50,7 @@ class AutofillCryptoTest { } @Test - fun `get key - lost`() = runBlocking { + fun `get key - lost`() = runTest { val storage = mock() val crypto = AutofillCrypto(testContext, securePrefs, storage) val key = crypto.getOrGenerateKey() @@ -63,7 +66,7 @@ class AutofillCryptoTest { } @Test - fun `get key - corrupted`() = runBlocking { + fun `get key - corrupted`() = runTest { val storage = mock() val crypto = AutofillCrypto(testContext, securePrefs, storage) val key = crypto.getOrGenerateKey() @@ -80,7 +83,7 @@ class AutofillCryptoTest { } @Test - fun `get key - corrupted subtly`() = runBlocking { + fun `get key - corrupted subtly`() = runTest { val storage = mock() val crypto = AutofillCrypto(testContext, securePrefs, storage) val key = crypto.getOrGenerateKey() @@ -98,7 +101,7 @@ class AutofillCryptoTest { } @Test - fun `encrypt and decrypt card - normal`() = runBlocking { + fun `encrypt and decrypt card - normal`() = runTest { val crypto = AutofillCrypto(testContext, securePrefs, mock()) val key = crypto.getOrGenerateKey() val plaintext1 = CreditCardNumber.Plaintext("4111111111111111") @@ -115,7 +118,7 @@ class AutofillCryptoTest { } @Test - fun `encrypt and decrypt card - bad keys`() = runBlocking { + fun `encrypt and decrypt card - bad keys`() = runTest { val crypto = AutofillCrypto(testContext, securePrefs, mock()) val plaintext = CreditCardNumber.Plaintext("4111111111111111") diff --git a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt index 3b812988173..2cdaa9b98e9 100644 --- a/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt +++ b/components/service/sync-autofill/src/test/java/mozilla/components/service/sync/autofill/GeckoCreditCardsAddressesStorageDelegateTest.kt @@ -7,9 +7,6 @@ package mozilla.components.service.sync.autofill import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher import mozilla.components.concept.storage.CreditCard import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.concept.storage.CreditCardNumber @@ -19,8 +16,11 @@ import mozilla.components.lib.dataprotect.SecureAbove22Preferences import mozilla.components.support.ktx.kotlin.last4Digits import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn @@ -31,18 +31,20 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class GeckoCreditCardsAddressesStorageDelegateTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val scope = coroutinesTestRule.scope + private lateinit var storage: AutofillCreditCardsAddressesStorage private lateinit var securePrefs: SecureAbove22Preferences private lateinit var delegate: GeckoCreditCardsAddressesStorageDelegate - private lateinit var scope: TestScope init { testContext.getDatabasePath(AUTOFILL_DB_NAME)!!.parentFile!!.mkdirs() } @Before - fun before() = runBlocking { - scope = TestScope(UnconfinedTestDispatcher()) + fun before() { // forceInsecure is set in the tests because a keystore wouldn't be configured in the test environment. securePrefs = SecureAbove22Preferences(testContext, "autofill", forceInsecure = true) storage = AutofillCreditCardsAddressesStorage(testContext, lazy { securePrefs }) @@ -51,7 +53,7 @@ class GeckoCreditCardsAddressesStorageDelegateTest { @Test fun `GIVEN a newly added credit card WHEN decrypt is called THEN it returns the plain credit card number`() = - runBlocking { + runTestOnMain { val plaintextNumber = CreditCardNumber.Plaintext("4111111111111111") val creditCardFields = NewCreditCardFields( billingName = "Jon Doe", diff --git a/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/UtilsKtTest.kt b/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/UtilsKtTest.kt index 792c11a6af5..6c987b9a67f 100644 --- a/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/UtilsKtTest.kt +++ b/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/UtilsKtTest.kt @@ -5,7 +5,8 @@ package mozilla.components.support.ktx.kotlinx.coroutines import kotlinx.coroutines.delay -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Test @@ -13,7 +14,7 @@ import org.junit.Test class UtilsKtTest { @Test - fun throttle() = runBlockingTest { + fun throttle() = runTest(UnconfinedTestDispatcher()) { val skipTime = 300L var value = 0 val throttleBlock = throttleLatest(skipTime, coroutineScope = this) { diff --git a/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/flow/FlowKtTest.kt b/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/flow/FlowKtTest.kt index 16e2276ba14..e8a4723f7e0 100644 --- a/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/flow/FlowKtTest.kt +++ b/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlinx/coroutines/flow/FlowKtTest.kt @@ -6,7 +6,7 @@ package mozilla.components.support.ktx.kotlinx.coroutines.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Test @@ -17,10 +17,10 @@ class FlowKtTest { data class IntState(val value: Int) @Test - fun `ifChanged operator`() { + fun `ifChanged operator`() = runTest { val originalFlow = flowOf("A", "B", "B", "C", "A", "A", "A", "D", "A") - val items = runBlocking { originalFlow.ifChanged().toList() } + val items = originalFlow.ifChanged().toList() assertEquals( listOf("A", "B", "C", "A", "D", "A"), @@ -29,12 +29,10 @@ class FlowKtTest { } @Test - fun `ifChanged operator with block`() { + fun `ifChanged operator with block`() = runTest { val originalFlow = flowOf("banana", "bus", "apple", "big", "coconut", "circle", "home") - val items = runBlocking { - originalFlow.ifChanged { item -> item[0] }.toList() - } + val items = originalFlow.ifChanged { item -> item[0] }.toList() assertEquals( listOf("banana", "apple", "big", "coconut", "home"), @@ -43,7 +41,7 @@ class FlowKtTest { } @Test - fun `ifChanged operator uses structural equality`() { + fun `ifChanged operator uses structural equality`() = runTest { val originalFlow = flowOf( StringState("A"), StringState("B"), @@ -56,7 +54,7 @@ class FlowKtTest { StringState("A") ) - val items = runBlocking { originalFlow.ifChanged().toList() } + val items = originalFlow.ifChanged().toList() assertEquals( listOf( @@ -72,12 +70,10 @@ class FlowKtTest { } @Test - fun `ifAnyChanged operator with block`() { + fun `ifAnyChanged operator with block`() = runTest { val originalFlow = flowOf("banana", "bandanna", "bus", "apple", "big", "coconut", "circle", "home") - val items = runBlocking { - originalFlow.ifAnyChanged { item -> arrayOf(item[0], item[1]) }.toList() - } + val items = originalFlow.ifAnyChanged { item -> arrayOf(item[0], item[1]) }.toList() assertEquals( listOf("banana", "bus", "apple", "big", "coconut", "circle", "home"), @@ -86,15 +82,14 @@ class FlowKtTest { } @Test - fun `ifAnyChanged operator uses structural equality`() { + fun `ifAnyChanged operator uses structural equality`() = runTest { val originalFlow = flowOf("banana", "bandanna", "bus", "apple", "big", "coconut", "circle", "home") - val items = runBlocking { + val items = originalFlow.ifAnyChanged { item -> arrayOf(CharState(item[0]), CharState(item[1])) }.toList() - } assertEquals( listOf("banana", "bus", "apple", "big", "coconut", "circle", "home"), @@ -103,25 +98,25 @@ class FlowKtTest { } @Test - fun `filterChanged operator`() { + fun `filterChanged operator`() = runTest { val intFlow = flowOf(listOf(0), listOf(0, 1), listOf(0, 1, 2, 3), listOf(4), listOf(5, 6, 7, 8)) - val identityItems = runBlocking { intFlow.filterChanged { item -> item }.toList() } + val identityItems = intFlow.filterChanged { item -> item }.toList() assertEquals(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8), identityItems) val moduloFlow = flowOf(listOf(1), listOf(1, 2), listOf(3, 4, 5), listOf(3, 4)) - val moduloItems = runBlocking { moduloFlow.filterChanged { item -> item % 2 }.toList() } + val moduloItems = moduloFlow.filterChanged { item -> item % 2 }.toList() assertEquals(listOf(1, 2, 3, 4, 5), moduloItems) // Here we simulate a non-pure transform function (a function with a side-effect), causing // the transformed values to be different for the same input. var counter = 0 val sideEffectFlow = flowOf(listOf(0), listOf(0, 1), listOf(0, 1, 2, 3), listOf(4), listOf(5, 6, 7, 8)) - val sideEffectItems = runBlocking { sideEffectFlow.filterChanged { item -> item + counter++ }.toList() } + val sideEffectItems = sideEffectFlow.filterChanged { item -> item + counter++ }.toList() assertEquals(listOf(0, 0, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8), sideEffectItems) } @Test - fun `filterChanged operator check for structural equality`() { + fun `filterChanged operator check for structural equality`() = runTest { val intFlow = flowOf( listOf(IntState(0)), listOf(IntState(0), IntState(1)), @@ -130,7 +125,7 @@ class FlowKtTest { listOf(IntState(5), IntState(6), IntState(7), IntState(8)) ) - val identityItems = runBlocking { intFlow.filterChanged { item -> item }.toList() } + val identityItems = intFlow.filterChanged { item -> item }.toList() assertEquals( listOf( IntState(0), diff --git a/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt b/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt index f2c1c361d32..5b9e9a5840a 100644 --- a/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt +++ b/components/support/locale/src/test/java/mozilla/components/support/locale/LocaleMiddlewareTest.kt @@ -6,7 +6,7 @@ package mozilla.components.support.locale import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.LocaleAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore @@ -41,7 +41,7 @@ class LocaleMiddlewareTest { @Test @Ignore("Failing intermittently. To be fixed for https://github.com/mozilla-mobile/android-components/issues/9954") @Config(qualifiers = "en-rUS") - fun `GIVEN a locale has been chosen in the app WHEN we restore state THEN locale is retrieved from storage`() = runBlockingTest { + fun `GIVEN a locale has been chosen in the app WHEN we restore state THEN locale is retrieved from storage`() = runTest { val localeManager = spy(LocaleManager) val currentLocale = localeManager.getCurrentLocale(testContext) assertNull(currentLocale) @@ -70,7 +70,7 @@ class LocaleMiddlewareTest { @Test @Config(qualifiers = "en-rUS") - fun `WHEN we update the locale THEN the locale manager is updated`() = runBlockingTest { + fun `WHEN we update the locale THEN the locale manager is updated`() = runTest { val localeManager = spy(LocaleManager) val currentLocale = localeManager.getCurrentLocale(testContext) assertNull(currentLocale) diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt index 0f72302bf40..efb07e184a7 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/AddonMigrationTest.kt @@ -5,7 +5,7 @@ package mozilla.components.support.migration import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.webextension.DisabledFlags import mozilla.components.concept.engine.webextension.Metadata @@ -36,7 +36,7 @@ class AddonMigrationTest { val coroutinesTestRule = MainCoroutineRule() @Test - fun `No addons installed`() = runBlocking { + fun `No addons installed`() = runTest { val engine: Engine = mock() val callbackCaptor = argumentCaptor<((List) -> Unit)>() whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer { @@ -51,7 +51,7 @@ class AddonMigrationTest { } @Test - fun `All addons migrated successfully`() = runBlocking { + fun `All addons migrated successfully`() = runTest { val addon1: WebExtension = mock() whenever(addon1.id).thenReturn("addon1") whenever(addon1.isBuiltIn()).thenReturn(false) @@ -92,7 +92,7 @@ class AddonMigrationTest { } @Test - fun `Failure when querying installed addons`() = runBlocking { + fun `Failure when querying installed addons`() = runTest { val engine: Engine = mock() val errorCallback = argumentCaptor<((Throwable) -> Unit)>() whenever(engine.listInstalledWebExtensions(any(), errorCallback.capture())).thenAnswer { @@ -113,7 +113,7 @@ class AddonMigrationTest { } @Test - fun `Failure when migrating some addons`() = runBlocking { + fun `Failure when migrating some addons`() = runTest { val addon1: WebExtension = mock() val addon2: WebExtension = mock() val addon3: WebExtension = mock() @@ -160,7 +160,7 @@ class AddonMigrationTest { } @Test - fun `Unsupported addons are disabled during migration`() = runBlocking { + fun `Unsupported addons are disabled during migration`() = runTest { val addon1: WebExtension = mock() whenever(addon1.isBuiltIn()).thenReturn(false) @@ -195,7 +195,7 @@ class AddonMigrationTest { } @Test - fun `Supported addons remain enabled during migration`() = runBlocking { + fun `Supported addons remain enabled during migration`() = runTest { val addon1: WebExtension = mock() whenever(addon1.id).thenReturn("supportedAddon") whenever(addon1.isEnabled()).thenReturn(true) @@ -247,7 +247,7 @@ class AddonMigrationTest { } @Test - fun `Previously unsupported addons are enabled during migration if supported`() = runBlocking { + fun `Previously unsupported addons are enabled during migration if supported`() = runTest { val metadata: Metadata = mock() whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(DisabledFlags.APP_SUPPORT)) @@ -293,7 +293,7 @@ class AddonMigrationTest { } @Test - fun `Fall back to hardcoded list of supported addons`() = runBlocking { + fun `Fall back to hardcoded list of supported addons`() = runTest { val addon1: WebExtension = mock() whenever(addon1.id).thenReturn("supportedAddon") whenever(addon1.isEnabled()).thenReturn(true) diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/FennecFxaMigrationTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/FennecFxaMigrationTest.kt index f293fdec032..aa328544e9f 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/FennecFxaMigrationTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/FennecFxaMigrationTest.kt @@ -5,7 +5,7 @@ package mozilla.components.support.migration import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.MigrationResult import mozilla.components.service.fxa.sharing.ShareableAccount @@ -29,7 +29,7 @@ import java.io.File @RunWith(AndroidJUnit4::class) class FennecFxaMigrationTest { @Test - fun `no state`() = runBlocking { + fun `no state`() = runTest { val fxaPath = File(getTestPath("fxa"), "no-file.json") val accountManager: FxaAccountManager = mock() @@ -41,7 +41,7 @@ class FennecFxaMigrationTest { } @Test - fun `separated fxa state v4`() = runBlocking { + fun `separated fxa state v4`() = runTest { val fxaPath = File(getTestPath("fxa"), "separated-v4.json") val accountManager: FxaAccountManager = mock() @@ -59,7 +59,7 @@ class FennecFxaMigrationTest { } @Test - fun `doghouse fxa state v4`() = runBlocking { + fun `doghouse fxa state v4`() = runTest { val fxaPath = File(getTestPath("fxa"), "doghouse-v4.json") val accountManager: FxaAccountManager = mock() @@ -77,7 +77,7 @@ class FennecFxaMigrationTest { } @Test - fun `married fxa state v4 successfull sign-in`() = runBlocking { + fun `married fxa state v4 successfull sign-in`() = runTest { val fxaPath = File(getTestPath("fxa"), "married-v4.json") val accountManager: FxaAccountManager = mock() @@ -99,7 +99,7 @@ class FennecFxaMigrationTest { } @Test - fun `cohabiting fxa state v4 successful sign-in`() = runBlocking { + fun `cohabiting fxa state v4 successful sign-in`() = runTest { val fxaPath = File(getTestPath("fxa"), "cohabiting-v4.json") val accountManager: FxaAccountManager = mock() @@ -121,7 +121,7 @@ class FennecFxaMigrationTest { } @Test - fun `cohabiting fxa state v4 will retry sign-in`() = runBlocking { + fun `cohabiting fxa state v4 will retry sign-in`() = runTest { val fxaPath = File(getTestPath("fxa"), "cohabiting-v4.json") val accountManager: FxaAccountManager = mock() @@ -143,7 +143,7 @@ class FennecFxaMigrationTest { } @Test - fun `married fxa state v4 failed sign-in`() = runBlocking { + fun `married fxa state v4 failed sign-in`() = runTest { val fxaPath = File(getTestPath("fxa"), "married-v4.json") val accountManager: FxaAccountManager = mock() @@ -167,7 +167,7 @@ class FennecFxaMigrationTest { } @Test - fun `custom token server`() = runBlocking { + fun `custom token server`() = runTest { val fxaPath = File(getTestPath("fxa"), "custom-sync-config-token.json") val accountManager: FxaAccountManager = mock() @@ -181,7 +181,7 @@ class FennecFxaMigrationTest { } @Test - fun `custom idp server`() = runBlocking { + fun `custom idp server`() = runTest { val fxaPath = File(getTestPath("fxa"), "custom-sync-config-idp.json") val accountManager: FxaAccountManager = mock() @@ -195,7 +195,7 @@ class FennecFxaMigrationTest { } @Test - fun `custom idp and token servers`() = runBlocking { + fun `custom idp and token servers`() = runTest { val fxaPath = File(getTestPath("fxa"), "custom-sync-config-idp-token.json") val accountManager: FxaAccountManager = mock() @@ -209,7 +209,7 @@ class FennecFxaMigrationTest { } @Test - fun `china idp and token servers`() = runBlocking { + fun `china idp and token servers`() = runTest { customServerAssertAllowed("china-sync-config-idp-token.json") } @@ -235,7 +235,7 @@ class FennecFxaMigrationTest { } @Test - fun `custom idp and token servers - allowed, but failed sign-in`() = runBlocking { + fun `custom idp and token servers - allowed, but failed sign-in`() = runTest { val fxaPath = File(getTestPath("fxa"), "china-sync-config-idp-token.json") val accountManager: FxaAccountManager = mock() @@ -259,7 +259,7 @@ class FennecFxaMigrationTest { } @Test - fun `cohabiting fxa state v4 failed sign-in`() = runBlocking { + fun `cohabiting fxa state v4 failed sign-in`() = runTest { val fxaPath = File(getTestPath("fxa"), "cohabiting-v4.json") val accountManager: FxaAccountManager = mock() @@ -283,7 +283,7 @@ class FennecFxaMigrationTest { } @Test - fun `corrupt married fxa state v4`() = runBlocking { + fun `corrupt married fxa state v4`() = runTest { val fxaPath = File(getTestPath("fxa"), "corrupt-married-v4.json") val accountManager: FxaAccountManager = mock() @@ -305,7 +305,7 @@ class FennecFxaMigrationTest { } @Test - fun `corrupt missing versions fxa state v4`() = runBlocking { + fun `corrupt missing versions fxa state v4`() = runTest { val fxaPath = File(getTestPath("fxa"), "corrupt-separated-missing-versions-v4.json") val accountManager: FxaAccountManager = mock() @@ -327,7 +327,7 @@ class FennecFxaMigrationTest { } @Test - fun `corrupt bad fxa state`() = runBlocking { + fun `corrupt bad fxa state`() = runTest { val fxaPath = File(getTestPath("fxa"), "separated-bad-state.json") val accountManager: FxaAccountManager = mock() @@ -349,7 +349,7 @@ class FennecFxaMigrationTest { } @Test - fun `separated - bad account version`() = runBlocking { + fun `separated - bad account version`() = runTest { val fxaPath = File(getTestPath("fxa"), "separated-bad-account-version-v4.json") val accountManager: FxaAccountManager = mock() @@ -374,7 +374,7 @@ class FennecFxaMigrationTest { } @Test - fun `separated - bad pickle version`() = runBlocking { + fun `separated - bad pickle version`() = runTest { val fxaPath = File(getTestPath("fxa"), "separated-bad-pickle-version-v4.json") val accountManager: FxaAccountManager = mock() @@ -399,7 +399,7 @@ class FennecFxaMigrationTest { } @Test - fun `separated - bad state version`() = runBlocking { + fun `separated - bad state version`() = runTest { val fxaPath = File(getTestPath("fxa"), "separated-bad-state-version-v10.json") val accountManager: FxaAccountManager = mock() diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt index 90bf3bf8382..dc04a122730 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/FennecMigratorTest.kt @@ -7,7 +7,7 @@ package mozilla.components.support.migration import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.uniffi.PlacesException import mozilla.components.browser.state.action.BrowserAction @@ -68,7 +68,7 @@ class FennecMigratorTest { } @Test - fun `no-op migration`() = runBlocking { + fun `no-op migration`() = runTest { val migrator = FennecMigrator.Builder(testContext, mock()) .setCoroutineContext(this.coroutineContext) .build() @@ -148,7 +148,7 @@ class FennecMigratorTest { } @Test - fun `is not a fennec install detected`() = runBlocking { + fun `is not a fennec install detected`() = runTest { val historyStore = PlacesHistoryStorage(testContext) val migrator1 = FennecMigrator.Builder(testContext, mock()) @@ -175,7 +175,7 @@ class FennecMigratorTest { } @Test - fun `migrations versioning basics`() = runBlocking { + fun `migrations versioning basics`() = runTest { val historyStore = PlacesHistoryStorage(testContext) val bookmarksStore = PlacesBookmarksStorage(testContext) val topSiteStorage = mock() @@ -282,7 +282,7 @@ class FennecMigratorTest { } @Test - fun `pinned sites migration`() = runBlocking { + fun `pinned sites migration`() = runTest { val historyStore = PlacesHistoryStorage(testContext) val bookmarksStore = PlacesBookmarksStorage(testContext) val topSiteStorage = mock() @@ -342,7 +342,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 1`() = runBlocking { + fun `failing migrations are reported - case 1`() = runTest { val historyStorage = PlacesHistoryStorage(testContext) // DB path is set, but db is corrupt. @@ -365,7 +365,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 2`() = runBlocking { + fun `failing migrations are reported - case 2`() = runTest { val historyStorage: PlacesHistoryStorage = mock() // Fail during history migration. @@ -400,7 +400,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 4`() = runBlocking { + fun `failing migrations are reported - case 4`() = runTest { val bookmarkStorage: PlacesBookmarksStorage = mock() val historyStorage: PlacesHistoryStorage = mock() @@ -448,7 +448,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 5`() = runBlocking { + fun `failing migrations are reported - case 5`() = runTest { val bookmarkStorage: PlacesBookmarksStorage = mock() val historyStorage: PlacesHistoryStorage = mock() @@ -487,7 +487,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 6`() = runBlocking { + fun `failing migrations are reported - case 6`() = runTest { // Open tabs migration without configured path to sessions. val migrator = FennecMigrator.Builder(testContext, mock()) .migrateOpenTabs(mock()) @@ -503,7 +503,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 7, corrupt fxa state`() = runBlocking { + fun `failing migrations are reported - case 7, corrupt fxa state`() = runTest { val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) .migrateFxa(lazy { accountManager }) @@ -522,7 +522,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 8, unsupported pickle version`() = runBlocking { + fun `failing migrations are reported - case 8, unsupported pickle version`() = runTest { val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) .migrateFxa(lazy { accountManager }) @@ -541,7 +541,7 @@ class FennecMigratorTest { } @Test - fun `failing migrations are reported - case 8, unsupported state version`() = runBlocking { + fun `failing migrations are reported - case 8, unsupported state version`() = runTest { val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) .migrateFxa(lazy { accountManager }) @@ -560,7 +560,7 @@ class FennecMigratorTest { } @Test - fun `fxa migration - no account`() = runBlocking { + fun `fxa migration - no account`() = runTest { // FxA migration without configured path to pickle file (test environment path isn't the same as real path). val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) @@ -586,7 +586,7 @@ class FennecMigratorTest { } @Test - fun `fxa migration - unauthenticated account`() = runBlocking { + fun `fxa migration - unauthenticated account`() = runTest { // FxA migration without configured path to pickle file (test environment path isn't the same as real path). val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) @@ -613,7 +613,7 @@ class FennecMigratorTest { } @Test - fun `fxa migration - authenticated account, sign-in succeeded`() = runBlocking { + fun `fxa migration - authenticated account, sign-in succeeded`() = runTest { val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) .migrateFxa(lazy { accountManager }) @@ -650,7 +650,7 @@ class FennecMigratorTest { } @Test - fun `fxa migration - authenticated account, sign-in will retry`() = runBlocking { + fun `fxa migration - authenticated account, sign-in will retry`() = runTest { val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) .migrateFxa(lazy { accountManager }) @@ -687,7 +687,7 @@ class FennecMigratorTest { } @Test - fun `fxa migration - authenticated account, sign-in failed`() = runBlocking { + fun `fxa migration - authenticated account, sign-in failed`() = runTest { val accountManager: FxaAccountManager = mock() val migrator = FennecMigrator.Builder(testContext, mock()) .migrateFxa(lazy { accountManager }) @@ -725,7 +725,7 @@ class FennecMigratorTest { } @Test - fun `logins migrations - no master password`() = runBlocking { + fun `logins migrations - no master password`() = runTest { val crashReporter: CrashReporting = mock() val loginStorage = SyncableLoginsStorage(testContext, lazy { securePrefs }).also { it.wipeLocal() @@ -780,7 +780,7 @@ class FennecMigratorTest { } @Test - fun `logins migrations - with master password`() = runBlocking { + fun `logins migrations - with master password`() = runTest { val crashReporter: CrashReporting = mock() val loginStorage = SyncableLoginsStorage(testContext, lazy { securePrefs }) val migrator = FennecMigrator.Builder(testContext, crashReporter) @@ -808,7 +808,7 @@ class FennecMigratorTest { } @Test - fun `logins migrations - with mp and empty key4db`() = runBlocking { + fun `logins migrations - with mp and empty key4db`() = runTest { val crashReporter: CrashReporting = mock() val loginStorage = SyncableLoginsStorage(testContext, lazy { securePrefs }).also { it.wipeLocal() @@ -836,7 +836,7 @@ class FennecMigratorTest { } @Test - fun `logins migrations - with mp and no nss in key4db`() = runBlocking { + fun `logins migrations - with mp and no nss in key4db`() = runTest { val crashReporter: CrashReporting = mock() val loginStorage = SyncableLoginsStorage(testContext, lazy { securePrefs }) val migrator = FennecMigrator.Builder(testContext, crashReporter) @@ -863,7 +863,7 @@ class FennecMigratorTest { } @Test - fun `logins migrations - missing profile`() = runBlocking { + fun `logins migrations - missing profile`() = runTest { val crashReporter: CrashReporting = mock() val loginStorage = SyncableLoginsStorage(testContext, lazy { securePrefs }).also { it.wipeLocal() @@ -887,7 +887,7 @@ class FennecMigratorTest { } @Test - fun `logins migrations - with master password, old signons version`() = runBlocking { + fun `logins migrations - with master password, old signons version`() = runTest { val crashReporter: CrashReporting = mock() val loginStorage = SyncableLoginsStorage(testContext, lazy { securePrefs }) val migrator = FennecMigrator.Builder(testContext, crashReporter) @@ -913,7 +913,7 @@ class FennecMigratorTest { } @Test - fun `logins migrations - without master password, old signons version`() = runBlocking { + fun `logins migrations - without master password, old signons version`() = runTest { val crashReporter: CrashReporting = mock() val loginStorage = SyncableLoginsStorage(testContext, lazy { securePrefs }) val migrator = FennecMigrator.Builder(testContext, crashReporter) @@ -938,7 +938,7 @@ class FennecMigratorTest { } @Test - fun `settings migration - no fennec prefs`() = runBlocking { + fun `settings migration - no fennec prefs`() = runTest { // Fennec SharedPreferences are missing / empty val crashReporter: CrashReporting = mock() val migrator = FennecMigrator.Builder(testContext, crashReporter) @@ -956,7 +956,7 @@ class FennecMigratorTest { } @Test - fun `settings migration - missing FHR value`() = runBlocking { + fun `settings migration - missing FHR value`() = runTest { val fennecAppPrefs = testContext.getSharedPreferences(FennecSettingsMigration.FENNEC_APP_SHARED_PREFS_NAME, Context.MODE_PRIVATE) // Make prefs non-empty. @@ -982,7 +982,7 @@ class FennecMigratorTest { } @Test - fun `addon migration - no addons installed`() = runBlocking { + fun `addon migration - no addons installed`() = runTest { val addonUpdater: AddonUpdater = mock() val addonCollectionProvider: AddonCollectionProvider = mock() val engine: Engine = mock() @@ -1007,7 +1007,7 @@ class FennecMigratorTest { } @Test - fun `addon migration - successful migration`() = runBlocking { + fun `addon migration - successful migration`() = runTest { val addon1: WebExtension = mock() val addon2: WebExtension = mock() @@ -1040,7 +1040,7 @@ class FennecMigratorTest { } @Test - fun `addon migration - failed to query installed addons`() = runBlocking { + fun `addon migration - failed to query installed addons`() = runTest { val addonUpdater: AddonUpdater = mock() val addonCollectionProvider: AddonCollectionProvider = mock() val engine: Engine = mock() @@ -1070,7 +1070,7 @@ class FennecMigratorTest { } @Test - fun `addon migration - failed to migrate some addons`() = runBlocking { + fun `addon migration - failed to migrate some addons`() = runTest { val addon1: WebExtension = mock() val addon2: WebExtension = mock() val addon3: WebExtension = mock() @@ -1124,7 +1124,7 @@ class FennecMigratorTest { } @Test - fun `gecko migration - no prefs_js`() = runBlocking { + fun `gecko migration - no prefs_js`() = runTest { val crashReporter: CrashReporting = mock() val migrator = FennecMigrator.Builder(testContext, crashReporter) .migrateGecko() @@ -1146,7 +1146,7 @@ class FennecMigratorTest { } @Test - fun `gecko migration - invalid prefs_js removed`() = runBlocking { + fun `gecko migration - invalid prefs_js removed`() = runTest { val crashReporter: CrashReporting = mock() val migrator = FennecMigrator.Builder(testContext, crashReporter) .migrateGecko() @@ -1168,7 +1168,7 @@ class FennecMigratorTest { } @Test - fun `gecko migration - prefs_js migrated`() = runBlocking { + fun `gecko migration - prefs_js migrated`() = runTest { val crashReporter: CrashReporting = mock() val migrator = FennecMigrator.Builder(testContext, crashReporter) .migrateGecko() @@ -1190,7 +1190,7 @@ class FennecMigratorTest { } @Test - fun `gecko migration - prefs_js no prefs to migrate`() = runBlocking { + fun `gecko migration - prefs_js no prefs to migrate`() = runTest { val crashReporter: CrashReporting = mock() val migrator = FennecMigrator.Builder(testContext, crashReporter) .migrateGecko() diff --git a/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt b/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt index 6e6c3eaa430..03011d60662 100644 --- a/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt +++ b/components/support/migration/src/test/java/mozilla/components/support/migration/SearchEngineMigrationTest.kt @@ -7,8 +7,8 @@ package mozilla.components.support.migration import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.SearchAction import mozilla.components.browser.state.search.RegionState import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine @@ -43,7 +43,7 @@ class SearchEngineMigrationTest { } @Test - fun `default Google with en_US_US list`() { + fun `default Google with en_US_US list`() = runTest { val (store, result) = migrate( fennecDefault = "Google", language = "en", @@ -61,7 +61,7 @@ class SearchEngineMigrationTest { } @Test - fun `default DuckDuckGo with en_US_US list`() { + fun `default DuckDuckGo with en_US_US list`() = runTest { val (store, result) = migrate( fennecDefault = "DuckDuckGo", language = "en", @@ -79,7 +79,7 @@ class SearchEngineMigrationTest { } @Test - fun `default Bing with de_DE_DE list`() { + fun `default Bing with de_DE_DE list`() = runTest { val (store, result) = migrate( fennecDefault = "Bing", language = "de", @@ -97,7 +97,7 @@ class SearchEngineMigrationTest { } @Test - fun `default Qwant with de_DE_DE list`() { + fun `default Qwant with de_DE_DE list`() = runTest { val (store, result) = migrate( fennecDefault = "Qwant", language = "de", @@ -115,7 +115,7 @@ class SearchEngineMigrationTest { } @Test - fun `default Qwant with en_US_US list`() { + fun `default Qwant with en_US_US list`() = runTest { val (store, result) = migrate( fennecDefault = "Qwant", language = "en", @@ -134,7 +134,7 @@ class SearchEngineMigrationTest { } @Test - fun `default null with en_US_US list`() { + fun `default null with en_US_US list`() = runTest { val (store, result) = migrate( fennecDefault = null, language = "en", @@ -174,37 +174,35 @@ private fun assertIsFailure( assertEquals(expected, wrapper.failure) } -private fun migrate( +private suspend fun migrate( fennecDefault: String?, language: String, country: String, region: String ): Pair> { - return runBlocking { - val store = storeFor( - language, - country, - region - ) + val store = storeFor( + language, + country, + region + ) - if (fennecDefault != null) { - ApplicationProvider.getApplicationContext().getSharedPreferences( - FennecSettingsMigration.FENNEC_APP_SHARED_PREFS_NAME, - Context.MODE_PRIVATE - ).edit() - .putString("search.engines.defaultname", fennecDefault) - .apply() - } + if (fennecDefault != null) { + ApplicationProvider.getApplicationContext().getSharedPreferences( + FennecSettingsMigration.FENNEC_APP_SHARED_PREFS_NAME, + Context.MODE_PRIVATE + ).edit() + .putString("search.engines.defaultname", fennecDefault) + .apply() + } - val result = SearchEngineMigration.migrate( - ApplicationProvider.getApplicationContext(), - store - ) + val result = SearchEngineMigration.migrate( + ApplicationProvider.getApplicationContext(), + store + ) - store.waitUntilIdle() + store.waitUntilIdle() - Pair(store, result) - } + return Pair(store, result) } private fun storeFor(language: String, country: String, region: String): BrowserStore { diff --git a/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt b/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt index 368b7a89d8b..ae5f3980848 100644 --- a/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt +++ b/components/support/utils/src/test/java/mozilla/components/support/utils/RunWhenReadyQueueTest.kt @@ -5,9 +5,9 @@ package mozilla.components.support.utils import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest import mozilla.components.support.test.mock import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Rule @@ -24,7 +24,7 @@ class RunWhenReadyQueueTest { private val scope = coroutinesTestRule.scope @Test - fun `task should not run until ready is called`() = runBlockingTest { + fun `task should not run until ready is called`() = runTestOnMain { val task = mock<() -> Unit>() val queue = RunWhenReadyQueue(scope) @@ -39,7 +39,7 @@ class RunWhenReadyQueueTest { } @Test - fun `task should run if ready was called`() = runBlockingTest { + fun `task should run if ready was called`() = runTestOnMain { val task = mock<() -> Unit>() val queue = RunWhenReadyQueue(scope) queue.ready() @@ -52,7 +52,7 @@ class RunWhenReadyQueueTest { } @Test - fun `tasks should run in the order they were queued`() = runBlockingTest { + fun `tasks should run in the order they were queued`() = runTestOnMain { val task1 = mock<() -> Unit>() val task2 = mock<() -> Unit>() val task3 = mock<() -> Unit>() From fb7f69b535337bddaca153741cbdf0ba133f33f4 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Wed, 20 Apr 2022 15:20:05 +0300 Subject: [PATCH 064/160] For #11175 - Migrate runBlocking and runBlockingTest to runTest in ui tests `runBlockingTest` is deprecated and cannot be used in ui tests either. Took this opportunity to also refactor some `runBlocking` usages also to `runTest` as the recommended api to run tests inside a coroutine. --- components/browser/icons/build.gradle | 1 + .../browser/icons/OnDeviceBrowserIconsTest.kt | 6 ++++-- .../feature/containers/ContainerStorageTest.kt | 8 ++++---- .../feature/containers/db/ContainerDaoTest.kt | 6 +++--- .../downloads/OnDeviceDownloadStorageTest.kt | 15 +++++++-------- .../feature/downloads/db/DownloadDaoTest.kt | 10 +++++----- .../db/OnDeviceSitePermissionsStorageTest.kt | 4 ++-- components/feature/tab-collections/build.gradle | 1 + .../tab/collections/TabCollectionStorageTest.kt | 10 ++++++---- components/feature/top-sites/build.gradle | 1 + .../top/sites/OnDevicePinnedSitesStorageTest.kt | 14 ++++++++------ 11 files changed, 42 insertions(+), 34 deletions(-) diff --git a/components/browser/icons/build.gradle b/components/browser/icons/build.gradle index 3777dfe554e..c7670778039 100644 --- a/components/browser/icons/build.gradle +++ b/components/browser/icons/build.gradle @@ -77,6 +77,7 @@ dependencies { androidTestImplementation Dependencies.androidx_test_core androidTestImplementation Dependencies.androidx_test_runner androidTestImplementation Dependencies.androidx_test_rules + androidTestImplementation Dependencies.testing_coroutines } apply from: '../../../publish.gradle' diff --git a/components/browser/icons/src/androidTest/java/mozilla/components/browser/icons/OnDeviceBrowserIconsTest.kt b/components/browser/icons/src/androidTest/java/mozilla/components/browser/icons/OnDeviceBrowserIconsTest.kt index 9dfd0fefd4e..826a3a186a3 100644 --- a/components/browser/icons/src/androidTest/java/mozilla/components/browser/icons/OnDeviceBrowserIconsTest.kt +++ b/components/browser/icons/src/androidTest/java/mozilla/components/browser/icons/OnDeviceBrowserIconsTest.kt @@ -6,7 +6,8 @@ package mozilla.components.browser.icons import android.content.Context import androidx.test.core.app.ApplicationProvider -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.browser.icons.generator.IconGenerator import mozilla.components.concept.engine.manifest.Size import mozilla.components.concept.fetch.Client @@ -20,8 +21,9 @@ class OnDeviceBrowserIconsTest { private val context: Context get() = ApplicationProvider.getApplicationContext() + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun dataUriLoad() = runBlocking { + fun dataUriLoad() = runTest { val request = IconRequest( url = "https://www.mozilla.org", size = IconRequest.Size.DEFAULT, diff --git a/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/ContainerStorageTest.kt b/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/ContainerStorageTest.kt index 605f97d35a0..45ea80c8728 100644 --- a/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/ContainerStorageTest.kt +++ b/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/ContainerStorageTest.kt @@ -11,7 +11,7 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.Container import mozilla.components.browser.state.state.ContainerState.Color import mozilla.components.browser.state.state.ContainerState.Icon @@ -52,7 +52,7 @@ class ContainerStorageTest { } @Test - fun testAddingContainer() = runBlockingTest { + fun testAddingContainer() = runTest { storage.addContainer("1", "Personal", Color.RED, Icon.FINGERPRINT) storage.addContainer("2", "Shopping", Color.BLUE, Icon.CART) @@ -71,7 +71,7 @@ class ContainerStorageTest { } @Test - fun testRemovingContainers() = runBlockingTest { + fun testRemovingContainers() = runTest { storage.addContainer("1", "Personal", Color.RED, Icon.FINGERPRINT) storage.addContainer("2", "Shopping", Color.BLUE, Icon.CART) @@ -92,7 +92,7 @@ class ContainerStorageTest { } @Test - fun testGettingContainers() = runBlockingTest { + fun testGettingContainers() = runTest { storage.addContainer("1", "Personal", Color.RED, Icon.FINGERPRINT) storage.addContainer("2", "Shopping", Color.BLUE, Icon.CART) diff --git a/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/db/ContainerDaoTest.kt b/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/db/ContainerDaoTest.kt index 8c0515253eb..5a42960298e 100644 --- a/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/db/ContainerDaoTest.kt +++ b/components/feature/containers/src/androidTest/java/mozilla/components/feature/containers/db/ContainerDaoTest.kt @@ -10,7 +10,7 @@ import androidx.paging.PagedList import androidx.room.Room import androidx.test.core.app.ApplicationProvider import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.ContainerState.Color import mozilla.components.browser.state.state.ContainerState.Icon import org.junit.After @@ -48,7 +48,7 @@ class ContainerDaoTest { } @Test - fun testAddingContainer() = runBlockingTest { + fun testAddingContainer() = runTest { val container = ContainerEntity( contextId = UUID.randomUUID().toString(), @@ -70,7 +70,7 @@ class ContainerDaoTest { } @Test - fun testRemovingContainer() = runBlockingTest { + fun testRemovingContainer() = runTest { val container1 = ContainerEntity( contextId = UUID.randomUUID().toString(), diff --git a/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/OnDeviceDownloadStorageTest.kt b/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/OnDeviceDownloadStorageTest.kt index f4c984c8680..d1c858068df 100644 --- a/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/OnDeviceDownloadStorageTest.kt +++ b/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/OnDeviceDownloadStorageTest.kt @@ -13,8 +13,7 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.feature.downloads.db.DownloadsDatabase import mozilla.components.feature.downloads.db.Migrations @@ -165,7 +164,7 @@ class OnDeviceDownloadStorageTest { } @Test - fun testAddingDownload() = runBlockingTest { + fun testAddingDownload() = runTest { val download1 = createMockDownload("1", "url1") val download2 = createMockDownload("2", "url2") val download3 = createMockDownload("3", "url3") @@ -184,7 +183,7 @@ class OnDeviceDownloadStorageTest { } @Test - fun testAddingDataURLDownload() = runBlockingTest { + fun testAddingDataURLDownload() = runTest { val download1 = createMockDownload("1", "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==") val download2 = createMockDownload("2", "url2") @@ -200,7 +199,7 @@ class OnDeviceDownloadStorageTest { } @Test - fun testUpdatingDataURLDownload() = runBlockingTest { + fun testUpdatingDataURLDownload() = runTest { val download1 = createMockDownload("1", "url1") val download2 = createMockDownload("2", "url2") @@ -227,7 +226,7 @@ class OnDeviceDownloadStorageTest { } @Test - fun testRemovingDownload() = runBlockingTest { + fun testRemovingDownload() = runTest { val download1 = createMockDownload("1", "url1") val download2 = createMockDownload("2", "url2") @@ -246,7 +245,7 @@ class OnDeviceDownloadStorageTest { } @Test - fun testGettingDownloads() = runBlockingTest { + fun testGettingDownloads() = runTest { val download1 = createMockDownload("1", "url1") val download2 = createMockDownload("2", "url2") @@ -262,7 +261,7 @@ class OnDeviceDownloadStorageTest { } @Test - fun testRemovingDownloads() = runBlocking { + fun testRemovingDownloads() = runTest { for (index in 1..2) { storage.add(createMockDownload(index.toString(), "url1")) } diff --git a/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/db/DownloadDaoTest.kt b/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/db/DownloadDaoTest.kt index 04acf453d47..72cfae55662 100644 --- a/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/db/DownloadDaoTest.kt +++ b/components/feature/downloads/src/androidTest/java/mozilla/components/feature/downloads/db/DownloadDaoTest.kt @@ -9,7 +9,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.paging.PagedList import androidx.room.Room import androidx.test.core.app.ApplicationProvider -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.feature.downloads.DownloadStorage import org.junit.After @@ -46,7 +46,7 @@ class DownloadDaoTest { } @Test - fun testInsertingAndReadingDownloads() = runBlocking { + fun testInsertingAndReadingDownloads() = runTest { val download = insertMockDownload("1", "https://www.mozilla.org/file1.txt") val pagedList = getDownloadsPagedList() @@ -55,7 +55,7 @@ class DownloadDaoTest { } @Test - fun testRemoveAllDownloads() = runBlocking { + fun testRemoveAllDownloads() = runTest { for (index in 1..4) { insertMockDownload(index.toString(), "https://www.mozilla.org/file1.txt") } @@ -71,7 +71,7 @@ class DownloadDaoTest { } @Test - fun testRemovingDownloads() = runBlocking { + fun testRemovingDownloads() = runTest { for (index in 1..2) { insertMockDownload(index.toString(), "https://www.mozilla.org/file1.txt") } @@ -90,7 +90,7 @@ class DownloadDaoTest { } @Test - fun testUpdateDownload() = runBlocking { + fun testUpdateDownload() = runTest { insertMockDownload("1", "https://www.mozilla.org/file1.txt") var pagedList = getDownloadsPagedList() diff --git a/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt b/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt index 267720b906e..42c29e24fbc 100644 --- a/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt +++ b/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt @@ -11,7 +11,7 @@ import androidx.room.testing.MigrationTestHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.permission.SitePermissions import mozilla.components.concept.engine.permission.SitePermissions.AutoplayStatus import mozilla.components.concept.engine.permission.SitePermissions.Status @@ -56,7 +56,7 @@ class OnDeviceSitePermissionsStorageTest { } @Test - fun testStorageInteraction() = runBlockingTest { + fun testStorageInteraction() = runTest { val origin = "https://www.mozilla.org".toUri().host!! val sitePermissions = SitePermissions( origin = origin, diff --git a/components/feature/tab-collections/build.gradle b/components/feature/tab-collections/build.gradle index c0ef3cbd9f7..48c1d43fcd6 100644 --- a/components/feature/tab-collections/build.gradle +++ b/components/feature/tab-collections/build.gradle @@ -81,6 +81,7 @@ dependencies { androidTestImplementation Dependencies.androidx_test_core androidTestImplementation Dependencies.androidx_test_runner androidTestImplementation Dependencies.androidx_test_rules + androidTestImplementation Dependencies.testing_coroutines } apply from: '../../../publish.gradle' diff --git a/components/feature/tab-collections/src/androidTest/java/mozilla/components/feature/tab/collections/TabCollectionStorageTest.kt b/components/feature/tab-collections/src/androidTest/java/mozilla/components/feature/tab/collections/TabCollectionStorageTest.kt index 98b8a76dcd0..e13588b99d7 100644 --- a/components/feature/tab-collections/src/androidTest/java/mozilla/components/feature/tab/collections/TabCollectionStorageTest.kt +++ b/components/feature/tab-collections/src/androidTest/java/mozilla/components/feature/tab/collections/TabCollectionStorageTest.kt @@ -9,8 +9,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.paging.PagedList import androidx.room.Room import androidx.test.core.app.ApplicationProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.state.recover.RecoverableTab @@ -27,6 +28,7 @@ import org.junit.Test import java.util.concurrent.ExecutorService import java.util.concurrent.Executors +@ExperimentalCoroutinesApi // for runTest @Suppress("LargeClass") // Large test is large class TabCollectionStorageTest { private lateinit var context: Context @@ -220,7 +222,7 @@ class TabCollectionStorageTest { @Test @Suppress("ComplexMethod") - fun testGettingCollections() = runBlocking { + fun testGettingCollections() = runTest { storage.createCollection( "Articles", listOf( @@ -302,7 +304,7 @@ class TabCollectionStorageTest { @Test @Suppress("ComplexMethod") - fun testGettingCollectionsList() = runBlocking { + fun testGettingCollectionsList() = runTest { storage.createCollection( "Articles", listOf( @@ -382,7 +384,7 @@ class TabCollectionStorageTest { } @Test - fun testGettingTabCollectionCount() = runBlocking { + fun testGettingTabCollectionCount() = runTest { assertEquals(0, storage.getTabCollectionsCount()) storage.createCollection( diff --git a/components/feature/top-sites/build.gradle b/components/feature/top-sites/build.gradle index 85a43687eac..e5a7bc7809e 100644 --- a/components/feature/top-sites/build.gradle +++ b/components/feature/top-sites/build.gradle @@ -75,6 +75,7 @@ dependencies { androidTestImplementation Dependencies.androidx_test_core androidTestImplementation Dependencies.androidx_test_runner androidTestImplementation Dependencies.androidx_test_rules + androidTestImplementation Dependencies.testing_coroutines } apply from: '../../../publish.gradle' diff --git a/components/feature/top-sites/src/androidTest/java/mozilla/components/feature/top/sites/OnDevicePinnedSitesStorageTest.kt b/components/feature/top-sites/src/androidTest/java/mozilla/components/feature/top/sites/OnDevicePinnedSitesStorageTest.kt index 509a610b897..44fe82621ee 100644 --- a/components/feature/top-sites/src/androidTest/java/mozilla/components/feature/top/sites/OnDevicePinnedSitesStorageTest.kt +++ b/components/feature/top-sites/src/androidTest/java/mozilla/components/feature/top/sites/OnDevicePinnedSitesStorageTest.kt @@ -11,7 +11,8 @@ import androidx.room.testing.MigrationTestHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import mozilla.components.feature.top.sites.db.Migrations import mozilla.components.feature.top.sites.db.TopSiteDatabase import org.junit.After @@ -26,6 +27,7 @@ import java.util.concurrent.Executors private const val MIGRATION_TEST_DB = "migration-test" +@ExperimentalCoroutinesApi // for runTest @Suppress("LargeClass") class OnDevicePinnedSitesStorageTest { private lateinit var context: Context @@ -60,7 +62,7 @@ class OnDevicePinnedSitesStorageTest { } @Test - fun testAddingAllDefaultSites() = runBlocking { + fun testAddingAllDefaultSites() = runTest { val defaultTopSites = listOf( Pair("Mozilla", "https://www.mozilla.org"), Pair("Firefox", "https://www.firefox.com"), @@ -90,7 +92,7 @@ class OnDevicePinnedSitesStorageTest { } @Test - fun testAddingPinnedSite() = runBlocking { + fun testAddingPinnedSite() = runTest { storage.addPinnedSite("Mozilla", "https://www.mozilla.org") storage.addPinnedSite("Firefox", "https://www.firefox.com", isDefault = true) @@ -108,7 +110,7 @@ class OnDevicePinnedSitesStorageTest { } @Test - fun testRemovingPinnedSites() = runBlocking { + fun testRemovingPinnedSites() = runTest { storage.addPinnedSite("Mozilla", "https://www.mozilla.org") storage.addPinnedSite("Firefox", "https://www.firefox.com") @@ -129,7 +131,7 @@ class OnDevicePinnedSitesStorageTest { } @Test - fun testGettingPinnedSites() = runBlocking { + fun testGettingPinnedSites() = runTest { storage.addPinnedSite("Mozilla", "https://www.mozilla.org") storage.addPinnedSite("Firefox", "https://www.firefox.com", isDefault = true) @@ -153,7 +155,7 @@ class OnDevicePinnedSitesStorageTest { } @Test - fun testUpdatingPinnedSites() = runBlocking { + fun testUpdatingPinnedSites() = runTest { storage.addPinnedSite("Mozilla", "https://www.mozilla.org") var pinnedSites = storage.getPinnedSites() From 6df1bedbd683155e8a7047279aa4fbb702639bcb Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Tue, 10 May 2022 13:32:32 +0000 Subject: [PATCH 065/160] Update GeckoView (Nightly) to 102.0.20220510095538. --- buildSrc/src/main/java/Gecko.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index a9cee1302e3..70c7c85ec9e 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -9,7 +9,7 @@ object Gecko { /** * GeckoView Version. */ - const val version = "102.0.20220509094710" + const val version = "102.0.20220510095538" /** * GeckoView channel From c2d8a5d17e5c9c3f517973c029e6299df7558a30 Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Wed, 10 Nov 2021 01:16:49 -0500 Subject: [PATCH 066/160] Issue #11249: Add support for save credit card prompt --- .../gecko/prompt/GeckoPromptDelegate.kt | 38 ++++++- .../gecko/prompt/GeckoPromptDelegateTest.kt | 102 ++++++++++++++++++ .../concept/engine/prompt/PromptRequest.kt | 12 +++ .../feature/prompts/PromptFeature.kt | 2 + docs/changelog.md | 3 +- 5 files changed, 155 insertions(+), 2 deletions(-) diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt index 5dda762dc92..e6592805e98 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt @@ -67,6 +67,43 @@ typealias AC_FILE_FACING_MODE = PromptRequest.File.FacingMode internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSession) : PromptDelegate { + override fun onCreditCardSave( + session: GeckoSession, + request: AutocompleteRequest + ): GeckoResult { + val geckoResult = GeckoResult() + + val onConfirm: (CreditCardEntry) -> Unit = { creditCard -> + if (!request.isComplete) { + geckoResult.complete( + request.confirm( + Autocomplete.CreditCardSelectOption(creditCard.toAutocompleteCreditCard()) + ) + ) + } + } + + val onDismiss: () -> Unit = { + request.dismissSafely(geckoResult) + } + + geckoEngineSession.notifyObservers { + onPromptRequest( + PromptRequest.SaveCreditCard( + creditCard = request.options[0].value.toCreditCardEntry(), + onConfirm = onConfirm, + onDismiss = onDismiss + ).also { + request.delegate = PromptInstanceDismissDelegate( + geckoEngineSession, it + ) + } + ) + } + + return geckoResult + } + /** * Handle a credit card selection prompt request. This is triggered by the user * focusing on a credit card input field. @@ -757,7 +794,6 @@ internal fun Date.toString(format: String): String { * Only dismiss if the prompt is not already dismissed. */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal fun PromptDelegate.BasePrompt.dismissSafely(geckoResult: GeckoResult) { if (!this.isComplete) { geckoResult.complete(dismiss()) diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt index dbdc26fb268..04ea9846f45 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt @@ -836,6 +836,95 @@ class GeckoPromptDelegateTest { passwordField = passwordField ) + @Test + fun `Calling onCreditCardSave must provide an SaveCreditCard PromptRequest`() { + val mockSession = GeckoEngineSession(runtime) + var onCreditCardSaved = false + var onDismissWasCalled = false + + var saveCreditCardPrompt: PromptRequest.SaveCreditCard = mock() + + val promptDelegate = spy(GeckoPromptDelegate(mockSession)) + + mockSession.register(object : EngineSession.Observer { + override fun onPromptRequest(promptRequest: PromptRequest) { + saveCreditCardPrompt = promptRequest as PromptRequest.SaveCreditCard + } + }) + + val creditCard = CreditCardEntry( + guid = "1", + name = "Banana Apple", + number = "4111111111111110", + expiryMonth = "5", + expiryYear = "2030", + cardType = "amex" + ) + val creditCardSaveOption = + Autocomplete.CreditCardSaveOption(creditCard.toAutocompleteCreditCard()) + + var geckoResult = promptDelegate.onCreditCardSave( + mock(), + geckoCreditCardSavePrompt(arrayOf(creditCardSaveOption)) + ) + + geckoResult.accept { + onDismissWasCalled = true + } + + saveCreditCardPrompt.onDismiss() + shadowOf(getMainLooper()).idle() + assertTrue(onDismissWasCalled) + + val geckoPrompt = geckoCreditCardSavePrompt(arrayOf(creditCardSaveOption)) + geckoResult = promptDelegate.onCreditCardSave(mock(), geckoPrompt) + + geckoResult.accept { + onCreditCardSaved = true + } + + saveCreditCardPrompt.onConfirm(creditCard) + shadowOf(getMainLooper()).idle() + + assertTrue(onCreditCardSaved) + + whenever(geckoPrompt.isComplete).thenReturn(true) + onCreditCardSaved = false + saveCreditCardPrompt.onConfirm(creditCard) + + assertFalse(onCreditCardSaved) + } + + @Test + fun `Calling onCreditSave must set a PromptInstanceDismissDelegate`() { + val mockSession = GeckoEngineSession(runtime) + var saveCreditCardPrompt: PromptRequest.SaveCreditCard = mock() + val promptDelegate = spy(GeckoPromptDelegate(mockSession)) + + mockSession.register(object : EngineSession.Observer { + override fun onPromptRequest(promptRequest: PromptRequest) { + saveCreditCardPrompt = promptRequest as PromptRequest.SaveCreditCard + } + }) + + val creditCard = CreditCardEntry( + guid = "1", + name = "Banana Apple", + number = "4111111111111110", + expiryMonth = "5", + expiryYear = "2030", + cardType = "amex" + ) + val creditCardSaveOption = + Autocomplete.CreditCardSaveOption(creditCard.toAutocompleteCreditCard()) + val geckoPrompt = geckoCreditCardSavePrompt(arrayOf(creditCardSaveOption)) + + promptDelegate.onCreditCardSave(mock(), geckoPrompt) + + assertNotNull(saveCreditCardPrompt) + assertNotNull(geckoPrompt.delegate) + } + @Test fun `Calling onCreditCardSelect must provide as CreditCardSelectOption PromptRequest`() { val mockSession = GeckoEngineSession(runtime) @@ -1698,4 +1787,17 @@ class GeckoPromptDelegateTest { ReflectionUtils.setField(prompt, "options", addresses) return prompt } + + @Suppress("UNCHECKED_CAST") + private fun geckoCreditCardSavePrompt( + creditCard: Array + ): GeckoSession.PromptDelegate.AutocompleteRequest { + val prompt = Mockito.mock( + GeckoSession.PromptDelegate.AutocompleteRequest::class.java, + Mockito.RETURNS_DEEP_STUBS // for testing prompt.delegate + ) as GeckoSession.PromptDelegate.AutocompleteRequest + + ReflectionUtils.setField(prompt, "options", creditCard) + return prompt + } } diff --git a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt b/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt index 941b8559867..f21b49f5a0a 100644 --- a/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt +++ b/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt @@ -93,6 +93,18 @@ sealed class PromptRequest( val onStay: () -> Unit ) : PromptRequest() + /** + * Value type that represents a request for a save credit card prompt. + * @property creditCard the [CreditCardEntry] to save or update. + * @property onConfirm callback that is called when the user confirms the save credit card request. + * @property onDismiss callback to let the page know the user dismissed the dialog. + */ + data class SaveCreditCard( + val creditCard: CreditCardEntry, + val onConfirm: (CreditCardEntry) -> Unit, + override val onDismiss: () -> Unit + ) : PromptRequest(shouldDismissOnLoad = false), Dismissible + /** * Value type that represents a request for a select credit card prompt. * @property creditCards a list of [CreditCardEntry]s to select from. diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt index ee3a5d9d0eb..ffbc39efc27 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt @@ -33,6 +33,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.Popup import mozilla.components.concept.engine.prompt.PromptRequest.Repost +import mozilla.components.concept.engine.prompt.PromptRequest.SaveCreditCard import mozilla.components.concept.engine.prompt.PromptRequest.SaveLoginPrompt import mozilla.components.concept.engine.prompt.PromptRequest.SelectAddress import mozilla.components.concept.engine.prompt.PromptRequest.SelectCreditCard @@ -791,6 +792,7 @@ class PromptFeature private constructor( is SaveLoginPrompt, is SelectLoginPrompt, is SelectCreditCard, + is SaveCreditCard, is Share -> true is SelectAddress -> false is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs diff --git a/docs/changelog.md b/docs/changelog.md index bdd9517b9b5..6d266ff7c25 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,7 +16,8 @@ permalink: /changelog/ * ⚠️ **This is a breaking change**: `MainCoroutineRule.runBlockingTest` is replaced with a `runTestOnMain` top level function. . This method is preferred over the global `runTest` because it reparents new child coroutines to the test coroutine context. * **concept-engine**: - * Added support for `SelectAddress` prompt request. See [issue #12060](https://github.com/mozilla-mobile/android-components/issues/12060) + * Adds a new `SelectAddress` in `PromptRequest` to display a prompt for selecting an address to autofill. [#12060](https://github.com/mozilla-mobile/android-components/issues/12060) + * Adds a new `SaveCreditCard` in `PromptRequest` to display a prompt for saving a credit card on autofill. [#11249](https://github.com/mozilla-mobile/android-components/issues/11249) * **feature-autofill** * 🚒 Bug fixed [issue #11893](https://github.com/mozilla-mobile/android-components/issues/11893) - Fix issue with autofilling in 3rd party applications not being immediately available after unlocking the autofill service. From 8b8db2d131ae91a3ce879130f19430809cb2d7db Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Wed, 10 Nov 2021 14:32:51 -0500 Subject: [PATCH 067/160] Issue #11338: Add a SaveCreditCard dialog to handle saving and updating a credit card --- .../GeckoAutocompleteStorageDelegate.kt | 8 + components/concept/storage/build.gradle | 4 + .../storage/CreditCardsAddressesStorage.kt | 27 +- .../concept/storage/CreditCardEntryTest.kt | 40 +++ .../feature/prompts/PromptFeature.kt | 64 +++- .../creditcard/CreditCardItemViewHolder.kt | 25 +- .../CreditCardSaveDialogFragment.kt | 179 +++++++++++ .../prompts/dialog/PromptDialogFragment.kt | 6 + ...feature_prompt_save_credit_card_prompt.xml | 138 ++++++++ .../prompts/src/main/res/values/strings.xml | 10 +- .../feature/prompts/PromptFeatureTest.kt | 296 +++++++++++++++++- .../CreditCardSaveDialogFragmentTest.kt | 256 +++++++++++++++ docs/changelog.md | 3 + 13 files changed, 1020 insertions(+), 36 deletions(-) create mode 100644 components/concept/storage/src/test/java/mozilla/components/concept/storage/CreditCardEntryTest.kt create mode 100644 components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSaveDialogFragment.kt create mode 100644 components/feature/prompts/src/main/res/layout/mozac_feature_prompt_save_credit_card_prompt.xml create mode 100644 components/feature/prompts/src/test/java/mozilla/components/feature/prompts/creditcard/CreditCardSaveDialogFragmentTest.kt diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt index 6f766d00fc8..a1185497a87 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoAutocompleteStorageDelegate.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import mozilla.components.browser.engine.gecko.ext.toCreditCardEntry import mozilla.components.browser.engine.gecko.ext.toLoginEntry import mozilla.components.concept.storage.CreditCard import mozilla.components.concept.storage.CreditCardsAddressesStorageDelegate @@ -63,6 +64,13 @@ class GeckoAutocompleteStorageDelegate( return result } + override fun onCreditCardSave(creditCard: Autocomplete.CreditCard) { + @OptIn(DelicateCoroutinesApi::class) + GlobalScope.launch(IO) { + creditCardsAddressesStorageDelegate.onCreditCardSave(creditCard.toCreditCardEntry()) + } + } + override fun onLoginSave(login: Autocomplete.LoginEntry) { loginStorageDelegate.onLoginSave(login.toLoginEntry()) } diff --git a/components/concept/storage/build.gradle b/components/concept/storage/build.gradle index 587ba4a5d1c..ec60671b4d9 100644 --- a/components/concept/storage/build.gradle +++ b/components/concept/storage/build.gradle @@ -29,6 +29,10 @@ dependencies { api Dependencies.kotlin_coroutines implementation project(':support-ktx') + + testImplementation project(':support-test') + testImplementation Dependencies.testing_junit + testImplementation Dependencies.testing_mockito } apply from: '../../../publish.gradle' diff --git a/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt b/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt index 87e965567c0..cc0861bc692 100644 --- a/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt +++ b/components/concept/storage/src/main/java/mozilla/components/concept/storage/CreditCardsAddressesStorage.kt @@ -11,6 +11,9 @@ import mozilla.components.concept.storage.CreditCard.Companion.ellipsesEnd import mozilla.components.concept.storage.CreditCard.Companion.ellipsesStart import mozilla.components.concept.storage.CreditCard.Companion.ellipsis import mozilla.components.support.ktx.kotlin.last4Digits +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale /** * An interface which defines read/write methods for credit card and address data. @@ -235,6 +238,7 @@ data class CreditCard( * @property expiryYear The credit card expiry year. * @property cardType The credit card network ID. */ +@Parcelize data class CreditCardEntry( val guid: String? = null, val name: String, @@ -242,12 +246,33 @@ data class CreditCardEntry( val expiryMonth: String, val expiryYear: String, val cardType: String -) { +) : Parcelable { val obfuscatedCardNumber: String get() = ellipsesStart + ellipsis + ellipsis + ellipsis + ellipsis + number.last4Digits() + ellipsesEnd + + /** + * Credit card expiry date formatted according to the locale. + */ + val expiryDate: String + get() { + val dateFormat = SimpleDateFormat(DATE_PATTERN, Locale.getDefault()) + + val calendar = Calendar.getInstance() + calendar.set(Calendar.DAY_OF_MONTH, 1) + // Subtract 1 from the expiry month since Calendar.Month is based on a 0-indexed. + calendar.set(Calendar.MONTH, expiryMonth.toInt() - 1) + calendar.set(Calendar.YEAR, expiryYear.toInt()) + + return dateFormat.format(calendar.time) + } + + companion object { + // Date format pattern for the credit card expiry date. + private const val DATE_PATTERN = "MM/yyyy" + } } /** diff --git a/components/concept/storage/src/test/java/mozilla/components/concept/storage/CreditCardEntryTest.kt b/components/concept/storage/src/test/java/mozilla/components/concept/storage/CreditCardEntryTest.kt new file mode 100644 index 00000000000..86a49028704 --- /dev/null +++ b/components/concept/storage/src/test/java/mozilla/components/concept/storage/CreditCardEntryTest.kt @@ -0,0 +1,40 @@ +/* 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 mozilla.components.concept.storage + +import mozilla.components.concept.storage.CreditCard.Companion.ellipsesEnd +import mozilla.components.concept.storage.CreditCard.Companion.ellipsesStart +import mozilla.components.concept.storage.CreditCard.Companion.ellipsis +import mozilla.components.support.ktx.kotlin.last4Digits +import org.junit.Assert.assertEquals +import org.junit.Test + +class CreditCardEntryTest { + + private val creditCard = CreditCardEntry( + guid = "1", + name = "Banana Apple", + number = "4111111111111110", + expiryMonth = "5", + expiryYear = "2030", + cardType = "amex" + ) + + @Test + fun `WHEN obfuscatedCardNumber getter is called THEN the expected obfuscated card number is returned`() { + assertEquals( + ellipsesStart + + ellipsis + ellipsis + ellipsis + ellipsis + + creditCard.number.last4Digits() + + ellipsesEnd, + creditCard.obfuscatedCardNumber + ) + } + + @Test + fun `WHEN expiryDdate getter is called THEN the expected expiry date string is returned`() { + assertEquals("0${creditCard.expiryMonth}/${creditCard.expiryYear}", creditCard.expiryDate) + } +} diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt index ffbc39efc27..fced4766373 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt @@ -43,11 +43,13 @@ import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.concept.engine.prompt.PromptRequest.TextPrompt import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection import mozilla.components.concept.storage.CreditCardEntry +import mozilla.components.concept.storage.CreditCardValidationDelegate import mozilla.components.concept.storage.Login import mozilla.components.concept.storage.LoginEntry import mozilla.components.concept.storage.LoginValidationDelegate import mozilla.components.feature.prompts.concept.SelectablePromptView import mozilla.components.feature.prompts.creditcard.CreditCardPicker +import mozilla.components.feature.prompts.creditcard.CreditCardSaveDialogFragment import mozilla.components.feature.prompts.dialog.AlertDialogFragment import mozilla.components.feature.prompts.dialog.AuthenticationDialogFragment import mozilla.components.feature.prompts.dialog.ChoiceDialogFragment @@ -139,6 +141,7 @@ class PromptFeature private constructor( private var customTabId: String?, private val fragmentManager: FragmentManager, private val shareDelegate: ShareDelegate, + override val creditCardValidationDelegate: CreditCardValidationDelegate? = null, override val loginValidationDelegate: LoginValidationDelegate? = null, private val isSaveLoginEnabled: () -> Boolean = { false }, private val isCreditCardAutofillEnabled: () -> Boolean = { false }, @@ -177,6 +180,7 @@ class PromptFeature private constructor( customTabId: String? = null, fragmentManager: FragmentManager, shareDelegate: ShareDelegate = DefaultShareDelegate(), + creditCardValidationDelegate: CreditCardValidationDelegate? = null, loginValidationDelegate: LoginValidationDelegate? = null, isSaveLoginEnabled: () -> Boolean = { false }, isCreditCardAutofillEnabled: () -> Boolean = { false }, @@ -193,6 +197,7 @@ class PromptFeature private constructor( customTabId = customTabId, fragmentManager = fragmentManager, shareDelegate = shareDelegate, + creditCardValidationDelegate = creditCardValidationDelegate, loginValidationDelegate = loginValidationDelegate, isSaveLoginEnabled = isSaveLoginEnabled, isCreditCardAutofillEnabled = isCreditCardAutofillEnabled, @@ -211,6 +216,7 @@ class PromptFeature private constructor( customTabId: String? = null, fragmentManager: FragmentManager, shareDelegate: ShareDelegate = DefaultShareDelegate(), + creditCardValidationDelegate: CreditCardValidationDelegate? = null, loginValidationDelegate: LoginValidationDelegate? = null, isSaveLoginEnabled: () -> Boolean = { false }, isCreditCardAutofillEnabled: () -> Boolean = { false }, @@ -227,6 +233,7 @@ class PromptFeature private constructor( customTabId = customTabId, fragmentManager = fragmentManager, shareDelegate = shareDelegate, + creditCardValidationDelegate = creditCardValidationDelegate, loginValidationDelegate = loginValidationDelegate, isSaveLoginEnabled = isSaveLoginEnabled, isCreditCardAutofillEnabled = isCreditCardAutofillEnabled, @@ -282,6 +289,8 @@ class PromptFeature private constructor( loginPicker?.dismissCurrentLoginSelect(activePromptRequest as SelectLoginPrompt) } else if (activePromptRequest is SaveLoginPrompt) { (activePrompt?.get() as? SaveLoginDialogFragment)?.dismissAllowingStateLoss() + } else if (activePromptRequest is SaveCreditCard) { + (activePrompt?.get() as? CreditCardSaveDialogFragment)?.dismissAllowingStateLoss() } else if (activePromptRequest is SelectCreditCard) { creditCardPicker?.dismissSelectCreditCardRequest( activePromptRequest as SelectCreditCard @@ -490,6 +499,7 @@ class PromptFeature private constructor( is Share -> it.onSuccess() + is SaveCreditCard -> it.onConfirm(value as CreditCardEntry) is SaveLoginPrompt -> it.onConfirm(value as LoginEntry) is Confirm -> { @@ -557,6 +567,9 @@ class PromptFeature private constructor( ) } + /** + * Called from on [onPromptRequested] to handle requests for showing native dialogs. + */ @Suppress("ComplexMethod", "LongMethod") @VisibleForTesting(otherwise = PRIVATE) internal fun handleDialogsRequest( @@ -565,16 +578,41 @@ class PromptFeature private constructor( ) { // Requests that are handled with dialogs val dialog = when (promptRequest) { + is SaveCreditCard -> { + if (!isCreditCardAutofillEnabled.invoke() || creditCardValidationDelegate == null) { + dismissDialogRequest(promptRequest, session) + + if (creditCardValidationDelegate == null) { + logger.debug( + "Ignoring received SaveCreditCard because PromptFeature." + + "creditCardValidationDelegate is null. If you are trying to autofill " + + "credit cards, try attaching a CreditCardValidationDelegate to PromptFeature" + ) + } + + return + } + + CreditCardSaveDialogFragment.newInstance( + sessionId = session.id, + promptRequestUID = promptRequest.uid, + shouldDismissOnLoad = false, + creditCard = promptRequest.creditCard + ) + } is SaveLoginPrompt -> { - if (!isSaveLoginEnabled.invoke()) return + if (!isSaveLoginEnabled.invoke() || loginValidationDelegate == null) { + dismissDialogRequest(promptRequest, session) + + if (loginValidationDelegate == null) { + logger.debug( + "Ignoring received SaveLoginPrompt because PromptFeature." + + "loginValidationDelegate is null. If you are trying to autofill logins, " + + "try attaching a LoginValidationDelegate to PromptFeature" + ) + } - if (loginValidationDelegate == null) { - logger.debug( - "Ignoring received SaveLoginPrompt because PromptFeature." + - "loginValidationDelegate is null. If you are trying to autofill logins, " + - "try attaching a LoginValidationDelegate to PromptFeature" - ) return } @@ -773,12 +811,20 @@ class PromptFeature private constructor( activePromptsToDismiss.add(dialog) } } else { - (promptRequest as Dismissible).onDismiss() - store.dispatch(ContentAction.ConsumePromptRequestAction(session.id, promptRequest)) + dismissDialogRequest(promptRequest, session) } promptAbuserDetector.updateJSDialogAbusedState() } + /** + * Dismiss and consume the given prompt request for the session. + */ + @VisibleForTesting + internal fun dismissDialogRequest(promptRequest: PromptRequest, session: SessionState) { + (promptRequest as Dismissible).onDismiss() + store.dispatch(ContentAction.ConsumePromptRequestAction(session.id, promptRequest)) + } + private fun canShowThisPrompt(promptRequest: PromptRequest): Boolean { return when (promptRequest) { is SingleChoice, diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt index e4ffe121c80..a1a89840538 100644 --- a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardItemViewHolder.kt @@ -11,9 +11,6 @@ import androidx.recyclerview.widget.RecyclerView import mozilla.components.concept.storage.CreditCardEntry import mozilla.components.feature.prompts.R import mozilla.components.support.utils.creditCardIssuerNetwork -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale /** * View holder for displaying a credit card item. @@ -37,33 +34,15 @@ class CreditCardItemViewHolder( itemView.findViewById(R.id.credit_card_number).text = creditCard.obfuscatedCardNumber - bindCreditCardExpiryDate(creditCard) + itemView.findViewById(R.id.credit_card_expiration_date).text = + creditCard.expiryDate itemView.setOnClickListener { onCreditCardSelected(creditCard) } } - /** - * Set the credit card expiry date formatted according to the locale. - */ - private fun bindCreditCardExpiryDate(creditCard: CreditCardEntry) { - val dateFormat = SimpleDateFormat(DATE_PATTERN, Locale.getDefault()) - - val calendar = Calendar.getInstance() - calendar.set(Calendar.DAY_OF_MONTH, 1) - // Subtract 1 from the expiry month since Calendar.Month is based on a 0-indexed. - calendar.set(Calendar.MONTH, creditCard.expiryMonth.toInt() - 1) - calendar.set(Calendar.YEAR, creditCard.expiryYear.toInt()) - - itemView.findViewById(R.id.credit_card_expiration_date).text = - dateFormat.format(calendar.time) - } - companion object { val LAYOUT_ID = R.layout.mozac_feature_prompts_credit_card_list_item - - // Date format pattern for the credit card expiry date. - private const val DATE_PATTERN = "MM/yyyy" } } diff --git a/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSaveDialogFragment.kt b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSaveDialogFragment.kt new file mode 100644 index 00000000000..1e84328a24e --- /dev/null +++ b/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/creditcard/CreditCardSaveDialogFragment.kt @@ -0,0 +1,179 @@ +/* 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 mozilla.components.feature.prompts.creditcard + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.VisibleForTesting +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.view.isVisible +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import mozilla.components.concept.storage.CreditCardEntry +import mozilla.components.concept.storage.CreditCardValidationDelegate.Result +import mozilla.components.feature.prompts.R +import mozilla.components.feature.prompts.dialog.KEY_PROMPT_UID +import mozilla.components.feature.prompts.dialog.KEY_SESSION_ID +import mozilla.components.feature.prompts.dialog.KEY_SHOULD_DISMISS_ON_LOAD +import mozilla.components.feature.prompts.dialog.PromptDialogFragment +import mozilla.components.support.ktx.android.view.toScope +import mozilla.components.support.utils.creditCardIssuerNetwork + +private const val KEY_CREDIT_CARD = "KEY_CREDIT_CARD" + +/** + * [android.support.v4.app.DialogFragment] implementation to display a dialog that allows + * user to save a new credit card or update an existing credit card. + */ +internal class CreditCardSaveDialogFragment : PromptDialogFragment() { + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal val creditCard by lazy { safeArguments.getParcelable(KEY_CREDIT_CARD)!! } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return BottomSheetDialog(requireContext(), R.style.MozDialogStyle).apply { + setCancelable(true) + setOnShowListener { + val bottomSheet = + findViewById(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout + val behavior = BottomSheetBehavior.from(bottomSheet) + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return LayoutInflater.from(requireContext()).inflate( + R.layout.mozac_feature_prompt_save_credit_card_prompt, + container, + false + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + view.findViewById(R.id.credit_card_logo) + .setImageResource(creditCard.cardType.creditCardIssuerNetwork().icon) + + view.findViewById(R.id.credit_card_number).text = creditCard.obfuscatedCardNumber + view.findViewById(R.id.credit_card_expiration_date).text = creditCard.expiryDate + + view.findViewById