[Jetpack Social] Remove feature flag

Fixes #19576

To test:

Taken from https://github.com/wordpress-mobile/WordPress-Android/pull/18897

1 - Open the app
2 - Go to Posts
3 - Open a Draft post / create a new post
4 - Open the Post Settings
5 - Verify the JP Social section is available
6 - Hit Publish
7 - Verify the JP Social item is available in the pre-publish sheet

## Regression Notes
1. Potential unintended areas of impact
Jetpack social section on edit post flow

2. What I did to test those areas of impact (or what existing automated tests I relied on)
Manual testing and updated unit tests

3. What automated tests I added (or what prevented me from doing so)
Updated `EditorJetpackSocialViewModelTest.kt`

PR submission checklist:

- [x] I have completed the Regression Notes.
- [x] I have considered adding accessibility improvements for my changes.
- [x] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

UI Changes testing checklist:

- [ ] Portrait and landscape orientations.
- [ ] Light and dark modes.
- [ ] Fonts: Larger, smaller and bold text.
- [ ] High contrast.
- [ ] Talkback.
- [ ] Languages with large words or with letters/accents not frequently used in English.
- [ ] Right-to-left languages. (Even if translation isn’t complete, formatting should still respect the right-to-left layout)
- [ ] Large and small screen sizes. (Tablet and smaller phones)
- [ ] Multi-tasking: Split screen and Pop-up view. (Android 10 or higher)

[Jetpack Social] Remove feature flag

[Jetpack Social] Remove feature flag
Remove Jetpack Social feature flag, since the feature was successfully released.

diff --git a/WordPress/build.gradle b/WordPress/build.gradle
index 64b5255f59ea..f5166ba8e345 100644
--- a/WordPress/build.gradle
+++ b/WordPress/build.gradle
@@ -138,7 +138,6 @@ android {
         buildConfigField "boolean", "ENABLE_BLAZE_FEATURE", "false"
         buildConfigField "boolean", "WP_INDIVIDUAL_PLUGIN_OVERLAY", "false"
         buildConfigField "boolean", "SITE_EDITOR_MVP", "false"
-        buildConfigField "boolean", "JETPACK_SOCIAL", "false"
         buildConfigField "boolean", "CONTACT_SUPPORT_CHATBOT", "false"
         buildConfigField "boolean", "ENABLE_DOMAIN_MANAGEMENT_FEATURE", "false"
         buildConfigField "boolean", "PLANS_IN_SITE_CREATION", "false"
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModel.kt
index 8716b4e72dff..5b13090746c4 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModel.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModel.kt
@@ -29,7 +29,6 @@ import org.wordpress.android.usecase.social.GetJetpackSocialShareMessageUseCase
 import org.wordpress.android.usecase.social.GetPublicizeConnectionsForUserUseCase
 import org.wordpress.android.usecase.social.JetpackSocialFlow
 import org.wordpress.android.usecase.social.ShareLimit
-import org.wordpress.android.util.config.JetpackSocialFeatureConfig
 import org.wordpress.android.util.extensions.doesNotContain
 import org.wordpress.android.util.extensions.updatePublicizeSkipConnections
 import org.wordpress.android.viewmodel.ScopedViewModel
@@ -39,7 +38,6 @@ import javax.inject.Named

 class EditorJetpackSocialViewModel @Inject constructor(
     private val dispatcher: Dispatcher,
-    private val jetpackSocialFeatureConfig: JetpackSocialFeatureConfig,
     private val accountStore: AccountStore,
     private val getPublicizeConnectionsForUserUseCase: GetPublicizeConnectionsForUserUseCase,
     private val getJetpackSocialShareMessageUseCase: GetJetpackSocialShareMessageUseCase,
@@ -101,7 +99,6 @@ class EditorJetpackSocialViewModel @Inject constructor(
     }

     fun onResume(jetpackSocialFlow: JetpackSocialFlow) {
-        if (!jetpackSocialFeatureConfig.isEnabled()) return
         _jetpackSocialUiState.value?.let { uiState ->
             if (uiState is JetpackSocialUiState.Loaded && uiState.showShareLimitUi) {
                 jetpackSocialSharingTracker.trackShareLimitDisplayed(jetpackSocialFlow)
@@ -190,8 +187,7 @@ class EditorJetpackSocialViewModel @Inject constructor(
         }
     }

-    private fun shouldShowJetpackSocial() = jetpackSocialFeatureConfig.isEnabled()
-            && !editPostRepository.isPage
+    private fun shouldShowJetpackSocial() = !editPostRepository.isPage
             && siteModel.supportsPublicize()
             && currentPost?.status != PostStatus.PRIVATE.toString()

diff --git a/WordPress/src/main/java/org/wordpress/android/util/config/JetpackSocialFeatureConfig.kt b/WordPress/src/main/java/org/wordpress/android/util/config/JetpackSocialFeatureConfig.kt
deleted file mode 100644
index ed17a64a6129..000000000000
--- a/WordPress/src/main/java/org/wordpress/android/util/config/JetpackSocialFeatureConfig.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.wordpress.android.util.config
-
-import org.wordpress.android.BuildConfig
-import org.wordpress.android.annotation.Feature
-import javax.inject.Inject
-
-private const val JETPACK_SOCIAL_REMOTE_FIELD = "jetpack_social_improvements_v1"
-
-@Feature(JETPACK_SOCIAL_REMOTE_FIELD, defaultValue = true)
-class JetpackSocialFeatureConfig @Inject constructor(
-    appConfig: AppConfig
-) : FeatureConfig(
-    appConfig,
-    BuildConfig.JETPACK_SOCIAL,
-    JETPACK_SOCIAL_REMOTE_FIELD,
-) {
-    override fun isEnabled(): Boolean {
-        return super.isEnabled() && BuildConfig.IS_JETPACK_APP
-    }
-}

diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModelTest.kt
index 1e05b7d4f486..a7c45b2b0238 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModelTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/posts/EditorJetpackSocialViewModelTest.kt
@@ -9,6 +9,7 @@ import org.mockito.kotlin.any
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
+import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import org.wordpress.android.BaseUnitTest
@@ -36,16 +37,12 @@ import org.wordpress.android.usecase.social.GetJetpackSocialShareMessageUseCase
 import org.wordpress.android.usecase.social.GetPublicizeConnectionsForUserUseCase
 import org.wordpress.android.usecase.social.JetpackSocialFlow
 import org.wordpress.android.usecase.social.ShareLimit
-import org.wordpress.android.util.config.JetpackSocialFeatureConfig

 @OptIn(ExperimentalCoroutinesApi::class)
 class EditorJetpackSocialViewModelTest : BaseUnitTest() {
     @Mock
     lateinit var dispatcher: Dispatcher

-    @Mock
-    lateinit var jetpackSocialFeatureConfig: JetpackSocialFeatureConfig
-
     @Mock
     lateinit var accountStore: AccountStore

@@ -89,7 +86,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
     fun setUp() {
         classToTest = EditorJetpackSocialViewModel(
             dispatcher = dispatcher,
-            jetpackSocialFeatureConfig = jetpackSocialFeatureConfig,
             accountStore = accountStore,
             getPublicizeConnectionsForUserUseCase = getPublicizeConnectionsForUserUseCase,
             getJetpackSocialShareMessageUseCase = getJetpackSocialShareMessageUseCase,
@@ -110,18 +106,8 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
         classToTest.actionEvents.observeForever(actionEventsObserver)
     }

-    @Test
-    fun `Should NOT load jetpack social if FF is disabled`() = test {
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(false)
-        classToTest.start(FAKE_SITE_MODEL, editPostRepository)
-        verify(getPublicizeConnectionsForUserUseCase, never()).execute(any(), any(), any())
-    }
-
     @Test
     fun `Should NOT load jetpack social if post is page`() = test {
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(editPostRepository.isPage).thenReturn(true)
         classToTest.start(FAKE_SITE_MODEL, editPostRepository)
         verify(getPublicizeConnectionsForUserUseCase, never()).execute(any(), any(), any())
@@ -129,8 +115,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {

     @Test
     fun `Should NOT load jetpack social if PostStatus is PRIVATE`() = test {
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(editPostRepository.isPage).thenReturn(false)
         whenever(editPostRepository.getPost()).thenReturn(PostModel().apply {
             setStatus(PostStatus.PRIVATE.toString())
@@ -139,55 +123,42 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
         verify(getPublicizeConnectionsForUserUseCase, never()).execute(any(), any(), any())
     }

-    @Test
-    fun `Should hide jetpack social container if FF is disabled`() = test {
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(false)
-        classToTest.start(FAKE_SITE_MODEL, editPostRepository)
-        verify(showJetpackSocialContainerObserver).onChanged(JetpackSocialContainerVisibility.ALL_HIDDEN)
-    }

     @Test
-    fun `Should show jetpack social container if FF is enabled`() = test {
+    fun `Should show jetpack social container`() = test {
         mockUserId()
         whenever(getPublicizeConnectionsForUserUseCase.execute(any(), any(), any()))
             .thenReturn(emptyList())
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(true), editPostRepository)
         verify(showJetpackSocialContainerObserver).onChanged(JetpackSocialContainerVisibility.ALL_VISIBLE)
     }

     @Test
-    fun `Should get publicize connections for user if jetpack social FF is enabled`() = test {
+    fun `Should get publicize connections for user`() = test {
         mockUserId()
         whenever(getPublicizeConnectionsForUserUseCase.execute(any(), any(), any()))
             .thenReturn(emptyList())
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(), editPostRepository)
         verify(getPublicizeConnectionsForUserUseCase).execute(FAKE_REMOTE_SITE_ID, FAKE_USER_ID)
     }

     @Test
-    fun `Should get jetpack social share limit status if jetpack social FF is enabled`() = test {
+    fun `Should get jetpack social share limit status`() = test {
         mockUserId()
         whenever(getPublicizeConnectionsForUserUseCase.execute(any(), any(), any()))
             .thenReturn(emptyList())
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(), editPostRepository)
         verify(getJetpackSocialShareLimitStatusUseCase).execute(FAKE_SITE_MODEL)
     }

     @Test
-    fun `Should map no connections UI if connections are empty, FF is enabled and never dismissed by user`() = test {
+    fun `Should map no connections UI if connections are empty and never dismissed by user`() = test {
         mockUserId()
         whenever(getPublicizeConnectionsForUserUseCase.execute(any(), any(), any()))
             .thenReturn(emptyList())
@@ -195,8 +166,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(ShareLimit.Disabled)
         whenever(publicizeTableWrapper.getServiceList())
             .thenReturn(listOf(PublicizeService().apply { status = PublicizeService.Status.OK }))
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(appPrefsWrapper.getShouldShowJetpackSocialNoConnections(any(), any()))
             .thenReturn(true)

@@ -205,7 +174,7 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
     }

     @Test
-    fun `Should emit no connections UI if connections are empty, FF is enabled and never dismissed by user`() = test {
+    fun `Should emit no connections UI if connections are empty and never dismissed by user`() = test {
         val noConnections = JetpackSocialUiState.NoConnections(
             trainOfIconsModels = listOf(),
             message = "message",
@@ -221,8 +190,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(ShareLimit.Disabled)
         whenever(publicizeTableWrapper.getServiceList())
             .thenReturn(listOf(PublicizeService().apply { status = PublicizeService.Status.OK }))
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(jetpackUiStateMapper.mapNoConnections(any(), any()))
             .thenReturn(noConnections)
         whenever(appPrefsWrapper.getShouldShowJetpackSocialNoConnections(any(), any()))
@@ -240,8 +207,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(ShareLimit.Disabled)
         whenever(publicizeTableWrapper.getServiceList())
             .thenReturn(listOf(PublicizeService().apply { status = PublicizeService.Status.OK }))
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(appPrefsWrapper.getShouldShowJetpackSocialNoConnections(any(), any()))
             .thenReturn(false)

@@ -250,7 +215,7 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
     }

     @Test
-    fun `Should map loaded UI state if connections list is NOT empty and jetpack social FF is enabled`() = test {
+    fun `Should map loaded UI state if connections list is NOT empty`() = test {
         mockUserId()
         whenever(getPublicizeConnectionsForUserUseCase.execute(any(), any(), any()))
             .thenReturn(listOf(FAKE_PUBLICIZE_CONNECTION))
@@ -258,14 +223,12 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn("Message")
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(true), editPostRepository)
         verify(jetpackUiStateMapper).mapLoaded(any(), any(), any(), any(), any(), any(), any(), any())
     }

     @Test
-    fun `Should emit loaded UI state if connections list is NOT empty and jetpack social FF is enabled`() = test {
+    fun `Should emit loaded UI state if connections list is NOT empty`() = test {
         val loaded = JetpackSocialUiState.Loaded(
             jetpackSocialConnectionDataList = listOf(
                 JetpackSocialConnectionData(
@@ -289,8 +252,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(ShareLimit.Disabled)
         whenever(getJetpackSocialShareMessageUseCase.execute(any()))
             .thenReturn("Message")
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(jetpackUiStateMapper.mapLoaded(any(), any(), any(), any(), any(), any(), any(), any()))
             .thenReturn(loaded)
         classToTest.start(fakeSiteModel(true), editPostRepository)
@@ -322,8 +283,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(ShareLimit.Disabled)
         whenever(getJetpackSocialShareMessageUseCase.execute(any()))
             .thenReturn("Message")
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(jetpackUiStateMapper.mapLoaded(any(), any(), any(), any(), any(), any(), any(), any()))
             .thenReturn(loaded)

@@ -340,7 +299,12 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
     }

     @Test
-    fun `Should emit OpenSocialConnectionList when onJetpackSocialConnectProfilesClick is called`() {
+    fun `Should emit OpenSocialConnectionList when onJetpackSocialConnectProfilesClick is called`() = test {
+        mockUserId()
+        whenever(getPublicizeConnectionsForUserUseCase.execute(any(), any(), any()))
+            .thenReturn(emptyList())
+        whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
+            .thenReturn(ShareLimit.Disabled)
         classToTest.start(fakeSiteModel(), editPostRepository)
         classToTest.onJetpackSocialConnectProfilesClick(JetpackSocialFlow.POST_SETTINGS)
         verify(actionEventsObserver).onChanged(
@@ -368,8 +332,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(ShareLimit.Disabled)
         whenever(publicizeTableWrapper.getServiceList())
             .thenReturn(listOf(PublicizeService().apply { status = PublicizeService.Status.OK }))
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(jetpackUiStateMapper.mapNoConnections(any(), any()))
             .thenReturn(noConnections)
         whenever(appPrefsWrapper.getShouldShowJetpackSocialNoConnections(any(), any()))
@@ -393,8 +355,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(ShareLimit.Disabled)
         whenever(publicizeTableWrapper.getServiceList())
             .thenReturn(listOf(PublicizeService().apply { status = PublicizeService.Status.OK }))
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(appPrefsWrapper.getShouldShowJetpackSocialNoConnections(any(), any()))
             .thenReturn(false)

@@ -433,8 +393,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(mock())
         whenever(getJetpackSocialShareMessageUseCase.execute(any()))
             .thenReturn("Message")
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(jetpackUiStateMapper.mapLoaded(any(), any(), any(), any(), any(), any(), any(), any()))
             .thenReturn(loaded)
         classToTest.start(fakeSiteModel(true), editPostRepository)
@@ -472,8 +430,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(mock())
         whenever(getJetpackSocialShareMessageUseCase.execute(any()))
             .thenReturn("Message")
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         whenever(jetpackUiStateMapper.mapLoaded(any(), any(), any(), any(), any(), any(), any(), any()))
             .thenReturn(loaded)
         classToTest.start(fakeSiteModel(true), editPostRepository)
@@ -492,8 +448,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(emptyList())
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(true), editPostRepository)

         classToTest.onJetpackSocialConnectProfilesClick(JetpackSocialFlow.POST_SETTINGS)
@@ -507,8 +461,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(emptyList())
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(true), editPostRepository)

         classToTest.onJetpackSocialNotNowClick(JetpackSocialFlow.POST_SETTINGS)
@@ -522,8 +474,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(emptyList())
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(true), editPostRepository)

         classToTest.onJetpackSocialSubscribeClick(JetpackSocialFlow.POST_SETTINGS)
@@ -537,8 +487,6 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
             .thenReturn(emptyList())
         whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
             .thenReturn(ShareLimit.Disabled)
-        whenever(jetpackSocialFeatureConfig.isEnabled())
-            .thenReturn(true)
         classToTest.start(fakeSiteModel(true), editPostRepository)

         classToTest.onJetpackSocialConnectionClick(mock(), true, JetpackSocialFlow.POST_SETTINGS)
@@ -549,9 +497,18 @@ class EditorJetpackSocialViewModelTest : BaseUnitTest() {
     }

     @Test
-    fun `Should start jetpack social if onPostVisibilityChanged is called`() {
+    fun `Should start jetpack social if onPostVisibilityChanged is called`() = test {
+        // Initialize EditPostRepository
+        mockUserId()
+        whenever(getPublicizeConnectionsForUserUseCase.execute(any(), any(), any()))
+            .thenReturn(emptyList())
+        whenever(getJetpackSocialShareLimitStatusUseCase.execute(any()))
+            .thenReturn(ShareLimit.Disabled)
+        classToTest.start(fakeSiteModel(true), editPostRepository)
+        //
         classToTest.onPostStatusChanged()
-        verify(showJetpackSocialContainerObserver).onChanged(any())
+        // verify 2 times: one for start method and another for onPostStatusChanged method
+        verify(showJetpackSocialContainerObserver, times(2)).onChanged(any())
     }

     private fun fakeSiteModel(supportsPublicize: Boolean = false): SiteModel = FAKE_SITE_MODEL.apply {


Cancel stale request on domain search screen

Fixes #19563

The request is canceled when it takes longer than the updated query is received. It was reached by using the flatMapLatest operator, which is a part of the Experimental Coroutines API though.

To test:
- Enable `domain_management` feature flag
- Go to Me -> Domains -> (+)
- Try to type random characters slowly (slower than the query debounce time (250ms)) but while you can see a loading stipes
- [ ] Ensure that you can see results for the very last query you typed

<details>
  <summary> Before </summary>

  https://github.com/wordpress-mobile/WordPress-Android/assets/16563318/e3626dd5-76f0-4b09-89fd-b23ab1f6949d

</details>

<details>
  <summary> After </summary>

https://github.com/wordpress-mobile/WordPress-Android/assets/16563318/e03aca49-6525-45f3-b240-b5482a81778a

</details>

## Regression Notes
1. Potential unintended areas of impact
Search for a domain screen

2. What I did to test those areas of impact (or what existing automated tests I relied on)
Manual testing / Unit testing

3. What automated tests I added (or what prevented me from doing so)
N/A

PR submission checklist:

- [x] I have completed the Regression Notes.
- [x] I have considered adding accessibility improvements for my changes.
- [x] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

UI Changes testing checklist:

- [ ] Portrait and landscape orientations.
- [ ] Light and dark modes.
- [ ] Fonts: Larger, smaller and bold text.
- [ ] High contrast.
- [ ] Talkback.
- [ ] Languages with large words or with letters/accents not frequently used in English.
- [ ] Right-to-left languages. (Even if translation isn’t complete, formatting should still respect the right-to-left layout)
- [ ] Large and small screen sizes. (Tablet and smaller phones)
- [ ] Multi-tasking: Split screen and Pop-up view. (Android 10 or higher)

🤖 Cancel stale requests on domain search screen
When searching for a new domain (to purchase) within the domain management flow, the results of multiple requests are loaded sequentially after typing. Only the latest request should be used to update the pending / loading screen.

Note that this is a separate concern from the debounce delay (used to throttle requests and reduce wasted fetches on keystrokes). Increasing the debounce delay _could_ mitigate the buggy results ux, but this has a tradeoff of increasing the total time to success. Instead, we should ensure that only the response to the latest request can update the ui state.

diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModel.kt
index 9c79f4b6a217..f1a3a1af30d2 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModel.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModel.kt
@@ -3,6 +3,7 @@ package org.wordpress.android.ui.domains.management.newdomainsearch
 import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -10,11 +11,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import org.wordpress.android.analytics.AnalyticsTracker
 import org.wordpress.android.modules.UI_THREAD
 import org.wordpress.android.ui.domains.management.newdomainsearch.domainsfetcher.NewDomainsSearchRepository
+import org.wordpress.android.ui.domains.management.newdomainsearch.domainsfetcher.NewDomainsSearchRepository.DomainsResult
 import org.wordpress.android.ui.domains.management.newdomainsearch.domainsfetcher.ProposedDomain
 import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
 import org.wordpress.android.viewmodel.ScopedViewModel
@@ -23,7 +27,7 @@ import javax.inject.Named

 private const val SEARCH_QUERY_DELAY_MS = 250L

-@OptIn(FlowPreview::class)
+@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
 @HiltViewModel
 class NewDomainSearchViewModel @Inject constructor(
     @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher,
@@ -43,24 +47,28 @@ class NewDomainSearchViewModel @Inject constructor(
         debouncedQuery
             .filter { it.isNotBlank() }
             .debounce(SEARCH_QUERY_DELAY_MS)
-            .onEach(::fetchDomains)
+            .flatMapLatest { query -> flowOf(fetchDomains(query)) }
+            .onEach(::handleDomainsResult)
             .launchIn(viewModelScope)
     }

-    private suspend fun fetchDomains(query: String) {
+    private suspend fun fetchDomains(query: String): DomainsResult {
         _uiStateFlow.emit(UiState.Loading)
-        val result = newDomainsSearchRepository.searchForDomains(query)
+        return newDomainsSearchRepository.searchForDomains(query)
+    }
+
+    private suspend fun handleDomainsResult(result: DomainsResult) {
         _uiStateFlow.emit(
             when (result) {
-                is NewDomainsSearchRepository.DomainsResult.Success -> UiState.PopulatedDomains(result.proposedDomains)
-                is NewDomainsSearchRepository.DomainsResult.Error -> UiState.Error
+                is DomainsResult.Success -> UiState.PopulatedDomains(result.proposedDomains)
+                is DomainsResult.Error -> UiState.Error
             }
         )
     }

     fun onSearchQueryChanged(query: String) {
         launch {
-           debouncedQuery.emit(query.trim())
+            debouncedQuery.emit(query.trim())
         }
     }

@@ -99,6 +107,7 @@ class NewDomainSearchViewModel @Inject constructor(
             val domain: String,
             val supportsPrivacy: Boolean
         ) : ActionEvent()
+
         data class TransferDomain(val url: String) : ActionEvent()
         object GoBack : ActionEvent()
     }

diff --git a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModelTest.kt
index 6298a2ee56d5..c27fdfb1fe44 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModelTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchViewModelTest.kt
@@ -11,6 +11,7 @@ import org.junit.Test
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.argWhere
+import org.mockito.kotlin.doSuspendableAnswer
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.never
 import org.mockito.kotlin.times
@@ -89,11 +90,11 @@ class NewDomainSearchViewModelTest : BaseUnitTest() {

     @Test
     fun `WHEN transfer domain button pressed THEN track DOMAIN_MANAGEMENT_TRANSFER_DOMAIN_TAPPED event`() = test {
-            viewModel.onTransferDomainClicked()
-            advanceUntilIdle()
+        viewModel.onTransferDomainClicked()
+        advanceUntilIdle()

-            verify(analyticsTracker).track(AnalyticsTracker.Stat.DOMAIN_MANAGEMENT_TRANSFER_DOMAIN_TAPPED)
-        }
+        verify(analyticsTracker).track(AnalyticsTracker.Stat.DOMAIN_MANAGEMENT_TRANSFER_DOMAIN_TAPPED)
+    }

     @Test
     fun `WHEN back button pressed THEN send GoBack action event`() = testWithActionEvents { events ->
@@ -106,8 +107,9 @@ class NewDomainSearchViewModelTest : BaseUnitTest() {
     @Suppress("MaxLineLength")
     @Test
     fun `GIVEN few queries requested within 250 ms query delay WHEN onSearchQueryChanged THEN search for domains with the last query only`() =
-        test {
-            whenever(repository.searchForDomains("third")).thenReturn(DomainsResult.Success(emptyList()))
+        testWithUiStates { states ->
+            val result = listOf(ProposedDomain(0, "", "", "", true))
+            whenever(repository.searchForDomains("third")).thenReturn(DomainsResult.Success(result))

             viewModel.onSearchQueryChanged("first")
             delay(200)
@@ -118,16 +120,22 @@ class NewDomainSearchViewModelTest : BaseUnitTest() {

             verify(repository, never()).searchForDomains("first")
             verify(repository, never()).searchForDomains("second")
-            verify(repository).searchForDomains("third")
+
+            assertThat(states.first()).isEqualTo(UiState.PopulatedDomains(emptyList())) // Initial
+            assertThat(states[1]).isEqualTo(UiState.Loading)
+            assertThat(states[2]).isEqualTo(UiState.PopulatedDomains(result))
         }

     @Suppress("MaxLineLength")
     @Test
     fun `GIVEN few queries requested outside 250 ms delay WHEN onSearchQueryChanged THEN search for domains with all the queries`() =
-        test {
-            whenever(repository.searchForDomains("first")).thenReturn(DomainsResult.Success(emptyList()))
-            whenever(repository.searchForDomains("second")).thenReturn(DomainsResult.Success(emptyList()))
-            whenever(repository.searchForDomains("third")).thenReturn(DomainsResult.Success(emptyList()))
+        testWithUiStates { states ->
+            val firstResult = listOf(ProposedDomain(0, "", "", "", true))
+            val secondResult = listOf(ProposedDomain(1, "", "", "", true))
+            val thirdResult = listOf(ProposedDomain(2, "", "", "", true))
+            whenever(repository.searchForDomains("first")).thenReturn(DomainsResult.Success(firstResult))
+            whenever(repository.searchForDomains("second")).thenReturn(DomainsResult.Success(secondResult))
+            whenever(repository.searchForDomains("third")).thenReturn(DomainsResult.Success(thirdResult))

             viewModel.onSearchQueryChanged("first")
             delay(250)
@@ -136,9 +144,36 @@ class NewDomainSearchViewModelTest : BaseUnitTest() {
             viewModel.onSearchQueryChanged("third")
             advanceUntilIdle()

-            verify(repository).searchForDomains("first")
-            verify(repository).searchForDomains("second")
-            verify(repository).searchForDomains("third")
+            assertThat(states.first()).isEqualTo(UiState.PopulatedDomains(emptyList())) // Initial
+            assertThat(states[1]).isEqualTo(UiState.Loading)
+            assertThat(states[2]).isEqualTo(UiState.PopulatedDomains(firstResult))
+            assertThat(states[3]).isEqualTo(UiState.Loading)
+            assertThat(states[4]).isEqualTo(UiState.PopulatedDomains(secondResult))
+            assertThat(states[5]).isEqualTo(UiState.Loading)
+            assertThat(states[6]).isEqualTo(UiState.PopulatedDomains(thirdResult))
+        }
+
+    @Suppress("MaxLineLength")
+    @Test
+    fun `GIVEN recent query is appeared when previous request is in progress WHEN onSearchQueryChanged THEN cancel previous request and return result for the last query only`() =
+        testWithUiStates { states ->
+            val firstResult = listOf(ProposedDomain(0, "", "", "", true))
+            val secondResult = listOf(ProposedDomain(1, "", "", "", true))
+
+            whenever(repository.searchForDomains("first")).doSuspendableAnswer {
+                delay(301)
+                DomainsResult.Success(firstResult)
+            }
+            whenever(repository.searchForDomains("second")).thenReturn(DomainsResult.Success(secondResult))
+
+            viewModel.onSearchQueryChanged("first")
+            delay(300)
+            viewModel.onSearchQueryChanged("second")
+            advanceUntilIdle()
+
+            assertThat(states.first()).isEqualTo(UiState.PopulatedDomains(emptyList())) // Initial
+            assertThat(states[1]).isEqualTo(UiState.Loading)
+            assertThat(states[2]).isEqualTo(UiState.PopulatedDomains(secondResult))
         }

     @Test
@@ -186,20 +221,21 @@ class NewDomainSearchViewModelTest : BaseUnitTest() {

     @Suppress("MaxLineLength")
     @Test
-    fun `GIVEN repeated queries with redundant blank symbols WHEN onSearchQueryChanged THEN fetch domains only once`() = test {
-        whenever(repository.searchForDomains("query")).thenReturn(DomainsResult.Success(emptyList()))
+    fun `GIVEN repeated queries with redundant blank symbols WHEN onSearchQueryChanged THEN fetch domains only once`() =
+        test {
+            whenever(repository.searchForDomains("query")).thenReturn(DomainsResult.Success(emptyList()))

-        viewModel.onSearchQueryChanged("query")
-        delay(250)
-        viewModel.onSearchQueryChanged(" query")
-        delay(250)
-        viewModel.onSearchQueryChanged("query ")
-        advanceUntilIdle()
+            viewModel.onSearchQueryChanged("query")
+            delay(250)
+            viewModel.onSearchQueryChanged(" query")
+            delay(250)
+            viewModel.onSearchQueryChanged("query ")
+            advanceUntilIdle()

-        verify(repository, times(1)).searchForDomains("query")
-        verify(repository, never()).searchForDomains(" query")
-        verify(repository, never()).searchForDomains("query ")
-    }
+            verify(repository, times(1)).searchForDomains("query")
+            verify(repository, never()).searchForDomains(" query")
+            verify(repository, never()).searchForDomains("query ")
+        }

     private fun testWithActionEvents(block: suspend TestScope.(events: List<ActionEvent>) -> Unit) = test {
         val actionEvents = mutableListOf<ActionEvent>()


Fixes #19484

This PR includes additional "blog name" and "status" properties to filter my domains list by. In addition, I moved the filter logic from the UI layer to the ViewModel to make it more efficient. The previous implementation caused unnecessary invoking of filtering while scrolling the list. Composables should just render data to avoid performance issues caused by repeated calculations while recomposition.

To test:
- Make sure you enabled `domain_management` feature flag
- If your account doesn't have a list of domains you can use a mocked list via sandboxing.  (Details here - p1692765970283879-slack-C05NS0YV7HS) Unfortunately, you can only search by domain and status using the mocked domains.
- Go to Me -> Domains
- [ ] Make sure you can search locally for domains by blog name and domain status

## Regression Notes
1. Potential unintended areas of impact
All Domains screen

2. What I did to test those areas of impact (or what existing automated tests I relied on)
Manual testing

3. What automated tests I added (or what prevented me from doing so)
N/A

PR submission checklist:

- [ ] I have completed the Regression Notes.
- [ ] I have considered adding accessibility improvements for my changes.
- [ ] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

UI Changes testing checklist:

- [ ] Portrait and landscape orientations.
- [ ] Light and dark modes.
- [ ] Fonts: Larger, smaller and bold text.
- [ ] High contrast.
- [ ] Talkback.
- [ ] Languages with large words or with letters/accents not frequently used in English.
- [ ] Right-to-left languages. (Even if translation isn’t complete, formatting should still respect the right-to-left layout)
- [ ] Large and small screen sizes. (Tablet and smaller phones)
- [ ] Multi-tasking: Split screen and Pop-up view. (Android 10 or higher)

🤖 Update search logic to include blog name and status
This task is to update the "My domains" search logic to match on `blogName` and `status`.

diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt
index 8d39603ac8cf..5fec8c1a1a04 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt
@@ -22,10 +22,11 @@ class DomainManagementActivity : AppCompatActivity() {
                 val uiState by viewModel.uiStateFlow.collectAsState()
                 MyDomainsScreen(
                     uiState = uiState,
+                    onSearchQueryChanged = viewModel::onSearchQueryChanged,
                     onDomainTapped = viewModel::onDomainTapped,
                     onAddDomainTapped = viewModel::onAddDomainClicked,
                     onFindDomainTapped = viewModel::onAddDomainClicked,
-                    onBackTapped = viewModel::onBackTapped
+                    onBackTapped = viewModel::onBackTapped,
                 )
             }
         }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt
index ba4617837131..9654e86fdb24 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt
@@ -5,18 +5,22 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import org.wordpress.android.R
 import org.wordpress.android.analytics.AnalyticsTracker.Stat
 import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain
 import org.wordpress.android.fluxc.network.rest.wpcom.site.DomainStatus
 import org.wordpress.android.fluxc.network.rest.wpcom.site.StatusType
 import org.wordpress.android.modules.UI_THREAD
+import org.wordpress.android.ui.domains.management.util.DomainLocalSearchEngine
 import org.wordpress.android.ui.domains.usecases.AllDomains
 import org.wordpress.android.ui.domains.usecases.FetchAllDomainsUseCase
 import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
@@ -32,6 +36,7 @@ class DomainManagementViewModel @Inject constructor(
     @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher,
     private val analyticsTracker: AnalyticsTrackerWrapper,
     private val fetchAllDomainsUseCase: FetchAllDomainsUseCase,
+    private val domainLocalSearchEngine: DomainLocalSearchEngine,
 ) : ScopedViewModel(mainDispatcher) {
     private val _actionEvents = MutableSharedFlow<ActionEvent>()
     val actionEvents: Flow<ActionEvent> = _actionEvents
@@ -39,19 +44,45 @@ class DomainManagementViewModel @Inject constructor(
     private val _uiStateFlow: MutableStateFlow<UiState> = MutableStateFlow(UiState.PopulatedList.Initial)
     val uiStateFlow = _uiStateFlow.asStateFlow()

+    private val searchQuery = MutableStateFlow("")
+
     init {
         analyticsTracker.track(Stat.DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_SHOWN)
         launch {
-            fetchAllDomainsUseCase.execute().let {
-                _uiStateFlow.value = when (it) {
-                    AllDomains.Empty -> UiState.Empty
-                    AllDomains.Error -> UiState.Error
-                    is AllDomains.Success -> UiState.PopulatedList.Loaded(it.domains)
-                }
+            fetchAllDomains()
+        }
+        launch {
+            searchQuery
+                .onEach(::filterDomains)
+                .launchIn(viewModelScope)
+        }
+    }
+
+    private suspend fun fetchAllDomains() {
+        fetchAllDomainsUseCase.execute().let {
+            _uiStateFlow.value = when (it) {
+                AllDomains.Empty -> UiState.Empty
+                AllDomains.Error -> UiState.Error
+                is AllDomains.Success -> UiState.PopulatedList.Loaded.Complete(it.domains)
             }
         }
     }

+    private fun filterDomains(query: String) {
+        val domains = when (val state = _uiStateFlow.value) {
+            is UiState.PopulatedList.Loaded -> state.allDomains
+            else -> return
+        }
+        _uiStateFlow.value = if (query.isBlank()) {
+            UiState.PopulatedList.Loaded.Complete(allDomains = domains)
+        } else {
+            UiState.PopulatedList.Loaded.Filtered(
+                filtered = domainLocalSearchEngine.filter(domains, query),
+                allDomains = domains
+            )
+        }
+    }
+
     fun onDomainTapped(detailUrl: String) {
         analyticsTracker.track(Stat.DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_DOMAIN_TAPPED)
         launch {
@@ -72,6 +103,12 @@ class DomainManagementViewModel @Inject constructor(
         }
     }

+    fun onSearchQueryChanged(query: String) {
+        launch {
+            searchQuery.emit(query.trim())
+        }
+    }
+
     sealed class ActionEvent {
         data class DomainTapped(val detailUrl: String): ActionEvent()
         object AddDomainTapped: ActionEvent()
@@ -81,12 +118,15 @@ class DomainManagementViewModel @Inject constructor(
     sealed class UiState {
         sealed class PopulatedList: UiState() {
             object Initial: PopulatedList()
-            data class Loaded(val domains: List<AllDomainsDomain>): PopulatedList()
-
-            fun filter(query: String): PopulatedList = if (this is Loaded) {
-                copy(domains = domains.filter { domain -> domain.matches(query) })
-            } else {
-                this
+            sealed class Loaded : PopulatedList() {
+                abstract val allDomains: List<AllDomainsDomain>
+                data class Complete(
+                    override val allDomains: List<AllDomainsDomain>
+                ): Loaded()
+                data class Filtered(
+                    override val allDomains: List<AllDomainsDomain>,
+                    val filtered: List<AllDomainsDomain>
+                ) : Loaded()
             }
         }
         object Empty: UiState()
@@ -169,7 +209,3 @@ val DomainStatus.isBold
         null -> true
         else -> false
     }
-
-private fun AllDomainsDomain.matches(query: String) =
-    domain?.contains(query, true) ?: false
-            || siteSlug?.contains(query, true) ?: false
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt
index 7a76f20c68c2..1a03e5d279d6 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt
@@ -49,6 +49,7 @@ import org.wordpress.android.ui.domains.management.composable.DomainsSearchTextF
 @Composable
 fun MyDomainsScreen(
     uiState: UiState,
+    onSearchQueryChanged: (String) -> Unit,
     onDomainTapped: (detailUrl: String) -> Unit,
     onAddDomainTapped: () -> Unit,
     onFindDomainTapped: () -> Unit,
@@ -87,12 +88,15 @@ fun MyDomainsScreen(
             MyDomainsSearchInput(
                 elevation.value,
                 queryString = queryString,
-                onQueryStringChanged = { queryString = it },
+                onQueryStringChanged = {
+                    queryString = it
+                    onSearchQueryChanged(it)
+                },
                 enabled = uiState is PopulatedList.Loaded,
             )
             when (uiState) {
                 is PopulatedList -> MyDomainsList(
-                    listUiState = uiState.filter(queryString),
+                    listUiState = uiState,
                     listState = listState,
                     onDomainTapped,
                 )
@@ -211,8 +215,14 @@ fun MyDomainsList(
                     }
                 }

-            is PopulatedList.Loaded -> {
-                items(items = listUiState.domains) {
+            is PopulatedList.Loaded.Complete -> {
+                items(items = listUiState.allDomains) {
+                    DomainListCard(uiState = DomainCardUiState.fromDomain(domain = it), onDomainTapped)
+                }
+            }
+
+            is PopulatedList.Loaded.Filtered -> {
+                items(items = listUiState.filtered) {
                     DomainListCard(uiState = DomainCardUiState.fromDomain(domain = it), onDomainTapped)
                 }
             }
@@ -227,6 +237,7 @@ fun PreviewMyDomainsScreen() {
     M3Theme {
         MyDomainsScreen(
             uiState = PopulatedList.Initial,
+            onSearchQueryChanged = {},
             onAddDomainTapped = {},
             onDomainTapped = {},
             onFindDomainTapped = {},
@@ -242,6 +253,7 @@ fun PreviewMyDomainsScreenError() {
     M3Theme {
         MyDomainsScreen(
             uiState = Error,
+            onSearchQueryChanged = {},
             onAddDomainTapped = {},
             onDomainTapped = {},
             onFindDomainTapped = {},
@@ -257,6 +269,7 @@ fun PreviewMyDomainsScreenEmpty() {
     M3Theme {
         MyDomainsScreen(
             uiState = Empty,
+            onSearchQueryChanged = {},
             onAddDomainTapped = {},
             onDomainTapped = {},
             onFindDomainTapped = {},
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchActivity.kt
index ceab64625fb9..07d39004e059 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchActivity.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/newdomainsearch/NewDomainSearchActivity.kt
@@ -32,7 +32,7 @@ class NewDomainSearchActivity : AppCompatActivity() {
                     onSearchQueryChanged = viewModel::onSearchQueryChanged,
                     onTransferDomainClicked = viewModel::onTransferDomainClicked,
                     onDomainTapped = viewModel::onDomainTapped,
-                    onBackPressed = viewModel::onBackPressed
+                    onBackPressed = viewModel::onBackPressed,
                 )
             }
         }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/util/DomainLocalSearchEngine.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/util/DomainLocalSearchEngine.kt
new file mode 100644
index 000000000000..3337dbb0d4c2
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/util/DomainLocalSearchEngine.kt
@@ -0,0 +1,15 @@
+package org.wordpress.android.ui.domains.management.util
+
+import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain
+import javax.inject.Inject
+
+class DomainLocalSearchEngine @Inject constructor() {
+    fun filter(domains: List<AllDomainsDomain>, query: String): List<AllDomainsDomain> =
+        domains.filter { it.matches(query) }
+
+    private fun AllDomainsDomain.matches(query: String) =
+        domain?.contains(query, true) ?: false
+                || siteSlug?.contains(query, true) ?: false
+                || blogName?.contains(query, true) ?: false
+                || domainStatus?.status?.contains(query, true) ?: false
+}

diff --git a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt
index 716480b54830..f7014f808bbe 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt
@@ -10,11 +10,15 @@ import org.junit.Test
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoInteractions
 import org.mockito.kotlin.whenever
 import org.wordpress.android.BaseUnitTest
 import org.wordpress.android.analytics.AnalyticsTracker.Stat.DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_DOMAIN_TAPPED
 import org.wordpress.android.analytics.AnalyticsTracker.Stat.DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_SHOWN
+import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain
 import org.wordpress.android.ui.domains.management.DomainManagementViewModel.ActionEvent
+import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState
+import org.wordpress.android.ui.domains.management.util.DomainLocalSearchEngine
 import org.wordpress.android.ui.domains.usecases.AllDomains
 import org.wordpress.android.ui.domains.usecases.FetchAllDomainsUseCase
 import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
@@ -27,22 +31,25 @@ class DomainManagementViewModelTest : BaseUnitTest() {
     @Mock
     lateinit var useCase: FetchAllDomainsUseCase

+    @Mock
+    lateinit var domainLocalSearchEngine: DomainLocalSearchEngine
+
     private lateinit var viewModel: DomainManagementViewModel

     @Before
-    fun setUp() = test {
+    fun setUp() {
         MockitoAnnotations.openMocks(this)
-        whenever(useCase.execute()).thenReturn(AllDomains.Empty)
-        viewModel = DomainManagementViewModel(testDispatcher(), analyticsTracker, useCase)
     }

     @Test
-    fun `WHEN ViewModel initialized THEN track DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_SHOWN event`() {
+    fun `WHEN ViewModel initialized THEN track DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_SHOWN event`() = test {
+        initializeViewModel()
         verify(analyticsTracker).track(DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_SHOWN)
     }

     @Test
-    fun `WHEN a domain is tapped THEN track DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_DOMAIN_TAPPED event`() {
+    fun `WHEN a domain is tapped THEN track DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_DOMAIN_TAPPED event`() = test {
+        initializeViewModel()
         viewModel.onDomainTapped(testDomain)
         verify(analyticsTracker).track(DOMAIN_MANAGEMENT_MY_DOMAINS_SCREEN_DOMAIN_TAPPED)
     }
@@ -62,15 +69,84 @@ class DomainManagementViewModelTest : BaseUnitTest() {
             assertThat(events.last()).isEqualTo(ActionEvent.NavigateBackTapped)
         }

-    private fun testWithActionEvents(block: suspend TestScope.(events: List<ActionEvent>) -> Unit) =
-        test {
-            val actionEvents = mutableListOf<ActionEvent>()
-            val job = launch { viewModel.actionEvents.toList(actionEvents) }
+    @Test
+    fun `GIVEN empty domains result WHEN initialized THEN send empty UI state`() = testWithState { state ->
+        assertThat(state.last()).isEqualTo(UiState.Empty)
+    }
+
+    @Test
+    fun `GIVEN error domains result WHEN initialized THEN send error UI state`() =
+        testWithState(initialAllDomainsFetchResult = AllDomains.Error) { state ->
+            assertThat(state.last()).isEqualTo(UiState.Error)
+        }
+
+    @Test
+    fun `GIVEN successful domains result WHEN initialized THEN send populated list loaded complete UI state`() {
+        val domains = listOf(AllDomainsDomain(), AllDomainsDomain())
+        testWithState(initialAllDomainsFetchResult = AllDomains.Success(domains)) { state ->
+            assertThat(state.last()).isEqualTo(UiState.PopulatedList.Loaded.Complete(domains))
+        }
+    }
+
+    @Suppress("MaxLineLength")
+    @Test
+    fun `GIVEN successful domains result and query is not blank WHEN search query changed THEN return populated loaded filtered UI state`() {
+        val domains = listOf(AllDomainsDomain(), AllDomainsDomain())
+        val filteredDomains = listOf(AllDomainsDomain())
+        whenever(domainLocalSearchEngine.filter(domains, query = "query")).thenReturn(filteredDomains)
+
+        testWithState(initialAllDomainsFetchResult = AllDomains.Success(domains)) { state ->
+            viewModel.onSearchQueryChanged("query")
+            advanceUntilIdle()
+            assertThat(state.last()).isEqualTo(
+                UiState.PopulatedList.Loaded.Filtered(allDomains = domains, filtered = filteredDomains)
+            )
+        }
+    }

-            block(actionEvents)
+    @Suppress("MaxLineLength")
+    @Test
+    fun `GIVEN successful domains result and blank query WHEN search query changed THEN return populated loaded complete UI state`() {
+        val domains = listOf(AllDomainsDomain(), AllDomainsDomain())

-            job.cancel()
+        testWithState(initialAllDomainsFetchResult = AllDomains.Success(domains)) { state ->
+            viewModel.onSearchQueryChanged(" ")
+            advanceUntilIdle()
+            assertThat(state.last()).isEqualTo(UiState.PopulatedList.Loaded.Complete(allDomains = domains))
+            verifyNoInteractions(domainLocalSearchEngine)
         }
+    }
+
+    private fun testWithActionEvents(
+        initialAllDomainsFetchResult: AllDomains = AllDomains.Empty,
+        block: suspend TestScope.(events: List<ActionEvent>) -> Unit
+    ) = test {
+        initializeViewModel(initialAllDomainsFetchResult)
+        val actionEvents = mutableListOf<ActionEvent>()
+        val job = launch { viewModel.actionEvents.toList(actionEvents) }
+
+        block(actionEvents)
+
+        job.cancel()
+    }
+
+    private fun testWithState(
+        initialAllDomainsFetchResult: AllDomains = AllDomains.Empty,
+        block: suspend TestScope.(states: List<UiState>) -> Unit
+    ) = test {
+        initializeViewModel(initialAllDomainsFetchResult)
+        val states = mutableListOf<UiState>()
+        val job = launch { viewModel.uiStateFlow.toList(states) }
+
+        block(states)
+
+        job.cancel()
+    }
+
+    private suspend fun initializeViewModel(initialAllDomainsFetchResult: AllDomains = AllDomains.Empty) {
+        whenever(useCase.execute()).thenReturn(initialAllDomainsFetchResult)
+        viewModel = DomainManagementViewModel(testDispatcher(), analyticsTracker, useCase, domainLocalSearchEngine)
+    }

     companion object {
         private const val testDomain = "domain"
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/PopulatedListTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/PopulatedListTest.kt
deleted file mode 100644
index e03a3140e9f2..000000000000
--- a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/PopulatedListTest.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.wordpress.android.ui.domains.management
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-import org.wordpress.android.BaseUnitTest
-import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain
-import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState.PopulatedList
-
-@ExperimentalCoroutinesApi
-class PopulatedListTest : BaseUnitTest() {
-    private val fakeDomainFoo = AllDomainsDomain(domain = "foo.com", siteSlug = "Foo Warehouse")
-    private val fakeDomainBar = AllDomainsDomain(domain = "bar.com", siteSlug = "Chocolate Bar")
-    private val fakeDomainBah = AllDomainsDomain(domain = "bah.com", siteSlug = "Black sheep")
-
-    private val uiState = PopulatedList.Loaded(
-        domains = listOf(fakeDomainFoo, fakeDomainBar, fakeDomainBah)
-    )
-
-    // PopulatedList
-
-    @Test
-    fun `PopulatedList filter matches correctly by domain`() {
-        // Given
-        val query = "foo"
-
-        // When
-        val result = uiState.filter(query)
-
-        // Then
-        assertThat(result).isEqualTo(PopulatedList.Loaded(listOf(fakeDomainFoo)))
-    }
-
-    @Test
-    fun `PopulatedList filter matches correctly by site slug`() {
-        // Given
-        val query = "chocolate"
-
-        // When
-        val result = uiState.filter(query)
-
-        // Then
-        assertThat(result).isEqualTo(PopulatedList.Loaded(listOf(fakeDomainBar)))
-    }
-
-    @Test
-    fun `PopulatedList filter matches all for the empty string`() {
-        // Given
-        val query = ""
-
-        // When
-        val result = uiState.filter(query)
-
-        // Then
-        assertThat(result).isEqualTo(uiState)
-    }
-}
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/util/DomainLocalSearchEngineTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/util/DomainLocalSearchEngineTest.kt
new file mode 100644
index 000000000000..93317d5ca245
--- /dev/null
+++ b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/util/DomainLocalSearchEngineTest.kt
@@ -0,0 +1,90 @@
+package org.wordpress.android.ui.domains.management.util
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain
+import org.wordpress.android.fluxc.network.rest.wpcom.site.DomainStatus
+
+class DomainLocalSearchEngineTest {
+    private val fakeDomainFoo = AllDomainsDomain(
+        domain = "foo.com",
+        siteSlug = "Foo Warehouse",
+        blogName = "Awesome blog",
+        domainStatus = DomainStatus(status = "Active")
+    )
+    private val fakeDomainBar = AllDomainsDomain(
+        domain = "bar.com",
+        siteSlug = "Chocolate Bar",
+        blogName = "Sweet blog",
+        domainStatus = DomainStatus(status = "Activating")
+    )
+    private val fakeDomainBah = AllDomainsDomain(
+        domain = "bah.com",
+        siteSlug = "Black sheep",
+        blogName = "Unique blog",
+        domainStatus = DomainStatus(status = "Expired")
+    )
+    private val allDomains = listOf(fakeDomainFoo, fakeDomainBar, fakeDomainBah)
+
+    private val engine = DomainLocalSearchEngine()
+
+    @Test
+    fun `PopulatedList filter matches correctly by domain`() {
+        // Given
+        val query = "foo"
+
+        // When
+        val result = engine.filter(allDomains, query)
+
+        // Then
+        assertThat(result).isEqualTo(listOf(fakeDomainFoo))
+    }
+
+    @Test
+    fun `PopulatedList filter matches correctly by site slug`() {
+        // Given
+        val query = "chocolate"
+
+        // When
+        val result = engine.filter(allDomains, query)
+
+        // Then
+        assertThat(result).isEqualTo(listOf(fakeDomainBar))
+    }
+
+    @Test
+    fun `PopulatedList filter matches correctly by domain status`() {
+        // Given
+        val query = "active"
+
+        // When
+        val result = engine.filter(allDomains, query)
+
+        // Then
+        assertThat(result).isEqualTo(listOf(fakeDomainFoo))
+    }
+
+    @Test
+    fun `PopulatedList filter matches correctly by blog name`() {
+        // Given
+        val query = "unique"
+
+        // When
+        val result = engine.filter(allDomains, query)
+
+        // Then
+        assertThat(result).isEqualTo(listOf(fakeDomainBah))
+    }
+
+    @Test
+    fun `PopulatedList filter matches all for the empty string`() {
+        // Given
+        val query = ""
+
+        // When
+        val result = engine.filter(allDomains, query)
+
+        // Then
+        assertThat(result).isEqualTo(allDomains)
+    }
+}


Handle navigation back button on the All Domain screen

Fixes #19503

This PR handles navigation back button on the "All Domains" screen.

To test:
- Make sure you have enabled `domain_management` feature flag
- Go to "Me" tab -> Domains
- [ ] Make sure that the navigation back button navigates back to the "Me" tab

https://github.com/wordpress-mobile/WordPress-Android/assets/16563318/f9c5802b-7180-4d49-87d5-bfc5a771353b

## Regression Notes
1. Potential unintended areas of impact
All Domains screen

2. What I did to test those areas of impact (or what existing automated tests I relied on)
Manual testing

3. What automated tests I added (or what prevented me from doing so)
N/A

PR submission checklist:

- [x] I have completed the Regression Notes.
- [x] I have considered adding accessibility improvements for my changes.
- [x] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

UI Changes testing checklist:

- [ ] Portrait and landscape orientations.
- [ ] Light and dark modes.
- [ ] Fonts: Larger, smaller and bold text.
- [ ] High contrast.
- [ ] Talkback.
- [ ] Languages with large words or with letters/accents not frequently used in English.
- [ ] Right-to-left languages. (Even if translation isn’t complete, formatting should still respect the right-to-left layout)
- [ ] Large and small screen sizes. (Tablet and smaller phones)
- [ ] Multi-tasking: Split screen and Pop-up view. (Android 10 or higher)

🤖 Add back button action in the domains list screen
Adding this as a reminder to add the back button action in the domains list screen

diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt
index 0fec612b0c80..8d39603ac8cf 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementActivity.kt
@@ -24,7 +24,8 @@ class DomainManagementActivity : AppCompatActivity() {
                     uiState = uiState,
                     onDomainTapped = viewModel::onDomainTapped,
                     onAddDomainTapped = viewModel::onAddDomainClicked,
-                    onFindDomainTapped = viewModel::onAddDomainClicked
+                    onFindDomainTapped = viewModel::onAddDomainClicked,
+                    onBackTapped = viewModel::onBackTapped
                 )
             }
         }
@@ -38,6 +39,7 @@ class DomainManagementActivity : AppCompatActivity() {
                 startActivity(DomainManagementDetailsActivity.createIntent(this, actionEvent.detailUrl))
             }
             is DomainManagementViewModel.ActionEvent.AddDomainTapped -> ActivityLauncher.openNewDomainSearch(this)
+            is DomainManagementViewModel.ActionEvent.NavigateBackTapped -> onBackPressedDispatcher.onBackPressed()
         }
     }
 }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt
index 2a0e76991749..ba4617837131 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/DomainManagementViewModel.kt
@@ -66,9 +66,16 @@ class DomainManagementViewModel @Inject constructor(
         }
     }

+    fun onBackTapped() {
+        launch {
+            _actionEvents.emit(ActionEvent.NavigateBackTapped)
+        }
+    }
+
     sealed class ActionEvent {
         data class DomainTapped(val detailUrl: String): ActionEvent()
         object AddDomainTapped: ActionEvent()
+        object NavigateBackTapped: ActionEvent()
     }

     sealed class UiState {
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt
index be4923e0ae8b..7a76f20c68c2 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/management/MyDomainsScreen.kt
@@ -41,9 +41,9 @@ import org.wordpress.android.ui.compose.components.MainTopAppBar
 import org.wordpress.android.ui.compose.components.NavigationIcons
 import org.wordpress.android.ui.compose.theme.AppColor
 import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState
-import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState.PopulatedList
-import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState.Error
 import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState.Empty
+import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState.Error
+import org.wordpress.android.ui.domains.management.DomainManagementViewModel.UiState.PopulatedList
 import org.wordpress.android.ui.domains.management.composable.DomainsSearchTextField

 @Composable
@@ -52,13 +52,14 @@ fun MyDomainsScreen(
     onDomainTapped: (detailUrl: String) -> Unit,
     onAddDomainTapped: () -> Unit,
     onFindDomainTapped: () -> Unit,
+    onBackTapped: () -> Unit,
 ) {
     Scaffold(
         topBar = {
             MainTopAppBar(
                 title = stringResource(R.string.domain_management_my_domains_title),
                 navigationIcon = NavigationIcons.BackIcon,
-                onNavigationIconClick = {},
+                onNavigationIconClick = onBackTapped,
                 actions = {
                     IconButton(
                         onClick = onAddDomainTapped,
@@ -75,7 +76,7 @@ fun MyDomainsScreen(
             )
         },
     ) { paddingValues ->
-        Column (Modifier.padding(paddingValues)) {
+        Column(Modifier.padding(paddingValues)) {
             var queryString by rememberSaveable { mutableStateOf("") }
             val listState = rememberLazyListState()

@@ -95,6 +96,7 @@ fun MyDomainsScreen(
                     listState = listState,
                     onDomainTapped,
                 )
+
                 Error -> ErrorScreen()
                 Empty -> EmptyScreen(onFindDomainTapped)
             }
@@ -104,7 +106,7 @@ fun MyDomainsScreen(

 @Composable
 fun ErrorScreen() {
-    Column (
+    Column(
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally,
         modifier = Modifier.fillMaxSize(),
@@ -124,7 +126,7 @@ fun ErrorScreen() {

 @Composable
 fun EmptyScreen(onFindDomainTapped: () -> Unit) {
-    Column (
+    Column(
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally,
         modifier = Modifier.fillMaxSize(),
@@ -171,7 +173,6 @@ fun PrimaryButton(
 }

-
 @Composable
 fun MyDomainsSearchInput(
     elevation: Dp,
@@ -179,7 +180,7 @@ fun MyDomainsSearchInput(
     onQueryStringChanged: (String) -> Unit,
     enabled: Boolean = false,
 ) {
-    Surface (shadowElevation = elevation, modifier = Modifier.zIndex(1f)) {
+    Surface(shadowElevation = elevation, modifier = Modifier.zIndex(1f)) {
         DomainsSearchTextField(
             value = queryString,
             onValueChange = onQueryStringChanged,
@@ -209,6 +210,7 @@ fun MyDomainsList(
                         DomainListCard(uiState = DomainCardUiState.Initial)
                     }
                 }
+
             is PopulatedList.Loaded -> {
                 items(items = listUiState.domains) {
                     DomainListCard(uiState = DomainCardUiState.fromDomain(domain = it), onDomainTapped)
@@ -223,15 +225,28 @@ fun MyDomainsList(
 @Composable
 fun PreviewMyDomainsScreen() {
     M3Theme {
-        MyDomainsScreen(PopulatedList.Initial, onAddDomainTapped = {}, onDomainTapped = {}, onFindDomainTapped = {})
+        MyDomainsScreen(
+            uiState = PopulatedList.Initial,
+            onAddDomainTapped = {},
+            onDomainTapped = {},
+            onFindDomainTapped = {},
+            onBackTapped = {}
+        )
     }
 }
+
 @Preview(device = Devices.PIXEL_3A, group = "Error / Offline")
 @Preview(device = Devices.PIXEL_3A, uiMode = UI_MODE_NIGHT_YES, group = "Error / Offline")
 @Composable
 fun PreviewMyDomainsScreenError() {
     M3Theme {
-        MyDomainsScreen(Error, onAddDomainTapped = {}, onDomainTapped = {}, onFindDomainTapped = {})
+        MyDomainsScreen(
+            uiState = Error,
+            onAddDomainTapped = {},
+            onDomainTapped = {},
+            onFindDomainTapped = {},
+            onBackTapped = {}
+        )
     }
 }

@@ -240,6 +255,12 @@ fun PreviewMyDomainsScreenError() {
 @Composable
 fun PreviewMyDomainsScreenEmpty() {
     M3Theme {
-        MyDomainsScreen(Empty, onAddDomainTapped = {}, onDomainTapped = {}, onFindDomainTapped = {})
+        MyDomainsScreen(
+            uiState = Empty,
+            onAddDomainTapped = {},
+            onDomainTapped = {},
+            onFindDomainTapped = {},
+            onBackTapped = {}
+        )
     }
 }

diff --git a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt
index b8eef4fd4fab..716480b54830 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/domains/management/DomainManagementViewModelTest.kt
@@ -54,6 +54,14 @@ class DomainManagementViewModelTest : BaseUnitTest() {
         assertThat(events.last()).isEqualTo(ActionEvent.DomainTapped(testDomain))
     }

+    @Test
+    fun `WHEN a navigation back button is tapped THEN send NavigateBackTapped action event`() =
+        testWithActionEvents { events ->
+            viewModel.onBackTapped()
+            advanceUntilIdle()
+            assertThat(events.last()).isEqualTo(ActionEvent.NavigateBackTapped)
+        }
+
     private fun testWithActionEvents(block: suspend TestScope.(events: List<ActionEvent>) -> Unit) =
         test {
             val actionEvents = mutableListOf<ActionEvent>()