diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt index acc2cc5d944..87123b7a0e6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt @@ -37,6 +37,7 @@ import androidx.navigation.NavDestination import androidx.navigation.NavOptions import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.navOptions import com.automattic.android.tracks.crashlogging.CrashLogging import com.google.android.material.appbar.AppBarLayout import com.woocommerce.android.AppPrefs @@ -183,13 +184,17 @@ class MainActivity : @Inject lateinit var trialStatusBarFormatterFactory: TrialStatusBarFormatterFactory - @Inject lateinit var animatorHelper: MainAnimatorHelper + @Inject + lateinit var animatorHelper: MainAnimatorHelper - @Inject lateinit var edgeToEdgeHelper: MainActivityEdgeToEdgeHelper + @Inject + lateinit var edgeToEdgeHelper: MainActivityEdgeToEdgeHelper - @Inject lateinit var posTabController: WooPosTabController + @Inject + lateinit var posTabController: WooPosTabController - @Inject lateinit var bookingsTabController: BookingsTabController + @Inject + lateinit var bookingsTabController: BookingsTabController private val viewModel: MainActivityViewModel by viewModels() @@ -1023,26 +1028,25 @@ class MainActivity : restart() } - override fun showProductDetail(remoteProductId: Long, popUpToProductList: Boolean) { - val action = when (popUpToProductList) { - true -> NavGraphMainDirections.actionGlobalProductDetailFragmentPopUpToProductList( - mode = ProductDetailFragment.Mode.ShowProduct(remoteProductId), - ) - else -> NavGraphMainDirections.actionGlobalProductDetailFragment( - mode = ProductDetailFragment.Mode.ShowProduct(remoteProductId), - ) - } - navController.navigateSafely(action) - } - - override fun showProductDetailWithSharedTransition(remoteProductId: Long, sharedView: View) { - val productCardDetailTransitionName = getString(R.string.product_card_detail_transition_name) - val extras = FragmentNavigatorExtras(sharedView to productCardDetailTransitionName) - + override fun showProductDetail(remoteProductId: Long, popUpToProductList: Boolean, sharedView: View?) { val action = NavGraphMainDirections.actionGlobalProductDetailFragment( mode = ProductDetailFragment.Mode.ShowProduct(remoteProductId), ) - navController.navigateSafely(directions = action, extras = extras) + val extras = if (sharedView != null) { + val productCardDetailTransitionName = getString(R.string.product_card_detail_transition_name) + FragmentNavigatorExtras(sharedView to productCardDetailTransitionName) + } else { + null + } + navController.navigateSafely( + directions = action, + extras = extras, + navOptions = navOptions { + if (popUpToProductList) { + popUpTo(R.id.products) + } + } + ) } override fun showProductVariationDetail(remoteProductId: Long, remoteVariationId: Long) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt index 1749b8a747e..fd1d60f1fe4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt @@ -7,11 +7,12 @@ interface MainNavigationRouter { fun isAtNavigationRoot(): Boolean fun isChildFragmentShowing(): Boolean - fun showProductDetail(remoteProductId: Long, popUpToProductList: Boolean = false) - fun showProductDetailWithSharedTransition( + fun showProductDetail( remoteProductId: Long, - sharedView: View, + popUpToProductList: Boolean = false, + sharedView: View? = null ) + fun showProductVariationDetail(remoteProductId: Long, remoteVariationId: Long) fun showOrderDetail( @@ -36,6 +37,7 @@ interface MainNavigationRouter { launchedFromNotification: Boolean, tempStatus: String? = null ) + fun showReviewDetailWithSharedTransition( remoteReviewId: Long, launchedFromNotification: Boolean, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductType.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductType.kt index ba230334aa0..1106d0e963a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductType.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductType.kt @@ -15,6 +15,7 @@ enum class ProductType(@StringRes val stringResource: Int = 0, val value: String BUNDLE(R.string.product_type_bundle, CoreProductType.BUNDLE.value), COMPOSITE(R.string.product_type_composite, "composite"), VARIATION(R.string.product_type_variation, "variation"), + BOOKING(R.string.product_type_booking, "booking"), OTHER; fun isVariableProduct() = this == VARIABLE || this == VARIABLE_SUBSCRIPTION @@ -34,6 +35,7 @@ enum class ProductType(@StringRes val stringResource: Int = 0, val value: String "bundle" -> BUNDLE "composite" -> COMPOSITE "variation" -> VARIATION + "booking" -> BOOKING else -> OTHER } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt index c14ba2ce400..9fb418ecaad 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt @@ -89,7 +89,6 @@ import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.UiHelpers.getTextOfUiString import com.woocommerce.android.util.WooAnimUtils import com.woocommerce.android.viewmodel.MultiLiveEvent.Event -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.LaunchUrlInChromeTab import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowUiStringSnackbar import com.woocommerce.android.widgets.CustomProgressDialog import com.woocommerce.android.widgets.SkeletonView @@ -402,9 +401,14 @@ class ProductDetailFragment : private fun observeEvents(viewModel: ProductDetailViewModel) { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { - is LaunchUrlInChromeTab -> ChromeCustomTabUtils.launchUrl(requireContext(), event.url) + is Event.LaunchUrlInChromeTab -> ChromeCustomTabUtils.launchUrl(requireContext(), event.url) is Event.LaunchUrlInAuthenticatedWebView -> findNavController(R.id.nav_host_fragment_main) .navigateSafely(NavGraphMainDirections.actionGlobalAuthenticatedWebViewFragment(event.url)) + is ProductDetailViewModel.OpenProductInWebView -> findNavController().navigateSafely( + ProductDetailFragmentDirections.actionProductDetailFragmentToProductDetailWebViewFragment( + remoteProductId = event.productId + ) + ) is RefreshMenu -> toolbarHelper.setupToolbar() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index 2413fb3b340..f0a48b5b6fd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt @@ -24,6 +24,7 @@ import com.woocommerce.android.extensions.clearList import com.woocommerce.android.extensions.containsItem import com.woocommerce.android.extensions.fastStripHtml import com.woocommerce.android.extensions.getList +import com.woocommerce.android.extensions.isCIABSite import com.woocommerce.android.extensions.isEligibleForAI import com.woocommerce.android.extensions.isEmpty import com.woocommerce.android.extensions.isSitePublic @@ -114,6 +115,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion @@ -403,6 +405,7 @@ class ProductDetailViewModel @Inject constructor( init { start() + openInWebViewIfNeeded() } fun start() { @@ -424,6 +427,17 @@ class ProductDetailViewModel @Inject constructor( observeProductCategorySearchQuery() } + fun openInWebViewIfNeeded() { + if (navArgs.mode !is ProductDetailFragment.Mode.ShowProduct) return + + launch { + val product = storedProductAggregate.filterNotNull().first().product + if (selectedSite.get().isCIABSite() && product.productType == ProductType.BOOKING) { + triggerEvent(OpenProductInWebView(product.remoteId)) + } + } + } + private fun initializeViewState() { when (val mode = navArgs.mode) { is ProductDetailFragment.Mode.AddNewProduct -> startAddNewProduct() @@ -2726,6 +2740,8 @@ class ProductDetailViewModel @Inject constructor( data class TrashProduct(val productId: Long) : Event() + data class OpenProductInWebView(val productId: Long) : Event() + /** * [productDraft] is used for the UI. Any updates to the fields in the UI would update this model. * [storedProductAggregate.value] is the [Product] model that is fetched from the API and available in the local db. diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewFragment.kt new file mode 100644 index 00000000000..c0ce427367e --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewFragment.kt @@ -0,0 +1,60 @@ +package com.woocommerce.android.ui.products.details.webview + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.woocommerce.android.R +import com.woocommerce.android.extensions.findNavController +import com.woocommerce.android.extensions.isTwoPanesShouldBeUsed +import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.compose.composeView +import com.woocommerce.android.ui.main.AppBarStatus +import com.woocommerce.android.viewmodel.MultiLiveEvent +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class ProductDetailWebViewFragment : BaseFragment() { + override val activityAppBarStatus: AppBarStatus + get() = AppBarStatus.Hidden + + private val viewModel: ProductDetailWebViewViewModel by viewModels() + private val isInDetailPane: Boolean + get() = requireContext().isTwoPanesShouldBeUsed && parentFragment?.id == R.id.detail_nav_container + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return composeView { + ProductDetailWebViewScreen( + viewModel = viewModel, + showNavigationIcon = shouldShowNavigationIcon() + ) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + handleEvents() + } + + private fun handleEvents() { + viewModel.event.observe(viewLifecycleOwner) { event -> + when (event) { + is MultiLiveEvent.Event.Exit -> navigateBack() + } + } + } + + private fun navigateBack() { + if (isInDetailPane) { + // If we are in the detail pane of a two-pane layout, we need to handle the back navigation + // in the Activity's main nav controller and not the detail pane's nav controller. + findNavController(R.id.nav_host_fragment_main).navigateUp() + } else { + findNavController().popBackStack(R.id.productDetailFragment, true) + } + } + + private fun shouldShowNavigationIcon(): Boolean = !isInDetailPane +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewScreen.kt new file mode 100644 index 00000000000..d9c7f4f1c67 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewScreen.kt @@ -0,0 +1,59 @@ +package com.woocommerce.android.ui.products.details.webview + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import com.woocommerce.android.ui.common.webview.WebViewAuthenticator +import com.woocommerce.android.ui.compose.component.Toolbar +import com.woocommerce.android.ui.compose.component.web.WCWebView +import org.wordpress.android.fluxc.network.UserAgent + +@Composable +fun ProductDetailWebViewScreen( + viewModel: ProductDetailWebViewViewModel, + showNavigationIcon: Boolean +) { + viewModel.viewState.observeAsState().value?.let { + ProductDetailWebViewScreen( + viewState = it, + showNavigationIcon = showNavigationIcon, + webViewAuthenticator = viewModel.webViewAuthenticator, + userAgent = viewModel.userAgent, + ) + } +} + +@Composable +private fun ProductDetailWebViewScreen( + viewState: ProductDetailWebViewViewModel.ViewState, + showNavigationIcon: Boolean, + webViewAuthenticator: WebViewAuthenticator, + userAgent: UserAgent +) { + BackHandler(onBack = viewState.onBackClick) + + Scaffold( + topBar = { + Toolbar( + title = viewState.title, + navigationIcon = if (showNavigationIcon) Icons.AutoMirrored.Default.ArrowBack else null, + onNavigationButtonClick = viewState.onBackClick + ) + } + ) { + WCWebView( + url = viewState.urlToLoad, + authenticator = webViewAuthenticator, + userAgent = userAgent, + modifier = Modifier + .padding(it) + .fillMaxSize() + ) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewViewModel.kt new file mode 100644 index 00000000000..944098a4e71 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewViewModel.kt @@ -0,0 +1,59 @@ +package com.woocommerce.android.ui.products.details.webview + +import androidx.lifecycle.LiveData +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.liveData +import com.woocommerce.android.extensions.adminUrlOrDefault +import com.woocommerce.android.model.Product +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.common.webview.WebViewAuthenticator +import com.woocommerce.android.ui.products.details.ProductDetailRepository +import com.woocommerce.android.viewmodel.MultiLiveEvent +import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.navArgs +import dagger.hilt.android.lifecycle.HiltViewModel +import org.wordpress.android.fluxc.network.UserAgent +import org.wordpress.android.fluxc.utils.extensions.slashJoin +import javax.inject.Inject + +@HiltViewModel +class ProductDetailWebViewViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val productDetailRepository: ProductDetailRepository, + private val selectedSite: SelectedSite, + val webViewAuthenticator: WebViewAuthenticator, + val userAgent: UserAgent +) : ScopedViewModel(savedStateHandle) { + private val navArgs by savedStateHandle.navArgs() + + val viewState: LiveData = liveData { + val product = productDetailRepository.getProductAsync(navArgs.remoteProductId) ?: run { + navigateBack() + return@liveData + } + + val urlToLoad = buildProductUrl(product) + emit( + ViewState( + urlToLoad = urlToLoad, + title = product.name, + onBackClick = ::navigateBack + ) + ) + } + + private fun buildProductUrl(product: Product): String { + val site = selectedSite.get() + return site.adminUrlOrDefault.slashJoin("?page=next-admin&p=/woocommerce/products/edit/${product.remoteId}") + } + + private fun navigateBack() { + triggerEvent(MultiLiveEvent.Event.Exit) + } + + data class ViewState( + val urlToLoad: String, + val title: String, + val onBackClick: () -> Unit + ) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt index 2218de1b524..a833666dc44 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt @@ -669,13 +669,7 @@ class ProductListFragment : private fun onProductClick(remoteProductId: Long, sharedView: View?) { if (shouldPreventDetailNavigation(remoteProductId)) return productListToolbar.disableSearchListeners() - (activity as? MainNavigationRouter)?.let { router -> - if (sharedView == null) { - router.showProductDetail(remoteProductId) - } else { - router.showProductDetailWithSharedTransition(remoteProductId, sharedView) - } - } + (activity as? MainNavigationRouter)?.showProductDetail(remoteProductId, sharedView = sharedView) } private fun showAddProductBottomSheet() { diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index 6060f84772f..464b433c336 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -410,23 +410,6 @@ android:defaultValue="PRODUCT_TAB" app:argType="com.woocommerce.android.ui.products.AddProductSource" /> - - - - - diff --git a/WooCommerce/src/main/res/navigation/nav_graph_products.xml b/WooCommerce/src/main/res/navigation/nav_graph_products.xml index 280c17fb61f..5bd6868daf8 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_products.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_products.xml @@ -146,6 +146,17 @@ app:argType="org.wordpress.android.fluxc.model.metadata.MetaDataParentItemType" android:defaultValue="PRODUCT" /> + + + + Bundle Composite product Variation product + Bookable product Select a product type diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index 83508f85074..d7b89942959 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -5,6 +5,7 @@ import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent import com.woocommerce.android.analytics.AnalyticsTrackerWrapper +import com.woocommerce.android.ciab.CIABSiteGateKeeper import com.woocommerce.android.extensions.takeIfNotEqualTo import com.woocommerce.android.media.MediaFilesRepository import com.woocommerce.android.media.ProductImagesServiceWrapper @@ -35,6 +36,7 @@ import com.woocommerce.android.ui.products.variations.domain.GenerateVariationCa import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.IsWindowClassLargeThanCompact import com.woocommerce.android.util.ProductUtils +import com.woocommerce.android.util.captureValues import com.woocommerce.android.util.getOrAwaitValue import com.woocommerce.android.util.runAndCaptureValues import com.woocommerce.android.viewmodel.BaseUnitTest @@ -1515,4 +1517,60 @@ class ProductDetailViewModelTest : BaseUnitTest() { // THEN: once the product is stored, trash option should be visible Assertions.assertThat(menuButtonsStates.last().trashOption).isTrue() } + + @Test + fun `given CIAB site and bookable product, when product detail is opened, then show WebView`() = testBlocking { + // GIVEN + whenever(selectedSite.get()).thenReturn( + SiteModel().apply { + setIsGardenSite(true) + gardenName = CIABSiteGateKeeper.CIAB_GARDEN_NAME + } + ) + savedState = ProductDetailFragmentArgs(ProductDetailFragment.Mode.ShowProduct(PRODUCT_REMOTE_ID)) + .toSavedStateHandle() + val testProductAggregate = productAggregate.copy( + product = productAggregate.product.copy(type = ProductType.BOOKING.value) + ) + doReturn(testProductAggregate).whenever(productRepository).fetchAndGetProductAggregate(any()) + doReturn(testProductAggregate).whenever(productRepository).getProductAggregate(any()) + + setup() + viewModel.start() + + // WHEN + val event = viewModel.event.getOrAwaitValue() + + // THEN + Assertions.assertThat(event).isEqualTo( + ProductDetailViewModel.OpenProductInWebView(testProductAggregate.product.remoteId) + ) + } + + @Test + fun `given CIAB site and non-bookable product, when product detail is opened, then don't show WebView`() = testBlocking { + // GIVEN + whenever(selectedSite.get()).thenReturn( + SiteModel().apply { + setIsGardenSite(true) + gardenName = CIABSiteGateKeeper.CIAB_GARDEN_NAME + } + ) + savedState = ProductDetailFragmentArgs(ProductDetailFragment.Mode.ShowProduct(PRODUCT_REMOTE_ID)) + .toSavedStateHandle() + val testProductAggregate = productAggregate.copy( + product = productAggregate.product.copy(type = ProductType.SIMPLE.value) + ) + doReturn(testProductAggregate).whenever(productRepository).fetchAndGetProductAggregate(any()) + doReturn(testProductAggregate).whenever(productRepository).getProductAggregate(any()) + + setup() + viewModel.start() + + // WHEN + val events = viewModel.event.captureValues() + + // THEN + Assertions.assertThat(events).noneMatch { it is ProductDetailViewModel.OpenProductInWebView } + } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewViewModelTest.kt new file mode 100644 index 00000000000..5cc47d208c9 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/webview/ProductDetailWebViewViewModelTest.kt @@ -0,0 +1,102 @@ +package com.woocommerce.android.ui.products.details.webview + +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.common.webview.WebViewAuthenticator +import com.woocommerce.android.ui.products.ProductTestUtils +import com.woocommerce.android.ui.products.details.ProductDetailRepository +import com.woocommerce.android.util.captureValues +import com.woocommerce.android.util.getOrAwaitValue +import com.woocommerce.android.util.runAndCaptureValues +import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.MultiLiveEvent +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.UserAgent + +@ExperimentalCoroutinesApi +class ProductDetailWebViewViewModelTest : BaseUnitTest() { + + private val productDetailRepository: ProductDetailRepository = mock() + private val selectedSite: SelectedSite = mock() + private val webViewAuthenticator: WebViewAuthenticator = mock() + private val userAgent: UserAgent = mock() + + private lateinit var viewModel: ProductDetailWebViewViewModel + private lateinit var savedStateHandle: SavedStateHandle + + private val testProduct = ProductTestUtils.generateProduct( + productId = 456L, + productName = "Test Product" + ) + + private val testSite = SiteModel().apply { + adminUrl = "https://example.com/wp-admin/" + } + + @Before + fun setup() { + whenever(selectedSite.get()).doReturn(testSite) + } + + private suspend fun setupViewModel( + remoteProductId: Long = testProduct.remoteId, + setupMocks: suspend () -> Unit = {} + ) { + setupMocks() + + savedStateHandle = SavedStateHandle().apply { + set("remoteProductId", remoteProductId) + } + + viewModel = ProductDetailWebViewViewModel( + savedStateHandle = savedStateHandle, + productDetailRepository = productDetailRepository, + selectedSite = selectedSite, + webViewAuthenticator = webViewAuthenticator, + userAgent = userAgent + ) + } + + @Test + fun `given valid product, when viewState is observed, then correct ViewState is emitted`() = testBlocking { + // GIVEN + setupViewModel { + whenever(productDetailRepository.getProductAsync(testProduct.remoteId)) + .doReturn(testProduct) + } + + // WHEN + val viewState = viewModel.viewState.getOrAwaitValue() + + // THEN + assertThat(viewState.title).isEqualTo(testProduct.name) + assertThat(viewState.urlToLoad).isEqualTo( + "https://example.com/wp-admin/?page=next-admin&p=/woocommerce/products/edit/${testProduct.remoteId}" + ) + } + + @Test + fun `given product not found, when viewState is observed, then navigation back event is triggered`() = testBlocking { + // GIVEN + setupViewModel { + whenever(productDetailRepository.getProductAsync(testProduct.remoteId)) + .doReturn(null) + } + + // WHEN + val event = viewModel.event.runAndCaptureValues { + // Trigger viewState observation + viewModel.viewState.captureValues() + }.last() + + // THEN + assertThat(event).isEqualTo(MultiLiveEvent.Event.Exit) + } +}