Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
suejung-sentry committed Jan 17, 2025
1 parent 3f335c5 commit 455f129
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ describe('AddressCard', () => {
{ wrapper }
)

expect(screen.getByText('Cardholder name')).toBeInTheDocument()
expect(screen.getByText('Full name')).toBeInTheDocument()
expect(screen.getByText('N/A')).toBeInTheDocument()
expect(screen.getByText('Billing address')).toBeInTheDocument()
expect(screen.queryByText(/null/)).not.toBeInTheDocument()
Expand All @@ -241,7 +241,7 @@ describe('AddressCard', () => {
{ wrapper }
)

expect(screen.getByText(/Cardholder name/)).toBeInTheDocument()
expect(screen.getByText(/Full name/)).toBeInTheDocument()
expect(screen.getByText(/Bob Smith/)).toBeInTheDocument()
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function AddressCard({
{!isFormOpen && (
<>
<div className="flex justify-between">
<h4 className="font-semibold">Cardholder name</h4>
<h4 className="font-semibold">Full name</h4>
<A
variant="semibold"
onClick={() => setIsFormOpen(true)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { z } from 'zod'

import bankLogo from 'assets/billing/bank.svg'
import { SubscriptionDetailSchema } from 'services/account'

interface BankInformationProps {
subscriptionDetail: z.infer<typeof SubscriptionDetailSchema>
}
function BankInformation({ subscriptionDetail }: BankInformationProps) {
return (
<div className="flex flex-col gap-2">
<div className="flex gap-1">
<img src={bankLogo} alt="bank logo" />
<div className="ml-1 flex flex-col self-center">
<b>
{subscriptionDetail?.defaultPaymentMethod?.usBankAccount?.bankName}
&nbsp;••••&nbsp;
{subscriptionDetail?.defaultPaymentMethod?.usBankAccount?.last4}
</b>
</div>
</div>
</div>
)
}

export default BankInformation
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import A from 'ui/A'
import Button from 'ui/Button'
import Icon from 'ui/Icon'

import BankInformation from './BankInformation'
import CardInformation from './CardInformation'
import PaymentMethodForm from './PaymentMethodForm'
function PaymentCard({ subscriptionDetail, provider, owner }) {
const [isFormOpen, setIsFormOpen] = useState(false)
const card = subscriptionDetail?.defaultPaymentMethod?.card
const usBankAccount = subscriptionDetail?.defaultPaymentMethod?.usBankAccount

return (
<div className="flex flex-col gap-2 border-t p-4">
Expand All @@ -20,7 +22,7 @@ function PaymentCard({ subscriptionDetail, provider, owner }) {
<A
variant="semibold"
onClick={() => setIsFormOpen(true)}
hook="edit-card"
hook="edit-payment-method"
>
Edit <Icon name="chevronRight" size="sm" variant="solid" />
</A>
Expand All @@ -31,14 +33,17 @@ function PaymentCard({ subscriptionDetail, provider, owner }) {
provider={provider}
owner={owner}
closeForm={() => setIsFormOpen(false)}
subscriptionDetail={subscriptionDetail}
/>
) : card ? (
<CardInformation card={card} subscriptionDetail={subscriptionDetail} />
) : usBankAccount ? (
<BankInformation subscriptionDetail={subscriptionDetail} />
) : (
<div className="flex flex-col gap-4 text-ds-gray-quinary">
<p className="mt-4">
No credit card set. Please contact support if you think it’s an
error or set it yourself.
No payment method set. Please contact support if you think it&apos;s
an error or set it yourself.
</p>
<div className="flex self-start">
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

Expand All @@ -6,18 +7,26 @@ import { Plans } from 'shared/utils/billing'

import PaymentCard from './PaymentCard'

const queryClient = new QueryClient()

const mocks = vi.hoisted(() => ({
useUpdateCard: vi.fn(),
useUpdatePaymentMethod: vi.fn(),
useCreateStripeSetupIntent: vi.fn(),
}))

vi.mock('services/account', async () => {
const actual = await vi.importActual('services/account')
return {
...actual,
useUpdateCard: mocks.useUpdateCard,
useUpdatePaymentMethod: mocks.useUpdatePaymentMethod,
useCreateStripeSetupIntent: mocks.useCreateStripeSetupIntent,
}
})

afterEach(() => {
vi.clearAllMocks()
})

const subscriptionDetail = {
defaultPaymentMethod: {
card: {
Expand All @@ -35,7 +44,9 @@ const subscriptionDetail = {
}

const wrapper = ({ children }) => (
<ThemeContextProvider>{children}</ThemeContextProvider>
<QueryClientProvider client={queryClient}>
<ThemeContextProvider>{children}</ThemeContextProvider>
</QueryClientProvider>
)

// mocking all the stripe components; and trusting the library :)
Expand All @@ -48,9 +59,10 @@ vi.mock('@stripe/react-stripe-js', () => {
return {
useElements: () => ({
getElement: vi.fn(),
submit: vi.fn(),
}),
useStripe: () => ({}),
CardElement: makeFakeComponent('CardElement'),
PaymentElement: makeFakeComponent('PaymentElement'),
}
})

Expand All @@ -64,20 +76,20 @@ describe('PaymentCard', () => {
describe(`when the user doesn't have any subscriptionDetail`, () => {
// NOTE: This test is misleading because we hide this component from a higher level in
// BillingDetails.tsx if there is no subscriptionDetail
it('renders the set card message', () => {
it('renders the set payment method message', () => {
render(
<PaymentCard subscriptionDetail={null} provider="gh" owner="codecov" />
)

expect(
screen.getByText(
/No credit card set. Please contact support if you think its an error or set it yourself./
/No payment method set. Please contact support if you think it's an error or set it yourself./
)
).toBeInTheDocument()
})
})

describe(`when the user doesn't have any card`, () => {
describe(`when the user doesn't have any payment method`, () => {
it('renders an error message', () => {
render(
<PaymentCard
Expand All @@ -93,7 +105,7 @@ describe('PaymentCard', () => {

expect(
screen.getByText(
/No credit card set. Please contact support if you think its an error or set it yourself./
/No payment method set. Please contact support if you think it's an error or set it yourself./
)
).toBeInTheDocument()
})
Expand All @@ -113,7 +125,7 @@ describe('PaymentCard', () => {
{ wrapper }
)

mocks.useUpdateCard.mockReturnValue({
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: () => null,
isLoading: false,
})
Expand All @@ -136,14 +148,14 @@ describe('PaymentCard', () => {
{ wrapper }
)

mocks.useUpdateCard.mockReturnValue({
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: () => null,
isLoading: false,
})
await user.click(screen.getByTestId('open-modal'))

expect(
screen.getByRole('button', { name: /update/i })
screen.getByRole('button', { name: /save/i })
).toBeInTheDocument()
})
})
Expand Down Expand Up @@ -199,9 +211,9 @@ describe('PaymentCard', () => {
describe('when the user clicks on Edit card', () => {
it(`doesn't render the card anymore`, async () => {
const { user } = setup()
const updateCard = vi.fn()
mocks.useUpdateCard.mockReturnValue({
mutate: updateCard,
const updatePaymentMethod = vi.fn()
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: updatePaymentMethod,
isLoading: false,
})

Expand All @@ -213,16 +225,16 @@ describe('PaymentCard', () => {
/>,
{ wrapper }
)
await user.click(screen.getByTestId('edit-card'))
await user.click(screen.getByTestId('edit-payment-method'))

expect(screen.queryByText(/Visa/)).not.toBeInTheDocument()
})

it('renders the form', async () => {
const { user } = setup()
const updateCard = vi.fn()
mocks.useUpdateCard.mockReturnValue({
mutate: updateCard,
const updatePaymentMethod = vi.fn()
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: updatePaymentMethod,
isLoading: false,
})
render(
Expand All @@ -233,21 +245,23 @@ describe('PaymentCard', () => {
/>,
{ wrapper }
)
await user.click(screen.getByTestId('edit-card'))
await user.click(screen.getByTestId('edit-payment-method'))

expect(
screen.getByRole('button', { name: /update/i })
).toBeInTheDocument()
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument()
})

describe('when submitting', () => {
it('calls the service to update the card', async () => {
const { user } = setup()
const updateCard = vi.fn()
mocks.useUpdateCard.mockReturnValue({
mutate: updateCard,
const updatePaymentMethod = vi.fn()
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: updatePaymentMethod,
isLoading: false,
})
mocks.useCreateStripeSetupIntent.mockReturnValue({
data: { clientSecret: 'test-secret' },
})

render(
<PaymentCard
subscriptionDetail={subscriptionDetail}
Expand All @@ -256,17 +270,17 @@ describe('PaymentCard', () => {
/>,
{ wrapper }
)
await user.click(screen.getByTestId('edit-card'))
await user.click(screen.queryByRole('button', { name: /update/i }))
await user.click(screen.getByTestId('edit-payment-method'))
await user.click(screen.getByRole('button', { name: /save/i }))

expect(updateCard).toHaveBeenCalled()
expect(updatePaymentMethod).toHaveBeenCalled()

Check failure on line 276 in src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx

View workflow job for this annotation

GitHub Actions / Test Runner #2 - Vitest

src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx > PaymentCard > when the user clicks on Edit card > when submitting > calls the service to update the card

AssertionError: expected "spy" to be called at least once ❯ src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx:276:37
})
})

describe('when the user clicks on cancel', () => {
it(`doesn't render the form anymore`, async () => {
const { user } = setup()
mocks.useUpdateCard.mockReturnValue({
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: vi.fn(),
isLoading: false,
})
Expand All @@ -279,11 +293,11 @@ describe('PaymentCard', () => {
{ wrapper }
)

await user.click(screen.getByTestId('edit-card'))
await user.click(screen.getByTestId('edit-payment-method'))
await user.click(screen.getByRole('button', { name: /Cancel/ }))

expect(
screen.queryByRole('button', { name: /save/i })
screen.queryByTestId('update-payment-method')
).not.toBeInTheDocument()
})
})
Expand All @@ -293,7 +307,7 @@ describe('PaymentCard', () => {
it('renders the error', async () => {
const { user } = setup()
const randomError = 'not rich enough'
mocks.useUpdateCard.mockReturnValue({
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: vi.fn(),
error: { message: randomError },
})
Expand All @@ -306,7 +320,7 @@ describe('PaymentCard', () => {
{ wrapper }
)

await user.click(screen.getByTestId('edit-card'))
await user.click(screen.getByTestId('edit-payment-method'))

expect(screen.getByText(randomError)).toBeInTheDocument()

Check failure on line 325 in src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx

View workflow job for this annotation

GitHub Actions / Test Runner #2 - Vitest

src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx > PaymentCard > when there is an error in the form > renders the error

TestingLibraryElementError: Unable to find an element with the text: not rich enough. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body class="light" > <div> <div class="flex flex-col gap-2 border-t p-4" > <div class="flex justify-between" > <h4 class="font-semibold" > Payment method </h4> </div> <form aria-label="form" > <div class="flex flex-col gap-3" > <div class="mt-2 flex flex-col gap-2" > PaymentElement <p class="mt-1 text-ds-primary-red" /> <div class="mb-8 mt-4 flex gap-1" > <button class=" flex items-center gap-1 rounded py-1 px-4 transition-colors duration-150 motion-reduce:transition-none focus:outline-none focus:ring disabled:cursor-not-allowed disabled:text-ds-gray-quaternary disabled:border-ds-gray-tertiary disabled:bg-ds-gray-primary justify-center font-semibold text-white bg-ds-blue-darker dark:bg-ds-blue-nonary border-ds-blue-quinary border-solid border shadow hover:bg-ds-blue-quinary " data-cy="update-payment-method" data-marketing="update-payment-method" data-testid="update-payment-method" type="submit" > Save </button> <button class=" flex items-center gap-1 rounded py-1 px-4 transition-colors duration-150 motion-reduce:transition-none focus:outline-none focus:ring disabled:cursor-not-allowed disabled:text-ds-gray-quaternary disabled:border-ds-gray-tertiary disabled:bg-ds-gray-primary justify-center font-semibold text-ds-gray-quaternary hover:text-ds-gray-octonary focus:ring-0 " data-cy="cancel-payment" data-marketing="cancel-payment" data-testid="cancel-payment" type="button" > Cancel </button> </div> </div> </div> </form> </div> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx:325:21
})
Expand All @@ -315,7 +329,7 @@ describe('PaymentCard', () => {
describe('when the form is loading', () => {
it('has the error and save button disabled', async () => {
const { user } = setup()
mocks.useUpdateCard.mockReturnValue({
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: vi.fn(),
isLoading: true,
})
Expand All @@ -327,9 +341,9 @@ describe('PaymentCard', () => {
/>,
{ wrapper }
)
await user.click(screen.getByTestId('edit-card'))
await user.click(screen.getByTestId('edit-payment-method'))

expect(screen.queryByRole('button', { name: /update/i })).toBeDisabled()
expect(screen.getByTestId('update-payment-method')).toBeDisabled()

Check failure on line 346 in src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx

View workflow job for this annotation

GitHub Actions / Test Runner #2 - Vitest

src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx > PaymentCard > when the form is loading > has the error and save button disabled

Error: expect(element).toBeDisabled() Received element is not disabled: <button class=" flex items-center gap-1 rounded py-1 px-4 transition-colors duration-150 motion-reduce:transition-none· focus:outline-none focus:ring· disabled:cursor-not-allowed· disabled:text-ds-gray-quaternary disabled:border-ds-gray-tertiary disabled:bg-ds-gray-primary· justify-center font-semibold text-white bg-ds-blue-darker dark:bg-ds-blue-nonary border-ds-blue-quinary border-solid border shadow· hover:bg-ds-blue-quinary " data-cy="update-payment-method" data-marketing="update-payment-method" data-testid="update-payment-method" type="submit" /> ❯ src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx:346:59
expect(screen.queryByRole('button', { name: /cancel/i })).toBeDisabled()
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ afterEach(() => {
})

describe('PaymentMethodForm', () => {
describe('when the user clicks on Edit payment method', () => {
it(`doesn't render the payment method anymore`, async () => {
describe('when the user opens the Payment Method Form', () => {
it(`doesn't render the View payment method anymore`, async () => {
const user = userEvent.setup()
const updatePaymentMethod = vi.fn()
mocks.useUpdatePaymentMethod.mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { PaymentElement, useElements } from '@stripe/react-stripe-js'
import { StripePaymentElement } from '@stripe/stripe-js'
import cs from 'classnames'
import { useState } from 'react'
import { z } from 'zod'

import { SubscriptionDetailSchema } from 'services/account'
import { stripeAddress, SubscriptionDetailSchema } from 'services/account'
import { useUpdatePaymentMethod } from 'services/account/useUpdatePaymentMethod'
import { Provider } from 'shared/api/helpers'
import Button from 'ui/Button'
Expand Down Expand Up @@ -38,6 +39,9 @@ const PaymentMethodForm = ({
email:
subscriptionDetail?.defaultPaymentMethod?.billingDetails?.email ||
undefined,
address:
stripeAddress(subscriptionDetail?.defaultPaymentMethod?.billingDetails) ||
undefined,
})

async function submit(e: React.FormEvent) {
Expand All @@ -49,7 +53,9 @@ const PaymentMethodForm = ({

elements.submit()

const paymentElement = elements.getElement(PaymentElement)
const paymentElement = elements.getElement(
PaymentElement
) as StripePaymentElement

updatePaymentMethod(paymentElement, {
onSuccess: async () => {
Expand Down
Loading

0 comments on commit 455f129

Please sign in to comment.