Skip to content

Commit 0d8ba35

Browse files
committed
Call remote action to reply to product review
1 parent f241ec2 commit 0d8ba35

File tree

3 files changed

+142
-6
lines changed

3 files changed

+142
-6
lines changed

WooCommerce/Classes/ViewRelated/Reviews/ReviewDetailsViewController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,8 @@ private extension ReviewDetailsViewController {
389389
return
390390
}
391391

392-
let reviewReplyViewController = ReviewReplyHostingController(viewModel: ReviewReplyViewModel())
392+
let reviewReplyViewModel = ReviewReplyViewModel(siteID: self.siteID, reviewID: self.productReview.reviewID)
393+
let reviewReplyViewController = ReviewReplyHostingController(viewModel: reviewReplyViewModel)
393394
self.present(reviewReplyViewController, animated: true)
394395
}
395396
}

WooCommerce/Classes/ViewRelated/Reviews/ReviewReplyViewModel.swift

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import Yosemite
66
///
77
final class ReviewReplyViewModel: ObservableObject {
88

9+
private let siteID: Int64
10+
11+
/// ID for the product review being replied to.
12+
///
13+
private let reviewID: Int64
14+
915
/// New reply to send
1016
///
1117
@Published var newReply: String = ""
@@ -18,7 +24,14 @@ final class ReviewReplyViewModel: ObservableObject {
1824
///
1925
private let performingNetworkRequest: CurrentValueSubject<Bool, Never> = .init(false)
2026

21-
init() {
27+
/// Action dispatcher
28+
///
29+
private let stores: StoresManager
30+
31+
init(siteID: Int64, reviewID: Int64, stores: StoresManager = ServiceLocator.stores) {
32+
self.siteID = siteID
33+
self.reviewID = reviewID
34+
self.stores = stores
2235
bindNavigationTrailingItemPublisher()
2336
}
2437

@@ -27,8 +40,27 @@ final class ReviewReplyViewModel: ObservableObject {
2740
/// Use this method to send the reply and invoke a completion block when finished
2841
///
2942
func sendReply(onCompletion: @escaping (Bool) -> Void) {
30-
// TODO: Call CommentAction.replyToComment to send the reply to remote
31-
// Set `performingNetworkRequest` to true while the request is being performed
43+
guard newReply.isNotEmpty else {
44+
return
45+
}
46+
47+
let action = CommentAction.replyToComment(siteID: siteID, commentID: reviewID, content: newReply) { [weak self] result in
48+
guard let self = self else { return }
49+
50+
self.performingNetworkRequest.send(false)
51+
52+
switch result {
53+
case .success:
54+
// TODO: Show a success notice, e.g. "Reply sent!"
55+
onCompletion(true)
56+
case .failure(let error):
57+
// TODO: Show an error notice, e.g. "There was an error sending the reply"
58+
onCompletion(false)
59+
}
60+
}
61+
62+
performingNetworkRequest.send(true)
63+
stores.dispatch(action)
3264
}
3365
}
3466

WooCommerce/WooCommerceTests/ViewRelated/Reviews/ReviewReplyViewModelTests.swift

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import XCTest
22
@testable import WooCommerce
3+
@testable import Yosemite
34

45
class ReviewReplyViewModelTests: XCTestCase {
56

7+
private let sampleSiteID: Int64 = 12345
8+
9+
private let sampleReviewID: Int64 = 7
10+
611
func test_send_button_is_disabled_when_reply_content_is_empty() {
712
// Given
8-
let viewModel = ReviewReplyViewModel()
13+
let viewModel = ReviewReplyViewModel(siteID: sampleSiteID, reviewID: sampleReviewID)
914

1015
// When
1116
let navigationItem = viewModel.navigationTrailingItem
@@ -16,12 +21,110 @@ class ReviewReplyViewModelTests: XCTestCase {
1621

1722
func test_send_button_is_enabled_when_reply_is_entered() {
1823
// Given
19-
let viewModel = ReviewReplyViewModel()
24+
let viewModel = ReviewReplyViewModel(siteID: sampleSiteID, reviewID: sampleReviewID)
2025

2126
// When
2227
viewModel.newReply = "New reply"
2328

2429
// Then
2530
assertEqual(viewModel.navigationTrailingItem, .send(enabled: true))
2631
}
32+
33+
func test_loading_indicator_enabled_during_network_request() {
34+
// Given
35+
let stores = MockStoresManager(sessionManager: .testingInstance)
36+
let viewModel = ReviewReplyViewModel(siteID: sampleSiteID, reviewID: sampleReviewID, stores: stores)
37+
viewModel.newReply = "New reply"
38+
39+
// When
40+
let navigationItem: ReviewReplyNavigationItem = waitFor { promise in
41+
stores.whenReceivingAction(ofType: CommentAction.self) { action in
42+
switch action {
43+
case .replyToComment:
44+
promise(viewModel.navigationTrailingItem)
45+
default:
46+
XCTFail("Received unsupported action: \(action)")
47+
}
48+
}
49+
viewModel.sendReply { _ in }
50+
}
51+
52+
// Then
53+
XCTAssertEqual(navigationItem, .loading)
54+
}
55+
56+
func test_send_button_renabled_after_network_request_completes() {
57+
// Given
58+
let stores = MockStoresManager(sessionManager: .testingInstance)
59+
let viewModel = ReviewReplyViewModel(siteID: sampleSiteID, reviewID: sampleReviewID, stores: stores)
60+
stores.whenReceivingAction(ofType: CommentAction.self) { action in
61+
switch action {
62+
case let .replyToComment(_, _, _, onCompletion):
63+
onCompletion(.failure(NSError(domain: "", code: 0)))
64+
default:
65+
XCTFail("Received unsupported action: \(action)")
66+
}
67+
}
68+
69+
// When
70+
viewModel.newReply = "New reply"
71+
let navigationItem: ReviewReplyNavigationItem = waitFor { promise in
72+
viewModel.sendReply { _ in
73+
promise(viewModel.navigationTrailingItem)
74+
}
75+
}
76+
77+
// Then
78+
XCTAssertEqual(navigationItem, .send(enabled: true))
79+
}
80+
81+
func test_sendReply_completion_block_returns_true_after_successful_network_request() {
82+
// Given
83+
let stores = MockStoresManager(sessionManager: .testingInstance)
84+
let viewModel = ReviewReplyViewModel(siteID: sampleSiteID, reviewID: sampleReviewID, stores: stores)
85+
stores.whenReceivingAction(ofType: CommentAction.self) { action in
86+
switch action {
87+
case let .replyToComment(_, _, _, onCompletion):
88+
onCompletion(.success(.approved))
89+
default:
90+
XCTFail("Received unsupported action: \(action)")
91+
}
92+
}
93+
94+
// When
95+
viewModel.newReply = "New reply"
96+
let successResponse: Bool = waitFor { promise in
97+
viewModel.sendReply { successResponse in
98+
promise(successResponse)
99+
}
100+
}
101+
102+
// Then
103+
XCTAssertTrue(successResponse)
104+
}
105+
106+
func test_sendReply_completion_block_returns_false_after_failed_network_request() {
107+
// Given
108+
let stores = MockStoresManager(sessionManager: .testingInstance)
109+
let viewModel = ReviewReplyViewModel(siteID: sampleSiteID, reviewID: sampleReviewID, stores: stores)
110+
stores.whenReceivingAction(ofType: CommentAction.self) { action in
111+
switch action {
112+
case let .replyToComment(_, _, _, onCompletion):
113+
onCompletion(.failure(NSError(domain: "", code: 0)))
114+
default:
115+
XCTFail("Received unsupported action: \(action)")
116+
}
117+
}
118+
119+
// When
120+
viewModel.newReply = "New reply"
121+
let successResponse: Bool = waitFor { promise in
122+
viewModel.sendReply { successResponse in
123+
promise(successResponse)
124+
}
125+
}
126+
127+
// Then
128+
XCTAssertFalse(successResponse)
129+
}
27130
}

0 commit comments

Comments
 (0)