diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt index 355ea09edf2..771f586fb3d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt @@ -479,6 +479,8 @@ class AnalyticsTracker private constructor( // We have to call in non consistent way to match the iOS naming const val VALUE_MORE_MENU_POS = "pointOfSale" + const val KEY_POS_NOT_ELIGIBLE_REASON = "not_eligible_reason" + const val VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION = "outdated_woocommerce_version" const val VALUE_MORE_MENU_PAYMENTS_BADGE_VISIBLE = "badge_visible" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt index 8f1cbe68e5e..c573bc72ae0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt @@ -28,6 +28,7 @@ import com.woocommerce.android.ui.moremenu.MoreMenuEvent.NavigateToSubscriptions import com.woocommerce.android.ui.moremenu.MoreMenuEvent.NavigateToWooPosEvent import com.woocommerce.android.ui.moremenu.MoreMenuEvent.OpenBlazeCampaignCreationEvent import com.woocommerce.android.ui.moremenu.MoreMenuEvent.OpenBlazeCampaignListEvent +import com.woocommerce.android.ui.moremenu.MoreMenuEvent.ShowWooPosWooCoreUpdateRequiredEvent import com.woocommerce.android.ui.moremenu.MoreMenuEvent.StartSitePickerEvent import com.woocommerce.android.ui.moremenu.MoreMenuEvent.ViewAdminEvent import com.woocommerce.android.ui.moremenu.MoreMenuEvent.ViewCouponsEvent @@ -42,6 +43,7 @@ import com.woocommerce.android.ui.woopos.root.WooPosActivity import com.woocommerce.android.util.ChromeCustomTabUtils import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +import org.wordpress.android.util.ToastUtils import javax.inject.Inject @AndroidEntryPoint @@ -113,6 +115,10 @@ class MoreMenuFragment : TopLevelFragment() { is OpenBlazeCampaignCreationEvent -> openBlazeCreationFlow() is OpenBlazeCampaignListEvent -> openBlazeCampaignList() is NavigateToWooPosEvent -> openWooPos() + is ShowWooPosWooCoreUpdateRequiredEvent -> ToastUtils.showToast( + requireContext(), + R.string.more_menu_button_woo_pos_update_woocommerce_version_description + ) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuScreen.kt index b961ecd4634..0583f17dfd7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuScreen.kt @@ -294,7 +294,7 @@ private fun MoreMenuSection(section: MoreMenuItemSection) { section.items.forEach { item -> when (item.state) { MoreMenuItemButton.State.Loading -> MoreMenuLoading() - MoreMenuItemButton.State.Visible -> MoreMenuButton(item) + is MoreMenuItemButton.State.Visible -> MoreMenuButton(item) MoreMenuItemButton.State.Hidden -> Unit } Spacer(modifier = Modifier.height(8.dp)) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuState.kt index dddbb57449f..73b78a2c2cf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuState.kt @@ -32,6 +32,7 @@ sealed class MoreMenuEvent : MultiLiveEvent.Event() { data object ViewCustomersEvent : MoreMenuEvent() data object NavigateToWooPosEvent : MoreMenuEvent() + data object ShowWooPosWooCoreUpdateRequiredEvent : MoreMenuEvent() } data class MoreMenuItemSection( @@ -45,12 +46,17 @@ data class MoreMenuItemButton( @StringRes val description: Int, @DrawableRes val icon: Int, @DrawableRes val extraIcon: Int? = null, - val state: State = State.Visible, + val state: State = State.Visible.Enabled, val badgeState: BadgeState? = null, val onClick: () -> Unit = {}, ) { - enum class State { - Loading, Visible, Hidden, + sealed class State { + data object Loading : State() + sealed class Visible : State() { + data object Enabled : Visible() + data object WooCoreVersionNotSupported : Visible() + } + data object Hidden : State() } enum class Type { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt index f5c697cc2a0..b606253287b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt @@ -9,6 +9,7 @@ import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_CAMPAIGN_LIST_ENTR import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_ENTRY_POINT_DISPLAYED import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_OPTION +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_POS_NOT_ELIGIBLE_REASON import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_ADMIN_MENU import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_COUPONS import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_CUSTOMERS @@ -19,6 +20,7 @@ import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_M import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_REVIEWS import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_UPGRADES import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_VIEW_STORE +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.extensions.adminUrlOrDefault import com.woocommerce.android.notifications.UnseenReviewsCountHandler @@ -35,6 +37,7 @@ import com.woocommerce.android.ui.payments.taptopay.isAvailable import com.woocommerce.android.ui.plans.domain.SitePlan import com.woocommerce.android.ui.plans.repository.SitePlanRepository import com.woocommerce.android.ui.woopos.WooPosIsEnabled +import com.woocommerce.android.util.GetWooCorePluginCachedVersion import com.woocommerce.android.util.WooLog import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel @@ -71,7 +74,8 @@ class MoreMenuViewModel @Inject constructor( private val isGoogleForWooEnabled: IsGoogleForWooEnabled, private val hasGoogleAdsCampaigns: HasGoogleAdsCampaigns, private val isWooPosEnabled: WooPosIsEnabled, - private val analyticsTrackerWrapper: AnalyticsTrackerWrapper + private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, + private val getWooCoreVersion: GetWooCorePluginCachedVersion, ) : ScopedViewModel(savedState) { private var storeHasGoogleAdsCampaigns = false @@ -114,8 +118,8 @@ class MoreMenuViewModel @Inject constructor( buttonsStates: Map, count: Int, paymentsFeatureWasClicked: Boolean - ) = listOf( - generatePOSSection(buttonsStates[MoreMenuItemButton.Type.WooPos]!!), + ) = listOfNotNull( + generatePOSMenuButtons(buttonsStates), generateSettingsMenuButtons(buttonsStates[MoreMenuItemButton.Type.Settings]!!), generateGeneralSection( unseenReviewsCount = count, @@ -126,6 +130,31 @@ class MoreMenuViewModel @Inject constructor( ) ) + private fun generatePOSMenuButtons( + buttonsStates: Map + ): MoreMenuItemSection? { + return buttonsStates[MoreMenuItemButton.Type.WooPos]?.let { wooPosState -> + when (wooPosState) { + is MoreMenuItemButton.State.Loading, is MoreMenuItemButton.State.Visible.Enabled -> + generateWooPosSection( + wooPosState, + R.string.more_menu_button_woo_pos_description + ) { + onWooPosButtonClick() + } + + is MoreMenuItemButton.State.Visible.WooCoreVersionNotSupported -> generateWooPosSection( + wooPosState, + R.string.more_menu_button_woo_pos_update_woocommerce_version_description + ) { + onWooPOSNotEligibleButtonClick(WooPosNotEligible.OutdatedWooCommerceVersion) + } + + else -> null + } + } + } + fun onViewResumed() { moreMenuNewFeatureHandler.markNewFeatureAsSeen() launch { @@ -134,20 +163,25 @@ class MoreMenuViewModel @Inject constructor( } } - private fun generatePOSSection(wooPosState: MoreMenuItemButton.State) = - MoreMenuItemSection( + private fun generateWooPosSection( + wooPosState: MoreMenuItemButton.State, + description: Int, + onPosButtonClick: () -> Unit + ): MoreMenuItemSection { + return MoreMenuItemSection( title = null, items = listOf( MoreMenuItemButton( title = R.string.more_menu_button_woo_pos, - description = R.string.more_menu_button_woo_pos_description, + description = description, icon = R.drawable.ic_more_menu_pos, extraIcon = R.drawable.ic_more_menu_pos_extra, state = wooPosState, - onClick = ::onWooPosButtonClick, + onClick = onPosButtonClick, ) ) ) + } @Suppress("LongMethod") private fun generateGeneralSection( @@ -400,6 +434,20 @@ class MoreMenuViewModel @Inject constructor( triggerEvent(MoreMenuEvent.NavigateToWooPosEvent) } + private fun onWooPOSNotEligibleButtonClick(reason: WooPosNotEligible) { + when (reason) { + WooPosNotEligible.OutdatedWooCommerceVersion -> { + trackMoreMenuOptionSelected( + VALUE_MORE_MENU_POS, + extraOptions = mapOf( + KEY_POS_NOT_ELIGIBLE_REASON to VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION + ) + ) + triggerEvent(MoreMenuEvent.ShowWooPosWooCoreUpdateRequiredEvent) + } + } + } + private fun onViewAdminButtonClick() { trackMoreMenuOptionSelected(VALUE_MORE_MENU_ADMIN_MENU) triggerEvent(MoreMenuEvent.ViewAdminEvent(selectedSite.get().adminUrlOrDefault)) @@ -460,16 +508,54 @@ class MoreMenuViewModel @Inject constructor( .onStart { emit("") } private fun checkFeaturesAvailability(): Flow> { - val initialState = MoreMenuItemButton.Type.entries.associateWith { - MoreMenuItemButton.State.Loading - }.toMutableMap() + val initialState = + MoreMenuItemButton.Type.entries.associateWith { + MoreMenuItemButton.State.Loading + }.toMutableMap() return listOf( - doCheckAvailability(MoreMenuItemButton.Type.Blaze) { isBlazeEnabled() }, - doCheckAvailability(MoreMenuItemButton.Type.GoogleForWoo) { isGoogleForWooEnabled() }, - doCheckAvailability(MoreMenuItemButton.Type.Inbox) { moreMenuRepository.isInboxEnabled() }, - doCheckAvailability(MoreMenuItemButton.Type.Settings) { moreMenuRepository.isUpgradesEnabled() }, - doCheckAvailability(MoreMenuItemButton.Type.WooPos) { isWooPosEnabled() } + doCheckAvailability(MoreMenuItemButton.Type.Blaze) { + if (isBlazeEnabled()) { + MoreMenuItemButton.State.Visible.Enabled + } else { + MoreMenuItemButton.State.Hidden + } + }, + doCheckAvailability(MoreMenuItemButton.Type.GoogleForWoo) { + if (isGoogleForWooEnabled()) { + MoreMenuItemButton.State.Visible.Enabled + } else { + MoreMenuItemButton.State.Hidden + } + }, + doCheckAvailability(MoreMenuItemButton.Type.Inbox) { + if (moreMenuRepository.isInboxEnabled()) { + MoreMenuItemButton.State.Visible.Enabled + } else { + MoreMenuItemButton.State.Hidden + } + }, + doCheckAvailability(MoreMenuItemButton.Type.Settings) { + if (moreMenuRepository.isUpgradesEnabled()) { + MoreMenuItemButton.State.Visible.Enabled + } else { + MoreMenuItemButton.State.Hidden + } + }, + doCheckAvailability(MoreMenuItemButton.Type.WooPos) { + when (isWooPosEnabled()) { + is WooPosIsEnabled.Reason.Disabled.CountryCurrencyNotSupported, + WooPosIsEnabled.Reason.Disabled.FeatureFlagDisabled, + WooPosIsEnabled.Reason.Disabled.InvalidSelectedSite, + WooPosIsEnabled.Reason.Disabled.InvalidSiteSettings, + WooPosIsEnabled.Reason.Disabled.ScreenSizeNotAllowed -> MoreMenuItemButton.State.Hidden + + WooPosIsEnabled.Reason.Disabled.WooCoreVersionNotSupported -> + MoreMenuItemButton.State.Visible.WooCoreVersionNotSupported + + WooPosIsEnabled.Reason.Enabled -> MoreMenuItemButton.State.Visible.Enabled + } + } ).merge() .map { update -> initialState[update.first] = update.second @@ -480,12 +566,16 @@ class MoreMenuViewModel @Inject constructor( private fun doCheckAvailability( type: MoreMenuItemButton.Type, - checker: suspend () -> Boolean + checker: suspend () -> MoreMenuItemButton.State ): Flow> = flow { - val state = if (checker()) MoreMenuItemButton.State.Visible else MoreMenuItemButton.State.Hidden + val state = checker() emit(type to state) } private val SitePlan.formattedPlanName get() = generateFormattedPlanName(resourceProvider) + + sealed class WooPosNotEligible { + data object OutdatedWooCommerceVersion : WooPosNotEligible() + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabled.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabled.kt index 5fc34257a10..1328698aec0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabled.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabled.kt @@ -19,19 +19,31 @@ class WooPosIsEnabled @Inject constructor( private val isRemoteFeatureFlagEnabled: IsRemoteFeatureFlagEnabled, ) { @Suppress("ReturnCount") - suspend operator fun invoke(): Boolean = coroutineScope { - val selectedSite = selectedSite.getOrNull() ?: return@coroutineScope false + suspend operator fun invoke(): Reason = coroutineScope { + val selectedSite = selectedSite.getOrNull() + ?: return@coroutineScope Reason.Disabled.InvalidSelectedSite - if (!isRemoteFeatureFlagEnabled(WOO_POS)) return@coroutineScope false - if (!isScreenSizeAllowed()) return@coroutineScope false - if (!isWooCoreSupportsOrderAutoDraftsAndExtraPaymentsProps()) return@coroutineScope false + if (!isRemoteFeatureFlagEnabled(WOO_POS)) return@coroutineScope Reason.Disabled.FeatureFlagDisabled + if (!isScreenSizeAllowed()) return@coroutineScope Reason.Disabled.ScreenSizeNotAllowed + if (!isWooCoreSupportsOrderAutoDraftsAndExtraPaymentsProps()) { + return@coroutineScope Reason.Disabled.WooCoreVersionNotSupported + } - val siteSettings = wooCommerceStore.getSiteSettings(selectedSite) ?: return@coroutineScope false + val siteSettings = wooCommerceStore.getSiteSettings(selectedSite) + ?: return@coroutineScope Reason.Disabled.InvalidSiteSettings - return@coroutineScope isCountryAndCurrencySupported( - countryCode = siteSettings.countryCode, - currency = siteSettings.currencyCode - ) + return@coroutineScope if (isCountryAndCurrencySupported( + countryCode = siteSettings.countryCode, + currency = siteSettings.currencyCode + ) + ) { + Reason.Enabled + } else { + Reason.Disabled.CountryCurrencyNotSupported( + country = siteSettings.countryCode, + currency = siteSettings.currencyCode + ) + } } private fun isCountryAndCurrencySupported(countryCode: String, currency: String) = @@ -47,4 +59,16 @@ class WooPosIsEnabled @Inject constructor( val SUPPORTED_COUNTRY_CURRENCY_PAIRS = listOf("us" to "usd", "gb" to "gbp") } + + sealed class Reason { + data object Enabled : Reason() + sealed class Disabled : Reason() { + data object InvalidSelectedSite : Disabled() + data object InvalidSiteSettings : Disabled() + data object FeatureFlagDisabled : Disabled() + data object ScreenSizeNotAllowed : Disabled() + data class CountryCurrencyNotSupported(val country: String, val currency: String) : Disabled() + data object WooCoreVersionNotSupported : Disabled() + } + } } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 3e1f5618560..bbc6476d261 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3832,6 +3832,7 @@ Point of Sale Mode Accept payments at your physical store + Upgrade your site to the latest WooCommerce to use Point of Sale Customers diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt index 41a0a0c802f..4c6fa73e2a5 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt @@ -3,6 +3,8 @@ package com.woocommerce.android.ui.moremenu import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_POS_NOT_ELIGIBLE_REASON +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.notifications.UnseenReviewsCountHandler import com.woocommerce.android.tools.SelectedSite @@ -14,6 +16,7 @@ import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus import com.woocommerce.android.ui.plans.domain.SitePlan import com.woocommerce.android.ui.plans.repository.SitePlanRepository import com.woocommerce.android.ui.woopos.WooPosIsEnabled +import com.woocommerce.android.util.GetWooCorePluginCachedVersion import com.woocommerce.android.util.captureValues import com.woocommerce.android.util.runAndCaptureValues import com.woocommerce.android.viewmodel.BaseUnitTest @@ -85,7 +88,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { private val hasGoogleAdsCampaigns: HasGoogleAdsCampaigns = mock() private val isWooPosEnabled: WooPosIsEnabled = mock { - onBlocking { invoke() } doReturn true + onBlocking { invoke() } doReturn WooPosIsEnabled.Reason.Enabled } private val analyticsTrackerWrapper: AnalyticsTrackerWrapper = mock() @@ -94,6 +97,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { private lateinit var viewModel: MoreMenuViewModel private val tapToPayAvailabilityStatus: TapToPayAvailabilityStatus = mock() + private val getWooCoreVersion: GetWooCorePluginCachedVersion = mock() suspend fun setup(setupMocks: suspend () -> Unit = {}) { setupMocks() @@ -113,6 +117,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { hasGoogleAdsCampaigns = hasGoogleAdsCampaigns, isWooPosEnabled = isWooPosEnabled, analyticsTrackerWrapper = analyticsTrackerWrapper, + getWooCoreVersion = getWooCoreVersion, ) } @@ -436,7 +441,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { testBlocking { // GIVEN setup { - whenever(isWooPosEnabled.invoke()).thenReturn(false) + whenever(isWooPosEnabled.invoke()).thenReturn(WooPosIsEnabled.Reason.Disabled.InvalidSiteSettings) } // WHEN @@ -449,11 +454,32 @@ class MoreMenuViewModelTests : BaseUnitTest() { ).isNull() } + @Test + fun `given isWooPosEnabled returns outdated woocommerce version, when building state, then WooPOS section is displayed with appropriate description`() = + testBlocking { + // GIVEN + setup { + whenever(isWooPosEnabled.invoke()) + .thenReturn(WooPosIsEnabled.Reason.Disabled.WooCoreVersionNotSupported) + } + + // WHEN + val states = viewModel.moreMenuViewState.captureValues() + + // THEN + assertThat( + states.last().menuSections.flatMap { it.items } + .firstOrNull { + it.description == R.string.more_menu_button_woo_pos_update_woocommerce_version_description + }?.state + ).isEqualTo(MoreMenuItemButton.State.Visible.WooCoreVersionNotSupported) + } + @Test fun `given isWooPosEnabled returns true, when building state, then WooPOS section is displayed`() = testBlocking { // GIVEN setup { - whenever(isWooPosEnabled.invoke()).thenReturn(true) + whenever(isWooPosEnabled.invoke()).thenReturn(WooPosIsEnabled.Reason.Enabled) } // WHEN @@ -465,7 +491,53 @@ class MoreMenuViewModelTests : BaseUnitTest() { assertThat( states.last().menuSections.flatMap { it.items } .first { it.title == R.string.more_menu_button_woo_pos }.state - ).isEqualTo(MoreMenuItemButton.State.Visible) + ).isEqualTo(MoreMenuItemButton.State.Visible.Enabled) + } + + @Test + fun `given outdated WooCommerce version, when building state, then WooPOS section is displayed with upgrade woocommerce description`() = testBlocking { + // GIVEN + setup { + whenever(isWooPosEnabled.invoke()).thenReturn(WooPosIsEnabled.Reason.Disabled.WooCoreVersionNotSupported) + } + + // WHEN + val states = viewModel.moreMenuViewState.captureValues() + + // THEN + assertThat( + states.last().menuSections.flatMap { it.items } + .first { it.title == R.string.more_menu_button_woo_pos }.state + ).isEqualTo(MoreMenuItemButton.State.Visible.WooCoreVersionNotSupported) + assertThat( + states.last().menuSections.flatMap { it.items } + .first { + it.description == R.string.more_menu_button_woo_pos_update_woocommerce_version_description + }.state + ).isEqualTo(MoreMenuItemButton.State.Visible.WooCoreVersionNotSupported) + } + + @Test + fun `given eligible WooCommerce version, when building state, then WooPOS section is displayed with proper description`() = testBlocking { + // GIVEN + setup { + whenever(isWooPosEnabled.invoke()).thenReturn(WooPosIsEnabled.Reason.Enabled) + } + + // WHEN + val states = viewModel.moreMenuViewState.captureValues() + + // THEN + assertThat( + states.last().menuSections.flatMap { it.items } + .first { it.title == R.string.more_menu_button_woo_pos }.state + ).isEqualTo(MoreMenuItemButton.State.Visible.Enabled) + assertThat( + states.last().menuSections.flatMap { it.items } + .first { + it.description == R.string.more_menu_button_woo_pos_description + }.state + ).isEqualTo(MoreMenuItemButton.State.Visible.Enabled) } @Test @@ -473,7 +545,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { testBlocking { // GIVEN setup { - whenever(isWooPosEnabled.invoke()).thenReturn(true) + whenever(isWooPosEnabled.invoke()).thenReturn(WooPosIsEnabled.Reason.Enabled) whenever(moreMenuRepository.isUpgradesEnabled()).thenReturn(true) } @@ -500,7 +572,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { fun `when building state, then all optional buttons start with loading state`() = testBlocking { // GIVEN setup { - whenever(isWooPosEnabled()).thenReturn(true) + whenever(isWooPosEnabled()).thenReturn(WooPosIsEnabled.Reason.Enabled) whenever(isBlazeEnabled.invoke()).thenReturn(true) whenever(isGoogleForWooEnabled.invoke()).thenReturn(true) whenever(moreMenuRepository.isUpgradesEnabled()).thenReturn(true) @@ -526,7 +598,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { fun `when WooPOS button clicked, then VALUE_MORE_MENU_POS tracking is triggered`() = testBlocking { // GIVEN setup { - whenever(isWooPosEnabled()).thenReturn(true) + whenever(isWooPosEnabled()).thenReturn(WooPosIsEnabled.Reason.Enabled) } // WHEN @@ -540,4 +612,42 @@ class MoreMenuViewModelTests : BaseUnitTest() { mapOf("option" to "pointOfSale") ) } + + @Test + fun `given outdated woocommerce version, when WooPOS button clicked, then VALUE_MORE_MENU_POS tracking is triggered with appropriate properties`() = testBlocking { + // GIVEN + setup { + whenever(isWooPosEnabled()).thenReturn(WooPosIsEnabled.Reason.Disabled.WooCoreVersionNotSupported) + } + + // WHEN + val state = viewModel.moreMenuViewState.captureValues().last() + val posButton = state.menuSections.flatMap { it.items }.first { it.title == R.string.more_menu_button_woo_pos } + posButton.onClick() + + // THEN + verify(analyticsTrackerWrapper).track( + AnalyticsEvent.HUB_MENU_OPTION_TAPPED, + mapOf( + "option" to "pointOfSale", + KEY_POS_NOT_ELIGIBLE_REASON to VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION + ) + ) + } + + @Test + fun `given outdated woocommerce version, when WooPOS button clicked, then appropriate event is triggered`() = testBlocking { + // GIVEN + setup { + whenever(isWooPosEnabled()).thenReturn(WooPosIsEnabled.Reason.Disabled.WooCoreVersionNotSupported) + } + + // WHEN + val state = viewModel.moreMenuViewState.captureValues().last() + val posButton = state.menuSections.flatMap { it.items }.first { it.title == R.string.more_menu_button_woo_pos } + posButton.onClick() + + // THEN + assertThat(viewModel.event.value).isEqualTo(MoreMenuEvent.ShowWooPosWooCoreUpdateRequiredEvent) + } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabledTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabledTest.kt index dc82e60e703..e10bae748f1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabledTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/WooPosIsEnabledTest.kt @@ -6,6 +6,7 @@ import com.woocommerce.android.util.IsRemoteFeatureFlagEnabled import com.woocommerce.android.util.RemoteFeatureFlag.WOO_POS import com.woocommerce.android.viewmodel.BaseUnitTest import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -14,8 +15,6 @@ import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.WCSettingsModel import org.wordpress.android.fluxc.store.WooCommerceStore import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) class WooPosIsEnabledTest : BaseUnitTest() { @@ -53,72 +52,72 @@ class WooPosIsEnabledTest : BaseUnitTest() { whenever(isRemoteFeatureFlagEnabled(WOO_POS)).thenReturn(true) whenever(isScreenSizeAllowed()).thenReturn(true) - assertTrue(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Enabled::class.java) } @Test fun `given feature flag disabled, when invoked, then return false`() = testBlocking { whenever(isRemoteFeatureFlagEnabled.invoke(WOO_POS)).thenReturn(false) - assertFalse(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Disabled.FeatureFlagDisabled::class.java) } @Test fun `given unsupported country, when invoked, then return false`() = testBlocking { val result = buildSiteSettings(countryCode = "CA", currencyCode = "USD") whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(result) - assertFalse(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Disabled.CountryCurrencyNotSupported::class.java) } @Test fun `given unsupported currency, when invoked, then return false`() = testBlocking { val result = buildSiteSettings(currencyCode = "CAD", countryCode = "US") whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(result) - assertFalse(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Disabled.CountryCurrencyNotSupported::class.java) } @Test fun `given uk country and pounds, when invoked, then return true`() = testBlocking { val result = buildSiteSettings(countryCode = "GB", currencyCode = "GBP") whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(result) - assertTrue(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Enabled::class.java) } @Test fun `given uk country and usd, when invoked, then return false`() = testBlocking { val result = buildSiteSettings(countryCode = "GB", currencyCode = "USD") whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(result) - assertFalse(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Disabled.CountryCurrencyNotSupported::class.java) } @Test fun `given us country and pounds, when invoked, then return false`() = testBlocking { val result = buildSiteSettings(countryCode = "US", currencyCode = "GBP") whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(result) - assertFalse(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Disabled.CountryCurrencyNotSupported::class.java) } @Test fun `given woo version 9_5_0, when invoked, then return false`() = testBlocking { whenever(getWooCoreVersion.invoke()).thenReturn("9.5.0") - assertFalse(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Disabled.WooCoreVersionNotSupported::class.java) } @Test fun `given woo version 9_6_0, when invoked, then return true`() = testBlocking { whenever(getWooCoreVersion.invoke()).thenReturn("9.6.0") - assertTrue(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Enabled::class.java) } @Test fun `given woo version 9_6_0_1, when invoked, then return true`() = testBlocking { whenever(getWooCoreVersion.invoke()).thenReturn("9.6.0.1") - assertTrue(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Enabled::class.java) } @Test fun `given woo version 10_0_1, when invoked, then return true`() = testBlocking { whenever(getWooCoreVersion.invoke()).thenReturn("10.0.1") - assertTrue(sut()) + assertThat(sut()).isInstanceOf(WooPosIsEnabled.Reason.Enabled::class.java) } private fun buildSiteSettings(