Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HACK week] Product list - Add favorite product filter option #13995

Merged
merged 7 commits into from
Sep 26, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ public struct StoredProductSettings: Codable, Equatable, GeneratedFakeable {
public let productStatusFilter: ProductStatus?
public let productTypeFilter: ProductType?
public let productCategoryFilter: ProductCategory?
public let favoriteProduct: Bool

public init(siteID: Int64,
sort: String?,
stockStatusFilter: ProductStockStatus?,
productStatusFilter: ProductStatus?,
productTypeFilter: ProductType?,
productCategoryFilter: ProductCategory?) {
productCategoryFilter: ProductCategory?,
favoriteProduct: Bool) {
self.siteID = siteID
self.sort = sort
self.stockStatusFilter = stockStatusFilter
self.productStatusFilter = productStatusFilter
self.productTypeFilter = productTypeFilter
self.productCategoryFilter = productCategoryFilter
self.favoriteProduct = favoriteProduct
}

public func numberOfActiveFilters() -> Int {
Expand All @@ -44,6 +47,10 @@ public struct StoredProductSettings: Codable, Equatable, GeneratedFakeable {
total += 1
}

if favoriteProduct {
total += 1
}

return total
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ protocol FavoriteProductsUseCase {
func favoriteProductIDs() async -> [Int64]
}

/// Used to filter favorite products
///
struct FavoriteProductsFilter: Equatable, FilterType {
let description = Localization.favoriteProducts

let isActive = true
}

private extension FavoriteProductsFilter {
enum Localization {
static let favoriteProducts = NSLocalizedString(
"favoriteProductsFilter.favoriteProducts",
value: "Favorite Products",
comment: "Description of the filter used to show only favorite products."
)
}
}

/// Used for marking/removing products as favorite
///
struct DefaultFavoriteProductsUseCase: FavoriteProductsUseCase {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class FilterProductListViewModel: FilterListViewModel {
let productStatus: ProductStatus?
let promotableProductType: PromotableProductType?
let productCategory: ProductCategory?
let favoriteProduct: FavoriteProductsFilter?

let numberOfActiveFilters: Int

Expand All @@ -22,18 +23,21 @@ final class FilterProductListViewModel: FilterListViewModel {
productStatus = nil
promotableProductType = nil
productCategory = nil
favoriteProduct = nil
numberOfActiveFilters = 0
}

init(stockStatus: ProductStockStatus?,
productStatus: ProductStatus?,
promotableProductType: PromotableProductType?,
productCategory: ProductCategory?,
favoriteProduct: FavoriteProductsFilter?,
numberOfActiveFilters: Int) {
self.stockStatus = stockStatus
self.productStatus = productStatus
self.promotableProductType = promotableProductType
self.productCategory = productCategory
self.favoriteProduct = favoriteProduct
self.numberOfActiveFilters = numberOfActiveFilters
}

Expand All @@ -52,35 +56,56 @@ final class FilterProductListViewModel: FilterListViewModel {
private let productStatusFilterViewModel: FilterTypeViewModel
private let productTypeFilterViewModel: FilterTypeViewModel
private let productCategoryFilterViewModel: FilterTypeViewModel
private let productFavoriteFilterViewModel: FilterTypeViewModel

private let featureFlagService: FeatureFlagService

/// - Parameters:
/// - filters: the filters to be applied initially.
init(filters: Filters, siteID: Int64) {
/// - siteID: Current selected store's ID
/// - featureFlagService: Feature flag service
init(filters: Filters,
siteID: Int64,
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
self.featureFlagService = featureFlagService
self.stockStatusFilterViewModel = ProductListFilter.stockStatus.createViewModel(filters: filters)
self.productStatusFilterViewModel = ProductListFilter.productStatus.createViewModel(filters: filters)
self.productTypeFilterViewModel = ProductListFilter.productType(siteID: siteID).createViewModel(filters: filters)
self.productCategoryFilterViewModel = ProductListFilter.productCategory(siteID: siteID).createViewModel(filters: filters)

self.filterTypeViewModels = [
stockStatusFilterViewModel,
productStatusFilterViewModel,
productTypeFilterViewModel,
productCategoryFilterViewModel
]
self.productFavoriteFilterViewModel = ProductListFilter.favoriteProducts.createViewModel(filters: filters)

if featureFlagService.isFeatureFlagEnabled(.favoriteProducts) {
self.filterTypeViewModels = [
stockStatusFilterViewModel,
productStatusFilterViewModel,
productTypeFilterViewModel,
productCategoryFilterViewModel,
productFavoriteFilterViewModel
]
} else {
self.filterTypeViewModels = [
stockStatusFilterViewModel,
productStatusFilterViewModel,
productTypeFilterViewModel,
productCategoryFilterViewModel,
]
}
}

var criteria: Filters {
let stockStatus = stockStatusFilterViewModel.selectedValue as? ProductStockStatus ?? nil
let productStatus = productStatusFilterViewModel.selectedValue as? ProductStatus ?? nil
let promotableProductType = productTypeFilterViewModel.selectedValue as? PromotableProductType ?? nil
let productCategory = productCategoryFilterViewModel.selectedValue as? ProductCategory ?? nil
let favoriteProduct = productFavoriteFilterViewModel.selectedValue as? FavoriteProductsFilter ?? nil

let numberOfActiveFilters = filterTypeViewModels.numberOfActiveFilters

return Filters(stockStatus: stockStatus,
productStatus: productStatus,
promotableProductType: promotableProductType,
productCategory: productCategory,
favoriteProduct: favoriteProduct,
numberOfActiveFilters: numberOfActiveFilters)
}

Expand All @@ -96,6 +121,9 @@ final class FilterProductListViewModel: FilterListViewModel {

let clearedProductCategory: ProductCategory? = nil
productCategoryFilterViewModel.selectedValue = clearedProductCategory

let clearedFavoriteProduct: ProductCategory? = nil
productFavoriteFilterViewModel.selectedValue = clearedFavoriteProduct
}
}

Expand All @@ -107,6 +135,7 @@ extension FilterProductListViewModel {
case productStatus
case productType(siteID: Int64)
case productCategory(siteID: Int64)
case favoriteProducts
}
}

Expand All @@ -121,10 +150,22 @@ private extension FilterProductListViewModel.ProductListFilter {
return NSLocalizedString("Product Type", comment: "Row title for filtering products by product type.")
case .productCategory:
return NSLocalizedString("Product Category", comment: "Row title for filtering products by product category.")
case .favoriteProducts:
return Localization.favoriteProduct
}
}
}

extension FilterProductListViewModel.ProductListFilter {
enum Localization {
static let favoriteProduct = NSLocalizedString(
"filterProductListViewModel.favoriteProduct",
value: "Favorite Products",
comment: "Row title for filtering products by favorite products."
)
}
}

extension FilterProductListViewModel.ProductListFilter {
func createViewModel(filters: FilterProductListViewModel.Filters,
storageManager: StorageManagerType = ServiceLocator.storageManager) -> FilterTypeViewModel {
Expand All @@ -148,6 +189,10 @@ extension FilterProductListViewModel.ProductListFilter {
return FilterTypeViewModel(title: title,
listSelectorConfig: .productCategories(siteID: siteID),
selectedValue: filters.productCategory)
case .favoriteProducts:
return FilterTypeViewModel(title: title,
listSelectorConfig: .staticOptions(options: [nil, FavoriteProductsFilter()]),
selectedValue: filters.favoriteProduct)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1409,7 +1409,8 @@ extension ProductsViewController: PaginationTrackerDelegate {
stockStatusFilter: filters.stockStatus,
productStatusFilter: filters.productStatus,
productTypeFilter: filters.promotableProductType?.productType,
productCategoryFilter: filters.productCategory) { (error) in
productCategoryFilter: filters.productCategory,
favoriteProduct: filters.favoriteProduct != nil) { (error) in
}
ServiceLocator.stores.dispatch(action)
}
Expand All @@ -1431,6 +1432,7 @@ extension ProductsViewController: PaginationTrackerDelegate {
productStatus: settings.productStatusFilter,
promotableProductType: promotableProductType,
productCategory: settings.productCategoryFilter,
favoriteProduct: settings.favoriteProduct ? FavoriteProductsFilter() : nil,
numberOfActiveFilters: settings.numberOfActiveFilters())
onCompletion(result)
}
Expand Down Expand Up @@ -1476,7 +1478,8 @@ extension ProductsViewController: PaginationTrackerDelegate {
stockStatusFilter: settings.stockStatusFilter,
productStatusFilter: settings.productStatusFilter,
productTypeFilter: settings.productTypeFilter,
productCategoryFilter: updatingProductCategory)
productCategoryFilter: updatingProductCategory,
favoriteProduct: settings.favoriteProduct)
}

onCompletion(completionSettings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,8 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: .draft,
promotableProductType: PromotableProductType(productType: .simple, isAvailable: true, promoteUrl: nil),
productCategory: nil,
numberOfActiveFilters: 3)
favoriteProduct: FavoriteProductsFilter(),
numberOfActiveFilters: 4)

viewModel.searchTerm = "shirt"
viewModel.updateFilters(filters)
Expand Down Expand Up @@ -866,6 +867,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: ProductStatus.draft,
promotableProductType: PromotableProductType(productType: .simple, isAvailable: true, promoteUrl: nil),
productCategory: nil,
favoriteProduct: nil,
numberOfActiveFilters: 3
)
viewModel.searchTerm = ""
Expand Down Expand Up @@ -1033,14 +1035,15 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: ProductStatus.draft,
promotableProductType: PromotableProductType(productType: .simple, isAvailable: true, promoteUrl: nil),
productCategory: nil,
numberOfActiveFilters: 3
favoriteProduct: FavoriteProductsFilter(),
numberOfActiveFilters: 4
)
viewModel.searchTerm = ""
viewModel.updateFilters(filters)
try await Task.sleep(nanoseconds: searchDebounceTime)

// Then
XCTAssertEqual(viewModel.filterButtonTitle, String.localizedStringWithFormat(NSLocalizedString("Filter (%ld)", comment: ""), 3))
XCTAssertEqual(viewModel.filterButtonTitle, String.localizedStringWithFormat(NSLocalizedString("Filter (%ld)", comment: ""), 4))
}

func test_productRows_are_updated_correctly_when_filters_are_applied() async throws {
Expand All @@ -1059,6 +1062,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: nil,
promotableProductType: PromotableProductType(productType: .simple, isAvailable: true, promoteUrl: nil),
productCategory: nil,
favoriteProduct: nil,
numberOfActiveFilters: 1
)
viewModel.updateFilters(filters)
Expand Down Expand Up @@ -1163,6 +1167,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: .draft,
promotableProductType: PromotableProductType(productType: .simple, isAvailable: true, promoteUrl: nil),
productCategory: .init(categoryID: 123, siteID: sampleSiteID, parentID: 1, name: "Test", slug: "test"),
favoriteProduct: nil,
numberOfActiveFilters: 1
)

Expand Down Expand Up @@ -1205,6 +1210,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: .draft,
promotableProductType: PromotableProductType(productType: .simple, isAvailable: true, promoteUrl: nil),
productCategory: .init(categoryID: 123, siteID: sampleSiteID, parentID: 1, name: "Test", slug: "test"),
favoriteProduct: nil,
numberOfActiveFilters: 1
)
stores.whenReceivingAction(ofType: ProductAction.self) { action in
Expand Down Expand Up @@ -1261,6 +1267,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: nil,
promotableProductType: PromotableProductType(productType: .variable, isAvailable: true, promoteUrl: nil),
productCategory: nil,
favoriteProduct: nil,
numberOfActiveFilters: 1
)
viewModel.updateFilters(filters)
Expand Down Expand Up @@ -1466,6 +1473,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
productStatus: nil,
promotableProductType: PromotableProductType(productType: .simple, isAvailable: true, promoteUrl: nil),
productCategory: nil,
favoriteProduct: nil,
numberOfActiveFilters: 1
)
viewModel.updateFilters(filters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class FilterProductListViewModel_numberOfActiveFiltersTests: XCTestCase {
productStatus: .draft,
promotableProductType: nil,
productCategory: nil,
favoriteProduct: nil,
numberOfActiveFilters: 0)
let filterTypeViewModels = createFilterTypeViewModels(filters: filters)
XCTAssertEqual(filterTypeViewModels.numberOfActiveFilters, 1)
Expand All @@ -24,6 +25,7 @@ final class FilterProductListViewModel_numberOfActiveFiltersTests: XCTestCase {
productStatus: .published,
promotableProductType: nil,
productCategory: nil,
favoriteProduct: nil,
numberOfActiveFilters: 0)
let filterTypeViewModels = createFilterTypeViewModels(filters: filters)
XCTAssertEqual(filterTypeViewModels.numberOfActiveFilters, 2)
Expand All @@ -36,6 +38,7 @@ final class FilterProductListViewModel_numberOfActiveFiltersTests: XCTestCase {
isAvailable: true,
promoteUrl: nil),
productCategory: nil,
favoriteProduct: nil,
numberOfActiveFilters: 0)
let filterTypeViewModels = createFilterTypeViewModels(filters: filters)
XCTAssertEqual(filterTypeViewModels.numberOfActiveFilters, 3)
Expand All @@ -49,10 +52,25 @@ final class FilterProductListViewModel_numberOfActiveFiltersTests: XCTestCase {
isAvailable: true,
promoteUrl: nil),
productCategory: filterProductCategory,
favoriteProduct: nil,
numberOfActiveFilters: 0)
let filterTypeViewModels = createFilterTypeViewModels(filters: filters)
XCTAssertEqual(filterTypeViewModels.numberOfActiveFilters, 4)
}

func test_five_active_filters() {
let filterProductCategory = ProductCategory(categoryID: 0, siteID: 0, parentID: 0, name: "", slug: "")
let filters = FilterProductListViewModel.Filters(stockStatus: .inStock,
productStatus: .published,
promotableProductType: PromotableProductType(productType: .variable,
isAvailable: true,
promoteUrl: nil),
productCategory: filterProductCategory,
favoriteProduct: FavoriteProductsFilter(),
numberOfActiveFilters: 0)
let filterTypeViewModels = createFilterTypeViewModels(filters: filters)
XCTAssertEqual(filterTypeViewModels.numberOfActiveFilters, 5)
}
}

private extension FilterProductListViewModel_numberOfActiveFiltersTests {
Expand All @@ -61,7 +79,8 @@ private extension FilterProductListViewModel_numberOfActiveFiltersTests {
FilterProductListViewModel.ProductListFilter.stockStatus.createViewModel(filters: filters),
FilterProductListViewModel.ProductListFilter.productStatus.createViewModel(filters: filters),
FilterProductListViewModel.ProductListFilter.productType(siteID: 123).createViewModel(filters: filters),
FilterProductListViewModel.ProductListFilter.productCategory(siteID: 0).createViewModel(filters: filters)
FilterProductListViewModel.ProductListFilter.productCategory(siteID: 0).createViewModel(filters: filters),
FilterProductListViewModel.ProductListFilter.favoriteProducts.createViewModel(filters: filters)
]
}
}
Loading