Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class WCAnalyticsCustomerMapperTests: XCTestCase {
customers = try! mapper.map(response: data)

// Then
XCTAssertEqual(customers.count, 3)
XCTAssertEqual(customers.count, 4)
}

func test_WCAnalyticsCustomer_array_response_values_are_correctly_parsed() {
Expand All @@ -50,11 +50,13 @@ class WCAnalyticsCustomerMapperTests: XCTestCase {
let customers = try! mapper.map(response: data)

// Then
XCTAssertEqual(customers[0].userID, 1)
XCTAssertEqual(customers[0].name, "John")
XCTAssertEqual(customers[1].userID, 2)
XCTAssertEqual(customers[1].name, "Paul")
XCTAssertEqual(customers[2].userID, 3)
XCTAssertEqual(customers[2].name, "John Doe")
XCTAssertEqual(customers[0].userID, 0)
XCTAssertEqual(customers[0].name, "Matt The Unregistered")
XCTAssertEqual(customers[1].userID, 1)
XCTAssertEqual(customers[1].name, "John")
XCTAssertEqual(customers[2].userID, 2)
XCTAssertEqual(customers[2].name, "Paul")
XCTAssertEqual(customers[3].userID, 3)
XCTAssertEqual(customers[3].name, "John Doe")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ class WCAnalyticsCustomerRemoteTests: XCTestCase {
let customers = try XCTUnwrap(result.get())
let hasSearchParameter = network.queryParameters?.contains(where: { $0 == "search=John" }) ?? false
XCTAssertTrue(hasSearchParameter)
assertEqual(3, customers.count)
assertEqual(1, customers[0].userID)
assertEqual(2, customers[1].userID)
assertEqual(3, customers[2].userID)
assertEqual("John", customers[0].name)
assertEqual("Paul", customers[1].name)
assertEqual("John Doe", customers[2].name)
assertEqual(4, customers.count)
assertEqual(0, customers[0].userID)
assertEqual(1, customers[1].userID)
assertEqual(2, customers[2].userID)
assertEqual(3, customers[3].userID)
assertEqual("Matt The Unregistered", customers[0].name)
assertEqual("John", customers[1].name)
assertEqual("Paul", customers[2].name)
assertEqual("John Doe", customers[3].name)
}

func test_WCAnalyticsCustomerRemote_when_calls_retrieveCustomersByName_fails_then_returns_result_isFailure() {
Expand Down
19 changes: 19 additions & 0 deletions Networking/NetworkingTests/Responses/wc-analytics-customers.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
{ "data":
[
{
"id":0,
"user_id":0,
"username":"Matt.the.unregistered",
"name":"Matt The Unregistered",
"email":"[email protected]",
"country":"US",
"city":"San Francisco",
"state":"CA",
"postcode":"94103",
"date_registered":null,
"date_last_active":"2022-07-12T08:36:54",
"date_last_order":"2022-07-12 08:36:54",
"orders_count":1,
"total_spend":10,
"avg_order_value":10,
"date_registered_gmt":null,
"date_last_active_gmt":"2022-07-12T08:36:54"
},
{
"id":1,
"user_id":1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,24 +97,6 @@ final class CreateOrderAddressFormViewModel: AddressFormViewModel, AddressFormVi
override func trackOnLoad() { }

func userDidCancelFlow() { }

/// Dispatches the searchCustomers action when the Search button is tapped.
/// The hardcoded `keyword` is temporary until we implement the rest of the feature:
/// https://github.com/woocommerce/woocommerce-ios/issues/7741
///
func customerSearchTapped() {
let action = CustomerAction.searchCustomers(
siteID: siteID,
keyword: "hello") { result in
switch result {
case .success(_):
print("Success")
case .failure(let error):
print(error)
}
}
stores.dispatch(action)
}
}

private extension CreateOrderAddressFormViewModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@ final class CustomerSearchUICommand: SearchUICommand {
}

func createStarterViewController() -> UIViewController? {
nil
createEmptyStateViewController()
}

func configureEmptyStateViewControllerBeforeDisplay(viewController: EmptyStateViewController, searchKeyword: String) {
let boldSearchKeyword = NSAttributedString(string: searchKeyword,
attributes: [.font: EmptyStateViewController.Config.messageFont.bold])
let format = Localization.emptySearchResults
let message = NSMutableAttributedString(string: format)

message.replaceFirstOccurrence(of: "%@", with: boldSearchKeyword)
viewController.configure(.simple(message: message, image: .emptySearchResultsImage))
}

func createCellViewModel(model: Customer) -> TitleAndSubtitleAndStatusTableViewCell.ViewModel {
Expand All @@ -61,10 +71,6 @@ final class CustomerSearchUICommand: SearchUICommand {
}

func didSelectSearchResult(model: Customer, from viewController: UIViewController, reloadData: () -> Void, updateActionButton: () -> Void) {
// Not implemented yet
print("1 - Customer tapped")
print("2 - Customer ID: \(model.customerID) - Name: \(model.firstName ?? ""))")
// Customer data will go up to EditOrderAddressForm, via OrderCustomerListView completion handler
onDidSelectSearchResult(model)
}

Expand All @@ -75,7 +81,11 @@ final class CustomerSearchUICommand: SearchUICommand {

private extension CustomerSearchUICommand {
enum Localization {
static let searchBarPlaceHolder = NSLocalizedString("Search all customers",
comment: "Customer Search Placeholder")
static let searchBarPlaceHolder = NSLocalizedString(
"Search all customers",
comment: "Customer Search Placeholder")
static let emptySearchResults = NSLocalizedString(
"We're sorry, we couldn't find results for “%@”",
comment: "Message for empty Customers search results. %@ is a placeholder for the text entered by the user.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Yosemite
import SwiftUI

/// `SwiftUI` wrapper for `SearchViewController` using `CustomerSearchUICommand`
/// TODO: Make it generic
///
struct OrderCustomerListView: UIViewControllerRepresentable {

let siteID: Int64
Expand All @@ -16,15 +16,13 @@ struct OrderCustomerListView: UIViewControllerRepresentable {
storeID: siteID,
command: CustomerSearchUICommand(siteID: siteID, onDidSelectSearchResult: onCustomerTapped),
cellType: TitleAndSubtitleAndStatusTableViewCell.self,
// Must conform to SearchResultCell.
// TODO: Proper cell for this cellType.
cellSeparator: .none
)
let navigationController = WooNavigationController(rootViewController: viewController)
return navigationController
}

func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// nope
// not implemented
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ protocol AddressFormViewModelProtocol: ObservableObject {
///
func createSecondaryStateViewModel() -> StateSelectorViewModel

/// Callback method when the Customer Search button is tapped
/// Triggers the logic to fill Customer Order details when a Customer is selected
///
func customerSearchTapped()
func customerSelectedFromSearch(customer: Customer)
}

/// Type to hold values from all the form fields
Expand Down Expand Up @@ -401,6 +401,40 @@ open class AddressFormViewModel: ObservableObject {
let secondaryEmailIsValid = secondaryFields.email.isEmpty || EmailFormatValidator.validate(string: fields.email)
return primaryEmailIsValid && secondaryEmailIsValid
}

/// Fills Order AddressFormFields with Customer details
///
func customerSelectedFromSearch(customer: Customer) {
fillCustomerFields(customer: customer)
let addressesDiffer = customer.billing != customer.shipping
showDifferentAddressForm = addressesDiffer
}

private func fillCustomerFields(customer: Customer) {
fields = populate(fields: fields, with: customer.billing)
secondaryFields = populate(fields: secondaryFields, with: customer.shipping)
}

private func populate(fields: AddressFormFields, with address: Address?) -> AddressFormFields {
var fields = fields

fields.firstName = address?.firstName ?? ""
fields.lastName = address?.lastName ?? ""
// Email is declared optional because we're using the same property from the Address model
// for both Shipping and Billing details:
// https://github.com/woocommerce/woocommerce-ios/issues/7993
fields.email = address?.email ?? ""
fields.phone = address?.phone ?? ""
fields.company = address?.company ?? ""
fields.address1 = address?.address1 ?? ""
fields.address2 = address?.address2 ?? ""
fields.city = address?.city ?? ""
fields.postcode = address?.postcode ?? ""
fields.country = address?.country ?? ""
fields.state = address?.state ?? ""

return fields
}
}

extension AddressFormViewModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,8 @@ struct EditOrderAddressForm<ViewModel: AddressFormViewModelProtocol>: View {
.notice($viewModel.notice)
.sheet(isPresented: $showingCustomerSearch, content: {
OrderCustomerListView(siteID: viewModel.siteID, onCustomerTapped: { customer in
// Not implemented yet.
print("3 - Customer Callback. Fill Order data with Customer details")
print("4 - Customer ID: \(customer.customerID) - Name: \(customer.firstName ?? ""))")
viewModel.customerSelectedFromSearch(customer: customer)
showingCustomerSearch = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can move to the view model too if you like? Can wait for a future PR

})
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@ final class EditOrderAddressFormViewModel: AddressFormViewModel, AddressFormView
func userDidCancelFlow() {
analytics.track(event: WooAnalyticsEvent.OrderDetailsEdit.orderDetailEditFlowCanceled(subject: self.analyticsFlowType()))
}

func customerSearchTapped() {}
}

extension EditOrderAddressFormViewModel {
Expand Down
12 changes: 12 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,7 @@
57F2C6CD246DECC10074063B /* SummaryTableViewCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F2C6CC246DECC10074063B /* SummaryTableViewCellViewModelTests.swift */; };
57F42E40253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F42E3F253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift */; };
581D5052274AA2480089B6AD /* View+AutofocusTextModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581D5051274AA2480089B6AD /* View+AutofocusTextModifier.swift */; };
682210ED2909666600814E14 /* CustomerSearchUICommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */; };
6827140F28A3988300E6E3F6 /* DismissableNoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6827140E28A3988300E6E3F6 /* DismissableNoticeView.swift */; };
6827141128A5410D00E6E3F6 /* NewSimplePaymentsLocationNoticeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6827141028A5410D00E6E3F6 /* NewSimplePaymentsLocationNoticeViewModel.swift */; };
6827141528A671B900E6E3F6 /* NewSimplePaymentsLocationNoticeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6827141428A671B900E6E3F6 /* NewSimplePaymentsLocationNoticeViewController.swift */; };
Expand Down Expand Up @@ -2890,6 +2891,7 @@
57F2C6CC246DECC10074063B /* SummaryTableViewCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryTableViewCellViewModelTests.swift; sourceTree = "<group>"; };
57F42E3F253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleAndEditableValueTableViewCellViewModelTests.swift; sourceTree = "<group>"; };
581D5051274AA2480089B6AD /* View+AutofocusTextModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AutofocusTextModifier.swift"; sourceTree = "<group>"; };
682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerSearchUICommandTests.swift; sourceTree = "<group>"; };
6827140E28A3988300E6E3F6 /* DismissableNoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableNoticeView.swift; sourceTree = "<group>"; };
6827141028A5410D00E6E3F6 /* NewSimplePaymentsLocationNoticeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewSimplePaymentsLocationNoticeViewModel.swift; sourceTree = "<group>"; };
6827141428A671B900E6E3F6 /* NewSimplePaymentsLocationNoticeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewSimplePaymentsLocationNoticeViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5990,6 +5992,7 @@
573D0ACC2458665C004DE614 /* Search */ = {
isa = PBXGroup;
children = (
682210EB2909664800814E14 /* Customer */,
020C908224C84638001E2BEB /* Product */,
);
path = Search;
Expand Down Expand Up @@ -6165,6 +6168,14 @@
path = TitleAndEditableValueTableViewCell;
sourceTree = "<group>";
};
682210EB2909664800814E14 /* Customer */ = {
isa = PBXGroup;
children = (
682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */,
);
path = Customer;
sourceTree = "<group>";
};
6856D6E9B1C3C89938DCAD5C /* Testing */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -10593,6 +10604,7 @@
03FBDAFD263EE4E800ACE257 /* CouponListViewModelTests.swift in Sources */,
036F6EA6281847D5006D84F8 /* PaymentCaptureOrchestratorTests.swift in Sources */,
B555531321B57E8800449E71 /* MockUserNotificationsCenterAdapter.swift in Sources */,
682210ED2909666600814E14 /* CustomerSearchUICommandTests.swift in Sources */,
4590B652261C8D1E00A6FCE0 /* WeightFormatterTests.swift in Sources */,
D8C11A6022E2479800D4A88D /* OrderPaymentDetailsViewModelTests.swift in Sources */,
B622BC74289CF19400B10CEC /* WaitingTimeTrackerTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,76 @@ final class EditOrderAddressFormViewModelTests: XCTestCase {
// Then
XCTAssertEqual(notice, AddressFormViewModel.NoticeFactory.createInvalidEmailNotice())
}

func test_OrderAddressForm_billing_fields_are_updated_when_customerSelectedFromSearch() {
// Given
let viewModel = EditOrderAddressFormViewModel(order: Order.fake(), type: .billing)
let customer = Customer.fake().copy(
email: "[email protected]",
firstName: "Johnny",
lastName: "Appleseed",
billing: sampleAddressWithEmptyNullableFields(),
shipping: sampleAddressWithEmptyNullableFields()
)

// When
viewModel.customerSelectedFromSearch(customer: customer)

// Then
XCTAssertEqual(viewModel.fields.email, customer.billing?.email)
XCTAssertEqual(viewModel.fields.firstName, customer.firstName)
XCTAssertEqual(viewModel.fields.lastName, customer.lastName)
XCTAssertEqual(viewModel.fields.company, customer.billing?.company)
XCTAssertEqual(viewModel.fields.address1, customer.billing?.address1)
XCTAssertEqual(viewModel.fields.address2, customer.billing?.address2)
XCTAssertEqual(viewModel.fields.city, customer.billing?.city)
XCTAssertEqual(viewModel.fields.state, customer.billing?.state)
XCTAssertEqual(viewModel.fields.postcode, customer.billing?.postcode)
XCTAssertEqual(viewModel.fields.country, customer.billing?.country)
XCTAssertEqual(viewModel.fields.phone, customer.billing?.phone)
}

func test_OrderAddressForm_shipping_fields_are_updated_when_customerSelectedFromSearch() {
// Given
let viewModel = EditOrderAddressFormViewModel(order: Order.fake(), type: .shipping)
let customer = Customer.fake().copy(
email: "[email protected]",
firstName: "Johnny",
lastName: "Appleseed",
billing: sampleAddressWithEmptyNullableFields(),
shipping: sampleAddressWithEmptyNullableFields()
)

// When
viewModel.customerSelectedFromSearch(customer: customer)

// Then
XCTAssertEqual(viewModel.fields.email, customer.shipping?.email)
XCTAssertEqual(viewModel.fields.firstName, customer.firstName)
XCTAssertEqual(viewModel.fields.lastName, customer.lastName)
XCTAssertEqual(viewModel.fields.company, customer.shipping?.company)
XCTAssertEqual(viewModel.fields.address1, customer.shipping?.address1)
XCTAssertEqual(viewModel.fields.address2, customer.shipping?.address2)
XCTAssertEqual(viewModel.fields.city, customer.shipping?.city)
XCTAssertEqual(viewModel.fields.state, customer.shipping?.state)
XCTAssertEqual(viewModel.fields.postcode, customer.shipping?.postcode)
XCTAssertEqual(viewModel.fields.country, customer.shipping?.country)
XCTAssertEqual(viewModel.fields.phone, customer.shipping?.phone)
}

func test_OrderAddressForm_shows_different_address_form_fields_when_addresses_differ_and_customerSelectedFromSearch() {
// Given
let viewModel = EditOrderAddressFormViewModel(order: Order.fake(), type: .billing)
let billing = sampleAddressWithEmptyNullableFields()
let shipping = Address.fake().copy(address1: "123 different fake street")
let customer = Customer.fake().copy(billing: billing, shipping: shipping)

// When
viewModel.customerSelectedFromSearch(customer: customer)

// Then
XCTAssertTrue(viewModel.showDifferentAddressForm)
}
Comment on lines +776 to +788
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏👏👏 So good to be able to test this kind of thing with confidence

}

private extension EditOrderAddressFormViewModelTests {
Expand Down
Loading