Skip to content

Commit dd2f224

Browse files
authored
Merge pull request #8610 from woocommerce/issue/8536-error-handling
Variations: Error handling
2 parents e646122 + 4bcf4f2 commit dd2f224

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

WooCommerce/Classes/ViewRelated/Products/Variations/ProductVariationsViewController.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ final class ProductVariationsViewController: UIViewController, GhostableViewCont
6464
///
6565
private let syncingCoordinator = SyncingCoordinator()
6666

67+
6768
private lazy var stateCoordinator: PaginatedListViewControllerStateCoordinator = {
6869
let stateCoordinator = PaginatedListViewControllerStateCoordinator(onLeavingState: { [weak self] state in
6970
self?.didLeave(state: state)
@@ -668,10 +669,18 @@ private extension ProductVariationsViewController {
668669
}
669670

670671
/// Dismiss any `InProgressViewController` being presented.
672+
/// By default retires the dismissal one time to overcome UIKit presentation delays.
671673
///
672-
private func dismissBlockingIndicator() {
674+
private func dismissBlockingIndicator(retry: Bool = true) {
673675
if let inProgressViewController = self.presentedViewController as? InProgressViewController {
674676
inProgressViewController.dismiss(animated: true)
677+
} else {
678+
// When this method is invoked right after `InProgressViewController` is presented, chances are that it won't exists in the view controller
679+
// hierarchy yet.
680+
// Here we are retrying it with a small delay to give UIKit APIs time to finish its presentation.
681+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
682+
self.dismissBlockingIndicator(retry: false)
683+
}
675684
}
676685
}
677686

WooCommerce/Classes/ViewRelated/Products/Variations/ProductVariationsViewModel.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ final class ProductVariationsViewModel {
7070
}
7171
}))
7272

73-
case .failure:
74-
// TODO: Log and inform error
75-
break
73+
case .failure(let error):
74+
onStateChanged(.error(.unableToFetchVariations))
75+
DDLogError("⛔️ Failed to create variations: \(error)")
7676
}
7777
}
7878
}
@@ -104,9 +104,9 @@ final class ProductVariationsViewModel {
104104
switch result {
105105
case .success:
106106
onCompletion(.success(()))
107-
case .failure:
108-
// TODO: Log Error
109-
break
107+
case .failure(let error):
108+
onCompletion(.failure(.unableToCreateVariations))
109+
DDLogError("⛔️ Failed to create variations: \(error)")
110110
}
111111
})
112112
stores.dispatch(action)
@@ -161,17 +161,27 @@ extension ProductVariationsViewModel {
161161
/// Type to represent known generation errors
162162
///
163163
enum GenerationError: LocalizedError, Equatable {
164+
case unableToFetchVariations
165+
case unableToCreateVariations
164166
case tooManyVariations(variationCount: Int)
165167

166168
var errorTitle: String {
167169
switch self {
170+
case .unableToFetchVariations:
171+
return NSLocalizedString("Unable to fetch variations", comment: "Error title for when we can't fetch existing variations.")
172+
case .unableToCreateVariations:
173+
return NSLocalizedString("Unable to create variations", comment: "Error title for when we can't create variations remotely.")
168174
case .tooManyVariations:
169-
return NSLocalizedString("Generation limit exceeded", comment: "Error title for for when there are too many variations to generate.")
175+
return NSLocalizedString("Generation limit exceeded", comment: "Error title for when there are too many variations to generate.")
170176
}
171177
}
172178

173179
var errorDescription: String? {
174180
switch self {
181+
case .unableToFetchVariations:
182+
return NSLocalizedString("Something went wrong, please try again later.", comment: "Error message for when we can't fetch existing variations.")
183+
case .unableToCreateVariations:
184+
return NSLocalizedString("Something went wrong, please try again later.", comment: "Error message for when we can't create variations remotely")
175185
case .tooManyVariations(let variationCount):
176186
let format = NSLocalizedString(
177187
"Currently creation is supported for 100 variations maximum. Generating variations for this product would create %d variations.",

WooCommerce/WooCommerceTests/ViewRelated/Products/Variations/ProductVariationsViewModelTests.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,73 @@ final class ProductVariationsViewModelTests: XCTestCase {
252252
// Then
253253
XCTAssertTrue(canceled)
254254
}
255+
256+
func test_failing_to_fetch_variations_sends_error_state() {
257+
// Given
258+
let product = Product.fake().copy(attributes: [
259+
ProductAttribute.fake().copy(attributeID: 1, name: "Size", options: ["XS", "S", "M", "L", "XL"]),
260+
ProductAttribute.fake().copy(attributeID: 2, name: "Color", options: ["Red", "Green", "Blue", "White", "Black"]),
261+
])
262+
263+
let stores = MockStoresManager(sessionManager: SessionManager.makeForTesting())
264+
stores.whenReceivingAction(ofType: ProductVariationAction.self) { action in
265+
switch action {
266+
case .synchronizeAllProductVariations(_, _, let onCompletion):
267+
onCompletion(.failure(NSError(domain: "", code: 0)))
268+
default:
269+
break
270+
}
271+
}
272+
273+
let viewModel = ProductVariationsViewModel(stores: stores, formType: .edit)
274+
275+
// When
276+
let error = waitFor { promise in
277+
viewModel.generateAllVariations(for: product) { state in
278+
if case .error(let error) = state {
279+
promise(error)
280+
}
281+
}
282+
}
283+
284+
// Then
285+
XCTAssertEqual(error, .unableToFetchVariations)
286+
}
287+
288+
func test_failing_to_create_variations_sends_error_state() {
289+
// Given
290+
let product = Product.fake().copy(attributes: [
291+
ProductAttribute.fake().copy(attributeID: 1, name: "Size", options: ["XS", "S", "M", "L", "XL"]),
292+
ProductAttribute.fake().copy(attributeID: 2, name: "Color", options: ["Red", "Green", "Blue", "White", "Black"]),
293+
])
294+
295+
let stores = MockStoresManager(sessionManager: SessionManager.makeForTesting())
296+
stores.whenReceivingAction(ofType: ProductVariationAction.self) { action in
297+
switch action {
298+
case .synchronizeAllProductVariations(_, _, let onCompletion):
299+
onCompletion(.success([]))
300+
case .createProductVariations(_, _, _, let onCompletion):
301+
onCompletion(.failure(NSError(domain: "", code: 0)))
302+
default:
303+
break
304+
}
305+
}
306+
307+
let viewModel = ProductVariationsViewModel(stores: stores, formType: .edit)
308+
309+
// When
310+
let error = waitFor { promise in
311+
viewModel.generateAllVariations(for: product) { state in
312+
if case let .confirmation(_, onCompletion) = state {
313+
onCompletion(true)
314+
}
315+
if case .error(let error) = state {
316+
promise(error)
317+
}
318+
}
319+
}
320+
321+
// Then
322+
XCTAssertEqual(error, .unableToCreateVariations)
323+
}
255324
}

0 commit comments

Comments
 (0)