diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index c519e93ed68..0b5468f4f02 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.home import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import dagger.hilt.android.scopes.ActivityRetainedScoped import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -25,7 +26,10 @@ sealed class ChildToParentEvent { ) : ChildToParentEvent() data object BackFromCheckoutToCartClicked : ChildToParentEvent() - data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() + data class ItemClickedInProductSelector( + val itemData: WooPosItemsViewModel.ItemClickedData, + val source: WooPosItemSource = WooPosItemSource.PRODUCT_LIST + ) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() data object PaymentCollecting : ChildToParentEvent() data object PaymentInProgress : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt index 67923b30c0b..5ce2712f9e4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.home import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import dagger.hilt.android.scopes.ActivityRetainedScoped import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -22,7 +23,8 @@ class WooPosParentToChildrenCommunication @Inject constructor() : sealed class ParentToChildrenEvent { data object BackFromCheckoutToCartClicked : ParentToChildrenEvent() data class ItemClickedInProductSelector( - val itemData: WooPosItemsViewModel.ItemClickedData + val itemData: WooPosItemsViewModel.ItemClickedData, + val source: WooPosItemSource ) : ParentToChildrenEvent() data class CheckoutClicked( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 64bef88aeea..db82d174be2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -129,7 +129,7 @@ class WooPosHomeViewModel @Inject constructor( is ChildToParentEvent.ItemClickedInProductSelector -> { sendEventToChildren( - ItemClickedInProductSelector(event.itemData) + ItemClickedInProductSelector(itemData = event.itemData, source = event.source) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt index 7bb83c6a3e2..840bb933b0f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt @@ -27,6 +27,7 @@ import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Eve import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.CheckoutTapped import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ClearCartTapped import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.InteractionWithCustomerStarted +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemRemovedFromCart import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEventConstant import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker @@ -211,12 +212,14 @@ class WooPosCartViewModel @Inject constructor( analyticsTracker.track(InteractionWithCustomerStarted) } _state.value = updateStateWithNewItem(itemClicked.await()) - WooPosAnalyticsEvent.Event.ItemAddedToCart.addProperties( - mapOf( - WooPosAnalyticsEventConstant.PRODUCT_TYPE to event.itemData.posItemNameForAnalytics() + + val source = WooPosItemSource.toAnalyticsString(event.source) + val itemAddedEvent = WooPosAnalyticsEvent.Event.ItemAddedToCart(source).apply { + addProperties( + mapOf(WooPosAnalyticsEventConstant.PRODUCT_TYPE to event.itemData.posItemNameForAnalytics()) ) - ) - analyticsTracker.track(WooPosAnalyticsEvent.Event.ItemAddedToCart) + } + analyticsTracker.track(itemAddedEvent) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemNavigationData.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemNavigationData.kt index 1ca6e1a5ef0..e149bd4353d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemNavigationData.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemNavigationData.kt @@ -1,9 +1,12 @@ package com.woocommerce.android.ui.woopos.home.items +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource + sealed class WooPosItemNavigationData(open val id: Long) { data class VariableProductData( override val id: Long, val name: String, val numOfVariations: Int, + val source: WooPosItemSource, ) : WooPosItemNavigationData(id) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt index 75b70bfc73f..53d9014c6f3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt @@ -7,10 +7,17 @@ import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState.SearchState +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.SearchButtonTapped +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.viewmodel.ResourceProvider import dagger.hilt.android.scopes.ActivityRetainedScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -18,7 +25,8 @@ import javax.inject.Inject class WooPosItemsSearchHelper @Inject constructor( private val resourceProvider: ResourceProvider, private val childToParentEventSender: WooPosChildrenToParentEventSender, - private val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver + private val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver, + private val analyticsTracker: WooPosAnalyticsTracker, ) { private lateinit var coroutineScope: CoroutineScope private lateinit var viewStateFlow: MutableStateFlow @@ -30,6 +38,7 @@ class WooPosItemsSearchHelper @Inject constructor( this.coroutineScope = coroutineScope this.viewStateFlow = viewStateFlow listenEventsFromParent() + observeAndTrackSearchInputStateOpen(viewStateFlow, coroutineScope) } private fun listenEventsFromParent() { @@ -165,6 +174,25 @@ class WooPosItemsSearchHelper @Inject constructor( ) } + private fun observeAndTrackSearchInputStateOpen( + viewStateFlow: MutableStateFlow, + coroutineScope: CoroutineScope + ) { + viewStateFlow + .map { it.search } + .distinctUntilChanged() + .map { it is SearchState.Visible && it.state is WooPosSearchInputState.Open } + .distinctUntilChanged() + .filter { it } + .onEach { + val event = SearchButtonTapped.apply { + addProperties(mapOf("item_list_type" to "products")) + } + analyticsTracker.track(event) + } + .launchIn(coroutineScope) + } + private fun getCurrentContentState(): WooPosItemsViewState { return viewStateFlow.value } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/coupons/WooPosCouponsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/coupons/WooPosCouponsViewModel.kt index 97b081a2b87..ff38f403477 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/coupons/WooPosCouponsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/coupons/WooPosCouponsViewModel.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.ui.woopos.home.items.coupons.WooPosCouponsUIEvent import com.woocommerce.android.ui.woopos.home.items.coupons.WooPosCouponsUIEvent.RetryTriggered import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator.WooPosItemsScreenNavigationEvent +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -96,7 +97,10 @@ class WooPosCouponsViewModel @Inject constructor( viewModelScope.launch { fromChildToParentEventSender.sendToParent( // CouponsProject: rename ItemClickedInProductSelector to ItemClicked - ChildToParentEvent.ItemClickedInProductSelector(ItemClickedData.Coupon(event.couponId)) + ChildToParentEvent.ItemClickedInProductSelector( + itemData = ItemClickedData.Coupon(event.couponId), + source = WooPosItemSource.COUPON_LIST + ) ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt index 36f4cb3b4b9..40734849907 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.ui.woopos.home.items.WooPosProductsViewState import com.woocommerce.android.ui.woopos.home.items.WooPosPullToRefreshState import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateToVariationsScreen +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ProductsPullToRefreshTriggered import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice @@ -101,6 +102,7 @@ class WooPosProductsViewModel @Inject constructor( id = event.item.id, name = event.item.name, numOfVariations = event.item.numOfVariations, + source = WooPosItemSource.PRODUCT_LIST ) ) ) @@ -252,7 +254,12 @@ class WooPosProductsViewModel @Inject constructor( } private fun onItemClicked(itemData: ItemClickedData) { - sendEventToParent(ChildToParentEvent.ItemClickedInProductSelector(itemData)) + sendEventToParent( + ChildToParentEvent.ItemClickedInProductSelector( + itemData = itemData, + source = WooPosItemSource.PRODUCT_LIST + ) + ) } private fun sendEventToParent(event: ChildToParentEvent) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt index 4846ee40c27..1c0e977b951 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt @@ -16,6 +16,10 @@ import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel.ItemCli import com.woocommerce.android.ui.woopos.home.items.WooPosPaginationState import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateToVariationsScreen +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemsNextPageLoaded +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.PreSearchRecentTermTapped +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job @@ -38,6 +42,7 @@ class WooPosItemsSearchViewModel @Inject constructor( private val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver, private val navigator: WooPosItemsNavigator, private val searchHelper: WooPosItemsSearchHelper, + private val analyticsTracker: WooPosAnalyticsTracker, ) : ViewModel() { private val _viewState = MutableStateFlow(WooPosItemsSearchViewState.Empty) @@ -62,7 +67,7 @@ class WooPosItemsSearchViewModel @Inject constructor( fun onUIEvent(event: WooPosItemsSearchUiEvent) { when (event) { WooPosItemsSearchUiEvent.OnNextPageRequested -> onEndOfListReached() - is WooPosItemsSearchUiEvent.OnItemClicked -> handleItemClicked(event.item) + is WooPosItemsSearchUiEvent.OnItemClicked -> handleItemClicked(event.item, WooPosItemSource.SEARCH_RESULT) WooPosItemsSearchUiEvent.LoadingErrorRetryButtonClicked -> { val currentState = _viewState.value as? WooPosItemsSearchViewState.Error ?: return performSearch(currentState.searchQuery) @@ -81,7 +86,8 @@ class WooPosItemsSearchViewModel @Inject constructor( is WooPosItemsSearchUiEvent.OnPopularItemClicked -> { viewModelScope.launch { emptyStateRepository.addPopularItemsToCache() - handleItemClicked(event.item) + handleItemClicked(event.item, WooPosItemSource.POPULAR_PRODUCTS) + trackPopularItemClicked() } } } @@ -189,6 +195,7 @@ class WooPosItemsSearchViewModel @Inject constructor( loadMoreJob = viewModelScope.launch { val result = dataSource.loadMore(query = currentState.searchQuery) _viewState.value = if (result.isSuccess) { + trackItemsNextPageLoaded() result.getOrThrow().toContentState( searchQuery = currentState.searchQuery, ) @@ -198,13 +205,37 @@ class WooPosItemsSearchViewModel @Inject constructor( } } - private fun handleItemClicked(item: WooPosItemSelectionViewState) { + private suspend fun trackItemsNextPageLoaded() { + val event = ItemsNextPageLoaded.apply { + addProperties( + mapOf( + "item_list_type" to "products", + "search" to "true" + ) + ) + } + analyticsTracker.track(event) + } + + private suspend fun trackPopularItemClicked() { + val event = PreSearchRecentTermTapped.apply { + addProperties( + mapOf( + "item_list_type" to "products", + ) + ) + } + analyticsTracker.track(event) + } + + private fun handleItemClicked(item: WooPosItemSelectionViewState, source: WooPosItemSource) { when (item) { is WooPosItemSelectionViewState.Product.Simple -> { viewModelScope.launch { childToParentEventSender.sendToParent( ChildToParentEvent.ItemClickedInProductSelector( - ItemClickedData.Product.Simple(id = item.id) + itemData = ItemClickedData.Product.Simple(id = item.id), + source = source, ) ) } @@ -220,6 +251,7 @@ class WooPosItemsSearchViewModel @Inject constructor( id = item.id, name = item.name, numOfVariations = item.numOfVariations, + source = WooPosItemSource.SEARCH_RESULT ) ) ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index 4e3ca1a3501..7d9f3960828 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -48,6 +48,7 @@ import com.woocommerce.android.ui.woopos.home.items.WooPosItemsEmptyList import com.woocommerce.android.ui.woopos.home.items.WooPosItemsLoadingIndicator import com.woocommerce.android.ui.woopos.home.items.WooPosPullToRefreshState import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -61,7 +62,7 @@ fun WooPosVariationsScreen( key = variableProductData.id.toString() ) LaunchedEffect(variableProductData.id) { - viewModel.init(variableProductData.id) + viewModel.init(variableProductData.id, variableProductData.source) } val state = viewModel.viewState WooPosVariationsScreens( @@ -298,6 +299,7 @@ fun WooPosVariationsScreenPreview() { id = 0, name = "Variable Product", numOfVariations = 20, + source = WooPosItemSource.PRODUCT_LIST, ), state = productState, ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index c6f324b277e..43501d82c22 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -14,6 +14,8 @@ import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel import com.woocommerce.android.ui.woopos.home.items.WooPosPaginationState import com.woocommerce.android.ui.woopos.home.items.WooPosPullToRefreshState import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemsNextPageLoaded import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.VariationsPullToRefreshTriggered import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice @@ -47,11 +49,13 @@ class WooPosVariationsViewModel @Inject constructor( ) private var fetchJob: Job? = null + private var variationsSource: WooPosItemSource = WooPosItemSource.PRODUCT_LIST @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal var loadMoreJob: Job? = null - fun init(productId: Long) { + fun init(productId: Long, source: WooPosItemSource) { + this.variationsSource = source viewModelScope.launch { variationsDataSource.resetState() } @@ -167,6 +171,7 @@ class WooPosVariationsViewModel @Inject constructor( loadMoreJob = viewModelScope.launch { val result = variationsDataSource.loadMore(productId) _viewState.value = if (result.isSuccess) { + trackItemsNextPageLoaded() WooPosVariationsViewState.Content( items = result.getOrThrow().map { WooPosItemSelectionViewState.Product.Variation( @@ -184,6 +189,19 @@ class WooPosVariationsViewModel @Inject constructor( } } + private suspend fun trackItemsNextPageLoaded() { + val event = ItemsNextPageLoaded.apply { + val isSearch: Boolean = variationsSource == WooPosItemSource.SEARCH_RESULT + addProperties( + mapOf( + "item_list_type" to "variations", + "search" to "$isSearch" + ) + ) + } + analyticsTracker.track(event) + } + fun onUIEvent(event: WooPosVariationsUIEvents) { when (event) { is WooPosVariationsUIEvents.EndOfItemsListReached -> { @@ -208,7 +226,8 @@ class WooPosVariationsViewModel @Inject constructor( private fun onVariationClicked(productId: Long, variationId: Long) { sendEventToParent( ChildToParentEvent.ItemClickedInProductSelector( - WooPosItemsViewModel.ItemClickedData.Product.Variation(productId, variationId) + itemData = WooPosItemsViewModel.ItemClickedData.Product.Variation(productId, variationId), + source = variationsSource ) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEvent.kt index 0ad62a573aa..ed976e163fc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEvent.kt @@ -77,8 +77,23 @@ sealed class WooPosAnalyticsEvent : IAnalyticsEvent { data object InteractionWithCustomerStarted : Event() { override val name: String = "interaction_with_customer_started" } - data object ItemAddedToCart : Event() { + data class ItemAddedToCart(val source: String = "list") : Event() { override val name: String = "item_added_to_cart" + + init { + addProperties(mapOf("source" to source)) + } + + enum class WooPosItemSource(val value: String) { + PRODUCT_LIST("list"), + SEARCH_RESULT("search_result"), + POPULAR_PRODUCTS("pre_search_list"), + COUPON_LIST("coupons"); + + companion object { + fun toAnalyticsString(source: WooPosItemSource): String = source.value + } + } } data object ItemRemovedFromCart : Event() { override val name: String = "item_removed_from_cart" @@ -105,6 +120,15 @@ sealed class WooPosAnalyticsEvent : IAnalyticsEvent { data object SimpleProductExplanationDialogShown : Event() { override val name: String = "simple_products_explanation_dialog_shown" } + data object SearchButtonTapped : Event() { + override val name: String = "search_button_tapped" + } + data object PreSearchRecentTermTapped : Event() { + override val name: String = "pre_search_recent_term_tapped" + } + data object ItemsNextPageLoaded : Event() { + override val name: String = "items_next_page_loaded" + } } sealed class PaymentFlowTrackerEvent : WooPosAnalyticsEvent() { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt index 534e677e0d9..ea37165db0d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt @@ -19,6 +19,8 @@ import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Eve import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.CheckoutTapped import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ClearCartTapped import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.InteractionWithCustomerStarted +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource.Companion.toAnalyticsString import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEventConstant import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTrackingDataKeeper @@ -117,7 +119,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -156,7 +159,8 @@ class WooPosCartViewModelTest { WooPosItemsViewModel.ItemClickedData.Product.Variation( id = variation.remoteVariationId, productId = variation.remoteProductId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -180,7 +184,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Coupon( id = 1L, - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -209,7 +214,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -246,7 +252,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Coupon( id = 1L, - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -287,7 +294,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -377,7 +385,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -438,14 +447,16 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product1.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) parentToChildrenEventsMutableFlow.emit( ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product2.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -466,7 +477,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product3.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -508,7 +520,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -528,7 +541,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Coupon( id = 1L, - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -582,7 +596,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) @@ -643,12 +658,16 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) // THEN - verify(analyticsTracker).track(WooPosAnalyticsEvent.Event.ItemAddedToCart) + verify(analyticsTracker) + .track( + WooPosAnalyticsEvent.Event.ItemAddedToCart(source = toAnalyticsString(WooPosItemSource.PRODUCT_LIST)) + ) } @Test @@ -664,12 +683,13 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Coupon( id = 1L, - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) // THEN - verify(analyticsTracker).track(WooPosAnalyticsEvent.Event.ItemAddedToCart) + verify(analyticsTracker).track(WooPosAnalyticsEvent.Event.ItemAddedToCart()) } @Test @@ -692,14 +712,15 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) // THEN verify(analyticsTracker).track( argThat { - this == WooPosAnalyticsEvent.Event.ItemAddedToCart && + this == WooPosAnalyticsEvent.Event.ItemAddedToCart() && ( this as WooPosAnalyticsEvent.Event.ItemAddedToCart ).properties[WooPosAnalyticsEventConstant.PRODUCT_TYPE] == "simple" @@ -735,14 +756,15 @@ class WooPosCartViewModelTest { WooPosItemsViewModel.ItemClickedData.Product.Variation( id = variation.remoteProductId, productId = variation.remoteProductId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) // THEN verify(analyticsTracker).track( argThat { - this == WooPosAnalyticsEvent.Event.ItemAddedToCart && + this == WooPosAnalyticsEvent.Event.ItemAddedToCart() && ( this as WooPosAnalyticsEvent.Event.ItemAddedToCart ).properties[WooPosAnalyticsEventConstant.PRODUCT_TYPE] == "variation" @@ -763,14 +785,15 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Coupon( id = 1L, - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) // THEN verify(analyticsTracker).track( argThat { - this == WooPosAnalyticsEvent.Event.ItemAddedToCart && + this == WooPosAnalyticsEvent.Event.ItemAddedToCart() && ( this as WooPosAnalyticsEvent.Event.ItemAddedToCart ).properties[WooPosAnalyticsEventConstant.PRODUCT_TYPE] == "coupon" @@ -794,7 +817,8 @@ class WooPosCartViewModelTest { ParentToChildrenEvent.ItemClickedInProductSelector( WooPosItemsViewModel.ItemClickedData.Product.Simple( id = product.remoteId - ) + ), + source = WooPosItemSource.PRODUCT_LIST ) ) return Pair(sut, states) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelperTest.kt index 805fcc75aca..b0a041092b3 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelperTest.kt @@ -7,7 +7,9 @@ import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -31,6 +33,7 @@ class WooPosItemsSearchHelperTest { private val mockResourceProvider: ResourceProvider = mock() private val mockChildToParentEventSender: WooPosChildrenToParentEventSender = mock() private val mockParentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock() + private val mockAnalyticsTracker: WooPosAnalyticsTracker = mock() private lateinit var viewStateFlow: MutableStateFlow private lateinit var searchHelper: WooPosItemsSearchHelper @@ -44,7 +47,8 @@ class WooPosItemsSearchHelperTest { searchHelper = WooPosItemsSearchHelper( resourceProvider = mockResourceProvider, childToParentEventSender = mockChildToParentEventSender, - parentToChildrenEventReceiver = mockParentToChildrenEventReceiver + parentToChildrenEventReceiver = mockParentToChildrenEventReceiver, + analyticsTracker = mockAnalyticsTracker, ) } @@ -79,7 +83,7 @@ class WooPosItemsSearchHelperTest { // GIVEN val searchQuery = "test query" val cursorPosition = searchQuery.length - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) // WHEN searchHelper.onSearchChanged(searchQuery, cursorPosition) @@ -96,7 +100,7 @@ class WooPosItemsSearchHelperTest { // GIVEN val searchQuery = "test query" val cursorPosition = searchQuery.length - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) // WHEN searchHelper.onSearchChanged(searchQuery, cursorPosition) @@ -116,7 +120,7 @@ class WooPosItemsSearchHelperTest { // GIVEN val emptyQuery = "" val cursorPosition = 0 - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) // WHEN searchHelper.onSearchChanged(emptyQuery, cursorPosition) @@ -131,7 +135,7 @@ class WooPosItemsSearchHelperTest { @Test fun `given open search state, when onCloseSearchClicked called, then updates to closed state`() = runTest { // GIVEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) searchHelper.onSearchChanged("initial query", "initial query".length) // WHEN @@ -147,7 +151,7 @@ class WooPosItemsSearchHelperTest { fun `given search with query, when onClearSearchClicked called, then resets to initial open state with hint`() = runTest { // GIVEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) searchHelper.onSearchChanged("initial query", "initial query".length) // WHEN @@ -168,7 +172,7 @@ class WooPosItemsSearchHelperTest { ) // WHEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) advanceUntilIdle() // THEN @@ -186,7 +190,7 @@ class WooPosItemsSearchHelperTest { ) // WHEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) // THEN val currentState = viewStateFlow.value as WooPosItemsViewState.ProductList @@ -198,10 +202,11 @@ class WooPosItemsSearchHelperTest { @Test fun `when animation completes, then hasAnimationPlayed flag is set to true`() = runTest { // GIVEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) // WHEN searchHelper.onAnimationComplete() + advanceUntilIdle() // THEN val currentState = viewStateFlow.value as WooPosItemsViewState.ProductList @@ -213,7 +218,7 @@ class WooPosItemsSearchHelperTest { @Test fun `given animation completed, when search changed, then hasAnimationPlayed is preserved`() = runTest { // GIVEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) searchHelper.onAnimationComplete() // WHEN @@ -233,7 +238,7 @@ class WooPosItemsSearchHelperTest { @Test fun `given animation has played, when onClearSearchClicked, then hasAnimationPlayed is false`() = runTest { // GIVEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) searchHelper.onAnimationComplete() @@ -250,7 +255,7 @@ class WooPosItemsSearchHelperTest { @Test fun `given animation complete, when order successful paid, then state is closed`() = runTest { // GIVEN - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) searchHelper.onAnimationComplete() // WHEN @@ -261,7 +266,7 @@ class WooPosItemsSearchHelperTest { ) ) ) - searchHelper.initialize(this, viewStateFlow) + searchHelper.initialize(CoroutineScope(coroutinesTestRule.testDispatcher), viewStateFlow) advanceUntilIdle() // THEN diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModelTest.kt index 175acf3f501..da0859c0978 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModelTest.kt @@ -180,6 +180,7 @@ class WooPosItemsViewModelTest { assertThat(value).isInstanceOf(WooPosItemsViewState.CouponList::class.java) } } + private fun createViewModel() = WooPosItemsViewModel( wooPosItemsNavigator, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/navigation/WooPosLeftPaneScreensViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/navigation/WooPosLeftPaneScreensViewModelTest.kt index c0dbd07426f..18f6007f765 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/navigation/WooPosLeftPaneScreensViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/navigation/WooPosLeftPaneScreensViewModelTest.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.home.items.navigation import com.woocommerce.android.ui.woopos.home.items.WooPosItemNavigationData.VariableProductData import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.test.runTest @@ -41,6 +42,7 @@ class WooPosLeftPaneScreensViewModelTest { 1L, "Product Name", numOfVariations = 10, + source = WooPosItemSource.PRODUCT_LIST, ) navigationEvents.emit(WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateToVariationsScreen(product)) @@ -58,6 +60,7 @@ class WooPosLeftPaneScreensViewModelTest { 1L, "Product Name", numOfVariations = 10, + source = WooPosItemSource.PRODUCT_LIST, ) navigationEvents.emit(WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateToVariationsScreen(product)) navigationEvents.emit(WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateBackToItemListScreen) @@ -71,6 +74,7 @@ class WooPosLeftPaneScreensViewModelTest { 1L, "Product Name", numOfVariations = 10, + source = WooPosItemSource.PRODUCT_LIST, ) navigationEvents.emit(WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateToVariationsScreen(product)) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModelTest.kt index 220154f695b..21de7546b06 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModelTest.kt @@ -6,13 +6,14 @@ import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.items.WooPosContentViewState import com.woocommerce.android.ui.woopos.home.items.WooPosItemNavigationData -import com.woocommerce.android.ui.woopos.home.items.WooPosItemSelectionViewState.Product +import com.woocommerce.android.ui.woopos.home.items.WooPosItemSelectionViewState import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel import com.woocommerce.android.ui.woopos.home.items.WooPosPaginationState import com.woocommerce.android.ui.woopos.home.items.WooPosProductsViewState import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule -import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ProductsPullToRefreshTriggered +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -104,7 +105,7 @@ class WooPosProductsViewModelTest { val value = awaitItem() as WooPosProductsViewState.Content @Suppress("UNCHECKED_CAST") - val items = value.items as List + val items = value.items as List assertThat(items).hasSize(2) assertThat(items[0].id).isEqualTo(1) assertThat(items[0].name).isEqualTo("Product 1") @@ -195,7 +196,7 @@ class WooPosProductsViewModelTest { @Test fun `when product clicked, then send event to parent`() = runTest { // GIVEN - val product = Product.Simple(id = 1, name = "", price = "", imageUrl = null) + val product = WooPosItemSelectionViewState.Product.Simple(id = 1, name = "", price = "", imageUrl = null) val viewModel = createViewModel() // WHEN @@ -299,7 +300,7 @@ class WooPosProductsViewModelTest { viewModel.onUIEvent(WooPosProductsUIEvent.PullToRefreshTriggered) // THEN - verify(analyticsTracker).track(ProductsPullToRefreshTriggered) + verify(analyticsTracker).track(WooPosAnalyticsEvent.Event.ProductsPullToRefreshTriggered) } @Test @@ -326,7 +327,7 @@ class WooPosProductsViewModelTest { // WHEN viewModel.onUIEvent( WooPosProductsUIEvent.ItemClicked( - Product.Variable( + WooPosItemSelectionViewState.Product.Variable( id = 1L, name = "Product 1", numOfVariations = 10, @@ -344,6 +345,7 @@ class WooPosProductsViewModelTest { id = 1, name = "Product 1", numOfVariations = 10, + source = WooPosItemSource.PRODUCT_LIST ) ) ) @@ -385,7 +387,9 @@ class WooPosProductsViewModelTest { // THEN val value = awaitItem() as WooPosProductsViewState.Content - assertThat(value.items.filterIsInstance().size).isEqualTo(1) + assertThat( + value.items.filterIsInstance().size + ).isEqualTo(1) } } @@ -462,6 +466,35 @@ class WooPosProductsViewModelTest { verify(productsDataSource, never()).loadMore() } + @Test + fun `when variable product is clicked from product list, then navigation event uses product list source`() = runTest { + // GIVEN + val viewModel = createViewModel() + val item = WooPosItemSelectionViewState.Product.Variable( + id = 1, + name = "Product", + price = "$10", + imageUrl = null, + numOfVariations = 2, + variationIds = emptyList() + ) + + // WHEN + viewModel.onUIEvent(WooPosProductsUIEvent.ItemClicked(item)) + + // THEN + verify(wooPosItemsNavigator).sendNavigationEvent( + WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateToVariationsScreen( + WooPosItemNavigationData.VariableProductData( + id = 1L, + name = "Product", + numOfVariations = 2, + source = WooPosItemSource.PRODUCT_LIST + ) + ) + ) + } + private fun createViewModel(): WooPosProductsViewModel { return WooPosProductsViewModel( productsDataSource = productsDataSource, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModelTest.kt index 3e7415c8137..ecc55ceb350 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModelTest.kt @@ -7,12 +7,16 @@ import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver import com.woocommerce.android.ui.woopos.home.items.WooPosItemNavigationData.VariableProductData +import com.woocommerce.android.ui.woopos.home.items.WooPosItemSelectionViewState import com.woocommerce.android.ui.woopos.home.items.WooPosItemSelectionViewState.Product import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel.ItemClickedData import com.woocommerce.android.ui.woopos.home.items.WooPosPaginationState import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateToVariationsScreen import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemsNextPageLoaded +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay @@ -28,6 +32,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any +import org.mockito.kotlin.argThat import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -49,6 +54,7 @@ class WooPosItemsSearchViewModelTest { private val mockParentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock() private val mockNavigator: WooPosItemsNavigator = mock() private val mockSearchHelper: com.woocommerce.android.ui.woopos.home.items.WooPosItemsSearchHelper = mock() + private val mockAnalyticsTracker: WooPosAnalyticsTracker = mock() private val defaultQuery = "test query" private val defaultProduct = ProductTestUtils.generateProduct( @@ -342,6 +348,35 @@ class WooPosItemsSearchViewModelTest { } } + @Test + fun `given content state and more pages available, when end of list reached, then track ItemsNextPageLoaded event`() = runTest { + // GIVEN + val additionalProduct = ProductTestUtils.generateProduct( + productId = 2, + productName = "Test Product 2", + amount = "20.0", + productType = "simple" + ) + + mockSuccessfulSearch(defaultQuery, listOf(defaultProduct)) + mockSuccessfulPagination(defaultQuery, listOf(additionalProduct)) + + // WHEN + val viewModel = createViewModel() + advanceTimeBy(600) + viewModel.onUIEvent(WooPosItemsSearchUiEvent.OnNextPageRequested) + advanceUntilIdle() + + // THEN + verify(mockAnalyticsTracker).track( + argThat { event -> + event is ItemsNextPageLoaded && + event.properties["item_list_type"] == "products" && + event.properties["search"] == "true" + } + ) + } + @Test fun `given content state when load more fails, then pagination state is error`() = runTest { // GIVEN @@ -588,7 +623,8 @@ class WooPosItemsSearchViewModelTest { // THEN verify(mockChildToParentEventSender).sendToParent( ChildToParentEvent.ItemClickedInProductSelector( - ItemClickedData.Product.Simple(id = 1) + itemData = ItemClickedData.Product.Simple(id = 1), + source = WooPosItemSource.SEARCH_RESULT ) ) } @@ -616,7 +652,8 @@ class WooPosItemsSearchViewModelTest { VariableProductData( id = 1, name = "Variable Product", - numOfVariations = 3 + numOfVariations = 3, + source = WooPosItemSource.SEARCH_RESULT, ) ) ) @@ -708,11 +745,41 @@ class WooPosItemsSearchViewModelTest { verify(mockEmptyStateProvider).addPopularItemsToCache() verify(mockChildToParentEventSender).sendToParent( ChildToParentEvent.ItemClickedInProductSelector( - ItemClickedData.Product.Simple(id = simpleProduct.id) + ItemClickedData.Product.Simple(id = simpleProduct.id), + source = WooPosItemSource.POPULAR_PRODUCTS ) ) } + @Test + fun `when variable product is clicked from search, then navigation event uses search source`() = runTest { + // GIVEN + val viewModel = createViewModel() + val item = WooPosItemSelectionViewState.Product.Variable( + id = 1, + name = "Product", + price = "$10", + imageUrl = null, + numOfVariations = 2, + variationIds = emptyList() + ) + + // WHEN + viewModel.onUIEvent(WooPosItemsSearchUiEvent.OnItemClicked(item)) + + // THEN + verify(mockNavigator).sendNavigationEvent( + NavigateToVariationsScreen( + VariableProductData( + id = 1L, + name = "Product", + numOfVariations = 2, + source = WooPosItemSource.SEARCH_RESULT + ) + ) + ) + } + private fun mockSuccessfulSearch(query: String, products: List) { wheneverBlocking { mockDataSource.searchLocalProducts(query) }.thenReturn(emptyList()) wheneverBlocking { mockDataSource.searchRemoteProducts(query) }.thenReturn(Result.success(products)) @@ -765,5 +832,6 @@ class WooPosItemsSearchViewModelTest { parentToChildrenEventReceiver = mockParentToChildrenEventReceiver, navigator = mockNavigator, searchHelper = mockSearchHelper, + analyticsTracker = mockAnalyticsTracker, ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 84c582c4c16..e11406f3b59 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -15,6 +15,7 @@ import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsU import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsViewModel import com.woocommerce.android.ui.woopos.home.items.variations.getNameForPOS import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ItemAddedToCart.WooPosItemSource import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.VariationsPullToRefreshTriggered import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice @@ -67,7 +68,7 @@ class WooPosVariationsViewModelTest { // WHEN val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.viewState.test { // THEN @@ -90,7 +91,7 @@ class WooPosVariationsViewModelTest { // WHEN val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.viewState.test { // THEN @@ -113,7 +114,7 @@ class WooPosVariationsViewModelTest { // WHEN val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.viewState.test { // THEN @@ -133,7 +134,7 @@ class WooPosVariationsViewModelTest { // WHEN val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.viewState.test { // THEN assertThat(awaitItem()).isEqualTo(WooPosVariationsViewState.Empty()) @@ -149,7 +150,7 @@ class WooPosVariationsViewModelTest { // WHEN val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.viewState.test { // THEN @@ -204,7 +205,7 @@ class WooPosVariationsViewModelTest { whenever(variationsDataSource.loadMore(any())).thenReturn(Result.success(emptyList())) val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) advanceUntilIdle() // WHEN @@ -241,7 +242,7 @@ class WooPosVariationsViewModelTest { ) val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.onUIEvent(WooPosVariationsUIEvents.EndOfItemsListReached(123L, 10)) // THEN @@ -273,7 +274,7 @@ class WooPosVariationsViewModelTest { ) val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) advanceUntilIdle() viewModel.onUIEvent(WooPosVariationsUIEvents.EndOfItemsListReached(123L, 10)) advanceUntilIdle() @@ -301,7 +302,7 @@ class WooPosVariationsViewModelTest { val viewModel = createViewModel() val activeJob = Job() viewModel.loadMoreJob = activeJob - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.onUIEvent(WooPosVariationsUIEvents.EndOfItemsListReached(1L, 10)) viewModel.viewState.test { @@ -325,7 +326,7 @@ class WooPosVariationsViewModelTest { // WHEN val viewModel = createViewModel() - viewModel.init(1L) + viewModel.init(1L, WooPosItemSource.PRODUCT_LIST) viewModel.viewState.test { // THEN @@ -335,9 +336,10 @@ class WooPosVariationsViewModelTest { } @Test - fun `given variation clicked, when item clicked, then send event to parent`() = runTest { + fun `given variation clicked and source is product list, when item clicked, then sends event with product list source`() = runTest { // GIVEN val viewModel = createViewModel() + viewModel.init(123L, WooPosItemSource.PRODUCT_LIST) // WHEN viewModel.onUIEvent(WooPosVariationsUIEvents.OnItemClicked(123L, 1L)) @@ -345,7 +347,26 @@ class WooPosVariationsViewModelTest { // THEN verify(fromChildToParentEventSender).sendToParent( ChildToParentEvent.ItemClickedInProductSelector( - WooPosItemsViewModel.ItemClickedData.Product.Variation(123L, 1L) + itemData = WooPosItemsViewModel.ItemClickedData.Product.Variation(123L, 1L), + source = WooPosItemSource.PRODUCT_LIST + ) + ) + } + + @Test + fun `given variation clicked and source is search result, when item clicked, then sends event with search result source`() = runTest { + // GIVEN + val viewModel = createViewModel() + viewModel.init(123L, WooPosItemSource.SEARCH_RESULT) + + // WHEN + viewModel.onUIEvent(WooPosVariationsUIEvents.OnItemClicked(123L, 1L)) + + // THEN + verify(fromChildToParentEventSender).sendToParent( + ChildToParentEvent.ItemClickedInProductSelector( + itemData = WooPosItemsViewModel.ItemClickedData.Product.Variation(123L, 1L), + source = WooPosItemSource.SEARCH_RESULT ) ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEventTrackerTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEventTrackerTest.kt index 1fad253a81d..8fc7515919c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEventTrackerTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/analytics/WooPosAnalyticsEventTrackerTest.kt @@ -21,7 +21,7 @@ class WooPosAnalyticsEventTrackerTest { @Test fun `given an event, when track is called, then it should track the event via wrapper`() = runTest { // GIVEN - val event = WooPosAnalyticsEvent.Event.ItemAddedToCart + val event = WooPosAnalyticsEvent.Event.ItemAddedToCart() // WHEN tracker.track(event) @@ -58,7 +58,7 @@ class WooPosAnalyticsEventTrackerTest { @Test fun `given an event and common properties, when track is called, then it should track the event with common properties`() = runTest { // GIVEN - val event = WooPosAnalyticsEvent.Event.ItemAddedToCart + val event = WooPosAnalyticsEvent.Event.ItemAddedToCart() val commonProperties = mapOf("test" to "test") whenever(commonPropertiesProvider.commonProperties).thenReturn(commonProperties)