From 169de5244e863dc14e51424a1c81f9baece8b85c Mon Sep 17 00:00:00 2001
From: Suejung Shin
Date: Mon, 13 Jan 2025 23:51:35 -0800
Subject: [PATCH] fix tests
---
src/pages/PlanPage/PlanPage.test.jsx | 28 +-
.../BillingDetails/BillingDetails.test.tsx | 16 +-
.../BillingDetails/BillingDetails.tsx | 2 +-
.../Address/AddressForm.test.tsx | 240 ++++++++++++++++
.../EditPaymentMethods.test.tsx | 81 ++++++
.../EditPaymentMethods/EditPaymentMethods.tsx | 23 +-
.../PaymentMethod/PaymentMethodForm.test.tsx | 218 ++++++++++++++
.../PaymentMethod/PaymentMethodForm.tsx | 9 +-
.../Address/AddressCard.test.tsx | 266 +++---------------
.../PaymentMethod/PaymentMethod.test.tsx | 243 ++++------------
.../PaymentMethod/PaymentMethod.tsx | 1 +
11 files changed, 675 insertions(+), 452 deletions(-)
create mode 100644 src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/Address/AddressForm.test.tsx
create mode 100644 src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.test.tsx
create mode 100644 src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.test.tsx
diff --git a/src/pages/PlanPage/PlanPage.test.jsx b/src/pages/PlanPage/PlanPage.test.jsx
index 3cdb714f09..b7bbc12378 100644
--- a/src/pages/PlanPage/PlanPage.test.jsx
+++ b/src/pages/PlanPage/PlanPage.test.jsx
@@ -11,6 +11,8 @@ import { MemoryRouter, Route } from 'react-router-dom'
import config from 'config'
+import { ThemeContextProvider } from 'shared/ThemeContext'
+
import PlanPage from './PlanPage'
vi.mock('config')
@@ -44,18 +46,20 @@ const wrapper =
({ children }) => (
-
-
- {children}
- {
- testLocation = location
- return null
- }}
- />
-
-
+
+
+
+ {children}
+ {
+ testLocation = location
+ return null
+ }}
+ />
+
+
+
)
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.test.tsx
index c524ccd390..47536948d5 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.test.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.test.tsx
@@ -8,11 +8,15 @@ import { Plans } from 'shared/utils/billing'
import BillingDetails from './BillingDetails'
-vi.mock('./PaymentCard/PaymentCard', () => ({ default: () => 'Payment Card' }))
+vi.mock('./ViewPaymentMethod/PaymentMethod/PaymentMethod', () => ({
+ default: () => 'Payment Method',
+}))
vi.mock('./EmailAddress/EmailAddress', () => ({
default: () => 'Email Address',
}))
-vi.mock('./Address/AddressCard', () => ({ default: () => 'Address Card' }))
+vi.mock('./ViewPaymentMethod/Address/AddressCard', () => ({
+ default: () => 'Address Card',
+}))
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
@@ -84,12 +88,12 @@ describe('BillingDetails', () => {
}
describe('when there is a subscription', () => {
- it('renders the payment card', async () => {
+ it('renders the payment method card', async () => {
setup({ hasSubscription: true })
render(, { wrapper })
- const paymentCard = await screen.findByText(/Payment Card/)
- expect(paymentCard).toBeInTheDocument()
+ const paymentCards = await screen.findAllByText(/Payment Method/)
+ expect(paymentCards.length).toBeGreaterThan(0)
})
it('renders the email address component', async () => {
@@ -132,7 +136,7 @@ describe('BillingDetails', () => {
it('renders the payment card', async () => {
render(, { wrapper })
- const paymentCard = screen.queryByText(/Payment Card/)
+ const paymentCard = screen.queryByText(/Payment Method/)
expect(paymentCard).not.toBeInTheDocument()
})
})
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx
index b9874d0dd4..bc74d38261 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx
@@ -71,7 +71,7 @@ function BillingDetails() {
setEditMode={setEditMode}
provider={provider}
owner={owner}
- existingSubscriptionDetail={subscriptionDetail}
+ subscriptionDetail={subscriptionDetail}
/>
) : (
<>
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/Address/AddressForm.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/Address/AddressForm.test.tsx
new file mode 100644
index 0000000000..8f6b836972
--- /dev/null
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/Address/AddressForm.test.tsx
@@ -0,0 +1,240 @@
+import { Elements } from '@stripe/react-stripe-js'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { MemoryRouter, Route } from 'react-router-dom'
+import { vi } from 'vitest'
+import { z } from 'zod'
+
+import { SubscriptionDetailSchema } from 'services/account/useAccountDetails'
+
+import AddressForm from './AddressForm'
+
+
+const queryClient = new QueryClient()
+
+const mockGetElement = vi.fn()
+const mockGetValue = vi.fn()
+
+vi.mock('@stripe/react-stripe-js', async () => {
+ const actual = await vi.importActual('@stripe/react-stripe-js')
+ return {
+ ...actual,
+ useElements: () => ({
+ getElement: mockGetElement.mockReturnValue({
+ getValue: mockGetValue.mockResolvedValue({
+ complete: true,
+ value: {
+ name: 'John Doe',
+ address: {
+ line1: '123 Main St',
+ line2: null,
+ city: 'San Francisco',
+ state: 'CA',
+ postal_code: '94105',
+ country: 'US',
+ },
+ },
+ }),
+ }),
+ }),
+ }
+})
+
+const wrapper: React.FC = ({ children }) => (
+
+
+
+ {children}
+
+
+
+)
+
+const mockSubscriptionDetail: z.infer = {
+ defaultPaymentMethod: {
+ billingDetails: {
+ address: {
+ line1: '123 Main St',
+ city: 'San Francisco',
+ state: 'CA',
+ postalCode: '94105',
+ country: 'US',
+ line2: null,
+ },
+ phone: '1234567890',
+ name: 'John Doe',
+ email: 'test@example.com',
+ },
+ card: {
+ brand: 'visa',
+ expMonth: 12,
+ expYear: 2025,
+ last4: '4242',
+ },
+ },
+ currentPeriodEnd: 1706851492,
+ cancelAtPeriodEnd: false,
+ customer: {
+ id: 'cust_123',
+ email: 'test@example.com',
+ },
+ latestInvoice: null,
+ taxIds: [],
+ trialEnd: null,
+}
+
+const mocks = {
+ useUpdateBillingAddress: vi.fn(),
+}
+
+vi.mock('src/services/account/useUpdateBillingAddress', () => ({
+ useUpdateBillingAddress: () => mocks.useUpdateBillingAddress(),
+}))
+
+afterEach(() => {
+ vi.clearAllMocks()
+})
+
+describe('AddressForm', () => {
+ const setup = () => {
+ const mutation = vi.fn()
+ mocks.useUpdateBillingAddress.mockReturnValue({
+ mutate: mutation,
+ isLoading: false,
+ })
+ return { user: userEvent.setup() }
+ }
+
+ it('renders the form', () => {
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+
+ expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument()
+ })
+
+ describe('when submitting', () => {
+ it.only('calls the service to update the address', async () => {
+ const { user } = setup()
+ const updateAddress = vi.fn()
+ mocks.useUpdateBillingAddress.mockReturnValue({
+ mutate: updateAddress,
+ isLoading: false,
+ })
+
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+
+ await user.click(screen.getByRole('button', { name: /save/i }))
+
+ expect(updateAddress).toHaveBeenCalled()
+ })
+ })
+
+ describe('when the user clicks on cancel', () => {
+ it('calls the closeForm prop', async () => {
+ const { user } = setup()
+ const closeForm = vi.fn()
+ mocks.useUpdateBillingAddress.mockReturnValue({
+ mutate: vi.fn(),
+ isLoading: false,
+ })
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await user.click(screen.getByRole('button', { name: /cancel/i }))
+
+ expect(closeForm).toHaveBeenCalled()
+ })
+ })
+
+ describe('when there is an error in the form', () => {
+ it('renders the error', async () => {
+ const randomError = 'not a valid address'
+ mocks.useUpdateBillingAddress.mockReturnValue({
+ mutate: vi.fn(),
+ error: randomError,
+ })
+
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+
+ expect(screen.getByText(randomError)).toBeInTheDocument()
+ })
+ })
+
+ describe('when the form is loading', () => {
+ it('has the save and cancel buttons disabled', () => {
+ mocks.useUpdateBillingAddress.mockReturnValue({
+ mutate: vi.fn(),
+ isLoading: true,
+ })
+
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+
+ expect(screen.getByRole('button', { name: /save/i })).toBeDisabled()
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeDisabled()
+ })
+ })
+})
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.test.tsx
new file mode 100644
index 0000000000..9cf7e4cba8
--- /dev/null
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.test.tsx
@@ -0,0 +1,81 @@
+import { Elements } from '@stripe/react-stripe-js'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { render, screen } from '@testing-library/react'
+import { MemoryRouter, Route } from 'react-router-dom'
+import { z } from 'zod'
+
+import { SubscriptionDetailSchema } from 'services/account'
+
+import EditPaymentMethods from './EditPaymentMethods'
+
+const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+})
+
+vi.mock('./PaymentMethod/PaymentMethodForm', () => ({
+ default: () => 'Payment Method Form',
+}))
+
+vi.mock('./Address/AddressForm', () => ({
+ default: () => 'Address Form',
+}))
+
+const wrapper: React.FC = ({ children }) => (
+
+
+
+ {children}
+
+
+
+)
+
+const mockSubscriptionDetail: z.infer = {
+ defaultPaymentMethod: {
+ billingDetails: {
+ address: {
+ line1: '123 Main St',
+ city: 'San Francisco',
+ state: 'CA',
+ postalCode: '94105',
+ country: 'US',
+ line2: null,
+ },
+ phone: '1234567890',
+ name: 'John Doe',
+ email: 'test@example.com',
+ },
+ card: {
+ brand: 'visa',
+ expMonth: 12,
+ expYear: 2025,
+ last4: '4242',
+ },
+ },
+ currentPeriodEnd: 1706851492,
+ cancelAtPeriodEnd: false,
+ customer: {
+ id: 'cust_123',
+ email: 'test@example.com',
+ },
+ latestInvoice: null,
+ taxIds: [],
+ trialEnd: null,
+}
+
+describe('EditPaymentMethod', () => {
+ it('renders the expected forms', () => {
+ render(
+ {}}
+ provider="gh"
+ owner="codecov"
+ subscriptionDetail={mockSubscriptionDetail}
+ />,
+ { wrapper }
+ )
+
+ expect(screen.getByText(/Payment Method Form/)).toBeInTheDocument()
+ expect(screen.getByText(/Address Form/)).toBeInTheDocument()
+ })
+})
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.tsx
index 3b135b829a..1d0539bdd2 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/EditPaymentMethods.tsx
@@ -8,19 +8,19 @@ import PaymentMethodForm from './PaymentMethod/PaymentMethodForm'
import { SECONDARY_PAYMENT_FEATURE_ENABLED } from '../BillingDetails'
-interface EditPaymentMethodProps {
+interface EditPaymentMethodsProps {
setEditMode: (isEditMode: boolean) => void
provider: string
owner: string
- existingSubscriptionDetail: z.infer
+ subscriptionDetail: z.infer
}
-const EditPaymentMethod = ({
+const EditPaymentMethods = ({
setEditMode,
provider,
owner,
- existingSubscriptionDetail,
-}: EditPaymentMethodProps) => {
+ subscriptionDetail,
+}: EditPaymentMethodsProps) => {
const [activeTab, setActiveTab] = useState<'primary' | 'secondary'>('primary')
const secondaryPaymentMethodFeatureEnabled = SECONDARY_PAYMENT_FEATURE_ENABLED
@@ -57,16 +57,15 @@ const EditPaymentMethod = ({
closeForm={() => setEditMode(false)}
provider={provider}
owner={owner}
- existingSubscriptionDetail={existingSubscriptionDetail}
+ subscriptionDetail={subscriptionDetail}
/>
setEditMode(false)}
provider={provider}
@@ -80,7 +79,7 @@ const EditPaymentMethod = ({
closeForm={() => setEditMode(false)}
provider={provider}
owner={owner}
- existingSubscriptionDetail={existingSubscriptionDetail}
+ subscriptionDetail={subscriptionDetail}
/>
setEditMode(false)}
@@ -95,4 +94,4 @@ const EditPaymentMethod = ({
)
}
-export default EditPaymentMethod
+export default EditPaymentMethods
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.test.tsx
new file mode 100644
index 0000000000..e77927c042
--- /dev/null
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.test.tsx
@@ -0,0 +1,218 @@
+import { Elements } from '@stripe/react-stripe-js'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { MemoryRouter, Route } from 'react-router-dom'
+import { vi } from 'vitest'
+import { z } from 'zod'
+
+import { SubscriptionDetailSchema } from 'services/account/useAccountDetails'
+
+import PaymentMethodForm from './PaymentMethodForm'
+
+const queryClient = new QueryClient()
+
+const mockElements = {
+ submit: vi.fn(),
+ getElement: vi.fn(),
+}
+
+vi.mock('@stripe/react-stripe-js', () => ({
+ Elements: ({ children }: { children: React.ReactNode }) => children,
+ useElements: () => mockElements,
+ PaymentElement: 'div',
+}))
+
+const wrapper: React.FC = ({ children }) => (
+
+
+
+ {children}
+
+
+
+)
+
+const subscriptionDetail: z.infer = {
+ defaultPaymentMethod: {
+ billingDetails: {
+ address: {
+ line1: '123 Main St',
+ city: 'San Francisco',
+ state: 'CA',
+ postalCode: '94105',
+ country: 'US',
+ line2: null,
+ },
+ phone: '1234567890',
+ name: 'John Doe',
+ email: 'test@example.com',
+ },
+ card: {
+ brand: 'visa',
+ expMonth: 12,
+ expYear: 2025,
+ last4: '4242',
+ },
+ },
+ currentPeriodEnd: 1706851492,
+ cancelAtPeriodEnd: false,
+ customer: {
+ id: 'cust_123',
+ email: 'test@example.com',
+ },
+ latestInvoice: null,
+ taxIds: [],
+ trialEnd: null,
+}
+
+const mocks = {
+ useUpdatePaymentMethod: vi.fn(),
+}
+
+vi.mock('services/account/useUpdatePaymentMethod', () => ({
+ useUpdatePaymentMethod: () => mocks.useUpdatePaymentMethod(),
+}))
+
+afterEach(() => {
+ vi.clearAllMocks()
+})
+
+describe('PaymentMethodForm', () => {
+ describe('when the user clicks on Edit payment method', () => {
+ it(`doesn't render the payment method anymore`, async () => {
+ const user = userEvent.setup()
+ const updatePaymentMethod = vi.fn()
+ mocks.useUpdatePaymentMethod.mockReturnValue({
+ mutate: updatePaymentMethod,
+ isLoading: false,
+ })
+
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+ await user.click(screen.getByTestId('update-payment-method'))
+
+ expect(screen.queryByText(/Visa/)).not.toBeInTheDocument()
+ })
+
+ it('renders the form', async () => {
+ const user = userEvent.setup()
+ const updatePaymentMethod = vi.fn()
+ mocks.useUpdatePaymentMethod.mockReturnValue({
+ mutate: updatePaymentMethod,
+ isLoading: false,
+ })
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+ await user.click(screen.getByTestId('update-payment-method'))
+
+ expect(screen.getByRole('button', { name: /Save/i })).toBeInTheDocument()
+ })
+
+ describe('when submitting', () => {
+ it('calls the service to update the payment method', async () => {
+ const user = userEvent.setup()
+ const updatePaymentMethod = vi.fn()
+ mocks.useUpdatePaymentMethod.mockReturnValue({
+ mutate: updatePaymentMethod,
+ isLoading: false,
+ })
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+ await user.click(screen.getByTestId('update-payment-method'))
+ expect(updatePaymentMethod).toHaveBeenCalled()
+ })
+ })
+
+ describe('when the user clicks on cancel', () => {
+ it(`doesn't render the form anymore`, async () => {
+ const user = userEvent.setup()
+ const closeForm = vi.fn()
+ mocks.useUpdatePaymentMethod.mockReturnValue({
+ mutate: vi.fn(),
+ isLoading: false,
+ })
+ render(
+ ,
+ { wrapper }
+ )
+
+ await user.click(screen.getByTestId('update-payment-method'))
+ await user.click(screen.getByRole('button', { name: /Cancel/ }))
+
+ expect(closeForm).toHaveBeenCalled()
+ })
+ })
+ })
+
+ describe('when there is an error in the form', () => {
+ it('renders the error', async () => {
+ const user = userEvent.setup()
+ const randomError = 'not rich enough'
+ mocks.useUpdatePaymentMethod.mockReturnValue({
+ mutate: vi.fn(),
+ error: { message: randomError },
+ })
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+
+ await user.click(screen.getByTestId('update-payment-method'))
+
+ expect(screen.getByText(randomError)).toBeInTheDocument()
+ })
+ })
+
+ describe('when the form is loading', () => {
+ it('has the error and save button disabled', async () => {
+ mocks.useUpdatePaymentMethod.mockReturnValue({
+ mutate: vi.fn(),
+ isLoading: true,
+ })
+ render(
+ {}}
+ />,
+ { wrapper }
+ )
+
+ expect(screen.queryByRole('button', { name: /Save/i })).toBeDisabled()
+ expect(screen.queryByRole('button', { name: /Cancel/i })).toBeDisabled()
+ })
+ })
+})
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.tsx
index 46ac35a50d..08ef6a0992 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethods/PaymentMethod/PaymentMethodForm.tsx
@@ -11,14 +11,14 @@ interface PaymentMethodFormProps {
closeForm: () => void
provider: string
owner: string
- existingSubscriptionDetail: z.infer
+ subscriptionDetail: z.infer
}
const PaymentMethodForm = ({
closeForm,
provider,
owner,
- existingSubscriptionDetail,
+ subscriptionDetail,
}: PaymentMethodFormProps) => {
const [errorState, _] = useState('')
const elements = useElements()
@@ -31,8 +31,7 @@ const PaymentMethodForm = ({
} = useUpdatePaymentMethod({
provider,
owner,
- email:
- existingSubscriptionDetail?.defaultPaymentMethod?.billingDetails?.email,
+ email: subscriptionDetail?.defaultPaymentMethod?.billingDetails?.email,
})
async function submit(e: React.FormEvent) {
@@ -75,7 +74,7 @@ const PaymentMethodForm = ({