Skip to content

Commit

Permalink
fix: refactor CreditCardForm for auction registration (#10642)
Browse files Browse the repository at this point in the history
* fix: refactor CreditCardForm for auction registration

* un-reinvent the wheel 🛞 aka use formik 😄

* update error messages

* update tests

* move field descriptions and validation out of component

* replace Stack and mark it as deprecated

* minor changes + update tests

* small refactor in CreditCardForm

* fix confirmBid tests

* refactor error displaying in credit card form
  • Loading branch information
MrSltun committed Aug 22, 2024
1 parent 3392154 commit 82a1cfa
Show file tree
Hide file tree
Showing 12 changed files with 518 additions and 311 deletions.
24 changes: 7 additions & 17 deletions src/app/Components/Bidding/Components/PaymentInfo.tests.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Text } from "@artsy/palette-mobile"
import { BidInfoRow } from "app/Components/Bidding/Components/BidInfoRow"
import { BillingAddress } from "app/Components/Bidding/Screens/BillingAddress"
import { CreditCardForm } from "app/Components/Bidding/Screens/CreditCardForm"
import NavigatorIOS, {
NavigatorIOSPushArgs,
Expand All @@ -24,26 +23,17 @@ it("renders without throwing an error", () => {
renderWithWrappersLEGACY(<PaymentInfo {...initialProps} />)
})

it("shows the billing address that the user typed in the billing address form", () => {
const billingAddressRow = renderWithWrappersLEGACY(
<PaymentInfo {...initialProps} />
).root.findAllByType(BidInfoRow)[1]
billingAddressRow.instance.props.onPress()
expect(nextStep.component).toEqual(BillingAddress)
it("shows the cc info that the user had typed into the form", async () => {
const { root } = renderWithWrappersLEGACY(<PaymentInfo {...initialProps} />)

expect(billingAddressRow.findAllByType(Text)[1].props.children).toEqual(
"401 Broadway 25th floor New York NY"
)
})
const creditCardRow = await root.findAllByType(BidInfoRow)

it("shows the cc info that the user had typed into the form", () => {
const creditCardRow = renderWithWrappersLEGACY(
<PaymentInfo {...initialProps} />
).root.findAllByType(BidInfoRow)[0]
creditCardRow.instance.props.onPress()
creditCardRow[0].instance.props.onPress()
expect(nextStep.component).toEqual(CreditCardForm)

expect(creditCardRow.findAllByType(Text)[1].props.children).toEqual("VISA •••• 4242")
const creditCardRowText = await creditCardRow[0].findAllByType(Text)

expect(creditCardRowText[1].props.children).toEqual("VISA •••• 4242")
})

const billingAddress = {
Expand Down
45 changes: 5 additions & 40 deletions src/app/Components/Bidding/Components/PaymentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { bullet } from "@artsy/palette-mobile"
import { Token } from "@stripe/stripe-react-native"
import { Card } from "@stripe/stripe-react-native/lib/typescript/src/types/Token"
import { FlexProps } from "app/Components/Bidding/Elements/Flex"
import { BillingAddress } from "app/Components/Bidding/Screens/BillingAddress"
import { CreditCardForm } from "app/Components/Bidding/Screens/CreditCardForm"
import { Address, PaymentCardTextFieldParams } from "app/Components/Bidding/types"
import NavigatorIOS from "app/utils/__legacy_do_not_use__navigator-ios-shim"
Expand All @@ -14,8 +13,7 @@ import { Divider } from "./Divider"

interface PaymentInfoProps extends FlexProps {
navigator?: NavigatorIOS
onCreditCardAdded: (t: Token.Result, p: PaymentCardTextFieldParams) => void
onBillingAddressAdded: (values: Address) => void
onCreditCardAdded: (t: Token.Result, a: Address) => void
billingAddress?: Address | null
creditCardFormParams?: PaymentCardTextFieldParams | null
creditCardToken?: Token.Result | null
Expand All @@ -31,36 +29,19 @@ export class PaymentInfo extends React.Component<PaymentInfoProps> {
component: CreditCardForm,
title: "",
passProps: {
onSubmit: (token: Token.Result, params: PaymentCardTextFieldParams) =>
this.onCreditCardAdded(token, params),
params: this.props.creditCardFormParams,
navigator: this.props.navigator,
},
})
}

presentBillingAddressForm() {
this.props.navigator?.push({
component: BillingAddress,
title: "",
passProps: {
onSubmit: (address: Address) => this.onBillingAddressAdded(address),
onSubmit: (token: Token.Result, address: Address) => this.onCreditCardAdded(token, address),
billingAddress: this.props.billingAddress,
navigator: this.props.navigator,
},
})
}

onCreditCardAdded(token: Token.Result, params: PaymentCardTextFieldParams) {
this.props.onCreditCardAdded(token, params)
}

onBillingAddressAdded(values: Address) {
this.props.onBillingAddressAdded(values)
onCreditCardAdded(token: Token.Result, address: Address) {
this.props.onCreditCardAdded(token, address)
}

render() {
const { billingAddress, creditCardToken: token } = this.props
const { creditCardToken: token } = this.props

return (
<View>
Expand All @@ -73,27 +54,11 @@ export class PaymentInfo extends React.Component<PaymentInfoProps> {
/>

<Divider />

<BidInfoRow
label="Billing address"
value={billingAddress ? this.formatAddress(billingAddress) : ""}
onPress={() => {
this.presentBillingAddressForm()
}}
/>

<Divider />
</View>
)
}

private formatCard(card: Card) {
return `${card.brand} ${bullet}${bullet}${bullet}${bullet} ${card.last4}`
}

private formatAddress(address: Address) {
return [address.addressLine1, address.addressLine2, address.city, address.state]
.filter((el) => el)
.join(" ")
}
}
51 changes: 33 additions & 18 deletions src/app/Components/Bidding/Screens/ConfirmBid.tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,14 @@ describe("ConfirmBid", () => {
expect(component.root.findAllByType(BidInfoRow).length).toEqual(1)
})

it("shows a checkbox and payment info if the user is not registered and has no cc on file", () => {
const component = mountConfirmBidComponent(initialPropsForUnqualifiedUser)
it("shows a checkbox and payment info if the user is not registered and has no cc on file", async () => {
const { root } = mountConfirmBidComponent(initialPropsForUnqualifiedUser)

expect(component.root.findAllByType(Checkbox).length).toEqual(1)
expect(component.root.findAllByType(BidInfoRow).length).toEqual(3)
const checkboxs = await root.findAllByType(Checkbox)
const bidInfoRows = await root.findAllByType(BidInfoRow)

expect(checkboxs.length).toEqual(1)
expect(bidInfoRows.length).toEqual(2)
})
})

Expand Down Expand Up @@ -700,15 +703,21 @@ describe("ConfirmBid", () => {
})

describe("ConfirmBid for unqualified user", () => {
const fillOutFormAndSubmit = (component: ReactTestRenderer) => {
const fillOutFormAndSubmit = async (component: ReactTestRenderer) => {
const confirmBidComponent = await component.root.findByType(ConfirmBid)
// manually setting state to avoid duplicating tests for skipping UI interaction, but practically better not to do this.
component.root.findByType(ConfirmBid).instance.setState({ billingAddress })
component.root.findByType(ConfirmBid).instance.setState({ creditCardToken: stripeToken })
component.root.findByType(Checkbox).props.onPress()
findPlaceBidButton(component).props.onPress()
confirmBidComponent.instance.setState({ billingAddress })
confirmBidComponent.instance.setState({ creditCardToken: stripeToken.token })

const checkbox = await component.root.findByType(Checkbox)
checkbox.props.onPress()

const bidButton = await findPlaceBidButton(component)
bidButton.props.onPress()
}

it("shows the billing address that the user typed in the billing address form", () => {
// skipping since we don't have billing address now
xit("shows the billing address that the user typed in the billing address form", () => {
const billingAddressRow = mountConfirmBidComponent(
initialPropsForUnqualifiedUser
).root.findAllByType(TouchableWithoutFeedback)[2]
Expand Down Expand Up @@ -794,7 +803,7 @@ describe("ConfirmBid", () => {
expect(screen.UNSAFE_getByType(Modal)).toHaveProp("visible", false)
})

it("shows the error screen with the default error message if there are unhandled errors from the createCreditCard mutation", () => {
it("shows the error screen with the default error message if there are unhandled errors from the createCreditCard mutation", async () => {
const errors = [{ message: "malformed error" }]

console.error = jest.fn() // Silences component logging.
Expand All @@ -806,15 +815,19 @@ describe("ConfirmBid", () => {

const component = mountConfirmBidComponent(initialPropsForUnqualifiedUser)

fillOutFormAndSubmit(component)
await fillOutFormAndSubmit(component)

const modal = await component.root.findByType(Modal)
const modalText = await modal.findAllByType(Text)
const modalButton = await modal.findByType(Button)

expect(component.root.findByType(Modal).findAllByType(Text)[1].props.children).toEqual([
expect(modalText[1].props.children).toEqual([
"There was a problem processing your information. Check your payment details and try again.",
])
component.root.findByType(Modal).findByType(Button).props.onPress()
modalButton.props.onPress()

// it dismisses the modal
expect(component.root.findByType(Modal).props.visible).toEqual(false)
expect(modal.props.visible).toEqual(false)
})

it("shows the error screen with the default error message if the creditCardMutation error message is empty", async () => {
Expand Down Expand Up @@ -846,7 +859,7 @@ describe("ConfirmBid", () => {
expect(screen.UNSAFE_getByType(Modal)).toHaveProp("visible", false)
})

it("shows the generic error screen on a createCreditCard mutation network failure", () => {
it("shows the generic error screen on a createCreditCard mutation network failure", async () => {
console.error = jest.fn() // Silences component logging.
;(createToken as jest.Mock).mockReturnValueOnce(stripeToken)
relay.commitMutation = commitMutationMock((_, { onError }) => {
Expand All @@ -856,7 +869,7 @@ describe("ConfirmBid", () => {

const component = mountConfirmBidComponent(initialPropsForUnqualifiedUser)

fillOutFormAndSubmit(component)
await fillOutFormAndSubmit(component)

expect(nextStep?.component).toEqual(BidResultScreen)
expect(nextStep?.passProps).toEqual(
Expand Down Expand Up @@ -895,7 +908,9 @@ describe("ConfirmBid", () => {

// UNSAFELY getting the component instance to set state for testing purposes only
screen.UNSAFE_getByType(ConfirmBid).instance.setState({ billingAddress })
screen.UNSAFE_getByType(ConfirmBid).instance.setState({ creditCardToken: stripeToken })
screen
.UNSAFE_getByType(ConfirmBid)
.instance.setState({ creditCardToken: stripeToken.token })

// Check the checkbox and press the Bid button
fireEvent.press(screen.UNSAFE_getByType(Checkbox))
Expand Down
19 changes: 7 additions & 12 deletions src/app/Components/Bidding/Screens/ConfirmBid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,12 @@ export class ConfirmBid extends React.Component<ConfirmBidProps, ConfirmBidState
/** Run through the full flow setting up the user account and making a bid */
async setupAddressCardAndBidderPosition() {
try {
await this.updatePhoneNumber()
const token = await this.createTokenFromAddress()
if (token.error) {
throw new Error(`[Stripe]: error creating the token: ${JSON.stringify(token.error)}`)
if (!this.state.creditCardToken) {
throw new Error("[ConfirmBid] Credit card token not present")
}
await this.createCreditCard(token.token)

await this.updatePhoneNumber()
await this.createCreditCard(this.state.creditCardToken)
await this.createBidderPosition()
} catch (error) {
if (!this.state.errorModalVisible) {
Expand Down Expand Up @@ -376,12 +376,8 @@ export class ConfirmBid extends React.Component<ConfirmBidProps, ConfirmBidState
)
}

onCreditCardAdded(token: Token.Result, params: PaymentCardTextFieldParams) {
this.setState({ creditCardToken: token, creditCardFormParams: params })
}

onBillingAddressAdded(values: Address) {
this.setState({ billingAddress: values })
onCreditCardAdded(token: Token.Result, address: Address) {
this.setState({ creditCardToken: token, billingAddress: address })
}

presentErrorResult(error: Error | ReadonlyArray<PayloadError> | null | undefined) {
Expand Down Expand Up @@ -533,7 +529,6 @@ export class ConfirmBid extends React.Component<ConfirmBidProps, ConfirmBidState
<PaymentInfo
navigator={isLoading ? ({ push: () => null } as any) : this.props.navigator}
onCreditCardAdded={this.onCreditCardAdded.bind(this)}
onBillingAddressAdded={this.onBillingAddressAdded.bind(this)}
billingAddress={this.state.billingAddress}
creditCardFormParams={this.state.creditCardFormParams}
creditCardToken={this.state.creditCardToken}
Expand Down
Loading

0 comments on commit 82a1cfa

Please sign in to comment.