Skip to content
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

chore: fix remaining shipping2 tests, complete initial refactor #13324

Merged
merged 18 commits into from
Feb 2, 2024

Conversation

erikdstock
Copy link
Contributor

@erikdstock erikdstock commented Dec 22, 2023

The type of this PR is: chore

It completes EMI-1526 and relates to EMI-1520. After this PR all existing legacy tests will have been implemented, opening the door to more refactoring. All skipped tests have been unskipped (there will be failures while in draft), but TODOs remain in the test file.

Summary of changes

  • User address updates are now handled consistently for different flows, always happening before the relevant mutation on the exchange order.
  • The hook enabling user address updates has been updated to handle multiple flows.
  • All integration tests in Shipping2.jest.tsx are working.
  • Added tests at the component level, migrating some to use react testing library.
  • Recently-pended tests have been left in the pending state (owing to timeout issues in CI), but they are passing in development. The ideal here will be to lean more on a mix of unit tests and integrity for testing in the long term.

The diff is quite large because of fixing these final specs revealed some bigger changes that were necessary.

old notes

Functionality

Because the fulfillment details steps has two separate form layouts (saved addresses with a modal, or no saved addresses with a form on the page itself) there are multiple different flows to account for:

  • With saved addresses we defer form validation to the saved addresses formik context (a single formik context could make this easier)
  • With saved addresses, we perform user address updates first, then save fulfillment details. with new address it is the opposite
    • if this were not necessary, it could be easier - ideally call the most restrictive mutation first
  • With saved addresses we do not perform address verification.
    • Someday we may want to add address verification when editing or creating a new address - on the old shipping form this was too complicated to implement.
  • Saved addresses are persisted to the order when they are selected, without needing to click save and continue. Thus the user must click save and continue again to proceed [with no mutation].
  • On load, SavedAddresses automatically selects an initial address value based on a) what is already saved on the order or b) the user's default address, triggering the save to the order. a) is new functionality designed to avoid a bug mentioned in EMI-1520 and below and it does this by comparing address fields.
    • Does the logic to match existing order fulfillment data with a user's saved addresses need to exist up in ShippingContext, or could it all happen in the SavedAddresses component?

Edge cases and possible bugs

So far in this draft PR I've covered most of these cases, but there may be more, especially with the edge cases noted in EMI-1520.

  • When the route loads, we want to re-save the fulfillment details on the order if it is an artsy shipping order (noted above). This causes shipping quotes to be re-fetched, but does not un-save a previously-saved shipping quote, which will no longer be visible and may have a different price or destination. (Wont fix)
  • Our current logic doesn't respect whether an artwork can ship to that automatically-selected address, relying on an error from the server to immediately tell the user to choose a different address. Likewise we do not disable address radio buttons that aren't valid for shipping. (Partially fixed - we only automatically select from countries that can be shipped-to)
  • Once in SavedAddresses mode (ie the user has a saved address) there is no option to not save a new address (Wont fix)
  • In this flow:
    • User applies shipping address to order, but doesn't save address for later use on their user
    • User adds a new, different saved address to their user elsewhere
    • User reloads shipping page, now in saved addresses mode
    • => The originally-saved address may be clobbered by the auto-saving feature, since their original address won't be in the saved address list.

@erikdstock erikdstock requested a review from damassi December 22, 2023 22:24
@erikdstock erikdstock self-assigned this Dec 22, 2023
@erikdstock erikdstock changed the title chore: fix remaining shipping2 tests DRAFT: chore: fix remaining shipping2 tests Dec 22, 2023
@erikdstock erikdstock force-pushed the erik.emi1526-saved-addresses branch from fb61cce to 17ac742 Compare December 22, 2023 22:34
const handleSelectSavedAddress = async (
address: ShippingAddressFormValues
) => {
await setValues({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to upgrade the formik version to give this the correct Promise return type (so that submitForm() will hopefully work as expected)

yarn.lock Show resolved Hide resolved
damassi
damassi previously approved these changes Dec 22, 2023
Copy link
Member

@damassi damassi left a comment

Choose a reason for hiding this comment

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

Once tests are green, LGTM 👍

(And congrats on wrapping this all up 🎉)

@@ -36,12 +36,21 @@ export const useSaveSelectedShippingQuote = (
try {
shippingContext.actions.setIsPerformingOperation(true)

if (
shippingContext.state.fulfillmentDetailsCtx?.values.meta.mode ===
Copy link
Contributor Author

@erikdstock erikdstock Feb 1, 2024

Choose a reason for hiding this comment

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

todo: can this be stored/pulled directly from shippingContext.state and not used in the form ctx?

I'll look into this but don't think it needs to block this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm going to leave this for now because it also includes the pickup mode, which isn't controlled from outside the component.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

update: I went ahead and did it. cleaner and also caught an invisible bug.

selectedSavedAddressId,
shippingContext.orderData.availableShippingCountries
)?.internalID
const addressList = compact<SavedAddressType>(
Copy link
Contributor Author

@erikdstock erikdstock Feb 1, 2024

Choose a reason for hiding this comment

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

Todo: Added addressList to shippingContext because it was used in hooks, and painful to thread in. But maybe this isn't necessary - a helper to compact an address list from anywhere could accomplish the same thing... Alternately it could be used here too instead of using local data from props.

Copy link
Contributor Author

@erikdstock erikdstock left a comment

Choose a reason for hiding this comment

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

Comments on approach and major changes.

const updateDefaultAddress = useUpdateUserDefaultAddress()
const shippingContext = useShippingContext()

const handleNewUserAddressUpdates = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file is key to the changes because the hook controls all updates to user addresses. It includes

  • handleNewUserAddressUpdates: deals with creating/updating/deleting a user's address when it is their first saved address (address form is still present, and they may edit it or even uncheck the box before saving shipping quotes)
  • executeUserAddressAction: Takes an action type (create/edit/delete) and performs that action, as well as setting as default if applicable. Used by the first method above.

Following this change all flows perform actions in the same order, Performing any user address updates before submitting fulfillment details/shipping quotes to exchange.

: null

useEffect(() => {
if (
bestArtsyShippingQuote &&
bestArtsyShippingQuote !==
shippingContext.orderData.selectedShippingQuoteId
shippingContext.orderData.selectedShippingQuoteID
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A lot of diff in this PR is me making _ID capitalization consistent. I chose the format used for internalID

"new_address"
) {
await handleNewUserAddressUpdates(
shippingContext.state.fulfillmentDetailsCtx.values
Copy link
Contributor Author

Choose a reason for hiding this comment

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

An effect of letting a user edit values in 2 steps at once - if they change the address, the shipping quotes will disappear and they will be back on the fulfillment details step. But if they check/uncheck the box we just need to save/delete the address - hence this line, which for consistency now happens before saving the shipping quote.

Another way to do this could be to have the save/unsave mutation triggered from inside fulfillment details immediately upon clicking the box. Might be a good follow-up.

"Order/Routes/Shipping/Hooks/useHandleUserAddressUpdates.tsx"
)

export const useHandleUserAddressUpdates = () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved to useUserAddressUpdates

* Go back to fulfillment details stage if the user edits the address or
* deletes a saved address.
*/
export const useBackToFullfillmentDetails = (me: Shipping2_me$data) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved this logic into a wrapper around specific handleChange callbacks that should trigger the change. It's cleaner, less code and easier to trace.

onChange={trackAutoCompleteEdits("postalCode", handleChange)}
onChange={withBackToFulfillmentDetails(
trackAutoCompleteEdits("postalCode", handleChange)
)}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

useBackToFulfillmentDetails hook behavior now wraps specific fields we want to track.

},
})

await formikContext.submitForm()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After saving a user's address, the form receives it in this callback, sets all the values (because it is in saved address mode the inputs are not visible) and submits the form. FulfillmentDetails controlls submitting the values to exchange only after SavedAddresses2 performs the user address updates.

export const GENERIC_FAIL_MESSAGE =
"Sorry, there has been an issue saving your address. Please try again."

const validationSchema = Yup.object().shape(ADDRESS_VALIDATION_SHAPE)
const handleGravityErrors = (
errors: ReadonlyArray<{ message: string }>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gravity mutation helpers in useUserAddressUpdates now always return a type something like {data: T} | {errors: [{message?: string}]} | null (null if no action was performed). This lets us handle the errors a bit more consistently.

title="Full name"
placeholder="Full name"
id="name"
name="attributes.name"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I copied these fields in so i could give them the attributes key nesting. On reflection, I'm not sure if this was necessary or was part of an earlier attempt to get mutation-calling consistent.

<Form data-testid="AddressModal">
{formikContext.status && (
<Banner my={2} data-testid="form-banner-error" variant="error">
{formikContext.status}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

using Formik's status for this now.

@erikdstock erikdstock changed the title DRAFT: chore: fix remaining shipping2 tests chore: fix remaining shipping2 tests, complete initial refactor Feb 1, 2024
@erikdstock erikdstock requested a review from damassi February 1, 2024 20:27
* TODO: This could be accomplished with useImperativeHandle(ref, formikContext)
*/
shippingContext.actions.setFormHelpers(formikContext)
shippingContext.actions.setFulfillmentDetailsCtx(formikContext)
Copy link
Member

Choose a reason for hiding this comment

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

We should be more descriptive in the name of this function, just so we can see that its a formik context versus some other type of context:

shippingContext.actions.setFulfillmentDetailsFormikContext(formikContext)

}
const addAddressButton = screen.getByText("Add a new address")
await userEvent.click(addAddressButton)
logTime("clicked button")
Copy link
Member

@damassi damassi Feb 1, 2024

Choose a reason for hiding this comment

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

Might want to remove these loggers before merge (though i can see how they might be helpful for future devs, too)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, maybe I can just link to this comment and make a ticket for it. Basically what I found was that the fillAddressForm helper takes a long time, not so much things related to relay.

type: string
}

export const useHandleSaveFulfillmentDetails = () => {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe just useSaveFulfillmentDetails, since the handleSaveFulfillmentDetails return value is just below? No need to double up

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, why did I do this ... 🤔 ...

  • useSaveFulfillmentDetails is the useMutation hook for just that operation
  • This hook includes prepping the values for the different mutation inputs (pickup, ship, artsy shipping) as well as the error handling
    I'll try to think about better naming.

@@ -30,7 +30,7 @@ export function useFeatureFlag(featureName: string): boolean | null {

if (flagEnabled === undefined) {
warnInDevelopment(
"[Force] Warning: cannot find flagName in featureFlags: ",
`[Force] Warning: cannot find ${featureName} in featureFlags: `,
Copy link
Member

Choose a reason for hiding this comment

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

👌

damassi
damassi previously approved these changes Feb 1, 2024
Copy link
Member

@damassi damassi left a comment

Choose a reason for hiding this comment

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

Beautiful @erikdstock 💯

@erikdstock erikdstock force-pushed the erik.emi1526-saved-addresses branch from b8f6f08 to e429d41 Compare February 2, 2024 14:17
@erikdstock erikdstock merged commit 78d4083 into main Feb 2, 2024
11 checks passed
@erikdstock erikdstock deleted the erik.emi1526-saved-addresses branch February 2, 2024 15:02
@artsy-peril artsy-peril bot mentioned this pull request Feb 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants