import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { UpdateBillingAddressModal } from 'components/interfaces/App/UpdateBillingAddressModal'
import { createMockOrganization, render } from 'tests/helpers'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('lib/constants', async (importOriginal) => {
const original = (await importOriginal()) as any
return { ...original, IS_PLATFORM: true }
})
const mockFlag = vi.fn(() => true)
vi.mock('common', async (importOriginal) => {
const original = (await importOriginal()) as any
return {
...original,
useFlag: (_name: string) => mockFlag(),
useParams: () => ({ slug: 'test-org' }),
}
})
const mockOrg = vi.fn(() =>
createMockOrganization({
slug: 'test-org',
organization_missing_address: true,
billing_partner: null,
})
)
vi.mock('hooks/misc/useSelectedOrganization', () => ({
useSelectedOrganizationQuery: () => ({ data: mockOrg() }),
}))
const mockCanWrite = vi.fn(() => true)
const mockPermissionsLoaded = vi.fn(() => true)
vi.mock('hooks/misc/useCheckPermissions', () => ({
useAsyncCheckPermissions: () => ({
can: mockCanWrite(),
isSuccess: mockPermissionsLoaded(),
}),
}))
const mockCustomerProfile = vi.fn(() => ({
address: { line1: '', line2: '', city: '', state: '', postal_code: '', country: '' },
billing_name: '',
}))
const mockProfileLoaded = vi.fn(() => true)
const mockProfileError = vi.fn(() => false)
vi.mock('data/organizations/organization-customer-profile-query', () => ({
useOrganizationCustomerProfileQuery: () => ({
data: mockCustomerProfile(),
isSuccess: mockProfileLoaded(),
isError: mockProfileError(),
}),
}))
const mockTaxId = vi.fn(() => null)
const mockTaxIdLoaded = vi.fn(() => true)
const mockTaxIdError = vi.fn(() => false)
vi.mock('data/organizations/organization-tax-id-query', () => ({
useOrganizationTaxIdQuery: () => ({
data: mockTaxId(),
isSuccess: mockTaxIdLoaded(),
isError: mockTaxIdError(),
}),
}))
const mockUpdateCustomerProfile = vi.fn(() => Promise.resolve())
vi.mock('data/organizations/organization-customer-profile-update-mutation', () => ({
useOrganizationCustomerProfileUpdateMutation: () => ({
mutateAsync: mockUpdateCustomerProfile,
}),
}))
const mockUpdateTaxId = vi.fn(() => Promise.resolve())
vi.mock('data/organizations/organization-tax-id-update-mutation', () => ({
useOrganizationTaxIdUpdateMutation: () => ({
mutateAsync: mockUpdateTaxId,
}),
}))
vi.mock('data/organizations/organizations-query', () => ({
invalidateOrganizationsQuery: vi.fn(() => Promise.resolve()),
}))
describe('UpdateBillingAddressModal', () => {
beforeEach(() => {
vi.clearAllMocks()
mockFlag.mockReturnValue(true)
mockOrg.mockReturnValue(
createMockOrganization({
slug: 'test-org',
organization_missing_address: true,
billing_partner: null,
plan: { id: 'pro', name: 'Pro' },
})
)
mockCanWrite.mockReturnValue(true)
mockPermissionsLoaded.mockReturnValue(true)
mockProfileLoaded.mockReturnValue(true)
mockTaxIdLoaded.mockReturnValue(true)
mockCustomerProfile.mockReturnValue({
address: { line1: '', line2: '', city: '', state: '', postal_code: '', country: '' },
billing_name: '',
})
mockTaxId.mockReturnValue(null)
mockProfileError.mockReturnValue(false)
mockTaxIdError.mockReturnValue(false)
})
it('renders when all conditions are met', async () => {
render()
expect(await screen.findByText('Billing address required')).toBeInTheDocument()
})
it('does not render when feature flag is off', () => {
mockFlag.mockReturnValue(false)
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('does not render when org has no missing address', () => {
mockOrg.mockReturnValue(createMockOrganization({ organization_missing_address: false }))
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('does not render for free orgs', () => {
mockOrg.mockReturnValue(
createMockOrganization({
organization_missing_address: true,
plan: { id: 'free', name: 'Free' },
})
)
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('does not render for partner-managed orgs', () => {
mockOrg.mockReturnValue(
createMockOrganization({
organization_missing_address: true,
billing_partner: 'vercel_marketplace',
})
)
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('does not render when user lacks billing write permission', () => {
mockCanWrite.mockReturnValue(false)
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('does not render when no org is selected', () => {
mockOrg.mockReturnValue(undefined as any)
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('does not render when customer profile query errors', () => {
mockProfileError.mockReturnValue(true)
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('does not render when tax id query errors', () => {
mockTaxIdError.mockReturnValue(true)
render()
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
it('shows loading shimmer while customer data loads', () => {
mockProfileLoaded.mockReturnValue(false)
render()
expect(screen.queryByText('Save address')).not.toBeInTheDocument()
})
it('dismisses on close button click', async () => {
render()
expect(await screen.findByText('Billing address required')).toBeInTheDocument()
// Click the X (close) button in DialogContent
const closeButton = screen.getByRole('button', { name: /close/i })
await userEvent.click(closeButton)
await waitFor(() => {
expect(screen.queryByText('Billing address required')).not.toBeInTheDocument()
})
})
})