Skip to content

Commit bbade0f

Browse files
authored
Merge pull request #4973 from woocommerce/issue/4971-shipping-label-address-validation-issues
Shipping Labels: Fix incorrect phone validation for non-US country and add instant local validation on value changes
2 parents 61df4d0 + 48d6baf commit bbade0f

File tree

3 files changed

+93
-20
lines changed

3 files changed

+93
-20
lines changed

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/Create Shipping Label Form/Shipping Address Validation/ShippingLabelAddressFormViewController.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,20 @@ private extension ShippingLabelAddressFormViewController {
142142
}
143143

144144
func registerTableViewCells() {
145-
tableView.registerNib(for: TitleAndTextFieldTableViewCell.self)
146-
tableView.registerNib(for: BasicTableViewCell.self)
145+
tableView.registerNib(for: TitleAndTextFieldTableViewCell.self)
146+
tableView.registerNib(for: BasicTableViewCell.self)
147147
}
148148

149149
func observeViewModel() {
150-
viewModel.onChange = { [weak self] in
150+
viewModel.onChange = { [weak self] focusedIndex in
151151
guard let self = self else { return }
152152
self.configureNavigationBar()
153153
self.updateTopBannerView()
154154
self.tableView.reloadData()
155+
if let index = focusedIndex,
156+
let cell = self.tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? TitleAndTextFieldTableViewCell {
157+
cell.textFieldBecomeFirstResponder()
158+
}
155159
}
156160
}
157161

@@ -459,8 +463,7 @@ private extension ShippingLabelAddressFormViewController {
459463
placeholder: placeholder,
460464
state: .normal,
461465
keyboardType: .default,
462-
textFieldAlignment: .leading) { _ in
463-
}
466+
textFieldAlignment: .leading) { _ in }
464467
cell.configure(viewModel: cellViewModel)
465468
cell.enableTextField(viewModel.statesOfSelectedCountry.isEmpty)
466469
}
@@ -471,8 +474,7 @@ private extension ShippingLabelAddressFormViewController {
471474
placeholder: Localization.countryFieldPlaceholder,
472475
state: .normal,
473476
keyboardType: .default,
474-
textFieldAlignment: .leading) { _ in
475-
}
477+
textFieldAlignment: .leading) { _ in }
476478
cell.configure(viewModel: cellViewModel)
477479
cell.enableTextField(false)
478480
}

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/Create Shipping Label Form/Shipping Address Validation/ShippingLabelAddressFormViewModel.swift

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ final class ShippingLabelAddressFormViewModel {
2525
private let stores: StoresManager
2626

2727
/// Closure to notify the `ViewController` when the view model properties change.
28+
/// Accepts optional index for row to be focused after reloading the table view.
2829
///
29-
var onChange: (() -> (Void))?
30+
var onChange: ((_ focusedRowIndex: Int?) -> (Void))?
3031

3132
/// Current `ViewModel` state.
3233
///
3334
private var state: State = State() {
3435
didSet {
3536
updateSections()
36-
onChange?()
37+
onChange?(nil)
3738
}
3839
}
3940

@@ -106,10 +107,14 @@ final class ShippingLabelAddressFormViewModel {
106107
case .state:
107108
address = address?.copy(state: newValue)
108109
case .country:
109-
address = address?.copy(country: newValue)
110+
address = address?.copy(country: newValue, state: "")
110111
default:
111112
return
112113
}
114+
115+
updateSections()
116+
let index: Int? = sections.first?.rows.firstIndex(where: { $0 == row })
117+
onChange?(index)
113118
}
114119

115120
func updateSections() {
@@ -181,23 +186,38 @@ extension ShippingLabelAddressFormViewModel {
181186
case invalidPhoneNumber
182187
}
183188

184-
/// Validates phone number for origin address.
189+
/// Validates phone number for the address.
185190
/// This take into account whether phone is not empty,
186191
/// has length 10 with additional "1" area code for US.
187192
///
188-
/// Note: This logic may need to be updated if there is a need for validating other cases.
189-
///
190193
private var isPhoneNumberValid: Bool {
191194
guard let phone = address?.phone, phone.isNotEmpty else {
192195
return false
193196
}
197+
guard address?.country == "US" else {
198+
return true
199+
}
194200
if phone.hasPrefix("1") {
195201
return phone.count == 11
196202
} else {
197203
return phone.count == 10
198204
}
199205
}
200206

207+
private var phoneValidationError: ValidationError? {
208+
guard let addressToBeValidated = address else {
209+
return nil
210+
}
211+
if phoneNumberRequired {
212+
if addressToBeValidated.phone.isEmpty {
213+
return .missingPhoneNumber
214+
} else if !isPhoneNumberValid {
215+
return .invalidPhoneNumber
216+
}
217+
}
218+
return nil
219+
}
220+
201221
private func validateAddressLocally() -> [ValidationError] {
202222
var errors: [ValidationError] = []
203223

@@ -220,12 +240,8 @@ extension ShippingLabelAddressFormViewModel {
220240
if addressToBeValidated.country.isEmpty {
221241
errors.append(.country)
222242
}
223-
if phoneNumberRequired {
224-
if addressToBeValidated.phone.isEmpty {
225-
errors.append(.missingPhoneNumber)
226-
} else if !isPhoneNumberValid {
227-
errors.append(.invalidPhoneNumber)
228-
}
243+
if let error = phoneValidationError {
244+
errors.append(error)
229245
}
230246
}
231247

WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/Create Shipping Label/ShippingLabelAddressFormViewModelTests.swift

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ final class ShippingLabelAddressFormViewModelTests: XCTestCase {
3333
XCTAssertEqual(viewModel.address?.postcode, "94121-2303")
3434
}
3535

36+
func test_handleAddressValueChanges_empties_stateOfCountry_when_country_is_updated() {
37+
38+
// Given
39+
let shippingAddress = MockShippingLabelAddress.sampleAddress(country: "US", state: "CA")
40+
let viewModel = ShippingLabelAddressFormViewModel(siteID: 10, type: .origin, address: shippingAddress, validationError: nil, countries: [])
41+
42+
// When
43+
viewModel.handleAddressValueChanges(row: .country, newValue: "AU")
44+
45+
// Then
46+
XCTAssertEqual(viewModel.address?.state, "")
47+
}
48+
3649
func test_sections_are_returned_correctly_if_there_are_no_errors() {
3750
// Given
3851
let shippingAddress = ShippingLabelAddress(company: "Automattic Inc.",
@@ -223,7 +236,7 @@ final class ShippingLabelAddressFormViewModelTests: XCTestCase {
223236

224237
func test_sections_are_returned_correctly_if_phone_is_required_and_invalid() {
225238
// Given
226-
let shippingAddress = MockShippingLabelAddress.sampleAddress(phone: "0123", country: "VN")
239+
let shippingAddress = MockShippingLabelAddress.sampleAddress(phone: "0123", country: "US")
227240
let stores = MockStoresManager(sessionManager: .testingInstance)
228241
let validationError = ShippingLabelAddressValidationError(addressError: "Error", generalError: nil)
229242

@@ -264,6 +277,48 @@ final class ShippingLabelAddressFormViewModelTests: XCTestCase {
264277
XCTAssertEqual(viewModel.sections, [ShippingLabelAddressFormViewModel.Section(rows: expectedRows)])
265278
}
266279

280+
func test_sections_are_returned_correctly_if_phone_is_required_and_country_is_nonUS() {
281+
// Given
282+
let shippingAddress = MockShippingLabelAddress.sampleAddress(phone: "0123", country: "VN")
283+
let stores = MockStoresManager(sessionManager: .testingInstance)
284+
let validationError = ShippingLabelAddressValidationError(addressError: "Error", generalError: nil)
285+
286+
// When
287+
stores.whenReceivingAction(ofType: ShippingLabelAction.self) { action in
288+
switch action {
289+
case let .validateAddress(_, _, onCompletion):
290+
onCompletion(.failure(validationError))
291+
default:
292+
break
293+
}
294+
}
295+
296+
let viewModel = ShippingLabelAddressFormViewModel(siteID: 10,
297+
type: .origin,
298+
address: shippingAddress,
299+
phoneNumberRequired: true,
300+
stores: stores,
301+
validationError: nil,
302+
countries: [])
303+
viewModel.validateAddress(onlyLocally: false) { _ in }
304+
305+
// Then
306+
let expectedRows: [ShippingLabelAddressFormViewModel.Row] = [.name,
307+
.fieldError(.name),
308+
.company,
309+
.phone,
310+
.address,
311+
.fieldError(.address),
312+
.address2,
313+
.city,
314+
.fieldError(.city),
315+
.postcode,
316+
.fieldError(.postcode),
317+
.state,
318+
.country]
319+
XCTAssertEqual(viewModel.sections, [ShippingLabelAddressFormViewModel.Section(rows: expectedRows)])
320+
}
321+
267322
func test_address_validation_returns_correct_values_if_succeeded() {
268323
// Given
269324
let shippingAddress = ShippingLabelAddress(company: "Automattic Inc.",

0 commit comments

Comments
 (0)