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

fix(EMI-2224): Refactor Register to Bid flow #11522

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ab5b11f
fix(EMI-2224): Refactor Register to Bid flow
MrSltun Jan 27, 2025
20f6fad
refactor confirm bid screen
MrSltun Feb 5, 2025
8f84872
refactor bid results screen
MrSltun Feb 5, 2025
b28c217
improve credit card screen performance
MrSltun Feb 6, 2025
4ec72ba
add BidFlowContextStore
MrSltun Feb 7, 2025
f4a084c
refactor Price Summary
MrSltun Feb 7, 2025
6184697
fix confirmBid tests
MrSltun Feb 10, 2025
94e3566
fix BidResult tests
MrSltun Feb 10, 2025
8f5165a
refactor bidding navigation
MrSltun Feb 10, 2025
961fa29
fix issue with Select options not rendering
MrSltun Feb 10, 2025
1d9b1a4
refactor and fix select max bid tests
MrSltun Feb 10, 2025
7278152
fix credit card form tests
MrSltun Feb 10, 2025
1c22dc0
fix phone number form tests
MrSltun Feb 10, 2025
4519003
fix registration result tests
MrSltun Feb 10, 2025
bdb7f90
refactor bid flow tests
MrSltun Feb 11, 2025
8bcaa9a
fix payment info tests
MrSltun Feb 11, 2025
b8821dc
fix registration tests
MrSltun Feb 11, 2025
5e45b53
fix bid result tests
MrSltun Feb 11, 2025
9a39d87
update confirm bid tests
MrSltun Feb 11, 2025
46f03cf
move BiddingNavigator to Navigation
MrSltun Feb 12, 2025
c7c60c4
remove else in backHandler
MrSltun Feb 12, 2025
408bcc9
add Sentry capturing for errors in the bid flow
MrSltun Feb 13, 2025
0bc6cde
replace captureException to captureMessage
MrSltun Feb 14, 2025
66f9f10
fix keyboard avoiding view for Android
MrSltun Feb 14, 2025
eeb2bca
promisify useCreateCreditCard hook
MrSltun Feb 14, 2025
cb37f7a
add sentry mocks in ConfirmBid tests
MrSltun Feb 14, 2025
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
301 changes: 164 additions & 137 deletions src/app/Components/Bidding/BidFlow.tests.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { Checkbox } from "@artsy/palette-mobile"
import { createToken } from "@stripe/stripe-react-native"
import { fireEvent, screen } from "@testing-library/react-native"
import { BidderPositionQuery$data } from "__generated__/BidderPositionQuery.graphql"
import {
BidFlowContextProvider,
BidFlowContextStore,
} from "app/Components/Bidding/Context/BidFlowContextProvider"
import { bidderPositionQuery } from "app/Components/Bidding/Screens/ConfirmBid/BidderPositionQuery"
import { Select } from "app/Components/Select"
import { extractText } from "app/utils/tests/extractText"
import { renderWithWrappersLEGACY } from "app/utils/tests/renderWithWrappers"
import { waitUntil } from "app/utils/tests/waitUntil"
import { BiddingNavigator } from "app/Navigation/AuthenticatedRoutes/BiddingNavigator"
import { useCreateBidderPosition } from "app/utils/mutations/useCreateBidderPosition"
import { useCreateCreditCard } from "app/utils/mutations/useCreateCreditCard"
import { useUpdateUserPhoneNumber } from "app/utils/mutations/useUpdateUserPhoneNumber"
import { setupTestWrapper } from "app/utils/tests/setupTestWrapper"
import "react-native"
import relay from "react-relay"
import { FakeNavigator } from "./Helpers/FakeNavigator"
import { SelectMaxBid } from "./Screens/SelectMaxBid"

jest.mock("app/Components/Bidding/Screens/ConfirmBid/PriceSummary", () => ({
PriceSummary: () => null,
}))
import { graphql } from "react-relay"

jest.mock("app/Components/Bidding/Screens/ConfirmBid/BidderPositionQuery", () => ({
bidderPositionQuery: jest.fn(),
Expand All @@ -23,116 +22,150 @@ jest.mock("@stripe/stripe-react-native", () => ({
createToken: jest.fn(),
}))

const commitMutationMock = (fn?: typeof relay.commitMutation) =>
jest.fn<typeof relay.commitMutation, Parameters<typeof relay.commitMutation>>(fn as any)
jest.mock("app/utils/mutations/useCreateBidderPosition", () => ({
useCreateBidderPosition: jest.fn(),
}))

jest.mock("app/utils/mutations/useCreateCreditCard", () => ({
useCreateCreditCard: jest.fn(),
}))

jest.mock("app/utils/mutations/useUpdateUserPhoneNumber", () => ({
useUpdateUserPhoneNumber: jest.fn(),
}))

describe("BidFlow", () => {
const bidderPositionQueryMock = bidderPositionQuery as jest.Mock<any>
const useUpdateUserPhoneNumberMock = useUpdateUserPhoneNumber as jest.Mock
const useCreateCreditCardMock = useCreateCreditCard as jest.Mock<any>
const useCreateBidderPositionMock = useCreateBidderPosition as jest.Mock<any>

const bidderPositionQueryMock = bidderPositionQuery as jest.Mock<any>
let fakeNavigator: FakeNavigator
let fakeRelay: any
let mockStore: ReturnType<typeof BidFlowContextStore.useStore>

beforeEach(() => {
fakeNavigator = new FakeNavigator()
fakeRelay = {
refetch: jest.fn(),
const MockStoreInstance = () => {
mockStore = BidFlowContextStore.useStore()
return null
}
})

it("allows bidders with a qualified credit card to bid", async () => {
let screen = renderWithWrappersLEGACY(
<SelectMaxBid
me={Me.qualifiedUser as any}
sale_artwork={SaleArtwork as any}
navigator={fakeNavigator as any}
relay={fakeRelay as any}
/>
)

screen.root.findByType(Select).props.onSelectValue(null, 2)
screen.root.findByProps({ testID: "next-button" }).props.onPress()

screen = fakeNavigator.nextStep()
expect(extractText(screen.root)).toContain("Confirm your bid")
;(createToken as jest.Mock).mockReturnValueOnce(stripeToken)

bidderPositionQueryMock.mockReturnValueOnce(
Promise.resolve(mockRequestResponses.pollingForBid.highestBidder)
)
relay.commitMutation = commitMutationMock((_, { onCompleted }) => {
onCompleted!(mockRequestResponses.placingBid.bidAccepted, null)
return { dispose: jest.fn() }
}) as any

screen.root.findByType(Checkbox).props.onPress()
screen.root.findByProps({ testID: "bid-button" }).props.onPress()

await waitUntil(() => fakeNavigator.stackSize() === 2)

screen = fakeNavigator.nextStep()
expect(extractText(screen.root)).toContain("You’re the highest bidder")
})
const { renderWithRelay } = setupTestWrapper({
Component: (props: any) => {
return (
<BidFlowContextProvider>
<BiddingNavigator
initialRouteName="SelectMaxBid"
artworkID="meteor-shower"
saleID="best-art-sale-in-town"
{...props}
/>
<MockStoreInstance />
</BidFlowContextProvider>
)
},
query: graphql`
query BidFlowTestsQuery($artworkID: String!, $saleID: String!) {
artwork(id: $artworkID) {
saleArtwork(saleID: $saleID) {
...SelectMaxBid_saleArtwork
}
}
me {
...SelectMaxBid_me
}
}
`,
variables: { artworkID: "meteor-shower", saleID: "best-art-sale-in-town" },
})

beforeEach(() => {
useUpdateUserPhoneNumberMock.mockReturnValue([jest.fn(), false])
useCreateCreditCardMock.mockReturnValue([jest.fn(), false])
useCreateBidderPositionMock.mockReturnValue([jest.fn(), false])
})

it("allows bidders without a qualified credit card to register a card and bid", async () => {
let screen = renderWithWrappersLEGACY(
<SelectMaxBid
me={Me.unqualifiedUser as any}
sale_artwork={SaleArtwork as any}
navigator={fakeNavigator as any}
relay={fakeRelay}
/>
)
it("allows bidders with a qualified credit card to bid", async () => {
const mockCreateBidderPositionMutation = jest.fn().mockImplementation(({ onCompleted }) => {
onCompleted(mockRequestResponses.placingBid.bidAccepted)
})

screen.root.findByType(Select).props.onSelectValue(null, 2)
screen.root.findByProps({ testID: "next-button" }).props.onPress()
useCreateBidderPositionMock.mockReturnValue([mockCreateBidderPositionMutation, false])
renderWithRelay({
SaleArtwork: () => saleArtwork,
Me: () => me.qualifiedUser,
})

screen = fakeNavigator.nextStep()
// Select Max Bid
expect(screen.getByText("$35,000")).toBeOnTheScreen()
mockStore.getActions().setSelectedBidIndex(2)
expect(screen.getByText("$45,000")).toBeOnTheScreen()
fireEvent.press(screen.getByText("Next"))

expect(extractText(screen.root)).toContain("Confirm your bid")
;(createToken as jest.Mock).mockReturnValueOnce(stripeToken)
// Confirm Bid
expect(screen.getByText("Confirm your bid")).toBeOnTheScreen()
;(createToken as jest.Mock).mockReturnValueOnce(stripeToken)

relay.commitMutation = jest
.fn()
.mockImplementationOnce((_, { onCompleted }) =>
onCompleted(mockRequestResponses.updateMyUserProfile)
bidderPositionQueryMock.mockReturnValueOnce(
Promise.resolve(mockRequestResponses.pollingForBid.highestBidder)
)
.mockImplementationOnce((_, { onCompleted }) =>

expect(
screen.getByText(/I agree to Artsy's General Terms and Conditions of Sale/)
).toBeOnTheScreen()

fireEvent.press(screen.getByTestId("disclaimer-checkbox"))
fireEvent.press(screen.getByTestId("bid-button"))

// Bid Result
await screen.findByText("You’re the highest bidder")
})

it("allows bidders without a qualified credit card to register a card and bid", async () => {
const mockUpdateUserPhoneNumberMutation = jest.fn().mockImplementation(({ onCompleted }) => {
onCompleted(mockRequestResponses.updateMyUserProfile)
})
const mockCreateCreditCardMutation = jest.fn().mockImplementation(({ onCompleted }) => {
onCompleted(mockRequestResponses.creatingCreditCardSuccess)
)
.mockImplementationOnce((_, { onCompleted }) =>
})
const mockCreateBidderPositionMutation = jest.fn().mockImplementation(({ onCompleted }) => {
onCompleted(mockRequestResponses.placingBid.bidAccepted)
})

useUpdateUserPhoneNumberMock.mockReturnValue([mockUpdateUserPhoneNumberMutation, false])
useCreateCreditCardMock.mockReturnValue([mockCreateCreditCardMutation, false])
useCreateBidderPositionMock.mockReturnValue([mockCreateBidderPositionMutation, false])

renderWithRelay({
SaleArtwork: () => saleArtwork,
Me: () => me.unqualifiedUser,
})

// Select Max Bid
expect(screen.getByText("$35,000")).toBeOnTheScreen()
mockStore.getActions().setSelectedBidIndex(2)
expect(screen.getByText("$45,000")).toBeOnTheScreen()
fireEvent.press(screen.getByText("Next"))

// Confirm Bid
expect(screen.getByText("Confirm your bid")).toBeOnTheScreen()
;(createToken as jest.Mock).mockReturnValueOnce(stripeToken)

bidderPositionQueryMock.mockReturnValueOnce(
Promise.resolve(mockRequestResponses.pollingForBid.highestBidder)
)
bidderPositionQueryMock.mockReturnValueOnce(
Promise.resolve(mockRequestResponses.pollingForBid.highestBidder)
)

// // manually setting state to avoid duplicating tests for skipping UI interaction, but practically better not to do this.
// screen.root.findByProps({ nextScreen: true }).instance.setState({
// billingAddress,
// creditCardFormParams,
// creditCardToken: {
// card: {
// brand: "visa",
// last4: "4242",
// },
// },
// })

// screen.root.findByType(Checkbox).props.onPress()
// await screen.root.findAllByType(Button)[1].props.onPress()

// expect(stripe.createTokenWithCard).toHaveBeenCalledWith({
// ...creditCardFormParams,
// name: billingAddress.fullName,
// addressLine1: billingAddress.addressLine1,
// addressLine2: billingAddress.addressLine2,
// addressCity: billingAddress.city,
// addressState: billingAddress.state,
// addressZip: billingAddress.postalCode,
// addressCountry: billingAddress.country.shortName,
// })

// screen = fakeNavigator.nextStep()

// expect(extractText(screen.root)).toContain("You’re the highest bidder")

// mimic adding a credit card
mockStore.getActions().setBillingAddress(billingAddress)
mockStore.getActions().setCreditCardToken(stripeToken as any)

expect(
screen.getByText(/I agree to Artsy's General Terms and Conditions of Sale/)
).toBeOnTheScreen()

fireEvent.press(screen.getByTestId("disclaimer-checkbox"))
fireEvent.press(screen.getByTestId("bid-button"))

// Bid Result
await screen.findByText("You’re the highest bidder")
})
})

const stripeToken = {
Expand All @@ -145,51 +178,45 @@ const stripeToken = {
},
}

// const billingAddress = {
// fullName: "Yuki Stockmeier",
// addressLine1: "401 Broadway",
// addressLine2: "25th floor",
// city: "New York",
// state: "NY",
// postalCode: "10013",
// phoneNumber: "111 222 333",
// country: {
// longName: "United States",
// shortName: "US",
// },
// }

// const creditCardFormParams = {
// number: "4242424242424242",
// expMonth: "12",
// expYear: "2020",
// cvc: "314",
// }

const Me = {
const billingAddress = {
fullName: "Yuki Stockmeier",
addressLine1: "401 Broadway",
addressLine2: "25th floor",
city: "New York",
state: "NY",
postalCode: "10013",
phoneNumber: "111 222 333",
country: {
longName: "United States",
shortName: "US",
},
}

const me = {
qualifiedUser: {
has_qualified_credit_cards: true,
hasQualifiedCreditCards: true,
},
unqualifiedUser: {
has_qualified_credit_cards: false,
hasQualifiedCreditCards: false,
},
}

const SaleArtwork = {
const saleArtwork = {
internalID: "sale-artwork-id",
artwork: {
id: "meteor shower",
title: "Meteor Shower",
date: "2015",
artist_names: "Makiko Kudo",
artistNames: "Makiko Kudo",
image: {
url: "https://d32dm0rphc51dk.cloudfront.net/5RvuM9YF68AyD8OgcdLw7g/small.jpg",
},
},
sale: {
id: "best-art-sale-in-town",
bidder: null,
},
lot_label: "538",
lotLabel: "538",
increments: [
{
display: "$35,000",
Expand Down Expand Up @@ -230,7 +257,7 @@ const mockRequestResponses = {
pollingForBid: {
highestBidder: {
me: {
bidder_position: {
bidderPosition: {
status: "WINNING",
position: {},
},
Expand Down
Loading