commit 198098e9253e65e98ecf5c16f34d0aa29b0a01c7
parent 3ff7b558d46a55785907ae7d093cd58d65cfa975
Author: mcarare <48995920+mcarare@users.noreply.github.com>
Date: Mon, 15 Dec 2025 08:47:18 +0000
Bug 2005228 - Refactor Address middlewares to use injected dispatchers. r=android-reviewers,giorga
Differential Revision: https://phabricator.services.mozilla.com/D276176
Diffstat:
3 files changed, 35 insertions(+), 23 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/address/store/AddressMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/address/store/AddressMiddleware.kt
@@ -8,8 +8,8 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import org.mozilla.fenix.GleanMetrics.Addresses
@@ -19,11 +19,13 @@ import org.mozilla.fenix.GleanMetrics.Addresses
*
* @param environment used to hold the dependencies.
* @param scope a [CoroutineScope] used to launch coroutines.
+ * @param mainDispatcher the dispatcher to run UI-related code on.
* @param ioDispatcher the dispatcher to run background code on.
*/
class AddressMiddleware(
private val environment: AddressEnvironment,
- private val scope: CoroutineScope = MainScope(),
+ private val scope: CoroutineScope,
+ private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
private val ioDispatcher: CoroutineDispatcher = IO,
) : Middleware<AddressState, AddressAction> {
override fun invoke(
@@ -56,7 +58,7 @@ class AddressMiddleware(
private fun runAndNavigateBack(action: suspend () -> Unit) = scope.launch(ioDispatcher) {
action()
- scope.launch(Dispatchers.Main) {
+ withContext(mainDispatcher) {
environment.navigateBack()
}
}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/address/store/AddressStructureMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/address/store/AddressStructureMiddleware.kt
@@ -6,8 +6,7 @@ package org.mozilla.fenix.settings.address.store
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.autofill.AddressStructure
import mozilla.components.lib.state.Middleware
@@ -45,8 +44,8 @@ data class UnknownLocalizationKey(
*/
class AddressStructureMiddleware(
private val environment: AddressEnvironment,
- private val scope: CoroutineScope = MainScope(),
- private val ioDispatcher: CoroutineDispatcher = IO,
+ private val scope: CoroutineScope,
+ private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
) : Middleware<AddressState, AddressAction> {
override fun invoke(
context: MiddlewareContext<AddressState, AddressAction>,
@@ -69,7 +68,7 @@ class AddressStructureMiddleware(
store: Store<AddressState, AddressAction>,
initialLoad: Boolean,
) = scope.launch(ioDispatcher) {
- val structure = environment?.getAddressStructure(store.state.address.country) ?: return@launch
+ val structure = environment.getAddressStructure(store.state.address.country)
structure.validate(store.state.address.country)
store.dispatch(
AddressStructureLoaded(
@@ -89,7 +88,7 @@ class AddressStructureMiddleware(
}
if (localizationKey is AddressStructure.Field.LocalizationKey.Unknown) {
- environment?.submitCaughtException(
+ environment.submitCaughtException(
UnknownLocalizationKey(countryCode, localizationKey.value),
)
}
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/address/store/AddressStoreTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/address/store/AddressStoreTest.kt
@@ -2,24 +2,23 @@ package org.mozilla.fenix.settings.address.store
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
import mozilla.components.concept.engine.autofill.AddressStructure
import mozilla.components.concept.storage.Address
import mozilla.components.concept.storage.UpdatableAddressFields
-import mozilla.components.support.test.rule.MainCoroutineRule
-import mozilla.components.support.test.rule.runTestOnMain
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AddressStoreTest {
- @get:Rule
- val coroutinesTestRule = MainCoroutineRule()
+
+ private val testDispatcher = StandardTestDispatcher()
@Test
- fun `GIVEN a store WHEN a user edits an address THEN the address structure is loaded`() = runTestOnMain {
+ fun `GIVEN a store WHEN a user edits an address THEN the address structure is loaded`() = runTest(testDispatcher) {
val expectedAddressStructure = AddressStructure(
listOf(
AddressStructure.Field.TextField(AddressStructure.Field.ID.Name, AddressStructure.Field.LocalizationKey.Name),
@@ -35,6 +34,7 @@ class AddressStoreTest {
}
store.dispatch(ViewAppeared)
+ testDispatcher.scheduler.advanceUntilIdle()
assertEquals(
AddressStructureState.Loaded(expectedAddressStructure),
@@ -43,7 +43,7 @@ class AddressStoreTest {
}
@Test
- fun `GIVEN a store WHEN a user edits an address and changes the country THEN the address structure is loaded`() = runTestOnMain {
+ fun `GIVEN a store WHEN a user edits an address and changes the country THEN the address structure is loaded`() = runTest(testDispatcher) {
val expectedAddressStructure = AddressStructure(
listOf(
AddressStructure.Field.TextField(AddressStructure.Field.ID.Name, AddressStructure.Field.LocalizationKey.Name),
@@ -82,7 +82,10 @@ class AddressStoreTest {
assertEquals("WA", store.state.address.addressLevel1)
store.dispatch(ViewAppeared)
+ testDispatcher.scheduler.advanceUntilIdle()
+
store.dispatch(FormChange.Country("CA"))
+ testDispatcher.scheduler.advanceUntilIdle()
assertEquals("CA", store.state.address.country)
assertEquals("AL", store.state.address.addressLevel1)
@@ -94,7 +97,7 @@ class AddressStoreTest {
}
@Test
- fun `GIVEN a store WHEN an address structure is loaded with an Unknown LocalizationKey THEN submit an exception`() = runTestOnMain {
+ fun `GIVEN a store WHEN an address structure is loaded with an Unknown LocalizationKey THEN submit an exception`() = runTest(testDispatcher) {
val expectedAddressStructure = AddressStructure(
listOf(
AddressStructure.Field.TextField(AddressStructure.Field.ID.Name, AddressStructure.Field.LocalizationKey.Name),
@@ -112,6 +115,7 @@ class AddressStoreTest {
}
store.dispatch(ViewAppeared)
+ testDispatcher.scheduler.advanceUntilIdle()
assertEquals(
UnknownLocalizationKey("US", "unknown-key"),
@@ -120,7 +124,7 @@ class AddressStoreTest {
}
@Test
- fun `GIVEN a store WHEN a user updates the address THEN the address is updated`() = runTestOnMain {
+ fun `GIVEN a store WHEN a user updates the address THEN the address is updated`() = runTest(testDispatcher) {
val store = makeStore(this) {
copy(
getAddressStructure = { _ ->
@@ -155,6 +159,7 @@ class AddressStoreTest {
FormChange.Tel("555-555-5555"),
FormChange.Email("mo@zilla.com"),
).forEach(store::dispatch)
+ testDispatcher.scheduler.advanceUntilIdle()
val expected = UpdatableAddressFields(
name = "Work",
@@ -172,7 +177,7 @@ class AddressStoreTest {
}
@Test
- fun `GIVEN a state with no guid WHEN a user taps save THEN create and navigateBack is called on the environment`() = runTestOnMain {
+ fun `GIVEN a state with no guid WHEN a user taps save THEN create and navigateBack is called on the environment`() = runTest(testDispatcher) {
var navigateBackCalled = false
var createdAddress: UpdatableAddressFields? = null
@@ -188,6 +193,7 @@ class AddressStoreTest {
store.dispatch(FormChange.Name("Work"))
store.dispatch(SaveTapped)
+ testDispatcher.scheduler.advanceUntilIdle()
val expected = emptyUpdatableAddress.copy(name = "Work")
@@ -196,7 +202,7 @@ class AddressStoreTest {
}
@Test
- fun `GIVEN a state with an existing address WHEN a user taps save THEN update and navigateBack is called on the environment`() = runTestOnMain {
+ fun `GIVEN a state with an existing address WHEN a user taps save THEN update and navigateBack is called on the environment`() = runTest(testDispatcher) {
var navigateBackCalled = false
var updatedAddress: Pair<String, UpdatableAddressFields>? = null
@@ -211,6 +217,7 @@ class AddressStoreTest {
store.dispatch(FormChange.Name("Home"))
store.dispatch(SaveTapped)
+ testDispatcher.scheduler.advanceUntilIdle()
val expected = Pair("BEEF", emptyUpdatableAddress.copy(name = "Home", organization = "Mozilla"))
@@ -219,7 +226,7 @@ class AddressStoreTest {
}
@Test
- fun `GIVEN a state with an existing address WHEN a user taps save THEN delete and navigateBack is called on the environment`() = runTestOnMain {
+ fun `GIVEN a state with an existing address WHEN a user taps save THEN delete and navigateBack is called on the environment`() = runTest(testDispatcher) {
var navigateBackCalled = false
var deletedGuid: String? = null
@@ -233,10 +240,14 @@ class AddressStoreTest {
}
assertEquals(DialogState.Inert, store.state.deleteDialog)
+
store.dispatch(DeleteTapped)
+ testDispatcher.scheduler.advanceUntilIdle()
assertEquals(DialogState.Presenting, store.state.deleteDialog)
+
store.dispatch(DeleteDialogAction.DeleteTapped)
+ testDispatcher.scheduler.advanceUntilIdle()
val expected = "BEEF"
@@ -254,8 +265,8 @@ class AddressStoreTest {
return AddressStore(
state,
listOf(
- AddressMiddleware(environment, scope, coroutinesTestRule.testDispatcher),
- AddressStructureMiddleware(environment, scope, coroutinesTestRule.testDispatcher),
+ AddressMiddleware(environment, scope, testDispatcher, testDispatcher),
+ AddressStructureMiddleware(environment, scope, testDispatcher),
),
)
}