Skip to content

Commit 2eba65b

Browse files
committed
Merge branch 'trunk' into issue/8729-update-ipp-feedback-status
# Conflicts: # WooCommerce/Classes/ViewRelated/Orders/OrderListViewController.swift # WooCommerce/Classes/ViewRelated/Orders/OrderListViewModel.swift # WooCommerce/WooCommerceTests/ViewRelated/Orders/OrderListViewModelTests.swift
2 parents 9d46054 + 19b2742 commit 2eba65b

File tree

45 files changed

+518
-145
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+518
-145
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,8 @@
744744
DEC2961C26BBE764005A056B /* ShippingLabelCustomsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC2961B26BBE764005A056B /* ShippingLabelCustomsForm.swift */; };
745745
DEC2B08D297AA048003923FB /* reviews-all-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEC2B08C297AA048003923FB /* reviews-all-without-data.json */; };
746746
DEC2B08F297AA123003923FB /* reviews-single-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEC2B08E297AA123003923FB /* reviews-single-without-data.json */; };
747+
DEC2B091297AA5A6003923FB /* categories-all-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEC2B090297AA5A6003923FB /* categories-all-without-data.json */; };
748+
DEC2B093297AA60D003923FB /* category-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEC2B092297AA60D003923FB /* category-without-data.json */; };
747749
DEC51A95274CDA52009F3DF4 /* SitePluginMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51A94274CDA52009F3DF4 /* SitePluginMapper.swift */; };
748750
DEC51A97274DD962009F3DF4 /* plugin.json in Resources */ = {isa = PBXBuildFile; fileRef = DEC51A96274DD962009F3DF4 /* plugin.json */; };
749751
DEC51A99274DDDC9009F3DF4 /* SitePluginMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51A98274DDDC9009F3DF4 /* SitePluginMapperTests.swift */; };
@@ -1595,6 +1597,8 @@
15951597
DEC2961B26BBE764005A056B /* ShippingLabelCustomsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelCustomsForm.swift; sourceTree = "<group>"; };
15961598
DEC2B08C297AA048003923FB /* reviews-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reviews-all-without-data.json"; sourceTree = "<group>"; };
15971599
DEC2B08E297AA123003923FB /* reviews-single-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reviews-single-without-data.json"; sourceTree = "<group>"; };
1600+
DEC2B090297AA5A6003923FB /* categories-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "categories-all-without-data.json"; sourceTree = "<group>"; };
1601+
DEC2B092297AA60D003923FB /* category-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "category-without-data.json"; sourceTree = "<group>"; };
15981602
DEC51A94274CDA52009F3DF4 /* SitePluginMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePluginMapper.swift; sourceTree = "<group>"; };
15991603
DEC51A96274DD962009F3DF4 /* plugin.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = plugin.json; sourceTree = "<group>"; };
16001604
DEC51A98274DDDC9009F3DF4 /* SitePluginMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePluginMapperTests.swift; sourceTree = "<group>"; };
@@ -2211,6 +2215,8 @@
22112215
B559EBA820A0B5B100836CD4 /* Responses */ = {
22122216
isa = PBXGroup;
22132217
children = (
2218+
DEC2B092297AA60D003923FB /* category-without-data.json */,
2219+
DEC2B090297AA5A6003923FB /* categories-all-without-data.json */,
22142220
DEC2B08E297AA123003923FB /* reviews-single-without-data.json */,
22152221
DEC2B08C297AA048003923FB /* reviews-all-without-data.json */,
22162222
DE66C5682977D62700DAA978 /* plugins-without-data.json */,
@@ -3083,6 +3089,7 @@
30833089
26BD9FCD2965EC3C004E0D15 /* product-variations-bulk-create.json in Resources */,
30843090
028CB718290223CB00331C09 /* account-username-suggestions.json in Resources */,
30853091
0282DD91233A120A006A5FDB /* products-search-photo.json in Resources */,
3092+
DEC2B093297AA60D003923FB /* category-without-data.json in Resources */,
30863093
45152825257A8B740076B03C /* product-attribute-update.json in Resources */,
30873094
68C87B342862D40E00A99054 /* setting-all-except-countries.json in Resources */,
30883095
3158FE6026129ADD00E566B9 /* wcpay-account-none.json in Resources */,
@@ -3163,6 +3170,7 @@
31633170
3105471C262E2F8000C5C02B /* wcpay-payment-intent-requires-action.json in Resources */,
31643171
3158FE6826129CE200E566B9 /* wcpay-account-rejected-fraud.json in Resources */,
31653172
02AAD53F250092A400BA1E26 /* product-add-or-delete.json in Resources */,
3173+
DEC2B091297AA5A6003923FB /* categories-all-without-data.json in Resources */,
31663174
D823D90B22376EFE00C90817 /* shipment_tracking_delete.json in Resources */,
31673175
31A451D727863A2E00FE81AA /* stripe-account-dev-test.json in Resources */,
31683176
74C947832193A6C70024CB60 /* comment-moderate-trash.json in Resources */,

Networking/Networking/Extensions/KeyedDecodingContainer+Woo.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ enum AlternativeDecodingType<T> {
55
case decimal(transform: (Decimal) -> T)
66
case string(transform: (String) -> T)
77
case bool(transform: (Bool) -> T)
8+
case integer(transform: (Int) -> T)
89
}
910

1011
// MARK: - KeyedDecodingContainer: Bulletproof JSON Decoding.
@@ -55,6 +56,10 @@ extension KeyedDecodingContainer {
5556
if let result = failsafeDecodeIfPresent(booleanForKey: key) {
5657
return transform(result)
5758
}
59+
case .integer(transform: let transform):
60+
if let result = failsafeDecodeIfPresent(integerForKey: key) {
61+
return transform(result)
62+
}
5863
}
5964
}
6065
return nil

Networking/Networking/Mapper/ProductCategoryListMapper.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ struct ProductCategoryListMapper: Mapper {
1717
.siteID: siteID
1818
]
1919

20-
return try decoder.decode(ProductCategoryListEnvelope.self, from: response).productCategories
20+
do {
21+
return try decoder.decode(ProductCategoryListEnvelope.self, from: response).productCategories
22+
} catch {
23+
return try decoder.decode([ProductCategory].self, from: response)
24+
}
2125
}
2226
}
2327

Networking/Networking/Mapper/ProductCategoryMapper.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ struct ProductCategoryMapper: Mapper {
2020
.siteID: siteID
2121
]
2222

23-
return try decoder.decode(ProductCategoryEnvelope.self, from: response).productCategory
23+
do {
24+
return try decoder.decode(ProductCategoryEnvelope.self, from: response).productCategory
25+
} catch {
26+
return try decoder.decode(ProductCategory.self, from: response)
27+
}
2428
}
2529
}
2630

Networking/Networking/Model/Product/Product.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,12 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
319319
.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })])
320320
?? ""
321321

322-
let purchasable = try container.decode(Bool.self, forKey: .purchasable)
322+
// Even though WooCommerce Core returns Bool values,
323+
// some plugins alter the field value from Bool to Int (1 or 0)
324+
let purchasable = container.failsafeDecodeIfPresent(targetType: Bool.self, forKey: .purchasable,
325+
alternativeTypes: [
326+
.integer(transform: { ($0 as NSNumber).boolValue })])
327+
?? true
323328
let totalSales = container.failsafeDecodeIfPresent(Int.self, forKey: .totalSales) ?? 0
324329
let virtual = try container.decode(Bool.self, forKey: .virtual)
325330

Networking/Networking/Model/Product/ProductVariation.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,12 @@ public struct ProductVariation: Codable, GeneratedCopiable, Equatable, Generated
194194
.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })])
195195
?? ""
196196

197-
let purchasable = try container.decode(Bool.self, forKey: .purchasable)
197+
// Even though WooCommerce Core returns Bool values,
198+
// some plugins alter the field value from Bool to Int (1 or 0)
199+
let purchasable = container.failsafeDecodeIfPresent(targetType: Bool.self, forKey: .purchasable,
200+
alternativeTypes: [
201+
.integer(transform: { ($0 as NSNumber).boolValue })])
202+
?? true
198203
let virtual = try container.decode(Bool.self, forKey: .virtual)
199204
let downloadable = try container.decode(Bool.self, forKey: .downloadable)
200205
let downloads = try container.decode([ProductDownload].self, forKey: .downloads)

Networking/Networking/Remote/ProductCategoriesRemote.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ public final class ProductCategoriesRemote: Remote {
2525
]
2626

2727
let path = Path.categories
28-
let request = JetpackRequest(wooApiVersion: .mark3, method: .get, siteID: siteID, path: path, parameters: parameters)
28+
let request = JetpackRequest(wooApiVersion: .mark3,
29+
method: .get,
30+
siteID: siteID,
31+
path: path,
32+
parameters: parameters,
33+
availableAsRESTRequest: true)
2934
let mapper = ProductCategoryListMapper(siteID: siteID)
3035

3136
enqueue(request, mapper: mapper, completion: completion)
@@ -42,7 +47,11 @@ public final class ProductCategoriesRemote: Remote {
4247
siteID: Int64,
4348
completion: @escaping (Result<ProductCategory, Error>) -> Void) -> Void {
4449
let path = Path.categories + "/\(categoryID)"
45-
let request = JetpackRequest(wooApiVersion: .mark3, method: .get, siteID: siteID, path: path)
50+
let request = JetpackRequest(wooApiVersion: .mark3,
51+
method: .get,
52+
siteID: siteID,
53+
path: path,
54+
availableAsRESTRequest: true)
4655
let mapper = ProductCategoryMapper(siteID: siteID)
4756

4857
enqueue(request, mapper: mapper, completion: completion)
@@ -69,7 +78,12 @@ public final class ProductCategoriesRemote: Remote {
6978
}
7079

7180
let path = Path.categories
72-
let request = JetpackRequest(wooApiVersion: .mark3, method: .post, siteID: siteID, path: path, parameters: parameters)
81+
let request = JetpackRequest(wooApiVersion: .mark3,
82+
method: .post,
83+
siteID: siteID,
84+
path: path,
85+
parameters: parameters,
86+
availableAsRESTRequest: true)
7387
let mapper = ProductCategoryMapper(siteID: siteID)
7488

7589
enqueue(request, mapper: mapper, completion: completion)

Networking/NetworkingTests/Mapper/ProductCategoryMapperTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ final class ProductCategoryMapperTests: XCTestCase {
2222
XCTAssertEqual(productCategory.slug, "Shirt")
2323
}
2424

25+
/// Verifies that all of the ProductCategory Fields are parsed correctly.
26+
///
27+
func test_ProductCategory_fields_are_properly_parsed_when_response_has_no_data_envelope() throws {
28+
let productCategory = try XCTUnwrap(mapProductCategoryResponseWithoutDataEnvelope())
29+
30+
XCTAssertEqual(productCategory.categoryID, 104)
31+
XCTAssertEqual(productCategory.parentID, 0)
32+
XCTAssertEqual(productCategory.siteID, dummySiteID)
33+
XCTAssertEqual(productCategory.name, "Dress")
34+
XCTAssertEqual(productCategory.slug, "Shirt")
35+
}
2536
}
2637

2738

@@ -44,4 +55,10 @@ private extension ProductCategoryMapperTests {
4455
func mapProductCategoryResponse() throws -> ProductCategory? {
4556
return try mapProductCategory(from: "category")
4657
}
58+
59+
/// Returns the ProductCategoryMapper output upon receiving `category-without-data`
60+
///
61+
func mapProductCategoryResponseWithoutDataEnvelope() throws -> ProductCategory? {
62+
return try mapProductCategory(from: "category-without-data")
63+
}
4764
}

Networking/NetworkingTests/Mapper/ProductCategoyListMapperTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ final class ProductCategoryListMapperTests: XCTestCase {
1919
XCTAssertEqual(secondProductCategory.name, "American")
2020
XCTAssertEqual(secondProductCategory.slug, "american")
2121
}
22+
23+
/// Verifies that all of the ProductCategory Fields are parsed correctly.
24+
///
25+
func test_ProductCategory_fields_are_properly_parsed_when_response_has_no_data_envelope() throws {
26+
let productCategories = try mapLoadAllProductCategoriesResponseWithoutDataEnvelope()
27+
XCTAssertEqual(productCategories.count, 2)
28+
29+
let secondProductCategory = productCategories[1]
30+
XCTAssertEqual(secondProductCategory.categoryID, 20)
31+
XCTAssertEqual(secondProductCategory.parentID, 17)
32+
XCTAssertEqual(secondProductCategory.siteID, dummySiteID)
33+
XCTAssertEqual(secondProductCategory.name, "American")
34+
XCTAssertEqual(secondProductCategory.slug, "american")
35+
}
2236
}
2337

2438

@@ -41,4 +55,10 @@ private extension ProductCategoryListMapperTests {
4155
func mapLoadAllProductCategoriesResponse() throws -> [ProductCategory] {
4256
return try mapProductCategories(from: "categories-all")
4357
}
58+
59+
/// Returns the ProductListMapper output upon receiving `categories-all-without-data`
60+
///
61+
func mapLoadAllProductCategoriesResponseWithoutDataEnvelope() throws -> [ProductCategory] {
62+
return try mapProductCategories(from: "categories-all-without-data")
63+
}
4464
}

Networking/NetworkingTests/Mapper/ProductMapperTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ final class ProductMapperTests: XCTestCase {
105105
}
106106

107107
/// Verifies that the fields of the Product with alternative types are parsed correctly when they have different types than in the struct.
108-
/// Currently, `price`, `regularPrice`, `salePrice`, `manageStock`, and `soldIndividually` allow alternative types.
108+
/// Currently, `price`, `regularPrice`, `salePrice`, `manageStock`, `soldIndividually`, and `purchasable` allow alternative types.
109109
///
110110
func test_that_product_alternative_types_are_properly_parsed() throws {
111111
let product = try XCTUnwrap(mapLoadProductResponseWithAlternativeTypes())
@@ -115,6 +115,7 @@ final class ProductMapperTests: XCTestCase {
115115
XCTAssertEqual(product.salePrice, "26.73")
116116
XCTAssertTrue(product.manageStock)
117117
XCTAssertFalse(product.soldIndividually)
118+
XCTAssertTrue(product.purchasable)
118119
}
119120

120121
/// Verifies that the `salePrice` field of the Product are parsed correctly when the product is on sale, and the sale price is an empty string

0 commit comments

Comments
 (0)