Skip to content

Commit 43cd538

Browse files
authored
Merge pull request #6974 from woocommerce/issue/6339-respond-immediately-to-additive-changes
Order Creation: Make UI immediately respond to additive changes to new order
2 parents 15743e2 + ab87e2a commit 43cd538

File tree

3 files changed

+65
-18
lines changed

3 files changed

+65
-18
lines changed

RELEASE-NOTES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
- [*] Orders: Now it's possible to select and copy text from the notes on an order. [https://github.com/woocommerce/woocommerce-ios/pull/6894]
88
- [*] Support Arabic numerals on amount fields. [https://github.com/woocommerce/woocommerce-ios/pull/6891]
99
- [*] Product Selector: Enabled selecting all variations on variable product rows. [https://github.com/woocommerce/woocommerce-ios/pull/6899]
10+
- [internal] Order Creation: Adding new products, shipping, fee, or customer details to an order now blocks the UI immediately while the order is syncing remotely. [https://github.com/woocommerce/woocommerce-ios/pull/6974]
11+
1012
- [*] Coupons: Now it's possible to update discount types for coupons. [https://github.com/woocommerce/woocommerce-ios/pull/6935]
1113

1214
9.2

WooCommerce/Classes/ViewRelated/Orders/Order Creation/Synchronizer/RemoteOrderSynchronizer.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ private extension RemoteOrderSynchronizer {
183183
///
184184
func bindOrderSync() {
185185
let syncTrigger: AnyPublisher<Order, Never> = orderSyncTrigger
186-
.debounce(for: 0.5, scheduler: DispatchQueue.main) // Group & wait for 0.5 since the last signal was emitted.
187186
.compactMap { [weak self] order in
188187
guard let self = self else { return nil }
189188
switch self.state {
@@ -210,7 +209,7 @@ private extension RemoteOrderSynchronizer {
210209
}
211210
.flatMap(maxPublishers: .max(1)) { [weak self] order -> AnyPublisher<Order, Never> in // Only allow one request at a time.
212211
guard let self = self else { return Empty().eraseToAnyPublisher() }
213-
self.state = .syncing(blocking: true) // Creating an oder is always a blocking operation
212+
self.state = .syncing(blocking: true) // Creating an order is always a blocking operation
214213

215214
return self.createOrderRemotely(order, type: .sync)
216215
.catch { [weak self] error -> AnyPublisher<Order, Never> in // When an error occurs, update state & finish.
@@ -234,9 +233,13 @@ private extension RemoteOrderSynchronizer {
234233
.filter { // Only continue if the order has been created.
235234
$0.orderID != .zero
236235
}
236+
.handleEvents(receiveOutput: { order in
237+
self.state = .syncing(blocking: order.containsLocalLines()) // Set a `blocking` state if the order contains new lines
238+
})
239+
.debounce(for: 1.0, scheduler: DispatchQueue.main) // Group & wait for 1.0 since the last signal was emitted.
237240
.map { [weak self] order -> AnyPublisher<Order, Never> in // Allow multiple requests, once per update request.
238241
guard let self = self else { return Empty().eraseToAnyPublisher() }
239-
self.state = .syncing(blocking: order.containsLocalLines()) // Set a `blocking` state if the order contains new lines
242+
240243
return self.updateOrderRemotely(order, type: .sync)
241244
.catch { [weak self] error -> AnyPublisher<Order, Never> in // When an error occurs, update state & finish.
242245
self?.state = .error(error)

WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Creation/Synchronizer/RemoteOrderSynchronizerTests.swift

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -443,9 +443,6 @@ class RemoteOrderSynchronizerTests: XCTestCase {
443443
}
444444

445445
// When
446-
let input = OrderSyncProductInput(product: .product(product), quantity: 1)
447-
synchronizer.setProduct.send(input)
448-
449446
let states: [OrderSyncState] = waitFor { promise in
450447
synchronizer.statePublisher
451448
.dropFirst()
@@ -454,6 +451,9 @@ class RemoteOrderSynchronizerTests: XCTestCase {
454451
promise(states)
455452
}
456453
.store(in: &self.subscriptions)
454+
455+
let input = OrderSyncProductInput(product: .product(product), quantity: 1)
456+
synchronizer.setProduct.send(input)
457457
}
458458

459459
// Then
@@ -476,12 +476,10 @@ class RemoteOrderSynchronizerTests: XCTestCase {
476476
}
477477
}
478478

479+
// Wait for order creation
479480
let input = OrderSyncProductInput(product: .product(product), quantity: 1)
480481
createOrder(on: synchronizer, input: input)
481482

482-
let input2 = OrderSyncProductInput(product: .product(product), quantity: 2)
483-
synchronizer.setProduct.send(input2)
484-
485483
let states: [OrderSyncState] = waitFor { promise in
486484
synchronizer.statePublisher
487485
.dropFirst()
@@ -490,6 +488,10 @@ class RemoteOrderSynchronizerTests: XCTestCase {
490488
promise(states)
491489
}
492490
.store(in: &self.subscriptions)
491+
492+
// Trigger order update
493+
let input2 = OrderSyncProductInput(product: .product(product), quantity: 2)
494+
synchronizer.setProduct.send(input2)
493495
}
494496

495497
// Then
@@ -513,12 +515,10 @@ class RemoteOrderSynchronizerTests: XCTestCase {
513515
}
514516
}
515517

518+
// Wait for order creation
516519
let input = OrderSyncProductInput(id: sampleInputID, product: .product(product), quantity: 1)
517520
createOrder(on: synchronizer, input: input)
518521

519-
let input2 = OrderSyncProductInput(id: sampleInputID, product: .product(product), quantity: 2)
520-
synchronizer.setProduct.send(input2)
521-
522522
let states: [OrderSyncState] = waitFor { promise in
523523
synchronizer.statePublisher
524524
.dropFirst()
@@ -527,6 +527,10 @@ class RemoteOrderSynchronizerTests: XCTestCase {
527527
promise(states)
528528
}
529529
.store(in: &self.subscriptions)
530+
531+
// Trigger order update
532+
let input2 = OrderSyncProductInput(id: self.sampleInputID, product: .product(product), quantity: 2)
533+
synchronizer.setProduct.send(input2)
530534
}
531535

532536
// Then
@@ -591,9 +595,6 @@ class RemoteOrderSynchronizerTests: XCTestCase {
591595
}
592596

593597
// When
594-
let input = OrderSyncProductInput(product: .product(product), quantity: 1)
595-
synchronizer.setProduct.send(input)
596-
597598
let states: [OrderSyncState] = waitFor { promise in
598599
synchronizer.statePublisher
599600
.dropFirst()
@@ -602,6 +603,9 @@ class RemoteOrderSynchronizerTests: XCTestCase {
602603
promise(states)
603604
}
604605
.store(in: &self.subscriptions)
606+
607+
let input = OrderSyncProductInput(product: .product(product), quantity: 1)
608+
synchronizer.setProduct.send(input)
605609
}
606610

607611
// Then
@@ -625,12 +629,10 @@ class RemoteOrderSynchronizerTests: XCTestCase {
625629
}
626630
}
627631

632+
// Wait for order creation
628633
let input = OrderSyncProductInput(product: .product(product), quantity: 1)
629634
createOrder(on: synchronizer, input: input)
630635

631-
let input2 = OrderSyncProductInput(product: .product(product), quantity: 2)
632-
synchronizer.setProduct.send(input2)
633-
634636
let states: [OrderSyncState] = waitFor { promise in
635637
synchronizer.statePublisher
636638
.dropFirst()
@@ -639,6 +641,10 @@ class RemoteOrderSynchronizerTests: XCTestCase {
639641
promise(states)
640642
}
641643
.store(in: &self.subscriptions)
644+
645+
// Trigger order update
646+
let input2 = OrderSyncProductInput(product: .product(product), quantity: 2)
647+
synchronizer.setProduct.send(input2)
642648
}
643649

644650
// Then
@@ -949,6 +955,42 @@ class RemoteOrderSynchronizerTests: XCTestCase {
949955
// Then
950956
XCTAssertTrue(result.isSuccess)
951957
}
958+
959+
func test_double_inputs_are_debounced_during_order_update() {
960+
// Given
961+
let product = Product.fake().copy(productID: sampleProductID)
962+
let stores = MockStoresManager(sessionManager: .testingInstance)
963+
let synchronizer = RemoteOrderSynchronizer(siteID: sampleSiteID, stores: stores)
964+
965+
// When
966+
let exp = expectation(description: #function)
967+
exp.expectedFulfillmentCount = 1
968+
exp.assertForOverFulfill = true
969+
970+
stores.whenReceivingAction(ofType: OrderAction.self) { action in
971+
switch action {
972+
case .createOrder(_, let order, let completion):
973+
completion(.success(order.copy(orderID: self.sampleOrderID)))
974+
case .updateOrder:
975+
exp.fulfill()
976+
default:
977+
break
978+
}
979+
}
980+
981+
// Wait for order creation
982+
let input1 = OrderSyncProductInput(id: sampleInputID, product: .product(product), quantity: 1)
983+
createOrder(on: synchronizer, input: input1)
984+
985+
// Trigger product quantity updates
986+
let input2 = OrderSyncProductInput(id: sampleInputID, product: .product(product), quantity: 2)
987+
let input3 = OrderSyncProductInput(id: sampleInputID, product: .product(product), quantity: 3)
988+
synchronizer.setProduct.send(input2)
989+
synchronizer.setProduct.send(input3)
990+
991+
// Then
992+
wait(for: [exp], timeout: 1.0)
993+
}
952994
}
953995

954996
private extension RemoteOrderSynchronizerTests {

0 commit comments

Comments
 (0)