Skip to content

Commit 58045f3

Browse files
committed
Add test cases for ProductSearchUICommand.
1 parent 0529bac commit 58045f3

File tree

7 files changed

+150
-15
lines changed

7 files changed

+150
-15
lines changed

Storage/StorageTests/Tools/StorageTypeExtensionsTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,11 +692,13 @@ final class StorageTypeExtensionsTests: XCTestCase {
692692
func test_loadProductSearchResult_by_keyboard() throws {
693693
// Given
694694
let keyword = "Keyword"
695+
let filterKey = "all"
695696
let searchResult = storage.insertNewObject(ofType: ProductSearchResults.self)
696697
searchResult.keyword = keyword
698+
searchResult.filterKey = filterKey
697699

698700
// When
699-
let storedSearchResult = try XCTUnwrap(storage.loadProductSearchResults(keyword: keyword))
701+
let storedSearchResult = try XCTUnwrap(storage.loadProductSearchResults(keyword: keyword, filterKey: filterKey))
700702

701703
// Then
702704
XCTAssertEqual(searchResult, storedSearchResult)

WooCommerce/Classes/ViewRelated/Search/Product/ProductSearchUICommand.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ final class ProductSearchUICommand: SearchUICommand {
1818
private var filter: ProductSearchFilter = .all
1919

2020
private let siteID: Int64
21+
private let stores: StoresManager
2122
private let isSearchProductsBySKUEnabled: Bool
2223

2324
init(siteID: Int64,
25+
stores: StoresManager = ServiceLocator.stores,
2426
isSearchProductsBySKUEnabled: Bool = ServiceLocator.featureFlagService.isFeatureFlagEnabled(.searchProductsBySKU)) {
2527
self.siteID = siteID
28+
self.stores = stores
2629
self.isSearchProductsBySKUEnabled = isSearchProductsBySKUEnabled
2730
}
2831

@@ -90,8 +93,11 @@ final class ProductSearchUICommand: SearchUICommand {
9093
///
9194
func synchronizeModels(siteID: Int64, keyword: String, pageNumber: Int, pageSize: Int, onCompletion: ((Bool) -> Void)?) {
9295
if isSearchProductsBySKUEnabled {
93-
// Returns early if the search query is the same for the given filter to avoid duplicate API requests when switching filter tabs.
94-
if let lastFilterSearchQuery = lastSearchQueryByFilter[filter], lastFilterSearchQuery == keyword {
96+
// Returns early if the search query is the same for the given filter and for the first page to avoid duplicate API requests when
97+
// switching filter tabs.
98+
if let lastFilterSearchQuery = lastSearchQueryByFilter[filter],
99+
lastFilterSearchQuery == keyword,
100+
pageNumber == SyncingCoordinator.Defaults.pageFirstIndex {
95101
onCompletion?(true)
96102
return
97103
}
@@ -110,7 +116,7 @@ final class ProductSearchUICommand: SearchUICommand {
110116
onCompletion?(result.isSuccess)
111117
}
112118

113-
ServiceLocator.stores.dispatch(action)
119+
stores.dispatch(action)
114120

115121
ServiceLocator.analytics.track(.productListSearched)
116122
}
@@ -122,12 +128,12 @@ final class ProductSearchUICommand: SearchUICommand {
122128
}
123129

124130
func searchResultsPredicate(keyword: String) -> NSPredicate? {
125-
guard keyword.isNotEmpty else {
126-
return nil
127-
}
128131
guard isSearchProductsBySKUEnabled else {
129132
return NSPredicate(format: "ANY searchResults.keyword = %@", keyword)
130133
}
134+
guard keyword.isNotEmpty else {
135+
return nil
136+
}
131137
return NSPredicate(format: "SUBQUERY(searchResults, $result, $result.keyword = %@ AND $result.filterKey = %@).@count > 0",
132138
keyword, filter.rawValue)
133139
}

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@
209209
0260B1B12805321B00FCFE8C /* OrderDetailsPaymentAlertsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0260B1B02805321B00FCFE8C /* OrderDetailsPaymentAlertsProtocol.swift */; };
210210
0260F40123224E8100EDA10A /* ProductsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0260F40023224E8100EDA10A /* ProductsViewController.swift */; };
211211
02619858256B53DD00E321E9 /* AggregatedShippingLabelOrderItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02619857256B53DD00E321E9 /* AggregatedShippingLabelOrderItems.swift */; };
212+
0261F5A728D454CF00B7AC72 /* ProductSearchUICommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0261F5A628D454CF00B7AC72 /* ProductSearchUICommandTests.swift */; };
212213
0262DA5323A238460029AF30 /* UnitInputTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0262DA5123A238460029AF30 /* UnitInputTableViewCell.swift */; };
213214
0262DA5423A238460029AF30 /* UnitInputTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0262DA5223A238460029AF30 /* UnitInputTableViewCell.xib */; };
214215
0262DA5823A23AC80029AF30 /* ProductShippingSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0262DA5623A23AC80029AF30 /* ProductShippingSettingsViewController.swift */; };
@@ -2114,6 +2115,7 @@
21142115
0260B1B02805321B00FCFE8C /* OrderDetailsPaymentAlertsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsPaymentAlertsProtocol.swift; sourceTree = "<group>"; };
21152116
0260F40023224E8100EDA10A /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = "<group>"; };
21162117
02619857256B53DD00E321E9 /* AggregatedShippingLabelOrderItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregatedShippingLabelOrderItems.swift; sourceTree = "<group>"; };
2118+
0261F5A628D454CF00B7AC72 /* ProductSearchUICommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductSearchUICommandTests.swift; sourceTree = "<group>"; };
21172119
0262DA5123A238460029AF30 /* UnitInputTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitInputTableViewCell.swift; sourceTree = "<group>"; };
21182120
0262DA5223A238460029AF30 /* UnitInputTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UnitInputTableViewCell.xib; sourceTree = "<group>"; };
21192121
0262DA5623A23AC80029AF30 /* ProductShippingSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingSettingsViewController.swift; sourceTree = "<group>"; };
@@ -3930,6 +3932,7 @@
39303932
isa = PBXGroup;
39313933
children = (
39323934
020C908324C84652001E2BEB /* ProductListMultiSelectorSearchUICommandTests.swift */,
3935+
0261F5A628D454CF00B7AC72 /* ProductSearchUICommandTests.swift */,
39333936
);
39343937
path = Product;
39353938
sourceTree = "<group>";
@@ -10424,6 +10427,7 @@
1042410427
0279F0E2252DC4BF0098D7DE /* ProductLoaderViewControllerModelTests.swift in Sources */,
1042510428
093B265727DF05270026F92D /* UnitInputViewModelTests.swift in Sources */,
1042610429
4552085B25829091001CF873 /* AddAttributeViewModelTests.swift in Sources */,
10430+
0261F5A728D454CF00B7AC72 /* ProductSearchUICommandTests.swift in Sources */,
1042710431
098FFA1727AD7F5D002EBEE4 /* OrderStatusListDataSourceTests.swift in Sources */,
1042810432
DE19BB1D26C6911900AB70D9 /* ShippingLabelCustomsFormListViewModelTests.swift in Sources */,
1042910433
D449C52C26E02F2F00D75B02 /* WhatsNewFactoryTests.swift in Sources */,

WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Creation/ProductSelectorViewModelTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
152152
let expectation = expectation(description: "Completed product search")
153153
stores.whenReceivingAction(ofType: ProductAction.self) { action in
154154
switch action {
155-
case let .searchProducts(_, _, _, _, _, _, _, _, _, onCompletion):
155+
case let .searchProducts(_, _, _, _, _, _, _, _, _, _, onCompletion):
156156
let product = Product.fake().copy(siteID: self.sampleSiteID, purchasable: true)
157157
self.insert(product, withSearchTerm: "shirt")
158158
onCompletion(.success(()))
@@ -180,7 +180,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
180180
let expectation = expectation(description: "Completed product search")
181181
stores.whenReceivingAction(ofType: ProductAction.self) { action in
182182
switch action {
183-
case let .searchProducts(_, _, _, _, _, _, _, _, _, onCompletion):
183+
case let .searchProducts(_, _, _, _, _, _, _, _, _, _, onCompletion):
184184
self.insert(shirt, withSearchTerm: "shirt")
185185
onCompletion(.success(()))
186186
expectation.fulfill()
@@ -226,7 +226,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
226226
insert([product.copy(name: "T-shirt"), product.copy(name: "Hoodie")])
227227
stores.whenReceivingAction(ofType: ProductAction.self) { action in
228228
switch action {
229-
case let .searchProducts(_, _, _, _, _, _, _, _, _, onCompletion):
229+
case let .searchProducts(_, _, _, _, _, _, _, _, _, _, onCompletion):
230230
self.insert(product.copy(name: "T-shirt"), withSearchTerm: "shirt")
231231
onCompletion(.success(()))
232232
case let .synchronizeProducts(_, _, _, _, _, _, _, _, _, _, onCompletion):
@@ -273,7 +273,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
273273
let notice: Notice? = waitFor { promise in
274274
self.stores.whenReceivingAction(ofType: ProductAction.self) { action in
275275
switch action {
276-
case let .searchProducts(_, _, _, _, _, _, _, _, _, onCompletion):
276+
case let .searchProducts(_, _, _, _, _, _, _, _, _, _, onCompletion):
277277
onCompletion(.failure(NSError(domain: "Error", code: 0)))
278278
promise(viewModel.notice)
279279
default:
@@ -613,7 +613,7 @@ final class ProductSelectorViewModelTests: XCTestCase {
613613
)
614614
stores.whenReceivingAction(ofType: ProductAction.self) { action in
615615
switch action {
616-
case let .searchProducts(_, _, _, _, stockStatus, productStatus, productType, category, _, onCompletion):
616+
case let .searchProducts(_, _, _, _, _, stockStatus, productStatus, productType, category, _, onCompletion):
617617
filteredStockStatus = stockStatus
618618
filteredProductType = productType
619619
filteredProductStatus = productStatus
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import XCTest
2+
3+
@testable import WooCommerce
4+
import Yosemite
5+
6+
final class ProductSearchUICommandTests: XCTestCase {
7+
private let sampleSiteID: Int64 = 134
8+
9+
// MARK: - `createHeaderView`
10+
11+
func test_createHeaderView_returns_a_non_nil_view() {
12+
// When
13+
let command = ProductSearchUICommand(siteID: sampleSiteID, isSearchProductsBySKUEnabled: true)
14+
15+
// Then
16+
XCTAssertNotNil(command.createHeaderView())
17+
}
18+
19+
func test_createHeaderView_returns_nil_when_searchProductsBySKU_is_disabled() {
20+
// When
21+
let command = ProductSearchUICommand(siteID: sampleSiteID, isSearchProductsBySKUEnabled: false)
22+
23+
// Then
24+
XCTAssertNil(command.createHeaderView())
25+
}
26+
27+
// MARK: - `searchResultsPredicate`
28+
29+
func test_searchResultsPredicate_is_nil_when_keyword_is_empty() {
30+
// Given
31+
let command = ProductSearchUICommand(siteID: sampleSiteID, isSearchProductsBySKUEnabled: true)
32+
33+
// When
34+
let predicate = command.searchResultsPredicate(keyword: "")
35+
36+
// Then
37+
XCTAssertNil(predicate)
38+
}
39+
40+
func test_searchResultsPredicate_includes_keyword_and_filter_when_keyword_is_not_empty() {
41+
// Given
42+
let command = ProductSearchUICommand(siteID: sampleSiteID, isSearchProductsBySKUEnabled: true)
43+
44+
// When
45+
let predicate = command.searchResultsPredicate(keyword: "🍁")
46+
47+
// Then
48+
XCTAssertEqual(predicate?.predicateFormat,
49+
"SUBQUERY(searchResults, $result, $result.keyword == \"🍁\" AND $result.filterKey == \"all\").@count > 0")
50+
}
51+
52+
func test_searchResultsPredicate_matches_any_products_with_search_results_when_keyword_is_empty_and_searchProductsBySKU_is_disabled() {
53+
// Given
54+
let command = ProductSearchUICommand(siteID: sampleSiteID, isSearchProductsBySKUEnabled: false)
55+
56+
// When
57+
let predicate = command.searchResultsPredicate(keyword: "")
58+
59+
// Then
60+
XCTAssertEqual(predicate?.predicateFormat, "ANY searchResults.keyword == \"\"")
61+
}
62+
63+
func test_searchResultsPredicate_includes_keyword_and_filter_when_keyword_is_not_empty_and_searchProductsBySKU_is_disabled() {
64+
// Given
65+
let command = ProductSearchUICommand(siteID: sampleSiteID, isSearchProductsBySKUEnabled: false)
66+
67+
// When
68+
let predicate = command.searchResultsPredicate(keyword: "Pineapple")
69+
70+
// Then
71+
XCTAssertEqual(predicate?.predicateFormat, "ANY searchResults.keyword == \"Pineapple\"")
72+
}
73+
74+
// MARK: - `synchronizeModels`
75+
76+
func test_synchronizeModels_does_not_dispatch_search_action_when_the_last_keyword_is_the_same_for_the_first_page() {
77+
// Given
78+
let stores = MockStoresManager(sessionManager: .testingInstance)
79+
let command = ProductSearchUICommand(siteID: sampleSiteID, stores: stores, isSearchProductsBySKUEnabled: true)
80+
81+
var invocationCount = 0
82+
stores.whenReceivingAction(ofType: ProductAction.self) { action in
83+
guard case let .searchProducts(_, _, _, _, _, _, _, _, _, _, onCompletion) = action else {
84+
return XCTFail("Unexpected action: \(action)")
85+
}
86+
invocationCount += 1
87+
onCompletion(.success(()))
88+
}
89+
90+
// When
91+
// Syncing models for the first time for the first page.
92+
waitFor { promise in
93+
command.synchronizeModels(siteID: self.sampleSiteID, keyword: "Melon", pageNumber: 1, pageSize: 10) { success in
94+
promise(())
95+
}
96+
}
97+
XCTAssertEqual(invocationCount, 1)
98+
99+
// Syncing models for the same keyword for the second time for the first page.
100+
waitFor { promise in
101+
command.synchronizeModels(siteID: self.sampleSiteID, keyword: "Melon", pageNumber: 1, pageSize: 10) { success in
102+
promise(())
103+
}
104+
}
105+
XCTAssertEqual(invocationCount, 1)
106+
107+
// Syncing models for the same keyword for the third time, but for the second page.
108+
waitFor { promise in
109+
command.synchronizeModels(siteID: self.sampleSiteID, keyword: "Melon", pageNumber: 2, pageSize: 10) { success in
110+
promise(())
111+
}
112+
}
113+
XCTAssertEqual(invocationCount, 2)
114+
}
115+
}

Yosemite/YosemiteTests/Mocks/Networking/Remote/MockProductsRemote.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ extension MockProductsRemote: ProductsRemoteProtocol {
167167
searchProductWithProductCategory = productCategory
168168
}
169169

170+
func searchProductsBySKU(for siteID: Int64,
171+
keyword: String,
172+
pageNumber: Int,
173+
pageSize: Int,
174+
completion: @escaping (Result<[Product], Error>) -> Void) {
175+
// no-op
176+
}
177+
170178
func searchSku(for siteID: Int64, sku: String, completion: @escaping (Result<String, Error>) -> Void) {
171179
// no-op
172180
}

Yosemite/YosemiteTests/Stores/ProductStoreTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ final class ProductStoreTests: XCTestCase {
972972

973973
/// Verifies that `ProductAction.searchProducts` effectively upserts the `ProductSearchResults` entity.
974974
///
975-
func test_searchProducts_effectively_persists_search_eesults_entity() throws {
975+
func test_searchProducts_effectively_persists_search_results_entity() throws {
976976
// Given
977977
let store = ProductStore(dispatcher: dispatcher, storageManager: storageManager, network: network)
978978
network.simulateResponse(requestUrlSuffix: "products", filename: "products-load-all")
@@ -993,12 +993,12 @@ final class ProductStoreTests: XCTestCase {
993993

994994
// Then
995995
XCTAssertTrue(result.isSuccess)
996-
let searchResults = try XCTUnwrap(viewStorage.loadProductSearchResults(keyword: keyword))
996+
let searchResults = try XCTUnwrap(viewStorage.loadProductSearchResults(keyword: keyword, filterKey: ProductSearchFilter.all.rawValue))
997997
XCTAssertEqual(searchResults.keyword, keyword)
998998
XCTAssertEqual(searchResults.products?.count, viewStorage.countObjects(ofType: Storage.Product.self))
999999

10001000
let anotherKeyword = "hello"
1001-
let searchResultsWithAnotherKeyword = viewStorage.loadProductSearchResults(keyword: anotherKeyword)
1001+
let searchResultsWithAnotherKeyword = viewStorage.loadProductSearchResults(keyword: anotherKeyword, filterKey: ProductSearchFilter.all.rawValue)
10021002
XCTAssertNil(searchResultsWithAnotherKeyword)
10031003
}
10041004

0 commit comments

Comments
 (0)