Skip to content

Commit

Permalink
Merge pull request #13517 from woocommerce/issue/13492-cannot-de-sele…
Browse files Browse the repository at this point in the history
…ct-product-product-selector-while-filtering

[Product Selection] Cannot de-select product in product selection screen while filtering
  • Loading branch information
samiuelson authored Feb 14, 2025
2 parents 5678b48 + a162c1d commit 1eb12a7
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 11 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
-----
- [**] Dashboard tab now supports tablet/bug screen layout [https://github.com/woocommerce/woocommerce-android/pull/13488]
- [**] Improved accessibility support (handling of increased text size) in in-person payment flows [https://github.com/woocommerce/woocommerce-android/pull/13414]
- [**] Added support for deselecting a product in the product selection screen when accessed from the order filter list.[https://github.com/woocommerce/woocommerce-android/pull/13517]
- [***] Fixed currency mismatch in order editing by ensuring products, custom amounts, and shipping lines respect the order’s currency. [https://github.com/woocommerce/woocommerce-android/pull/13483]
- [*] Updated the notification icon for better readability [https://github.com/woocommerce/woocommerce-android/pull/13516]
- [*] Automatically select the first available order when filtering is active in the two-pane layout.[https://github.com/woocommerce/woocommerce-android/pull/13491]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ class OrderFilterCategoriesFragment :
}

handleResult<Collection<SelectedItem>>(ProductSelectorFragment.PRODUCT_SELECTOR_RESULT) {
viewModel.onProductSelected(it.first().id)
it.firstOrNull()?.let {
viewModel.onProductSelected(it.id)
} ?: run {
viewModel.onClearProductFilter()
}
}

handleResult<Order.Customer>(KEY_CUSTOMER_RESULT) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ class OrderFilterCategoriesViewModel @Inject constructor(
)
}

fun onClearProductFilter() {
_categories = _categories.copy(
_categories.list.map {
if (it.categoryKey == PRODUCT) {
it.copy(
orderFilterOptions = it.orderFilterOptions
.clearAllFilterSelections()
.markOptionAllIfNothingSelected(),
displayValue = resourceProvider.getString(R.string.orderfilters_default_filter_value)
)
} else {
it
}
}
)
}

fun onFilterOptionsUpdated(updatedCategory: OrderFilterCategoryUiModel) {
_categories.list.let { filterOptions ->
_categories = OrderFilterCategories(
Expand Down Expand Up @@ -259,6 +276,7 @@ class OrderFilterCategoriesViewModel @Inject constructor(
when (selectedFilterCategoryKey) {
ORDER_STATUS -> getNumberOfSelectedFilterOptions()
.toString()

DATE_RANGE,
PRODUCT,
CUSTOMER -> first { it.isSelected }.displayName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,17 +375,25 @@ class ProductSelectorViewModel @Inject constructor(

fun onProductClick(item: ListItem, productSourceForTracking: ProductSourceForTracking) {
val productSource = updateProductSourceIfSearchIsEnabled(productSourceForTracking)
when (item) {
is ListItem.ProductListItem -> {
handleProductTap(item, productSource)
}
if (
navArgs.productSelectorFlow == OrderListFilter &&
_selectedItems.value.containsItemWith(item.id)
) {
selectedItemsSource.remove(item.id)
_selectedItems.update { it.filter { selectedItem -> selectedItem.id != item.id } }
} else {
when (item) {
is ListItem.ProductListItem -> {
handleProductTap(item, productSource)
}

is ListItem.VariationListItem -> {
handleVariationItemTap(item, productSource)
}
is ListItem.VariationListItem -> {
handleVariationItemTap(item, productSource)
}

is ListItem.ConfigurableListItem -> {
handleConfigurableItemTap(item)
is ListItem.ConfigurableListItem -> {
handleConfigurableItemTap(item)
}
}
}
}
Expand Down Expand Up @@ -692,7 +700,8 @@ class ProductSelectorViewModel @Inject constructor(
val ctaButtonTextOverride: String? = null,
val selectionEnabled: Boolean = true
) {
val isDoneButtonEnabled: Boolean = selectionMode == SelectionMode.MULTIPLE || selectedItemsCount > 0
val isDoneButtonEnabled: Boolean =
selectionMode == SelectionMode.MULTIPLE || selectedItemsCount > 0 || productFlow == OrderListFilter
val shouldDisplayFilterButton = searchState.searchQuery.isEmpty() && productFlow != OrderListFilter
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ class OrderFilterCategoriesViewModelTest : BaseUnitTest() {
)
}

@Test
fun `Given product filter is applied, when onClearProductFilter is called, then product filter is reset`() {
viewModel.onClearProductFilter()

assertThat(
viewModel.categories.liveData.getOrAwaitValue().list.filter {
it.categoryKey == OrderListFilterCategory.PRODUCT
}
)
.allMatch { it.orderFilterOptions.none { it.isSelected } }
}

private fun allFilterOptionsAreUnselected() = currentCategoryList
.map {
it.orderFilterOptions.any { filterOption ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.woocommerce.android.ui.products.variations.selector.VariationSelector
import com.woocommerce.android.ui.products.variations.selector.VariationSelectorViewModel
import com.woocommerce.android.util.CurrencyFormatter
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
Expand All @@ -49,6 +50,8 @@ import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.model.WCSettingsModel
import org.wordpress.android.fluxc.store.WCOrderStore
import org.wordpress.android.fluxc.store.WooCommerceStore
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@ExperimentalCoroutinesApi
internal class ProductSelectorViewModelTest : BaseUnitTest() {
Expand Down Expand Up @@ -188,6 +191,77 @@ internal class ProductSelectorViewModelTest : BaseUnitTest() {
}
}

@Test
fun `given order creation flow from order list filter, when item is de-selected, then remove selected item`() {
val navArgs = ProductSelectorFragmentArgs(
selectedItems = emptyArray(),
productSelectorFlow = ProductSelectorViewModel.ProductSelectorFlow.OrderListFilter,
).toSavedStateHandle()

val sut = createViewModel(navArgs)

sut.onProductClick(
item = ProductListItem(productId = 1, numVariations = 0, title = "", type = ProductType.SIMPLE),
productSourceForTracking = ProductSourceForTracking.ALPHABETICAL,
)
sut.viewState.observeForever { state ->
assertThat(state.selectedItemsCount).isEqualTo(1)
}
sut.onProductClick(
item = ProductListItem(productId = 1, numVariations = 0, title = "", type = ProductType.SIMPLE),
productSourceForTracking = ProductSourceForTracking.ALPHABETICAL,
)

sut.viewState.observeForever { state ->
assertThat(state.selectedItemsCount).isEqualTo(0)
}
}

@Test
fun `given order creation flow from order list filter, when item is de-selected, then done button should still be enabled`() {
val navArgs = ProductSelectorFragmentArgs(
selectedItems = emptyArray(),
productSelectorFlow = ProductSelectorViewModel.ProductSelectorFlow.OrderListFilter,
selectionMode = ProductSelectorViewModel.SelectionMode.SINGLE
).toSavedStateHandle()

val sut = createViewModel(navArgs)

sut.onProductClick(
item = ProductListItem(productId = 1, numVariations = 0, title = "", type = ProductType.SIMPLE),
productSourceForTracking = ProductSourceForTracking.ALPHABETICAL,
)
sut.onProductClick(
item = ProductListItem(productId = 1, numVariations = 0, title = "", type = ProductType.SIMPLE),
productSourceForTracking = ProductSourceForTracking.ALPHABETICAL,
)

val state = sut.viewState.getOrAwaitValue()
assertTrue(state.isDoneButtonEnabled)
}

@Test
fun `given order creation flow from any other flow other than order list filter, when item is de-selected, then done button should be disabled`() {
val navArgs = ProductSelectorFragmentArgs(
selectedItems = emptyArray(),
productSelectorFlow = ProductSelectorViewModel.ProductSelectorFlow.OrderCreation,
selectionMode = ProductSelectorViewModel.SelectionMode.SINGLE,
).toSavedStateHandle()

val sut = createViewModel(navArgs)

val state = sut.viewState.getOrAwaitValue()
assertFalse(state.isDoneButtonEnabled)

sut.onProductClick(
item = ProductListItem(productId = 1, numVariations = 0, title = "", type = ProductType.SIMPLE),
productSourceForTracking = ProductSourceForTracking.ALPHABETICAL,
)

val updatedState = sut.viewState.getOrAwaitValue()
assertTrue(updatedState.isDoneButtonEnabled)
}

@Test
fun `given order creation flow, when item is selected, should track analytic event`() {
val navArgs = ProductSelectorFragmentArgs(
Expand Down

0 comments on commit 1eb12a7

Please sign in to comment.