tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit b67d50e6454ca5d117468ac75267a4a147e8b25d
parent 8d41135430152398012691936d1e53fe0b828b64
Author: mcarare <48995920+mcarare@users.noreply.github.com>
Date:   Wed, 10 Dec 2025 11:01:42 +0000

Bug 2004035 - Refactor tests to use StandardTestDispatcher instead of Dispatchers.Main. r=android-reviewers,android-addons-reviewers,giorga,willdurand

This patch replaces the usage of `MainCoroutineRule`, `runTestOnMain`, and manual dispatcher injection in tests with `StandardTestDispatcher` and `runTest` from `kotlinx-coroutines-test`.

It also introduces `SupervisorJob` to coroutine scopes in `AbstractFirebasePushService`, `DefaultAddonUpdater`, and `BaseDomainAutocompleteProvider` to prevent the entire scope from being canceled when a child coroutine fails.

Differential Revision: https://phabricator.services.mozilla.com/D275198

Diffstat:
Mmobile/android/android-components/components/browser/domains/src/main/java/mozilla/components/browser/domains/autocomplete/Providers.kt | 8++++++--
Mmobile/android/android-components/components/browser/domains/src/test/java/mozilla/components/browser/domains/BaseDomainAutocompleteProviderTest.kt | 282++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mmobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoTrackingProtectionExceptionStorage.kt | 3---
Mmobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoTrackingProtectionExceptionStorageTest.kt | 3---
Mmobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/update/AddonUpdater.kt | 18+++++++++++++-----
Mmobile/android/android-components/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt | 24++++++++++--------------
Mmobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/file/FileUploadsDirCleaner.kt | 10++++------
Mmobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/file/FileUploadsDirCleanerTest.kt | 21+++++++++------------
Mmobile/android/android-components/components/lib/push-firebase/build.gradle | 1+
Mmobile/android/android-components/components/lib/push-firebase/src/main/java/mozilla/components/lib/push/firebase/AbstractFirebasePushService.kt | 14++++++++++++--
Mmobile/android/android-components/components/lib/push-firebase/src/test/java/mozilla/components/lib/push/firebase/AbstractFirebasePushServiceTest.kt | 48++++++++++++++++++++++++++++++++++++++++++------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt | 13++++++++-----
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragmentTest.kt | 19+++++++++----------
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt | 227++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/search/BrowserToolbarSearchMiddlewareTest.kt | 75+++++++++++++++++++++++++++++++++++++++------------------------------------
15 files changed, 490 insertions(+), 276 deletions(-)

diff --git a/mobile/android/android-components/components/browser/domains/src/main/java/mozilla/components/browser/domains/autocomplete/Providers.kt b/mobile/android/android-components/components/browser/domains/src/main/java/mozilla/components/browser/domains/autocomplete/Providers.kt @@ -5,8 +5,10 @@ package mozilla.components.browser.domains.autocomplete import android.content.Context +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.launch import mozilla.components.browser.domains.CustomDomains @@ -49,14 +51,16 @@ open class BaseDomainAutocompleteProvider( private val list: DomainList, private val domainsLoader: DomainsLoader, override val autocompletePriority: Int = 0, -) : AutocompleteProvider, CoroutineScope by CoroutineScope(Dispatchers.IO) { + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, +) : AutocompleteProvider { + private val scope = CoroutineScope(ioDispatcher + SupervisorJob()) // We compute 'domains' on the worker thread; make sure it's immediately visible on the UI thread. @Volatile var domains: List<Domain> = emptyList() fun initialize(context: Context) { - launch { + scope.launch { domains = async { domainsLoader(context) }.await() } } diff --git a/mobile/android/android-components/components/browser/domains/src/test/java/mozilla/components/browser/domains/BaseDomainAutocompleteProviderTest.kt b/mobile/android/android-components/components/browser/domains/src/test/java/mozilla/components/browser/domains/BaseDomainAutocompleteProviderTest.kt @@ -5,9 +5,8 @@ package mozilla.components.browser.domains import android.content.Context -import android.os.Looper.getMainLooper import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runTest import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainList @@ -19,13 +18,14 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.Shadows.shadowOf @RunWith(AndroidJUnit4::class) class BaseDomainAutocompleteProviderTest { + private val testDispatcher = StandardTestDispatcher() + @Test - fun `empty provider with DEFAULT list returns nothing`() { + fun `empty provider with DEFAULT list returns nothing`() = runTest(testDispatcher) { val provider = createAndInitProvider(testContext, DomainList.DEFAULT) { emptyList() } @@ -41,7 +41,7 @@ class BaseDomainAutocompleteProviderTest { } @Test - fun `empty provider with CUSTOM list returns nothing`() { + fun `empty provider with CUSTOM list returns nothing`() = runTest(testDispatcher) { val provider = createAndInitProvider(testContext, DomainList.CUSTOM) { emptyList() } @@ -57,81 +57,245 @@ class BaseDomainAutocompleteProviderTest { } @Test - fun `non-empty provider with DEFAULT list returns completion`() { + fun `non-empty provider with DEFAULT list returns completion`() = runTest(testDispatcher) { val domains = listOf("mozilla.org", "google.com", "facebook.com").into() val list = DomainList.DEFAULT val domainsCount = domains.size val provider = createAndInitProvider(testContext, list) { domains } - shadowOf(getMainLooper()).idle() - - assertCompletion(provider, list, domainsCount, "m", "m", "mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "moz", "moz", "mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "www", "www", "www.mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "www.face", "www.face", "www.facebook.com", "http://facebook.com") - assertCompletion(provider, list, domainsCount, "M", "m", "Mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "MOZ", "moz", "MOZilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "www.GOO", "www.goo", "www.GOOgle.com", "http://google.com") - assertCompletion(provider, list, domainsCount, "WWW.GOOGLE.", "www.google.", "WWW.GOOGLE.com", "http://google.com") - assertCompletion(provider, list, domainsCount, "www.facebook.com", "www.facebook.com", "www.facebook.com", "http://facebook.com") - assertCompletion(provider, list, domainsCount, "facebook.com", "facebook.com", "facebook.com", "http://facebook.com") + + assertCompletion( + provider, + list, + domainsCount, + "m", + "m", + "mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "moz", + "moz", + "mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "www", + "www", + "www.mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "www.face", + "www.face", + "www.facebook.com", + "http://facebook.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "M", + "m", + "Mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "MOZ", + "moz", + "MOZilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "www.GOO", + "www.goo", + "www.GOOgle.com", + "http://google.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "WWW.GOOGLE.", + "www.google.", + "WWW.GOOGLE.com", + "http://google.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "www.facebook.com", + "www.facebook.com", + "www.facebook.com", + "http://facebook.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "facebook.com", + "facebook.com", + "facebook.com", + "http://facebook.com", + ) assertNoCompletion(provider, "wwww") assertNoCompletion(provider, "yahoo") } @Test - fun `non-empty provider with CUSTOM list returns completion`() { + fun `non-empty provider with CUSTOM list returns completion`() = runTest(testDispatcher) { val domains = listOf("mozilla.org", "google.com", "facebook.com").into() val list = DomainList.CUSTOM val domainsCount = domains.size val provider = createAndInitProvider(testContext, list) { domains } - shadowOf(getMainLooper()).idle() - - assertCompletion(provider, list, domainsCount, "m", "m", "mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "moz", "moz", "mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "www", "www", "www.mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "www.face", "www.face", "www.facebook.com", "http://facebook.com") - assertCompletion(provider, list, domainsCount, "M", "m", "Mozilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "MOZ", "moz", "MOZilla.org", "http://mozilla.org") - assertCompletion(provider, list, domainsCount, "www.GOO", "www.goo", "www.GOOgle.com", "http://google.com") - assertCompletion(provider, list, domainsCount, "WWW.GOOGLE.", "www.google.", "WWW.GOOGLE.com", "http://google.com") - assertCompletion(provider, list, domainsCount, "www.facebook.com", "www.facebook.com", "www.facebook.com", "http://facebook.com") - assertCompletion(provider, list, domainsCount, "facebook.com", "facebook.com", "facebook.com", "http://facebook.com") + + assertCompletion( + provider, + list, + domainsCount, + "m", + "m", + "mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "moz", + "moz", + "mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "www", + "www", + "www.mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "www.face", + "www.face", + "www.facebook.com", + "http://facebook.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "M", + "m", + "Mozilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "MOZ", + "moz", + "MOZilla.org", + "http://mozilla.org", + ) + assertCompletion( + provider, + list, + domainsCount, + "www.GOO", + "www.goo", + "www.GOOgle.com", + "http://google.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "WWW.GOOGLE.", + "www.google.", + "WWW.GOOGLE.com", + "http://google.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "www.facebook.com", + "www.facebook.com", + "www.facebook.com", + "http://facebook.com", + ) + assertCompletion( + provider, + list, + domainsCount, + "facebook.com", + "facebook.com", + "facebook.com", + "http://facebook.com", + ) assertNoCompletion(provider, "wwww") assertNoCompletion(provider, "yahoo") } -} -private fun assertCompletion( - provider: AutocompleteProvider, - domainSource: DomainList, - sourceSize: Int, - input: String, - expectedInput: String, - completion: String, - expectedUrl: String, -) = runTest { - val result = provider.getAutocompleteSuggestion(input)!! - - assertTrue("Autocompletion shouldn't be empty", result.text.isNotEmpty()) - - assertEquals("Autocompletion input", expectedInput, result.input) - assertEquals("Autocompletion completion", completion, result.text) - assertEquals("Autocompletion source list", domainSource.listName, result.source) - assertEquals("Autocompletion url", expectedUrl, result.url) - assertEquals("Autocompletion source list size", sourceSize, result.totalItems) -} + private fun assertCompletion( + provider: AutocompleteProvider, + domainSource: DomainList, + sourceSize: Int, + input: String, + expectedInput: String, + completion: String, + expectedUrl: String, + ) = runTest { + val result = provider.getAutocompleteSuggestion(input)!! -private fun assertNoCompletion(provider: AutocompleteProvider, input: String) = runTest { - val result = provider.getAutocompleteSuggestion(input) + assertTrue("Autocompletion shouldn't be empty", result.text.isNotEmpty()) - assertNull("Result should be null", result) -} + assertEquals("Autocompletion input", expectedInput, result.input) + assertEquals("Autocompletion completion", completion, result.text) + assertEquals("Autocompletion source list", domainSource.listName, result.source) + assertEquals("Autocompletion url", expectedUrl, result.url) + assertEquals("Autocompletion source list size", sourceSize, result.totalItems) + } + + private fun assertNoCompletion(provider: AutocompleteProvider, input: String) = runTest { + val result = provider.getAutocompleteSuggestion(input) + + assertNull("Result should be null", result) + } -private fun createAndInitProvider(context: Context, list: DomainList, loader: DomainsLoader): AutocompleteProvider = - object : BaseDomainAutocompleteProvider(list, loader) { - override val coroutineContext = super.coroutineContext + Dispatchers.Main - }.apply { initialize(context) } + private fun createAndInitProvider( + context: Context, + list: DomainList, + loader: DomainsLoader, + ): AutocompleteProvider = + object : BaseDomainAutocompleteProvider(list, loader, ioDispatcher = testDispatcher) { + }.apply { + initialize(context) + testDispatcher.scheduler.advanceUntilIdle() + } +} diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoTrackingProtectionExceptionStorage.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoTrackingProtectionExceptionStorage.kt @@ -5,8 +5,6 @@ package mozilla.components.browser.engine.gecko import androidx.annotation.VisibleForTesting -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import mozilla.components.browser.engine.gecko.content.blocking.GeckoTrackingProtectionException import mozilla.components.browser.engine.gecko.ext.geckoTrackingProtectionPermission import mozilla.components.browser.engine.gecko.ext.isExcludedForTrackingProtection @@ -26,7 +24,6 @@ import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission.V internal class GeckoTrackingProtectionExceptionStorage( private val runtime: GeckoRuntime, ) : TrackingProtectionExceptionStorage { - internal var scope = CoroutineScope(Dispatchers.IO) override fun contains(session: EngineSession, onResult: (Boolean) -> Unit) { val url = (session as GeckoEngineSession).currentUrl diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoTrackingProtectionExceptionStorageTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoTrackingProtectionExceptionStorageTest.kt @@ -5,8 +5,6 @@ package mozilla.components.browser.engine.gecko import android.os.Looper.getMainLooper import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import mozilla.components.browser.engine.gecko.content.blocking.GeckoTrackingProtectionException import mozilla.components.browser.engine.gecko.permission.geckoContentPermission import mozilla.components.concept.engine.EngineSession @@ -45,7 +43,6 @@ class GeckoTrackingProtectionExceptionStorageTest { runtime = mock() whenever(runtime.settings).thenReturn(mock()) storage = spy(GeckoTrackingProtectionExceptionStorage(runtime)) - storage.scope = CoroutineScope(Dispatchers.Main) } @Test diff --git a/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/update/AddonUpdater.kt b/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/update/AddonUpdater.kt @@ -32,8 +32,10 @@ import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.components.concept.engine.webextension.WebExtension @@ -147,21 +149,27 @@ interface AddonUpdater { } /** - * An implementation of [AddonUpdater] that uses the work manager api for scheduling new updates. + * An implementation of [AddonUpdater] that uses [WorkManager] for scheduling add-on updates. + * + * This class handles both periodic checks for updates and immediate update requests. + * + * When an update requires new permissions, it presents a system notification to the user. + * The update flow is then paused until the user either grants or denies the new permissions via the notification. + * * @property applicationContext The application context. - * @param frequency (Optional) indicates how often updates should be performed, defaults - * to one day. + * @param frequency How often periodic updates should be checked for. Defaults to once a day. + * @param notificationsDelegate The delegate responsible for posting system notifications. */ @Suppress("LargeClass") class DefaultAddonUpdater( private val applicationContext: Context, private val frequency: Frequency = Frequency(1, TimeUnit.DAYS), private val notificationsDelegate: NotificationsDelegate, + ioDispatcher: CoroutineDispatcher = Dispatchers.IO, ) : AddonUpdater { private val logger = Logger("DefaultAddonUpdater") - @VisibleForTesting - internal var scope = CoroutineScope(Dispatchers.IO) + private val scope = CoroutineScope(ioDispatcher + SupervisorJob()) @VisibleForTesting internal val updateStatusStorage = UpdateStatusStorage() diff --git a/mobile/android/android-components/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt b/mobile/android/android-components/components/feature/addons/src/test/java/mozilla/components/feature/addons/update/DefaultAddonUpdaterTest.kt @@ -18,8 +18,8 @@ import androidx.work.testing.WorkManagerTestInitHelper import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension import mozilla.components.concept.engine.webextension.DisabledFlags import mozilla.components.concept.engine.webextension.Metadata @@ -33,11 +33,8 @@ import mozilla.components.support.base.android.NotificationsDelegate 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 import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers @@ -49,8 +46,7 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class DefaultAddonUpdaterTest { - @get:Rule - val coroutinesTestRule = MainCoroutineRule() + private val testDispatcher = StandardTestDispatcher() @Before fun setUp() { @@ -61,7 +57,7 @@ class DefaultAddonUpdaterTest { } @Test - fun `registerForFutureUpdates - schedule work for future update`() = runTestOnMain { + fun `registerForFutureUpdates - schedule work for future update`() = runTest(testDispatcher) { val frequency = Frequency(1, TimeUnit.DAYS) val updater = DefaultAddonUpdater(testContext, frequency, mock()) val addonId = "addonId" @@ -85,7 +81,7 @@ class DefaultAddonUpdaterTest { } @Test - fun `update - schedule work for immediate update`() = runTestOnMain { + fun `update - schedule work for immediate update`() = runTest(testDispatcher) { val updater = DefaultAddonUpdater( testContext, notificationsDelegate = mock(), @@ -406,10 +402,9 @@ class DefaultAddonUpdaterTest { } @Test - fun `unregisterForFutureUpdates - will remove scheduled work for future update`() = runTestOnMain { + fun `unregisterForFutureUpdates - will remove scheduled work for future update`() = runTest(testDispatcher) { val frequency = Frequency(1, TimeUnit.DAYS) - val updater = DefaultAddonUpdater(testContext, frequency, mock()) - updater.scope = CoroutineScope(Dispatchers.Main) + val updater = DefaultAddonUpdater(testContext, frequency, mock(), testDispatcher) val addonId = "addonId" @@ -430,6 +425,7 @@ class DefaultAddonUpdaterTest { assertExtensionIsRegisteredFoUpdates(updater, addonId) updater.unregisterForFutureUpdates(addonId) + testDispatcher.scheduler.advanceUntilIdle() workData = workManager.getWorkInfosForUniqueWork(workId).await() assertEquals(WorkInfo.State.CANCELLED, workData.first().state) @@ -455,7 +451,7 @@ class DefaultAddonUpdaterTest { } @Test - fun `registerForFutureUpdates - will register only unregistered extensions`() = runTestOnMain { + fun `registerForFutureUpdates - will register only unregistered extensions`() = runTest(testDispatcher) { val updater = DefaultAddonUpdater( testContext, notificationsDelegate = mock(), @@ -479,7 +475,7 @@ class DefaultAddonUpdaterTest { } @Test - fun `registerForFutureUpdates - will not register built-in and unsupported extensions`() = runTestOnMain { + fun `registerForFutureUpdates - will not register built-in and unsupported extensions`() = runTest(testDispatcher) { val updater = DefaultAddonUpdater( testContext, notificationsDelegate = mock(), diff --git a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/file/FileUploadsDirCleaner.kt b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/file/FileUploadsDirCleaner.kt @@ -23,6 +23,7 @@ import java.io.IOException @OptIn(DelicateCoroutinesApi::class) class FileUploadsDirCleaner( private val scope: CoroutineScope = GlobalScope, + private val ioDispatcher: CoroutineDispatcher = IO, private val cacheDirectory: () -> File, ) { private val logger = Logger("FileUploadsDirCleaner") @@ -32,9 +33,6 @@ class FileUploadsDirCleaner( @VisibleForTesting internal var fileNamesToBeDeleted: List<String> = emptyList() - @VisibleForTesting - internal var dispatcher: CoroutineDispatcher = IO - /** * Enqueue the [fileName] for future clean up. */ @@ -56,7 +54,7 @@ class FileUploadsDirCleaner( @VisibleForTesting internal fun performCleanRecentUploads() { - scope.launch(dispatcher) { + scope.launch(ioDispatcher) { val cacheUploadDirectory = File(getCacheDir(), DEFAULT_UPLOADS_DIR_NAME) fileNamesToBeDeleted = fileNamesToBeDeleted.filter { fileName -> try { @@ -75,7 +73,7 @@ class FileUploadsDirCleaner( * Remove the file uploads directory if exists. */ suspend fun cleanUploadsDirectory() { - withContext(dispatcher) { + withContext(ioDispatcher) { val cacheUploadDirectory = File(getCacheDir(), DEFAULT_UPLOADS_DIR_NAME) if (cacheUploadDirectory.exists()) { // To not collide with users uploading while, we are cleaning @@ -89,7 +87,7 @@ class FileUploadsDirCleaner( } } - private suspend fun getCacheDir(): File = withContext(dispatcher) { + private suspend fun getCacheDir(): File = withContext(ioDispatcher) { cacheDir } } diff --git a/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/file/FileUploadsDirCleanerTest.kt b/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/file/FileUploadsDirCleanerTest.kt @@ -5,17 +5,15 @@ package mozilla.components.feature.prompts.file import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import mozilla.components.concept.engine.prompt.PromptRequest.File.Companion.DEFAULT_UPLOADS_DIR_NAME 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 import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.io.File @@ -23,19 +21,17 @@ import java.io.File @RunWith(AndroidJUnit4::class) class FileUploadsDirCleanerTest { private lateinit var fileCleaner: FileUploadsDirCleaner - - @get:Rule - val coroutinesTestRule = MainCoroutineRule() + private val testDispatcher = StandardTestDispatcher() @Before fun setup() { fileCleaner = FileUploadsDirCleaner( - scope = TestScope(), + scope = CoroutineScope(testDispatcher), + ioDispatcher = testDispatcher, ) { testContext.cacheDir } fileCleaner.fileNamesToBeDeleted = emptyList() - fileCleaner.dispatcher = Dispatchers.Main } @Test @@ -47,7 +43,7 @@ class FileUploadsDirCleanerTest { @Test fun `WHEN calling cleanRecentUploads THEN all the enqueued files should be deleted and not enqueued files must be kept`() = - runTestOnMain { + runTest(testDispatcher) { val cachedDir = File(testContext.cacheDir, DEFAULT_UPLOADS_DIR_NAME) assertTrue(cachedDir.mkdir()) @@ -62,6 +58,7 @@ class FileUploadsDirCleanerTest { fileCleaner.enqueueForCleanup(fileToBeDeleted.name) fileCleaner.cleanRecentUploads() + testDispatcher.scheduler.advanceUntilIdle() assertTrue(fileCleaner.fileNamesToBeDeleted.isEmpty()) assertFalse(fileToBeDeleted.exists()) @@ -70,7 +67,7 @@ class FileUploadsDirCleanerTest { @Test fun `WHEN calling cleanUploadsDirectory THEN the uploads directory should emptied`() = - runTestOnMain { + runTest(testDispatcher) { val cachedDir = File(testContext.cacheDir, DEFAULT_UPLOADS_DIR_NAME) assertTrue(cachedDir.mkdir()) diff --git a/mobile/android/android-components/components/lib/push-firebase/build.gradle b/mobile/android/android-components/components/lib/push-firebase/build.gradle @@ -21,6 +21,7 @@ dependencies { testImplementation project(':components:support-test') testImplementation libs.androidx.test.core + testImplementation libs.kotlinx.coroutines.test testImplementation platform(libs.junit.bom) testImplementation libs.junit4 testRuntimeOnly libs.junit.platform.launcher diff --git a/mobile/android/android-components/components/lib/push-firebase/src/main/java/mozilla/components/lib/push/firebase/AbstractFirebasePushService.kt b/mobile/android/android-components/components/lib/push-firebase/src/main/java/mozilla/components/lib/push/firebase/AbstractFirebasePushService.kt @@ -14,6 +14,8 @@ import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import mozilla.components.concept.push.PushError import mozilla.components.concept.push.PushProcessor @@ -30,6 +32,7 @@ abstract class AbstractFirebasePushService( internal val coroutineContext: CoroutineContext = Dispatchers.IO, ) : FirebaseMessagingService(), PushService { + private val scope = CoroutineScope(coroutineContext + SupervisorJob()) private val logger = Logger("AbstractFirebasePushService") @VisibleForTesting @@ -88,9 +91,9 @@ abstract class AbstractFirebasePushService( * service hits the Firebase servers. */ override fun deleteToken() { - CoroutineScope(coroutineContext).launch { + scope.launch { try { - FirebaseMessaging.getInstance().deleteToken() + getFirebaseMessaging().deleteToken() } catch (e: IOException) { logger.error("Force registration renewable failed.", e) } @@ -100,4 +103,11 @@ abstract class AbstractFirebasePushService( override fun isServiceAvailable(context: Context): Boolean { return googleApiAvailability.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS } + + override fun onDestroy() { + super.onDestroy() + scope.cancel() + } + + protected open fun getFirebaseMessaging(): FirebaseMessaging = FirebaseMessaging.getInstance() } diff --git a/mobile/android/android-components/components/lib/push-firebase/src/test/java/mozilla/components/lib/push/firebase/AbstractFirebasePushServiceTest.kt b/mobile/android/android-components/components/lib/push-firebase/src/test/java/mozilla/components/lib/push/firebase/AbstractFirebasePushServiceTest.kt @@ -6,13 +6,16 @@ package mozilla.components.lib.push.firebase import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability +import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.RemoteMessage -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import mozilla.components.concept.push.PushProcessor import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -31,10 +34,13 @@ class AbstractFirebasePushServiceTest { private val processor: PushProcessor = mock() private val service = TestService() + private val mockMessaging: FirebaseMessaging = mock() + @Before fun setup() { reset(processor) PushProcessor.install(processor) + `when`(mockMessaging.deleteToken()).thenReturn(mock()) } @Test @@ -89,12 +95,42 @@ class AbstractFirebasePushServiceTest { } @Test - fun `force registration should never be on Main`() { - // Default dispatcher isn't main - assertTrue(service.coroutineContext != Dispatchers.Main) + fun `service is initialized with correct default background dispatcher`() = runTest { + val backgroundService = object : AbstractFirebasePushService() { + override fun getFirebaseMessaging(): FirebaseMessaging { + return mockMessaging + } + } + assertNotNull("Dispatcher should be present", backgroundService.coroutineContext) + + assertFalse( + "Default dispatcher should not be a Main dispatcher", + backgroundService.coroutineContext is kotlinx.coroutines.MainCoroutineDispatcher, + ) + } + + @Test + fun `service is initialized with correct background dispatcher`() = runTest { + val testDispatcher = StandardTestDispatcher() + + val backgroundService = object : AbstractFirebasePushService(testDispatcher) { + override fun getFirebaseMessaging(): FirebaseMessaging { + return mockMessaging + } + } + + assertTrue( + "Service context should use the provided dispatcher", + backgroundService.coroutineContext == testDispatcher, + ) + + backgroundService.deleteToken() + + verify(mockMessaging, never()).deleteToken() + + testDispatcher.scheduler.advanceUntilIdle() - val service = object : AbstractFirebasePushService(Dispatchers.Default) {} - service.deleteToken() + verify(mockMessaging).deleteToken() } @Test diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt @@ -98,8 +98,11 @@ class InstalledAddonDetailsFragment : Fragment() { internal fun provideAddonManager() = requireContext().components.addonManager @VisibleForTesting - internal fun bindAddon(dispatchers: CoroutineDispatcher = Dispatchers.IO) { - lifecycleScope.launch(dispatchers) { + internal fun bindAddon( + ioDispatcher: CoroutineDispatcher = Dispatchers.IO, + mainDispatcher: CoroutineDispatcher = Dispatchers.Main, + ) { + lifecycleScope.launch(ioDispatcher) { // Only needed in case we are not able to find the add-on. var breadcrumb: Breadcrumb? = null try { @@ -111,12 +114,12 @@ class InstalledAddonDetailsFragment : Fragment() { ) throw AddonManagerException(Exception("Addon ${addon.id} not found")) } else { - withContext(Dispatchers.Main) { + withContext(mainDispatcher) { addon = latestAddon bindUI() } } - withContext(Dispatchers.Main) { + withContext(mainDispatcher) { binding.addOnProgressBar.isVisible = false binding.addonContainer.isVisible = true } @@ -127,7 +130,7 @@ class InstalledAddonDetailsFragment : Fragment() { crashReporter?.recordCrashBreadcrumb(it) } crashReporter?.submitCaughtException(e) - lifecycleScope.launch(Dispatchers.Main) { + lifecycleScope.launch(mainDispatcher) { logger.error("Unable to bind addon", e) showUnableToQueryAddonsMessage() } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragmentTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragmentTest.kt @@ -19,7 +19,8 @@ import io.mockk.mockk import io.mockk.slot import io.mockk.spyk import io.mockk.verify -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import mozilla.components.concept.base.profiler.Profiler import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.webextension.EnableSource @@ -29,12 +30,9 @@ import mozilla.components.feature.search.SearchUseCases import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.tabs.TabsUseCases 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 -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.BuildConfig @@ -52,8 +50,7 @@ import mozilla.components.feature.addons.R as addonsR @RunWith(RobolectricTestRunner::class) class InstalledAddonDetailsFragmentTest { - @get:Rule - val coroutineRule = MainCoroutineRule() + private val testDispatcher = StandardTestDispatcher() private lateinit var fragment: InstalledAddonDetailsFragment private lateinit var addNewTabUseCase: TabsUseCases.AddNewTabUseCase private lateinit var loadUrlUseCase: SessionUseCases.DefaultLoadUrlUseCase @@ -288,7 +285,7 @@ class InstalledAddonDetailsFragmentTest { } @Test - fun `GIVEN a not found addon WHEN binding THEN show an error message`() = runTestOnMain { + fun `GIVEN a not found addon WHEN binding THEN show an error message`() = runTest(testDispatcher) { val addon = mockAddon() coEvery { addonManager.getAddonByID(any()) } returns null @@ -308,14 +305,15 @@ class InstalledAddonDetailsFragmentTest { false, ) - fragment.bindAddon(Dispatchers.Main) + fragment.bindAddon(testDispatcher, testDispatcher) + testDispatcher.scheduler.advanceUntilIdle() verify { fragment.showUnableToQueryAddonsMessage() } verify(exactly = 0) { fragment.bindUI() } } @Test - fun `GIVEN an addon WHEN binding THEN bind the UI`() = runTestOnMain { + fun `GIVEN an addon WHEN binding THEN bind the UI`() = runTest(testDispatcher) { val addon = mockAddon() coEvery { addonManager.getAddonByID(any()) } returns addon @@ -334,7 +332,8 @@ class InstalledAddonDetailsFragmentTest { false, ) - fragment.bindAddon(Dispatchers.Main) + fragment.bindAddon(testDispatcher, testDispatcher) + testDispatcher.scheduler.advanceUntilIdle() verify { fragment.bindUI() } verify(exactly = 0) { fragment.showUnableToQueryAddonsMessage() } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt @@ -17,8 +17,7 @@ import io.mockk.slot import io.mockk.spyk import io.mockk.verify import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.ContentAction @@ -86,7 +85,6 @@ import mozilla.components.support.ktx.util.URLStringUtils 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.MainLooperTestRule import mozilla.components.support.utils.ClipboardHandler import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -169,12 +167,13 @@ import mozilla.components.ui.tabcounter.R as tabcounterR @RunWith(AndroidJUnit4::class) class BrowserToolbarMiddlewareTest { - @get:Rule - val mainLooperRule = MainLooperTestRule() @get:Rule val gleanRule = FenixGleanTestRule(testContext) + private val testDispatcher = StandardTestDispatcher() + private val testScope = CoroutineScope(testDispatcher) + private val searchEngine: SearchEngine = fakeSearchState().customSearchEngines.first() private val browserScreenState: BrowserScreenState = mockk(relaxed = true) private val browserScreenStore: BrowserScreenStore = mockk(relaxed = true) { @@ -218,7 +217,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `WHEN initializing the toolbar THEN add browser start actions`() = runTest { + fun `WHEN initializing the toolbar THEN add browser start actions`() = runTest(testDispatcher) { val toolbarStore = buildStore() val toolbarBrowserActions = toolbarStore.state.displayState.browserActionsStart @@ -226,7 +225,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `WHEN initializing the toolbar THEN add browser end actions`() = runTest { + fun `WHEN initializing the toolbar THEN add browser end actions`() = runTest(testDispatcher) { val toolbarStore = buildStore() val toolbarBrowserActions = toolbarStore.state.displayState.browserActionsEnd assertEquals(3, toolbarBrowserActions.size) @@ -239,7 +238,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `WHEN initializing the toolbar on bottom THEN add browser end actions`() = runTest { + fun `WHEN initializing the toolbar on bottom THEN add browser end actions`() = runTest(testDispatcher) { every { settings.toolbarPosition } returns ToolbarPosition.BOTTOM val toolbarStore = buildStore() val toolbarBrowserActions = toolbarStore.state.displayState.browserActionsEnd @@ -253,7 +252,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN normal browsing mode WHEN initializing the toolbar THEN show the number of normal tabs in the tabs counter button`() = runTest { + fun `GIVEN normal browsing mode WHEN initializing the toolbar THEN show the number of normal tabs in the tabs counter button`() = runTest(testDispatcher) { val browsingModeManager = SimpleBrowsingModeManager(Normal) val browserStore = BrowserStore( initialState = BrowserState( @@ -270,7 +269,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN private browsing mode WHEN initializing the toolbar THEN show the number of private tabs in the tabs counter button`() = runTest { + fun `GIVEN private browsing mode WHEN initializing the toolbar THEN show the number of private tabs in the tabs counter button`() = runTest(testDispatcher) { val browsingModeManager = SimpleBrowsingModeManager(Private) val browserStore = BrowserStore( initialState = BrowserState( @@ -314,7 +313,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN ABOUT_HOME URL WHEN the page origin is modified THEN update the page origin`() = runTest { + fun `GIVEN ABOUT_HOME URL WHEN the page origin is modified THEN update the page origin`() = runTest(testDispatcher) { val tab = createTab("https://mozilla.com/") val browserStore = BrowserStore( BrowserState( @@ -335,7 +334,8 @@ class BrowserToolbarMiddlewareTest { assertEqualsOrigin(pageOrigin, toolbarStore.state.displayState.pageOrigin) browserStore.dispatch(UpdateUrlAction(sessionId = tab.id, url = ABOUT_HOME_URL)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() + testDispatcher.scheduler.advanceUntilIdle() assertEqualsOrigin( pageOrigin.copy( @@ -346,7 +346,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN narrow window WHEN changing to wide window THEN keep browser end actions`() = runTest { + fun `GIVEN narrow window WHEN changing to wide window THEN keep browser end actions`() = runTest(testDispatcher) { val appStore = AppStore( initialState = AppState( orientation = Portrait, @@ -367,7 +367,7 @@ class BrowserToolbarMiddlewareTest { isWideScreen = true appStore.dispatch(AppAction.OrientationChange(Landscape)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() toolbarBrowserActions = toolbarStore.state.displayState.browserActionsEnd assertEquals(3, toolbarBrowserActions.size) @@ -380,7 +380,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN wide window WHEN changing to narrow window THEN keep all browser end actions`() = runTest { + fun `GIVEN wide window WHEN changing to narrow window THEN keep all browser end actions`() = runTest(testDispatcher) { val appStore = AppStore( initialState = AppState( orientation = Landscape, @@ -408,14 +408,14 @@ class BrowserToolbarMiddlewareTest { isWideScreen = true appStore.dispatch(AppAction.OrientationChange(Portrait)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() toolbarBrowserActions = toolbarStore.state.displayState.browserActionsEnd assertEquals(3, toolbarBrowserActions.size) } @Test - fun `GIVEN in normal browsing WHEN the number of normal opened tabs is modified THEN update the tab counter`() = runTest { + fun `GIVEN in normal browsing WHEN the number of normal opened tabs is modified THEN update the tab counter`() = runTest(testDispatcher) { val browsingModeManager = SimpleBrowsingModeManager(Normal) val browserStore = BrowserStore() val middleware = buildMiddleware(browserStore = browserStore, browsingModeManager = browsingModeManager) @@ -431,7 +431,7 @@ class BrowserToolbarMiddlewareTest { val newPrivateTab = createTab("test.com", private = true) browserStore.dispatch(AddTabAction(newNormalTab)) browserStore.dispatch(AddTabAction(newPrivateTab)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() toolbarBrowserActions = toolbarStore.state.displayState.browserActionsEnd assertEquals(3, toolbarBrowserActions.size) @@ -440,7 +440,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN in private browsing WHEN the number of private opened tabs is modified THEN update the tab counter`() = runTest { + fun `GIVEN in private browsing WHEN the number of private opened tabs is modified THEN update the tab counter`() = runTest(testDispatcher) { val browsingModeManager = SimpleBrowsingModeManager(Private) val initialNormalTab = createTab("test.com", private = false) val initialPrivateTab = createTab("test.com", private = true) @@ -459,7 +459,7 @@ class BrowserToolbarMiddlewareTest { assertEqualsTabCounterButton(expectedTabCounterButton(1, true), tabCounterButton) browserStore.dispatch(RemoveTabAction(initialPrivateTab.id)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() toolbarBrowserActions = toolbarStore.state.displayState.browserActionsEnd assertEquals(3, toolbarBrowserActions.size) @@ -843,7 +843,7 @@ class BrowserToolbarMiddlewareTest { browsingModeManager = browsingModeManager, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val tabCounterButton = toolbarStore.state.displayState.browserActionsEnd[1] as TabCounterAction assertEqualsTabCounterButton(expectedTabCounterButton(2, true), tabCounterButton) @@ -876,7 +876,7 @@ class BrowserToolbarMiddlewareTest { browserStore = browserStore, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val tabCounterButton = toolbarStore.state.displayState.browserActionsEnd[1] as TabCounterAction assertEqualsTabCounterButton(expectedTabCounterButton(1, false), tabCounterButton) @@ -916,7 +916,7 @@ class BrowserToolbarMiddlewareTest { browsingModeManager = browsingModeManager, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val tabCounterButton = toolbarStore.state.displayState.browserActionsEnd[1] as TabCounterAction assertEqualsTabCounterButton(expectedTabCounterButton(1, true), tabCounterButton) @@ -962,7 +962,7 @@ class BrowserToolbarMiddlewareTest { browsingModeManager = browsingModeManager, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val tabCounterButton = toolbarStore.state.displayState.browserActionsEnd[1] as TabCounterAction assertEqualsTabCounterButton(expectedTabCounterButton(1, true), tabCounterButton) @@ -1014,7 +1014,7 @@ class BrowserToolbarMiddlewareTest { browsingModeManager = browsingModeManager, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val tabCounterButton = toolbarStore.state.displayState.browserActionsEnd[1] as TabCounterAction assertEqualsTabCounterButton(expectedTabCounterButton(1, true), tabCounterButton) @@ -1039,7 +1039,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN a bottom toolbar WHEN the loading progress of the current tab changes THEN update the progress bar`() = runTest { + fun `GIVEN a bottom toolbar WHEN the loading progress of the current tab changes THEN update the progress bar`() = runTest(testDispatcher) { every { settings.shouldUseBottomToolbar } returns true val currentTab = createTab("test.com") val browserStore = BrowserStore( @@ -1056,7 +1056,7 @@ class BrowserToolbarMiddlewareTest { } browserStore.dispatch(UpdateProgressAction(currentTab.id, 50)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() assertEquals( ProgressBarConfig( @@ -1068,7 +1068,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN a top toolbar WHEN the loading progress of the current tab changes THEN update the progress bar`() = runTest { + fun `GIVEN a top toolbar WHEN the loading progress of the current tab changes THEN update the progress bar`() = runTest(testDispatcher) { every { settings.shouldUseBottomToolbar } returns false val currentTab = createTab("test.com", private = true) val browserStore = BrowserStore( @@ -1085,7 +1085,7 @@ class BrowserToolbarMiddlewareTest { } browserStore.dispatch(UpdateProgressAction(currentTab.id, 71)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() assertEquals( ProgressBarConfig( @@ -1120,7 +1120,7 @@ class BrowserToolbarMiddlewareTest { ), ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val readerModeButton = toolbarStore.state.displayState.pageActionsEnd[0] as ActionButtonRes assertEquals(expectedReaderModeButton(false), readerModeButton) @@ -1154,7 +1154,7 @@ class BrowserToolbarMiddlewareTest { ), ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val readerModeButton = toolbarStore.state.displayState.pageActionsEnd[0] as ActionButtonRes assertEquals(expectedReaderModeButton(true), readerModeButton) @@ -1184,7 +1184,7 @@ class BrowserToolbarMiddlewareTest { ), ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val translateButton = toolbarStore.state.displayState.pageActionsEnd[0] assertEquals(expectedTranslateButton(source = Source.AddressBar.PageEnd), translateButton) @@ -1210,7 +1210,7 @@ class BrowserToolbarMiddlewareTest { ), ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() var translateButton = toolbarStore.state.displayState.pageActionsEnd[0] assertEquals(expectedTranslateButton(source = Source.AddressBar.PageEnd), translateButton) @@ -1223,7 +1223,7 @@ class BrowserToolbarMiddlewareTest { ), ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() translateButton = toolbarStore.state.displayState.pageActionsEnd[0] assertEquals( expectedTranslateButton(isActive = true, source = Source.AddressBar.PageEnd), @@ -1258,7 +1258,7 @@ class BrowserToolbarMiddlewareTest { ), ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val translateButton = toolbarStore.state.displayState.pageActionsEnd[0] as ActionButtonRes @@ -1309,7 +1309,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN the current tab shows a content page WHEN the share shortcut is clicked THEN record telemetry and start sharing the local resource`() = runTest { + fun `GIVEN the current tab shows a content page WHEN the share shortcut is clicked THEN record telemetry and start sharing the local resource`() = runTest(testDispatcher) { every { settings.isTabStripEnabled } returns true every { settings.shouldUseExpandedToolbar } returns false every { settings.shouldShowToolbarCustomization } returns true @@ -1330,13 +1330,13 @@ class BrowserToolbarMiddlewareTest { isWideScreen = { true }, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val shareButton = toolbarStore.state.displayState.browserActionsEnd[0] as ActionButtonRes assertEquals(expectedShareButton(), shareButton) toolbarStore.dispatch(shareButton.onClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() captureMiddleware.assertLastAction(ShareResourceAction.AddShareAction::class) { assertEquals(currentTab.id, it.tabId) assertEquals(ShareResourceState.LocalResource(currentTab.content.url), it.resource) @@ -1365,7 +1365,7 @@ class BrowserToolbarMiddlewareTest { isWideScreen = { true }, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val shareButton = toolbarStore.state.displayState.browserActionsEnd[0] as ActionButtonRes assertEquals(expectedShareButton(), shareButton) @@ -1412,7 +1412,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN expanded toolbar with tabstrip and tall window WHEN changing to short window THEN show new tab, tab counter and menu`() = runTest { + fun `GIVEN expanded toolbar with tabstrip and tall window WHEN changing to short window THEN show new tab, tab counter and menu`() = runTest(testDispatcher) { every { settings.isTabStripEnabled } returns true every { settings.shouldUseExpandedToolbar } returns true val browserScreenStore = buildBrowserScreenStore() @@ -1432,7 +1432,7 @@ class BrowserToolbarMiddlewareTest { isTallScreen = false appStore.dispatch(AppAction.OrientationChange(Portrait)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() navigationActions = toolbarStore.state.displayState.navigationActions assertEquals(0, navigationActions.size) @@ -1510,7 +1510,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN device has wide window WHEN a website is loaded THEN show navigation buttons`() = runTest { + fun `GIVEN device has wide window WHEN a website is loaded THEN show navigation buttons`() = runTest(testDispatcher) { every { settings.shouldUseBottomToolbar } returns false val middleware = buildMiddleware( isWideScreen = { true }, @@ -1524,7 +1524,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN the back button is shown WHEN interacted with THEN go back or show history`() = runTest { + fun `GIVEN the back button is shown WHEN interacted with THEN go back or show history`() = runTest(testDispatcher) { every { navController.currentDestination?.id } returns R.id.browserFragment every { settings.shouldUseBottomToolbar } returns false val currentTab = createTab("test.com", private = false) @@ -1562,7 +1562,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN the forward button is shown WHEN interacted with THEN go forward or show history`() = runTest { + fun `GIVEN the forward button is shown WHEN interacted with THEN go forward or show history`() = runTest(testDispatcher) { every { settings.shouldUseBottomToolbar } returns false val currentTab = createTab("test.com", private = false) val captureMiddleware = CaptureActionsMiddleware<BrowserState, BrowserAction>() @@ -1590,7 +1590,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN device has wide window WHEN a website is loaded THEN show refresh button`() = runTest { + fun `GIVEN device has wide window WHEN a website is loaded THEN show refresh button`() = runTest(testDispatcher) { val browsingModeManager = SimpleBrowsingModeManager(Private) val currentNavDestination: NavDestination = mockk { every { id } returns R.id.browserFragment @@ -1634,17 +1634,17 @@ class BrowserToolbarMiddlewareTest { val pageLoadButton = toolbarStore.state.displayState.browserActionsStart.last() as ActionButtonRes assertEquals(expectedRefreshButton, pageLoadButton) toolbarStore.dispatch(pageLoadButton.onClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() verify { reloadUseCases(currentTab.id, capture(loadUrlFlagsUsed)) } assertEquals(LoadUrlFlags.none().value, loadUrlFlagsUsed.first().value) toolbarStore.dispatch(pageLoadButton.onLongClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() verify { reloadUseCases(currentTab.id, capture(loadUrlFlagsUsed)) } assertEquals(LoadUrlFlags.BYPASS_CACHE, loadUrlFlagsUsed.last().value) } @Test - fun `GIVEN device have a wide window WHEN a website is loaded THEN show refresh button`() = runTest { + fun `GIVEN device have a wide window WHEN a website is loaded THEN show refresh button`() = runTest(testDispatcher) { val browsingModeManager = SimpleBrowsingModeManager(Private) val currentNavDestination: NavDestination = mockk { every { id } returns R.id.browserFragment @@ -1688,17 +1688,17 @@ class BrowserToolbarMiddlewareTest { toolbarStore.state.displayState.browserActionsStart.last() as ActionButtonRes assertEquals(expectedRefreshButton, pageLoadButton) toolbarStore.dispatch(pageLoadButton.onClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() verify { reloadUseCases(currentTab.id, capture(loadUrlFlagsUsed)) } assertEquals(LoadUrlFlags.none().value, loadUrlFlagsUsed.first().value) toolbarStore.dispatch(pageLoadButton.onLongClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() verify { reloadUseCases(currentTab.id, capture(loadUrlFlagsUsed)) } assertEquals(LoadUrlFlags.BYPASS_CACHE, loadUrlFlagsUsed.last().value) } @Test - fun `GIVEN a loaded tab WHEN the refresh button is pressed THEN show stop refresh button`() = runTest { + fun `GIVEN a loaded tab WHEN the refresh button is pressed THEN show stop refresh button`() = runTest(testDispatcher) { val browsingModeManager = SimpleBrowsingModeManager(Private) val currentNavDestination: NavDestination = mockk { every { id } returns R.id.browserFragment @@ -1741,30 +1741,30 @@ class BrowserToolbarMiddlewareTest { var pageLoadButton = toolbarStore.state.displayState.browserActionsStart.last() as ActionButtonRes assertEquals(expectedRefreshButton, pageLoadButton) toolbarStore.dispatch(pageLoadButton.onClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() verify { reloadUseCases(currentTab.id, capture(loadUrlFlagsUsed)) } assertEquals(LoadUrlFlags.none().value, loadUrlFlagsUsed.first().value) toolbarStore.dispatch(pageLoadButton.onLongClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() verify { reloadUseCases(currentTab.id, capture(loadUrlFlagsUsed)) } assertEquals(LoadUrlFlags.BYPASS_CACHE, loadUrlFlagsUsed.last().value) browserStore.dispatch(UpdateLoadingStateAction(currentTab.id, true)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() pageLoadButton = toolbarStore.state.displayState.browserActionsStart.last() as ActionButtonRes assertEquals(expectedStopButton, pageLoadButton) toolbarStore.dispatch(pageLoadButton.onClick as BrowserToolbarEvent) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() verify { stopUseCases(currentTab.id) } browserStore.dispatch(UpdateLoadingStateAction(currentTab.id, false)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() pageLoadButton = toolbarStore.state.displayState.browserActionsStart.last() as ActionButtonRes assertEquals(expectedRefreshButton, pageLoadButton) } @Test - fun `GIVEN the url if of a local file WHEN initializing the toolbar THEN add an appropriate security indicator`() = runTest { + fun `GIVEN the url if of a local file WHEN initializing the toolbar THEN add an appropriate security indicator`() = runTest(testDispatcher) { val browserStore = BrowserStore( BrowserState( tabs = listOf(tab), @@ -1791,7 +1791,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN the website is secure WHEN initializing the toolbar THEN add an appropriate security indicator`() = runTest { + fun `GIVEN the website is secure WHEN initializing the toolbar THEN add an appropriate security indicator`() = runTest(testDispatcher) { val browserStore = BrowserStore( BrowserState( tabs = listOf(tab), @@ -1820,7 +1820,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN the website is unknown WHEN initializing the toolbar THEN add an appropriate security indicator`() = runTest { + fun `GIVEN the website is unknown WHEN initializing the toolbar THEN add an appropriate security indicator`() = runTest(testDispatcher) { val middleware = buildMiddleware( browserStore = browserStore, useCases = useCases, @@ -1891,7 +1891,7 @@ class BrowserToolbarMiddlewareTest { assertEquals(expectedInsecureIndicator, securityIndicator) browserStore.dispatch(UpdateSecurityInfoAction(tab.id, SecurityInfo.Secure())) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() toolbarPageActions = toolbarStore.state.displayState.pageActionsStart assertEquals(1, toolbarPageActions.size) securityIndicator = toolbarPageActions[0] as ActionButtonRes @@ -1970,13 +1970,13 @@ class BrowserToolbarMiddlewareTest { it.dispatch(BrowserToolbarAction.Init()) } browserStore.dispatch(UpdateSecurityInfoAction(tab.id, SecurityInfo.Secure())) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val toolbarPageActions = toolbarStore.state.displayState.pageActionsStart assertEquals(1, toolbarPageActions.size) val securityIndicator = toolbarPageActions[0] as ActionButtonRes assertEquals(expectedSecureIndicator, securityIndicator) browserStore.dispatch(TrackingProtectionAction.ToggleAction(tabId = tabId, enabled = false)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val toolbarPageActions2 = toolbarStore.state.displayState.pageActionsStart assertEquals(1, toolbarPageActions2.size) val securityIndicator2 = toolbarPageActions2[0] as ActionButtonRes @@ -2019,14 +2019,14 @@ class BrowserToolbarMiddlewareTest { } browserStore.dispatch(UpdateSecurityInfoAction(tab.id, SecurityInfo.Secure())) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val toolbarPageActions = toolbarStore.state.displayState.pageActionsStart assertEquals(1, toolbarPageActions.size) val securityIndicator = toolbarPageActions[0] as ActionButtonRes assertEquals(expectedInsecureIndicator, securityIndicator) browserStore.dispatch(TrackingProtectionAction.ToggleAction(tabId = tabId, enabled = true)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val toolbarPageActions2 = toolbarStore.state.displayState.pageActionsStart assertEquals(1, toolbarPageActions2.size) val securityIndicator2 = toolbarPageActions2[0] as ActionButtonRes @@ -2085,7 +2085,7 @@ class BrowserToolbarMiddlewareTest { ), ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() toolbarPageActions = toolbarStore.state.displayState.pageActionsStart assertEquals(0, toolbarPageActions.size) @@ -2363,7 +2363,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN in expanded mode WHEN THEN no browser end actions`() = runTest { + fun `GIVEN in expanded mode WHEN THEN no browser end actions`() = runTest(testDispatcher) { every { settings.shouldUseExpandedToolbar } returns true val middleware = buildMiddleware(browserStore = browserStore) @@ -2404,7 +2404,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `WHEN initializing the navigation bar AND should not use simple toolbar THEN add navigation bar actions`() = runTest { + fun `WHEN initializing the navigation bar AND should not use simple toolbar THEN add navigation bar actions`() = runTest(testDispatcher) { every { settings.shouldUseExpandedToolbar } returns true val middleware = buildMiddleware() @@ -2428,7 +2428,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `WHEN initializing the navigation bar AND should not use simple toolbar AND in short window THEN add no navigation bar actions`() = runTest { + fun `WHEN initializing the navigation bar AND should not use simple toolbar AND in short window THEN add no navigation bar actions`() = runTest(testDispatcher) { every { settings.shouldUseExpandedToolbar } returns true val middleware = buildMiddleware( @@ -2441,7 +2441,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `WHEN should use expanded toolbar AND window is changing to short window THEN add no navigation bar actions`() = runTest { + fun `WHEN should use expanded toolbar AND window is changing to short window THEN add no navigation bar actions`() = runTest(testDispatcher) { val appStore = AppStore( initialState = AppState( orientation = Portrait, @@ -2467,7 +2467,7 @@ class BrowserToolbarMiddlewareTest { isTallScreen = false appStore.dispatch(AppAction.OrientationChange(Landscape)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() navigationActions = toolbarStore.state.displayState.navigationActions assertEquals(0, navigationActions.size) @@ -2477,7 +2477,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN current page is bookmarked WHEN initializing navigation bar THEN show ACTIVE EditBookmark button`() = runTest { + fun `GIVEN current page is bookmarked WHEN initializing navigation bar THEN show ACTIVE EditBookmark button`() = runTest(testDispatcher) { every { settings.shouldUseExpandedToolbar } returns true val tab = createTab("https://example.com") @@ -2498,7 +2498,8 @@ class BrowserToolbarMiddlewareTest { ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() + testDispatcher.scheduler.advanceUntilIdle() val navigationActions = toolbarStore.state.displayState.navigationActions val editButton = navigationActions.first() as ActionButtonRes @@ -2507,7 +2508,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN the menu button is not highlighted WHEN a menu item is highlighted THEN highlight menu button`() = runTest { + fun `GIVEN the menu button is not highlighted WHEN a menu item is highlighted THEN highlight menu button`() = runTest(testDispatcher) { val appStore = AppStore() val middleware = buildMiddleware(appStore = appStore) val toolbarStore = buildStore(middleware) @@ -2520,13 +2521,13 @@ class BrowserToolbarMiddlewareTest { SupportedMenuNotifications.Downloads, ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val updatedMenuButton = toolbarStore.state.displayState.browserActionsEnd[2] as ActionButtonRes assertEquals(expectedMenuButton(true), updatedMenuButton) } @Test - fun `GIVEN the menu button is highlighted WHEN no menu item is highlighted THEN remove highlight from menu button`() = runTest { + fun `GIVEN the menu button is highlighted WHEN no menu item is highlighted THEN remove highlight from menu button`() = runTest(testDispatcher) { val appStore = AppStore( initialState = AppState( supportedMenuNotifications = setOf(SupportedMenuNotifications.Downloads), @@ -2543,13 +2544,13 @@ class BrowserToolbarMiddlewareTest { SupportedMenuNotifications.Downloads, ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val updatedMenuButton = toolbarStore.state.displayState.browserActionsEnd[2] as ActionButtonRes assertEquals(expectedMenuButton(), updatedMenuButton) } @Test - fun `GIVEN site permissions different than default WHEN observing THEN SiteInfo button is highlighted`() = runTest { + fun `GIVEN site permissions different than default WHEN observing THEN SiteInfo button is highlighted`() = runTest(testDispatcher) { val currentTab = createTab( url = "example.com", private = false, @@ -2562,7 +2563,7 @@ class BrowserToolbarMiddlewareTest { val toolbarStore = buildStore(middleware).also { it.dispatch(BrowserToolbarAction.Init()) } - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() var siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(!siteInfo.highlighted) @@ -2573,14 +2574,14 @@ class BrowserToolbarMiddlewareTest { value = true, ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(siteInfo.highlighted) } @Test - fun `GIVEN no custom site permissions WHEN observing THEN SiteInfo button is NOT highlighted`() = runTest { + fun `GIVEN no custom site permissions WHEN observing THEN SiteInfo button is NOT highlighted`() = runTest(testDispatcher) { val currentTab = createTab("example.com", private = false) val browserStore = BrowserStore( BrowserState(tabs = listOf(currentTab), selectedTabId = currentTab.id), @@ -2589,14 +2590,14 @@ class BrowserToolbarMiddlewareTest { val toolbarStore = buildStore(middleware).also { it.dispatch(BrowserToolbarAction.Init()) } - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(!siteInfo.highlighted) } @Test - fun `GIVEN tracking protection ignored WHEN observing THEN SiteInfo button is highlighted`() = runTest { + fun `GIVEN tracking protection ignored WHEN observing THEN SiteInfo button is highlighted`() = runTest(testDispatcher) { val currentTab = createTab( url = "https://example.com", private = false, @@ -2613,14 +2614,14 @@ class BrowserToolbarMiddlewareTest { val toolbarStore = buildStore(middleware).also { it.dispatch(BrowserToolbarAction.Init()) } - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(siteInfo.highlighted) } @Test - fun `GIVEN tracking protection not ignored WHEN it becomes ignored THEN SiteInfo button becomes highlighted`() = runTest { + fun `GIVEN tracking protection not ignored WHEN it becomes ignored THEN SiteInfo button becomes highlighted`() = runTest(testDispatcher) { val currentTab = createTab( url = "https://example.com", private = false, @@ -2637,7 +2638,7 @@ class BrowserToolbarMiddlewareTest { val toolbarStore = buildStore(middleware).also { it.dispatch(BrowserToolbarAction.Init()) } - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() var siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(!siteInfo.highlighted) @@ -2648,14 +2649,14 @@ class BrowserToolbarMiddlewareTest { excluded = true, ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(siteInfo.highlighted) } @Test - fun `GIVEN tracking protection ignored WHEN it is no longer ignored THEN SiteInfo button is NOT highlighted`() = runTest { + fun `GIVEN tracking protection ignored WHEN it is no longer ignored THEN SiteInfo button is NOT highlighted`() = runTest(testDispatcher) { val currentTab = createTab( url = "https://example.com", private = false, @@ -2672,7 +2673,7 @@ class BrowserToolbarMiddlewareTest { val toolbarStore = buildStore(middleware).also { it.dispatch(BrowserToolbarAction.Init()) } - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() var siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(siteInfo.highlighted) @@ -2683,14 +2684,14 @@ class BrowserToolbarMiddlewareTest { excluded = false, ), ) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes assertTrue(!siteInfo.highlighted) } @Test - fun `GIVEN share shortcut is selected THEN update end page actions without share action`() = runTest { + fun `GIVEN share shortcut is selected THEN update end page actions without share action`() = runTest(testDispatcher) { every { settings.isTabStripEnabled } returns false every { settings.toolbarSimpleShortcut } returns ShortcutType.SHARE.value val browserScreenStore = buildBrowserScreenStore() @@ -2705,7 +2706,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN translate shortcut is selected THEN update end page actions without translate action`() = runTest { + fun `GIVEN translate shortcut is selected THEN update end page actions without translate action`() = runTest(testDispatcher) { every { settings.toolbarSimpleShortcut } returns ShortcutType.TRANSLATE.value val browserScreenStore = buildBrowserScreenStore() val middleware = buildMiddleware( @@ -2730,7 +2731,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `WHEN clicking the homepage button THEN navigate to application's home screen`() = runTest { + fun `WHEN clicking the homepage button THEN navigate to application's home screen`() = runTest(testDispatcher) { val browserAnimatorActionCaptor = slot<(Boolean) -> Unit>() every { browserAnimator.captureEngineViewAndDrawStatically( @@ -2750,7 +2751,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN homepage as new tab is enabled WHEN clicking the homepage button THEN navigate to homepage`() = runTest { + fun `GIVEN homepage as new tab is enabled WHEN clicking the homepage button THEN navigate to homepage`() = runTest(testDispatcher) { every { settings.enableHomepageAsNewTab } returns true every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.HOMEPAGE.value @@ -2764,7 +2765,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN expanded toolbar is used and navbar is hidden WHEN building end browser actions THEN use simple toolbar shortcuts`() = runTest { + fun `GIVEN expanded toolbar is used and navbar is hidden WHEN building end browser actions THEN use simple toolbar shortcuts`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.shouldUseExpandedToolbar } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.HOMEPAGE.value @@ -2781,7 +2782,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN simple toolbar use add bookmark shortcut AND the current page is not bookmarked WHEN initializing toolbar THEN show Bookmark in end browser actions`() = runTest { + fun `GIVEN simple toolbar use add bookmark shortcut AND the current page is not bookmarked WHEN initializing toolbar THEN show Bookmark in end browser actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.BOOKMARK.value val toolbarStore = buildStore() @@ -2791,7 +2792,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN simple toolbar use add bookmark shortcut AND the current page is bookmarked WHEN initializing toolbar THEN show ACTIVE EditBookmark in end browser actions`() = runTest { + fun `GIVEN simple toolbar use add bookmark shortcut AND the current page is bookmarked WHEN initializing toolbar THEN show ACTIVE EditBookmark in end browser actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.BOOKMARK.value @@ -2813,14 +2814,14 @@ class BrowserToolbarMiddlewareTest { ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val editButton = toolbarStore.state.displayState.browserActionsEnd[0] as ActionButtonRes assertEquals(expectedEditBookmarkButton(), editButton) } @Test - fun `GIVEN simple toolbar use translate shortcut AND current page is not translated WHEN initializing toolbar THEN show Translate in end browser actions`() = runTest { + fun `GIVEN simple toolbar use translate shortcut AND current page is not translated WHEN initializing toolbar THEN show Translate in end browser actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.TRANSLATE.value @@ -2839,7 +2840,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN simple toolbar use translate shortcut AND current page is translated WHEN initializing toolbar THEN show ACTIVE Translate in end browser actions`() = runTest { + fun `GIVEN simple toolbar use translate shortcut AND current page is translated WHEN initializing toolbar THEN show ACTIVE Translate in end browser actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.TRANSLATE.value @@ -2858,7 +2859,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN simple toolbar use homepage shortcut WHEN initializing toolbar THEN show Homepage in end browser actions`() = runTest { + fun `GIVEN simple toolbar use homepage shortcut WHEN initializing toolbar THEN show Homepage in end browser actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.HOMEPAGE.value @@ -2869,7 +2870,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN simple toolbar use back shortcut AND current page has no history WHEN initializing toolbar THEN show DISABLED Back in end browser actions`() = runTest { + fun `GIVEN simple toolbar use back shortcut AND current page has no history WHEN initializing toolbar THEN show DISABLED Back in end browser actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.BACK.value @@ -2880,7 +2881,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN simple toolbar use back shortcut AND current page has history WHEN initializing toolbar THEN show ACTIVE Back in end browser actions`() = runTest { + fun `GIVEN simple toolbar use back shortcut AND current page has history WHEN initializing toolbar THEN show ACTIVE Back in end browser actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarSimpleShortcut } returns ShortcutType.BACK.value @@ -2897,7 +2898,7 @@ class BrowserToolbarMiddlewareTest { browserStore = browserStore, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val backButton = toolbarStore.state.displayState.browserActionsEnd[0] as ActionButtonRes assertEquals(expectedGoBackButton(), backButton) @@ -2926,7 +2927,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN expanded toolbar use translate shortcut AND current page is not translated WHEN initializing toolbar THEN show Translate in navigation actions`() = runTest { + fun `GIVEN expanded toolbar use translate shortcut AND current page is not translated WHEN initializing toolbar THEN show Translate in navigation actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.shouldUseExpandedToolbar } returns true every { settings.toolbarExpandedShortcut } returns ShortcutType.TRANSLATE.value @@ -2946,7 +2947,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN expanded toolbar use translate shortcut AND current page is translated WHEN initializing toolbar THEN show ACTIVE Translate in navigation actions`() = runTest { + fun `GIVEN expanded toolbar use translate shortcut AND current page is translated WHEN initializing toolbar THEN show ACTIVE Translate in navigation actions`() = runTest(testDispatcher) { every { settings.shouldShowToolbarCustomization } returns true every { settings.shouldUseExpandedToolbar } returns true every { settings.toolbarExpandedShortcut } returns ShortcutType.TRANSLATE.value @@ -2966,7 +2967,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN expanded toolbar use homepage shortcut WHEN initializing toolbar THEN show Homepage in navigation actions`() = runTest { + fun `GIVEN expanded toolbar use homepage shortcut WHEN initializing toolbar THEN show Homepage in navigation actions`() = runTest(testDispatcher) { every { settings.shouldUseExpandedToolbar } returns true every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarExpandedShortcut } returns ShortcutType.HOMEPAGE.value @@ -2978,7 +2979,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN expanded toolbar use back shortcut AND current page has no history WHEN initializing toolbar THEN show DISABLED Back in navigation actions`() = runTest { + fun `GIVEN expanded toolbar use back shortcut AND current page has no history WHEN initializing toolbar THEN show DISABLED Back in navigation actions`() = runTest(testDispatcher) { every { settings.shouldUseExpandedToolbar } returns true every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarExpandedShortcut } returns ShortcutType.BACK.value @@ -2990,7 +2991,7 @@ class BrowserToolbarMiddlewareTest { } @Test - fun `GIVEN expanded toolbar use back shortcut AND current page has history WHEN initializing toolbar THEN show ACTIVE Back in navigation actions`() = runTest { + fun `GIVEN expanded toolbar use back shortcut AND current page has history WHEN initializing toolbar THEN show ACTIVE Back in navigation actions`() = runTest(testDispatcher) { every { settings.shouldUseExpandedToolbar } returns true every { settings.shouldShowToolbarCustomization } returns true every { settings.toolbarExpandedShortcut } returns ShortcutType.BACK.value @@ -3008,14 +3009,14 @@ class BrowserToolbarMiddlewareTest { browserStore = browserStore, ) val toolbarStore = buildStore(middleware) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() val backButton = toolbarStore.state.displayState.navigationActions.first() as ActionButtonRes assertEquals(expectedGoBackButton(source = Source.NavigationBar), backButton) } @Test - fun `ShortcutType toToolbarAction maps shortcuts`() = runTest { + fun `ShortcutType toToolbarAction maps shortcuts`() = runTest(testDispatcher) { val middleware = buildMiddleware() val newTab = with(middleware) { ShortcutType.NEW_TAB.toToolbarAction() } @@ -3258,7 +3259,7 @@ class BrowserToolbarMiddlewareTest { thumbnailsFeature: () -> BrowserThumbnails = { this.thumbnailsFeature }, isWideScreen: () -> Boolean = { false }, isTallScreen: () -> Boolean = { true }, - scope: CoroutineScope = MainScope(), + scope: CoroutineScope = testScope, ) = BrowserToolbarMiddleware( uiContext = testContext, appStore = appStore, @@ -3282,7 +3283,7 @@ class BrowserToolbarMiddlewareTest { isTallScreen = isTallScreen, sessionUseCases = sessionUseCases, scope = scope, - ioDispatcher = Dispatchers.Main, + ioDispatcher = testDispatcher, ) private fun buildStore( @@ -3290,7 +3291,7 @@ class BrowserToolbarMiddlewareTest { ) = BrowserToolbarStore( middleware = listOf(middleware), ).also { - mainLooperRule.idle() // to complete the initial setup happening in coroutines + testDispatcher.scheduler.advanceUntilIdle() // to complete the initial setup happening in coroutines } private fun buildBrowserScreenStore( diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/search/BrowserToolbarSearchMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/search/BrowserToolbarSearchMiddlewareTest.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.search import android.content.Context -import android.os.Looper import androidx.navigation.NavController import androidx.navigation.NavDirections import io.mockk.Runs @@ -19,8 +18,8 @@ import io.mockk.spyk import io.mockk.verify import io.mockk.verifyOrder import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.state.action.AwesomeBarAction.EngagementFinished import mozilla.components.browser.state.action.SearchAction.ApplicationSearchEnginesLoaded @@ -50,7 +49,6 @@ import mozilla.components.feature.syncedtabs.SyncedTabsAutocompleteProvider 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.MainLooperTestRule import mozilla.telemetry.glean.testing.GleanTestRule import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -100,7 +98,6 @@ import org.mozilla.fenix.telemetry.ACTION_SEARCH_ENGINE_SELECTOR_CLICKED import org.mozilla.fenix.telemetry.SOURCE_ADDRESS_BAR import org.mozilla.fenix.utils.Settings import org.robolectric.RobolectricTestRunner -import org.robolectric.Shadows.shadowOf import mozilla.components.browser.toolbar.R as toolbarR import mozilla.components.feature.qr.R as qrR import mozilla.components.ui.icons.R as iconsR @@ -111,8 +108,8 @@ class BrowserToolbarSearchMiddlewareTest { @get:Rule val gleanTestRule = GleanTestRule(testContext) - @get:Rule - val mainLooperRule = MainLooperTestRule() + private val testDispatcher = StandardTestDispatcher() + private val testScope = CoroutineScope(testDispatcher) val appStore = AppStore() val browserStore: BrowserStore = mockk(relaxed = true) { @@ -299,7 +296,7 @@ class BrowserToolbarSearchMiddlewareTest { } @Test - fun `GIVEN default engine selected WHEN entering in edit mode THEN set autocomplete suggestions and page end buttons`() { + fun `GIVEN default engine selected WHEN entering in edit mode THEN set autocomplete suggestions and page end buttons`() = runTest(testDispatcher) { every { settings.shouldAutocompleteInAwesomebar } returns true every { settings.shouldShowHistorySuggestions } returns true every { settings.shouldShowBookmarkSuggestions } returns true @@ -315,7 +312,7 @@ class BrowserToolbarSearchMiddlewareTest { val autocompleteProvidersSlot = slot<List<AutocompleteProvider>>() store.dispatch(EnterEditMode(false)) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify(exactly = 0) { middleware.fetchAutocomplete( autocompleteProviders = any(), @@ -328,7 +325,7 @@ class BrowserToolbarSearchMiddlewareTest { assertEquals(expectedQrButton, store.state.editState.editActionsEnd.last()) store.dispatch(SearchQueryUpdated(BrowserToolbarQuery("test"))) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify { middleware.fetchAutocomplete( autocompleteProviders = capture(autocompleteProvidersSlot), @@ -351,7 +348,7 @@ class BrowserToolbarSearchMiddlewareTest { } @Test - fun `GIVEN default engine selected and history suggestions disabled WHEN entering in edit mode THEN set autocomplete suggestions`() { + fun `GIVEN default engine selected and history suggestions disabled WHEN entering in edit mode THEN set autocomplete suggestions`() = runTest(testDispatcher) { every { settings.shouldAutocompleteInAwesomebar } returns true every { settings.shouldShowHistorySuggestions } returns false every { settings.shouldShowBookmarkSuggestions } returns true @@ -365,7 +362,7 @@ class BrowserToolbarSearchMiddlewareTest { val autocompleteProvidersSlot = slot<List<AutocompleteProvider>>() store.dispatch(EnterEditMode(false)) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify(exactly = 0) { middleware.fetchAutocomplete( autocompleteProviders = any(), @@ -375,7 +372,7 @@ class BrowserToolbarSearchMiddlewareTest { assertNull(store.state.editState.suggestion) store.dispatch(SearchQueryUpdated(BrowserToolbarQuery("test"))) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify { middleware.fetchAutocomplete( autocompleteProviders = capture(autocompleteProvidersSlot), @@ -394,7 +391,7 @@ class BrowserToolbarSearchMiddlewareTest { } @Test - fun `GIVEN default engine selected and bookmarks suggestions disabled WHEN entering in edit mode THEN set autocomplete suggestions`() { + fun `GIVEN default engine selected and bookmarks suggestions disabled WHEN entering in edit mode THEN set autocomplete suggestions`() = runTest(testDispatcher) { every { settings.shouldAutocompleteInAwesomebar } returns true every { settings.shouldShowHistorySuggestions } returns true every { settings.shouldShowBookmarkSuggestions } returns false @@ -408,7 +405,7 @@ class BrowserToolbarSearchMiddlewareTest { val autocompleteProvidersSlot = slot<List<AutocompleteProvider>>() store.dispatch(EnterEditMode(false)) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify(exactly = 0) { middleware.fetchAutocomplete( autocompleteProviders = any(), @@ -418,7 +415,8 @@ class BrowserToolbarSearchMiddlewareTest { assertNull(store.state.editState.suggestion) store.dispatch(SearchQueryUpdated(BrowserToolbarQuery("test"))) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() + coVerify { middleware.fetchAutocomplete( autocompleteProviders = capture(autocompleteProvidersSlot), @@ -438,7 +436,7 @@ class BrowserToolbarSearchMiddlewareTest { } @Test - fun `GIVEN default engine selected and history + bookmarks suggestions disabled WHEN entering in edit mode THEN set autocomplete suggestions`() { + fun `GIVEN default engine selected and history + bookmarks suggestions disabled WHEN entering in edit mode THEN set autocomplete suggestions`() = runTest(testDispatcher) { every { settings.shouldAutocompleteInAwesomebar } returns true every { settings.shouldShowHistorySuggestions } returns false every { settings.shouldShowBookmarkSuggestions } returns false @@ -452,7 +450,7 @@ class BrowserToolbarSearchMiddlewareTest { val autocompleteProvidersSlot = slot<List<AutocompleteProvider>>() store.dispatch(EnterEditMode(false)) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify(exactly = 0) { middleware.fetchAutocomplete( autocompleteProviders = any(), @@ -462,7 +460,9 @@ class BrowserToolbarSearchMiddlewareTest { assertNull(store.state.editState.suggestion) store.dispatch(SearchQueryUpdated(BrowserToolbarQuery("test"))) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() + testDispatcher.scheduler.advanceUntilIdle() + coVerify { middleware.fetchAutocomplete( autocompleteProviders = capture(autocompleteProvidersSlot), @@ -478,7 +478,7 @@ class BrowserToolbarSearchMiddlewareTest { } @Test - fun `GIVEN tabs engine selected WHEN entering in edit mode THEN set autocomplete suggestions and page end buttons`() { + fun `GIVEN tabs engine selected WHEN entering in edit mode THEN set autocomplete suggestions and page end buttons`() = runTest(testDispatcher) { every { settings.shouldAutocompleteInAwesomebar } returns true every { settings.shouldShowHistorySuggestions } returns true every { settings.shouldShowBookmarkSuggestions } returns true @@ -499,7 +499,7 @@ class BrowserToolbarSearchMiddlewareTest { fakeSearchState().applicationSearchEngines.first { it.id == TABS_SEARCH_ENGINE_ID }, ), ) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify(exactly = 0) { middleware.fetchAutocomplete( autocompleteProviders = any(), @@ -511,7 +511,7 @@ class BrowserToolbarSearchMiddlewareTest { assertEquals(expectedVoiceSearchButton, store.state.editState.editActionsEnd.first()) store.dispatch(SearchQueryUpdated(BrowserToolbarQuery("test"))) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify { middleware.fetchAutocomplete( autocompleteProviders = capture(autocompleteProvidersSlot), @@ -533,7 +533,7 @@ class BrowserToolbarSearchMiddlewareTest { } @Test - fun `GIVEN bookmarks engine selected WHEN entering in edit mode THEN set autocomplete suggestions`() { + fun `GIVEN bookmarks engine selected WHEN entering in edit mode THEN set autocomplete suggestions`() = runTest(testDispatcher) { every { settings.shouldAutocompleteInAwesomebar } returns true every { settings.shouldShowHistorySuggestions } returns true every { settings.shouldShowBookmarkSuggestions } returns true @@ -554,7 +554,7 @@ class BrowserToolbarSearchMiddlewareTest { fakeSearchState().applicationSearchEngines.first { it.id == BOOKMARKS_SEARCH_ENGINE_ID }, ), ) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify(exactly = 0) { middleware.fetchAutocomplete( autocompleteProviders = any(), @@ -566,7 +566,7 @@ class BrowserToolbarSearchMiddlewareTest { assertEquals(expectedVoiceSearchButton, store.state.editState.editActionsEnd.first()) store.dispatch(SearchQueryUpdated(BrowserToolbarQuery("test"))) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify { middleware.fetchAutocomplete( autocompleteProviders = capture(autocompleteProvidersSlot), @@ -585,7 +585,7 @@ class BrowserToolbarSearchMiddlewareTest { } @Test - fun `GIVEN history engine selected WHEN entering in edit mode THEN set autocomplete suggestions`() { + fun `GIVEN history engine selected WHEN entering in edit mode THEN set autocomplete suggestions`() = runTest(testDispatcher) { every { settings.shouldAutocompleteInAwesomebar } returns true every { settings.shouldShowHistorySuggestions } returns true every { settings.shouldShowBookmarkSuggestions } returns true @@ -618,13 +618,16 @@ class BrowserToolbarSearchMiddlewareTest { assertEquals(expectedVoiceSearchButton, store.state.editState.editActionsEnd.first()) store.dispatch(SearchQueryUpdated(BrowserToolbarQuery("test"))) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() + coVerify { middleware.fetchAutocomplete( autocompleteProviders = capture(autocompleteProvidersSlot), input = "test", ) } + testDispatcher.scheduler.advanceUntilIdle() + assertEquals( autocompleteProvidersSlot.captured.map { it.javaClass::getSimpleName }, listOfNotNull(components.core.historyStorage).map { it.javaClass::getSimpleName }, @@ -651,7 +654,7 @@ class BrowserToolbarSearchMiddlewareTest { store.dispatch(SearchSelectorItemClicked(mockk(relaxed = true))) store.dispatch(EnterEditMode(false)) - shadowOf(Looper.getMainLooper()).idle() + testDispatcher.scheduler.advanceUntilIdle() coVerify(exactly = 0) { middleware.fetchAutocomplete( @@ -673,7 +676,7 @@ class BrowserToolbarSearchMiddlewareTest { val newSearchEngines = fakeSearchState().applicationSearchEngines browserStore.dispatch(ApplicationSearchEnginesLoaded(newSearchEngines)) - shadowOf(Looper.getMainLooper()).idle() // wait for observing and processing the search engines update + testDispatcher.scheduler.advanceUntilIdle() // wait for observing and processing the search engines update assertSearchSelectorEquals( expectedSearchSelector(newSearchEngines[0], newSearchEngines), @@ -697,7 +700,7 @@ class BrowserToolbarSearchMiddlewareTest { val newSearchEngines = fakeSearchState().applicationSearchEngines browserStore.dispatch(ApplicationSearchEnginesLoaded(newSearchEngines)) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() assertSearchSelectorEquals( expectedSearchSelector(selectedSearchEngine, newSearchEngines), @@ -959,7 +962,7 @@ class BrowserToolbarSearchMiddlewareTest { store.dispatch(qrScannerButton.onClick as BrowserToolbarEvent) appStore.dispatch(QrScannerInputAvailable("mozilla.test")) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() assertEquals("mozilla.test", store.state.editState.query.current) appStoreActionsCaptor.assertLastAction(QrScannerInputConsumed::class) @@ -993,7 +996,7 @@ class BrowserToolbarSearchMiddlewareTest { store.dispatch(qrScannerButton.onClick as BrowserToolbarEvent) appStore.dispatch(QrScannerInputAvailable("test.mozilla")) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() assertEquals("test.mozilla", store.state.editState.query.current) appStoreActionsCaptor.assertLastAction(QrScannerInputConsumed::class) @@ -1032,7 +1035,7 @@ class BrowserToolbarSearchMiddlewareTest { store.dispatch(qrScannerButton.onClick as BrowserToolbarEvent) appStore.dispatch(QrScannerInputAvailable("test.com")) - mainLooperRule.idle() + testDispatcher.scheduler.advanceUntilIdle() assertEquals("test.com", store.state.editState.query.current) appStoreActionsCaptor.assertLastAction(QrScannerInputConsumed::class) @@ -1101,7 +1104,7 @@ class BrowserToolbarSearchMiddlewareTest { navController: NavController = this.navController, browsingModeManager: BrowsingModeManager = this.browsingModeManager, settings: Settings = this.settings, - scope: CoroutineScope = MainScope(), + scope: CoroutineScope = testScope, ): Pair<BrowserToolbarSearchMiddleware, BrowserToolbarStore> { val middleware = buildMiddleware( uiContext = uiContext, @@ -1132,7 +1135,7 @@ class BrowserToolbarSearchMiddlewareTest { navController: NavController = this.navController, browsingModeManager: BrowsingModeManager = this.browsingModeManager, settings: Settings = this.settings, - scope: CoroutineScope = MainScope(), + scope: CoroutineScope = testScope, ) = BrowserToolbarSearchMiddleware( uiContext = uiContext, appStore = appStore, @@ -1142,7 +1145,7 @@ class BrowserToolbarSearchMiddlewareTest { browsingModeManager = browsingModeManager, settings = settings, scope = scope, - autocompleteDispatcher = Dispatchers.Main, + autocompleteDispatcher = testDispatcher, ) private fun configureAutocompleteProvidersInComponents() {