diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ShippingMethod.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ShippingMethod.kt index 30386f36a9b..c323a3812e3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ShippingMethod.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ShippingMethod.kt @@ -1,6 +1,8 @@ package com.woocommerce.android.model import android.os.Parcelable +import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient +import com.woocommerce.android.ui.orders.creation.shipping.ShippingMethodsRepository import kotlinx.parcelize.Parcelize @Parcelize @@ -8,3 +10,7 @@ data class ShippingMethod( val id: String, val title: String ) : Parcelable + +fun ShippingMethodsRestClient.ShippingMethodDto.toAppModel(): ShippingMethod { + return ShippingMethod(id = this.id ?: ShippingMethodsRepository.OTHER_ID, title = this.title.orEmpty()) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/network/shippingmethods/ShippingMethodsRestClient.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/network/shippingmethods/ShippingMethodsRestClient.kt new file mode 100644 index 00000000000..82eb2dbf6f7 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/network/shippingmethods/ShippingMethodsRestClient.kt @@ -0,0 +1,35 @@ +package com.woocommerce.android.network.shippingmethods + +import org.wordpress.android.fluxc.generated.endpoint.WOOCOMMERCE +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooNetwork +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload +import org.wordpress.android.fluxc.utils.toWooPayload +import javax.inject.Inject + +class ShippingMethodsRestClient @Inject constructor(private val wooNetwork: WooNetwork) { + suspend fun fetchShippingMethods(site: SiteModel): WooPayload> { + val url = WOOCOMMERCE.shipping_methods.pathV3 + + return wooNetwork.executeGetGsonRequest( + site = site, + path = url, + clazz = Array::class.java, + ).toWooPayload { methods -> methods.toList() } + } + + suspend fun fetchShippingMethodsById(site: SiteModel, methodId: String): WooPayload { + val url = WOOCOMMERCE.shipping_methods.id(methodId).pathV3 + + return wooNetwork.executeGetGsonRequest( + site = site, + path = url, + clazz = ShippingMethodDto::class.java, + ).toWooPayload() + } + + data class ShippingMethodDto( + val id: String? = null, + val title: String? = null, + ) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodById.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodById.kt new file mode 100644 index 00000000000..27557892514 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodById.kt @@ -0,0 +1,17 @@ +package com.woocommerce.android.ui.orders.creation.shipping + +import com.woocommerce.android.model.ShippingMethod +import javax.inject.Inject + +class GetShippingMethodById @Inject constructor( + private val shippingMethodsRepository: ShippingMethodsRepository +) { + suspend operator fun invoke(shippingMethodId: String?): ShippingMethod { + val other = shippingMethodsRepository.getOtherShippingMethod() + if (shippingMethodId == ShippingMethodsRepository.OTHER_ID || shippingMethodId == null) { + return other + } + val result = shippingMethodsRepository.fetchShippingMethodById(shippingMethodId) + return result.model ?: other + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodsWithOtherValue.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodsWithOtherValue.kt new file mode 100644 index 00000000000..56eda5a5e25 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodsWithOtherValue.kt @@ -0,0 +1,26 @@ +package com.woocommerce.android.ui.orders.creation.shipping + +import com.woocommerce.android.model.ShippingMethod +import javax.inject.Inject + +class GetShippingMethodsWithOtherValue @Inject constructor( + private val shippingMethodsRepository: ShippingMethodsRepository +) { + suspend operator fun invoke(): Result> { + val result = shippingMethodsRepository.fetchShippingMethods() + return when { + result.model != null -> { + val shippingMethodsWithOtherValue = result.model!!.toMutableList().also { + it.add(shippingMethodsRepository.getOtherShippingMethod()) + } + + Result.success(shippingMethodsWithOtherValue) + } + + else -> { + val message = result.error.message ?: "error fetching shipping methods" + Result.failure(Exception(message)) + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModel.kt index 784309ec520..8a2b9f06441 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModel.kt @@ -6,14 +6,14 @@ 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 kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class OrderShippingMethodsViewModel @Inject constructor( - savedStateHandle: SavedStateHandle + savedStateHandle: SavedStateHandle, + private val getShippingMethodsWithOtherValue: GetShippingMethodsWithOtherValue ) : ScopedViewModel(savedStateHandle) { private val navArgs: OrderShippingMethodsFragmentArgs by savedState.navArgs() val viewState: MutableStateFlow @@ -33,33 +33,20 @@ class OrderShippingMethodsViewModel @Inject constructor( private suspend fun getShippingMethods() { viewState.value = ViewState.Loading - delay(1000) - val fetchedShippingMethods = listOf( - ShippingMethod( - id = "flat_rate", - title = "Flat Rate" - ), - ShippingMethod( - id = "free_shipping", - title = "Free shipping" - ), - ShippingMethod( - id = "local_pickup", - title = "Local pickup" - ), - ShippingMethod( - id = "other", - title = "Other" - ) - ) - - var methodsUIList = fetchedShippingMethods.map { ShippingMethodUI(it) } + getShippingMethodsWithOtherValue().fold( + onSuccess = { fetchedShippingMethods -> + var methodsUIList = fetchedShippingMethods.map { ShippingMethodUI(it) } - methodsUIList = navArgs.selectedMethodId?.let { selectedId -> - updateSelection(selectedId, fetchedShippingMethods.map { ShippingMethodUI(it) }) - } ?: methodsUIList + methodsUIList = navArgs.selectedMethodId?.let { selectedId -> + updateSelection(selectedId, fetchedShippingMethods.map { ShippingMethodUI(it) }) + } ?: methodsUIList - viewState.value = ViewState.ShippingMethodsState(methods = methodsUIList) + viewState.value = ViewState.ShippingMethodsState(methods = methodsUIList) + }, + onFailure = { + viewState.value = ViewState.Error + } + ) } fun onMethodSelected(selected: ShippingMethodUI) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModel.kt index ff49f19d18a..4e66b984e57 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModel.kt @@ -3,7 +3,6 @@ package com.woocommerce.android.ui.orders.creation.shipping import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.R -import com.woocommerce.android.extensions.capitalize import com.woocommerce.android.model.Order import com.woocommerce.android.model.ShippingMethod import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -11,7 +10,6 @@ import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -21,7 +19,8 @@ import javax.inject.Inject @HiltViewModel class OrderShippingViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val resourceProvider: ResourceProvider + private val resourceProvider: ResourceProvider, + private val getShippingMethodById: GetShippingMethodById ) : ScopedViewModel(savedStateHandle) { private val navArgs: OrderShippingFragmentArgs by savedState.navArgs() @@ -43,7 +42,7 @@ class OrderShippingViewModel @Inject constructor( navArgs.currentShippingLine?.let { shippingLine: Order.ShippingLine -> launch { viewState.value = ViewState.ShippingState( - method = getShippingMethod(shippingLine), + method = getShippingMethodById(shippingLine.methodId), name = shippingLine.methodTitle, amount = shippingLine.total, isEditFlow = true, @@ -53,19 +52,6 @@ class OrderShippingViewModel @Inject constructor( } } - @Suppress("MagicNumber") - private suspend fun getShippingMethod(shippingLine: Order.ShippingLine): ShippingMethod? { - return if (shippingLine.methodId == null) { - null - } else { - delay(1000) - ShippingMethod( - id = shippingLine.methodId, - title = shippingLine.methodId.capitalize() - ) - } - } - fun onNameChanged(name: String) { (viewState.value as? ViewState.ShippingState)?.let { viewState.value = it.copy( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/ShippingMethodsRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/ShippingMethodsRepository.kt new file mode 100644 index 00000000000..3ac8988d7d8 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/shipping/ShippingMethodsRepository.kt @@ -0,0 +1,72 @@ +package com.woocommerce.android.ui.orders.creation.shipping + +import com.woocommerce.android.R +import com.woocommerce.android.model.ShippingMethod +import com.woocommerce.android.model.toAppModel +import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.util.CoroutineDispatchers +import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.withContext +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.BaseRequest +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult +import javax.inject.Inject + +class ShippingMethodsRepository @Inject constructor( + private val selectedSite: SelectedSite, + private val shippingMethodsRestClient: ShippingMethodsRestClient, + private val resourceProvider: ResourceProvider, + private val dispatchers: CoroutineDispatchers +) { + companion object { + const val OTHER_ID = "other" + } + + suspend fun fetchShippingMethods(site: SiteModel = selectedSite.get()): WooResult> { + return withContext(dispatchers.io) { + val response = shippingMethodsRestClient.fetchShippingMethods(site) + when { + response.isError -> { + WooResult(response.error) + } + + response.result != null -> { + val shippingMethods = response.result!!.map { dto -> dto.toAppModel() } + WooResult(shippingMethods) + } + + else -> WooResult(WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN)) + } + } + } + + suspend fun fetchShippingMethodById( + methodId: String, + site: SiteModel = selectedSite.get() + ): WooResult { + return withContext(dispatchers.io) { + val response = shippingMethodsRestClient.fetchShippingMethodsById(site, methodId) + when { + response.isError -> { + WooResult(response.error) + } + + response.result != null -> { + WooResult(response.result!!.toAppModel()) + } + + else -> WooResult(WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN)) + } + } + } + + fun getOtherShippingMethod(): ShippingMethod { + return ShippingMethod( + id = OTHER_ID, + title = resourceProvider.getString(R.string.other) + ) + } +} diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodByIdTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodByIdTest.kt new file mode 100644 index 00000000000..76be65ce50b --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodByIdTest.kt @@ -0,0 +1,85 @@ +package com.woocommerce.android.ui.orders.creation.shipping + +import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.BaseRequest +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload + +@OptIn(ExperimentalCoroutinesApi::class) +class GetShippingMethodByIdTest : BaseUnitTest() { + private val siteModel = SiteModel() + private val selectedSite: SelectedSite = mock { + on { get() } doReturn siteModel + } + private val resourceProvider: ResourceProvider = mock() + private val shippingMethodsRestClient: ShippingMethodsRestClient = mock() + + private val shippingMethodsRepository = ShippingMethodsRepository( + selectedSite = selectedSite, + dispatchers = coroutinesTestRule.testDispatchers, + resourceProvider = resourceProvider, + shippingMethodsRestClient = shippingMethodsRestClient + ) + + val sut = GetShippingMethodById(shippingMethodsRepository) + + @Test + fun `when the method is in the result, then return is the expected`() = testBlocking { + val methodId = "id1" + val fetchResult = ShippingMethodsRestClient.ShippingMethodDto( + id = methodId, + title = "title1", + ) + + whenever(shippingMethodsRestClient.fetchShippingMethodsById(siteModel, methodId)).doReturn( + WooPayload(fetchResult) + ) + whenever(resourceProvider.getString(any())).doReturn("Other") + + val result = sut.invoke(methodId) + assertThat(result).isNotNull + assertThat(result.id).isEqualTo(methodId) + } + + @Test + fun `when the method id is other, then return is the expected`() = testBlocking { + whenever(resourceProvider.getString(any())).doReturn("Other") + + val result = sut.invoke(ShippingMethodsRepository.OTHER_ID) + + // If is other doesn't need to fetch the values from the API + verify(shippingMethodsRestClient, never()).fetchShippingMethodsById( + siteModel, + ShippingMethodsRepository.OTHER_ID + ) + assertThat(result).isNotNull + assertThat(result.id).isEqualTo(ShippingMethodsRepository.OTHER_ID) + } + + @Test + fun `when fetching shipping methods fail, then return other`() = testBlocking { + val methodId = "id8" + val fetchResult = WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN) + whenever(shippingMethodsRestClient.fetchShippingMethodsById(siteModel, methodId)) + .doReturn(WooPayload(fetchResult)) + whenever(resourceProvider.getString(any())).doReturn("Other") + + val result = sut.invoke(methodId) + assertThat(result).isNotNull + assertThat(result.id).isEqualTo(ShippingMethodsRepository.OTHER_ID) + } +} diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodsWithOtherValueTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodsWithOtherValueTest.kt new file mode 100644 index 00000000000..b77a6027b94 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/GetShippingMethodsWithOtherValueTest.kt @@ -0,0 +1,80 @@ +package com.woocommerce.android.ui.orders.creation.shipping + +import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.kotlin.any +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.BaseRequest +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload + +@OptIn(ExperimentalCoroutinesApi::class) +class GetShippingMethodsWithOtherValueTest : BaseUnitTest() { + + private val siteModel = SiteModel() + private val selectedSite: SelectedSite = mock { + on { get() } doReturn siteModel + } + private val resourceProvider: ResourceProvider = mock() + private val shippingMethodsRestClient: ShippingMethodsRestClient = mock() + + private val shippingMethodsRepository = ShippingMethodsRepository( + selectedSite = selectedSite, + dispatchers = coroutinesTestRule.testDispatchers, + resourceProvider = resourceProvider, + shippingMethodsRestClient = shippingMethodsRestClient + ) + + val sut = GetShippingMethodsWithOtherValue(shippingMethodsRepository) + + @Test + fun `when get shipping succeed then a success response is returned including other`() = testBlocking { + val fetchResult = List(3) { i -> + ShippingMethodsRestClient.ShippingMethodDto( + id = "id$i", + title = "title$i", + ) + } + whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) + whenever(resourceProvider.getString(any())).doReturn("Other") + + val result = sut.invoke() + assertThat(result).isNotNull + assertThat(result.isSuccess).isTrue() + assertThat(result.isFailure).isFalse() + assertThat(result.getOrNull()!!.size).isEqualTo(4) // List + Other + val other = result.getOrNull()!!.firstOrNull { it.id == ShippingMethodsRepository.OTHER_ID } + assertThat(other).isNotNull + } + + @Test + fun `when get shipping fails then an error response is returned`() = testBlocking { + val fetchResult = WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN) + whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) + + val result = sut.invoke() + assertThat(result).isNotNull + assertThat(result.isSuccess).isFalse() + assertThat(result.isFailure).isTrue() + } + + @Test + fun `when get shipping succeed but is null then an error response is returned`() = testBlocking { + val fetchResult = null + whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) + + val result = sut.invoke() + assertThat(result).isNotNull + assertThat(result.isSuccess).isFalse() + assertThat(result.isFailure).isTrue() + } +} diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModelTest.kt index 681dfb4e94e..027a6f9a1c1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingMethodsViewModelTest.kt @@ -4,9 +4,11 @@ import com.woocommerce.android.model.ShippingMethod import com.woocommerce.android.viewmodel.BaseUnitTest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first -import kotlinx.coroutines.test.advanceTimeBy import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) class OrderShippingMethodsViewModelTest : BaseUnitTest() { @@ -17,16 +19,25 @@ class OrderShippingMethodsViewModelTest : BaseUnitTest() { private val selectedArgs get() = OrderShippingMethodsFragmentArgs("other") + private val getShippingMethodsWithOtherValue: GetShippingMethodsWithOtherValue = mock() + + private val defaultShippingMethods = listOf( + ShippingMethod(id = "free_shipping", "Free Shipping"), + ShippingMethod(id = ShippingMethodsRepository.OTHER_ID, "Other"), + ShippingMethod(id = "local_pickup", "Local Pick Up"), + ) + fun setup(args: OrderShippingMethodsFragmentArgs) { viewModel = OrderShippingMethodsViewModel( - savedStateHandle = args.toSavedStateHandle() + savedStateHandle = args.toSavedStateHandle(), + getShippingMethodsWithOtherValue = getShippingMethodsWithOtherValue ) } @Test fun `given there is no shipping method selected, make sure selection is empty`() = testBlocking { + whenever(getShippingMethodsWithOtherValue.invoke()).doReturn(Result.success(defaultShippingMethods)) setup(noSelectedArgs) - advanceTimeBy(1001) val viewState = viewModel.viewState.first() assertThat(viewState).isNotNull assertThat(viewState).isInstanceOf(OrderShippingMethodsViewModel.ViewState.ShippingMethodsState::class.java) @@ -37,8 +48,8 @@ class OrderShippingMethodsViewModelTest : BaseUnitTest() { @Test fun `given there is a shipping method selected, make sure the item is marked as selected`() = testBlocking { + whenever(getShippingMethodsWithOtherValue.invoke()).doReturn(Result.success(defaultShippingMethods)) setup(selectedArgs) - advanceTimeBy(1001) val viewState = viewModel.viewState.first() assertThat(viewState).isNotNull assertThat(viewState).isInstanceOf(OrderShippingMethodsViewModel.ViewState.ShippingMethodsState::class.java) @@ -48,20 +59,22 @@ class OrderShippingMethodsViewModelTest : BaseUnitTest() { } @Test - fun `given there is no shipping method selected, if the selection changes, the the item is marked as selected`() = testBlocking { - setup(noSelectedArgs) - val selected = OrderShippingMethodsViewModel.ShippingMethodUI( - ShippingMethod("other", "Other"), - isSelected = false - ) + fun `given there is no shipping method selected, if the selection changes, the the item is marked as selected`() = + testBlocking { + whenever(getShippingMethodsWithOtherValue.invoke()).doReturn(Result.success(defaultShippingMethods)) - advanceTimeBy(1001) - viewModel.onMethodSelected(selected) - val viewState = viewModel.viewState.first() - assertThat(viewState).isNotNull - assertThat(viewState).isInstanceOf(OrderShippingMethodsViewModel.ViewState.ShippingMethodsState::class.java) - val selectedItem = (viewState as OrderShippingMethodsViewModel.ViewState.ShippingMethodsState) - .methods.firstOrNull { it.method.id == selected.method.id && it.isSelected } - assertThat(selectedItem).isNotNull - } + setup(noSelectedArgs) + + val selected = OrderShippingMethodsViewModel.ShippingMethodUI( + ShippingMethod("other", "Other"), + isSelected = false + ) + viewModel.onMethodSelected(selected) + val viewState = viewModel.viewState.first() + assertThat(viewState).isNotNull + assertThat(viewState).isInstanceOf(OrderShippingMethodsViewModel.ViewState.ShippingMethodsState::class.java) + val selectedItem = (viewState as OrderShippingMethodsViewModel.ViewState.ShippingMethodsState) + .methods.firstOrNull { it.method.id == selected.method.id && it.isSelected } + assertThat(selectedItem).isNotNull + } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModelTest.kt index d001653ac50..9ba2485a7bf 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/OrderShippingViewModelTest.kt @@ -27,10 +27,13 @@ class OrderShippingViewModelTest : BaseUnitTest() { ) ) + private val getShippingMethodById: GetShippingMethodById = mock() + fun setup(args: OrderShippingFragmentArgs) { viewModel = OrderShippingViewModel( savedStateHandle = args.toSavedStateHandle(), - resourceProvider = mock() + resourceProvider = mock(), + getShippingMethodById = getShippingMethodById ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/ShippingMethodsRepositoryTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/ShippingMethodsRepositoryTest.kt new file mode 100644 index 00000000000..3c5e64b9d7f --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/shipping/ShippingMethodsRepositoryTest.kt @@ -0,0 +1,71 @@ +package com.woocommerce.android.ui.orders.creation.shipping + +import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +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.BaseRequest +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload + +@OptIn(ExperimentalCoroutinesApi::class) +class ShippingMethodsRepositoryTest : BaseUnitTest() { + + private val selectedSite: SelectedSite = mock() + private val resourceProvider: ResourceProvider = mock() + private val shippingMethodsRestClient: ShippingMethodsRestClient = mock() + + private val sut = ShippingMethodsRepository( + selectedSite = selectedSite, + dispatchers = coroutinesTestRule.testDispatchers, + resourceProvider = resourceProvider, + shippingMethodsRestClient = shippingMethodsRestClient + ) + + @Test + fun `when shipping requests succeed then a success response is returned`() = testBlocking { + val siteModel = SiteModel() + val fetchResult = List(3) { i -> + ShippingMethodsRestClient.ShippingMethodDto( + id = "id$i", + title = "title$i", + ) + } + whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) + + val result = sut.fetchShippingMethods(siteModel) + assertThat(result.isError).isFalse() + assertThat(result.model).isNotNull + assertThat(result.model?.size).isEqualTo(fetchResult.size) + } + + @Test + fun `when shipping requests succeed but model is null then an error response is returned`() = testBlocking { + val siteModel = SiteModel() + val fetchResult = null + whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) + + val result = sut.fetchShippingMethods(siteModel) + assertThat(result.isError).isTrue() + assertThat(result.model).isNull() + } + + @Test + fun `when shipping requests fails then an error response is returned`() = testBlocking { + val siteModel = SiteModel() + val fetchResult = WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN) + whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) + + val result = sut.fetchShippingMethods(siteModel) + assertThat(result.isError).isTrue() + assertThat(result.model).isNull() + } +} diff --git a/build.gradle b/build.gradle index 5d7d75d1e18..c19c855d5e4 100644 --- a/build.gradle +++ b/build.gradle @@ -98,7 +98,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2.78.0' + fluxCVersion = 'trunk-cfe34975d20df76a2b7b4522dccd2faf53ef0f7c' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0'