-
Notifications
You must be signed in to change notification settings - Fork 121
Customer search: Fill customer fields on Order screen #7885
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Customer search: Fill customer fields on Order screen #7885
Conversation
You can test the changes from this Pull Request by:
|
|
|
||
| // When | ||
| let predicate = command.searchResultsPredicate(keyword: "some") | ||
| let expectedQuery = "siteID == 123 AND ANY searchResults.keyword == \"some\"" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double check if this actually filters down multiple sites.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed 👍 , for 2 customers with the same name and customer ID, in two different sites, only one is shown.
WooCommerce/WooCommerceTests/ViewRelated/Search/Customer/CustomerSearchUICommandTests.swift
Show resolved
Hide resolved
|
Thanks for your time yesterday to go through the review/pairing @joshheald, I made the following changes: |
joshheald
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much there on this, good work.
I noticed another difference from Android (the fields used for name and email) and a problem with one of your tests. A couple of other small comments too.
| fields = populate(fields: fields, with: customer, address: customer.billing) | ||
| secondaryFields = populate(fields: secondaryFields, with: customer, address: customer.shipping) | ||
| } | ||
|
|
||
| private func populate(fields: AddressFormFields, with customer: Customer, address: Address?) -> AddressFormFields { | ||
| var fields = fields | ||
|
|
||
| fields.firstName = customer.firstName ?? "" | ||
| fields.lastName = customer.lastName ?? "" | ||
| fields.email = customer.email |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| fields = populate(fields: fields, with: customer, address: customer.billing) | |
| secondaryFields = populate(fields: secondaryFields, with: customer, address: customer.shipping) | |
| } | |
| private func populate(fields: AddressFormFields, with customer: Customer, address: Address?) -> AddressFormFields { | |
| var fields = fields | |
| fields.firstName = customer.firstName ?? "" | |
| fields.lastName = customer.lastName ?? "" | |
| fields.email = customer.email | |
| 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 ?? "" | |
| fields.email = address.email |
Instead of the name fields on customer, I think we should use address.firstName and address.lastName. Testing Android, with my always-strange test data with different names all over the place, shows that they use these fields to update the data.
This makes sense, because the shipping address may be to a different person, e.g. if it's a gift.
There's address.email, which could differ from customer.email. Android seems to use billingAddress.email there. The email for the secondary fields isn't shown on either platform... but I'm not sure whether it's used in any requests.
It's worth checking that: what happens if we save a different email address in each place? Does it get added to the order twice? Is that what Android does? Is it a bug, one way or the other?
It's also just a nicer function... "Populate address fields with address"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I made the changes here: 121ba8a
Something that peaks my interest with this change is that the email property is now an optional, but it can't actually be optional because is a mandatory field for shippable orders. This seems to happen because when we declare both fields and secondaryFields in the protocol, both inherit from AddressFormFields, however these have slightly different parameters in the API (for example shipping details does not have an email field)
what happens if we save a different email address in each place? Does it get added to the order twice? Is that what Android does? Is it a bug, one way or the other?
This seems a bug from our end when declaring the model, as with the email example above, we're reusing the same AddressFormFields model for both shipping and billing, but these are not exactly the same in the API or WC core (it would seem that the API doc is outdated as well, as phone does not appear as property for shipping but in WC core it does). I've opened an issue here: #7993 as most likely this can be improved.
| 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 |
There was a problem hiding this comment.
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
| viewModel.customerSelectedFromSearch(customer: customer) | ||
|
|
||
| // Then | ||
| XCTAssertEqual(viewModel.fields.email, customer.email) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget about assertEqual: the nicer diffs when stuff is wrong are worth it, even though it's annoying having to swap around the actual and expected when you swap from XCTAssertEqual
| 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) | ||
| } |
There was a problem hiding this comment.
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
| override func setUp() { | ||
| super.setUp() | ||
| } | ||
|
|
||
| override func tearDown() { | ||
| super.tearDown() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can delete these
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! 7b58531
| func test_searchCustomers_returns_no_customers_when_customer_is_not_registered() throws { | ||
| // Given | ||
| let customerID = 0 | ||
| network.simulateResponse(requestUrlSuffix: "customers", filename: "wc-analytics-customers") | ||
| network.simulateResponse(requestUrlSuffix: "customers/\(customerID)", filename: "customer") | ||
|
|
||
| // When | ||
| let response: Result<[Networking.Customer], Error> = waitFor { promise in | ||
| let action = CustomerAction.searchCustomers(siteID: self.dummySiteID, keyword: self.dummyKeyword) { result in | ||
| promise(result) | ||
| } | ||
| self.dispatcher.dispatch(action) | ||
| } | ||
|
|
||
| // Then | ||
| let customers = try response.get() | ||
| XCTAssertEqual(customers.count, 0) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice test! This is business logic which is well worth checking.
However... it doesn't really test what it looks like. I had a go at making it fail (by removing the if result.userID == 0 check in the store) and it still passed.... When you're testing a negative, it's especially worthwhile making sure that the test fails when you expect. Think Red-Green-Refactor, even when you're not actually doing TDD.
The reason is that we don't have anyone in wc-analytics-customer.json with user_id: 0, you'll need to add another entry to that file to get the test to work.
We can improve it further though... here's another way to do it, which more directly checks that the request is not sent, without needing to see what the customer response is:
(N.B. This still needs an analytics customer with user_id: 0 in the json file)
| func test_searchCustomers_returns_no_customers_when_customer_is_not_registered() throws { | |
| // Given | |
| let customerID = 0 | |
| network.simulateResponse(requestUrlSuffix: "customers", filename: "wc-analytics-customers") | |
| network.simulateResponse(requestUrlSuffix: "customers/\(customerID)", filename: "customer") | |
| // When | |
| let response: Result<[Networking.Customer], Error> = waitFor { promise in | |
| let action = CustomerAction.searchCustomers(siteID: self.dummySiteID, keyword: self.dummyKeyword) { result in | |
| promise(result) | |
| } | |
| self.dispatcher.dispatch(action) | |
| } | |
| // Then | |
| let customers = try response.get() | |
| XCTAssertEqual(customers.count, 0) | |
| } | |
| func test_searchCustomers_returns_no_customers_when_customer_is_not_registered() throws { | |
| // Given | |
| network.simulateResponse(requestUrlSuffix: "customers", filename: "wc-analytics-customers") | |
| // When | |
| waitFor { promise in | |
| let action = CustomerAction.searchCustomers(siteID: self.dummySiteID, keyword: self.dummyKeyword) { result in | |
| promise(()) | |
| } | |
| self.dispatcher.dispatch(action) | |
| } | |
| // Then | |
| let requests = network.requestsForResponseData.compactMap { $0 as? JetpackRequest } | |
| XCTAssertFalse(requests.contains(where: { request in | |
| request.path == "customers/0" | |
| })) | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's nice! Made the changes on 7b58531
We’ll be populating the fields using the Address data rather than Customer data for these 3 fields, for consistency with Android. There’s some inconsistency with the API though, reported here: #7993
Part of: #7741
Closes: #7941
Description
This PR deals with:
Changes
OrderCustomerListViewreceives a completion handler with aCustomerobject when a merchant taps in the search button. Then we use this data to fill theEditOrderAddressFormviewModel.customerSearchTapped(), as we're not using it anymore.SearchResultstoCustomerobjects, we skip mapping those withid = 0(unregistered), avoiding requests to/wc/v3/customers/0that will always fail.Testing instructions
RELEASE-NOTES.txtif necessary.