tests/cancel-plan-modal.unit.test.ts
Rama main · réplica de lectura del repositorio original.
// @vitest-environment happy-dom
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createApp, h, nextTick } from 'vue'
import CancelPlanModal from '../src/components/billing/CancelPlanModal.vue'
const mocks = vi.hoisted(() => ({
getCancelSaveOffer: vi.fn(),
cancelSubscriptionWithReason: vi.fn(),
acceptCancelSaveOffer: vi.fn(),
}))
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) }))
vi.mock('vue-sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }))
vi.mock('../src/services/stripe', () => ({
CANCEL_REASONS: ['too_expensive', 'other'],
getCancelSaveOffer: mocks.getCancelSaveOffer,
cancelSubscriptionWithReason: mocks.cancelSubscriptionWithReason,
acceptCancelSaveOffer: mocks.acceptCancelSaveOffer,
formatStripeAmount: (amount: number) => `$${amount / 100}`,
}))
const offer = {
id: 'offer_1',
percentOff: 50,
durationMonths: 3,
monthlyAmount: 2900,
discountedAmount: 1450,
currency: 'usd',
}
function mountModal(onClose = vi.fn()) {
const onCanceled = vi.fn()
const onSaved = vi.fn()
const root = document.createElement('div')
document.body.appendChild(root)
const app = createApp({
render: () => h(CancelPlanModal, {
orgId: 'org_test',
planName: 'Solo',
onClose,
onCanceled,
onSaved,
}),
})
app.mount(root)
return { app, root, onClose, onCanceled, onSaved }
}
function shell() {
return document.body
}
async function pickReason() {
const input = shell().querySelector('[data-test="cancel-reason-too_expensive"] input') as HTMLInputElement
input.click()
await nextTick()
}
async function submitReason() {
const button = shell().querySelector('[data-test="cancel-plan-confirm"]') as HTMLButtonElement
button.click()
await nextTick()
}
describe('CancelPlanModal loss reminder', () => {
afterEach(() => {
document.body.replaceChildren()
vi.clearAllMocks()
})
it('shows the loss step after the reason form is submitted', async () => {
mountModal()
await pickReason()
await submitReason()
expect(shell().querySelector('[data-test="cancel-loss-reminder"]')).toBeTruthy()
const focusTarget = shell().querySelector('[data-test="cancel-loss-reminder"] [tabindex="-1"]')
await vi.waitFor(() => expect(document.activeElement).toBe(focusTarget))
expect(shell().querySelector('[data-test="cancel-plan-form"]')).toBeNull()
expect(mocks.getCancelSaveOffer).not.toHaveBeenCalled()
})
it('closes when keep plan is clicked on the loss step', async () => {
const onClose = vi.fn()
mountModal(onClose)
await pickReason()
await submitReason()
;(shell().querySelector('[data-test="cancel-loss-keep-plan"]') as HTMLButtonElement).click()
expect(onClose).toHaveBeenCalledOnce()
})
it('loads the offer after continue on the loss step', async () => {
mocks.getCancelSaveOffer.mockResolvedValue({ offer })
mountModal()
await pickReason()
await submitReason()
;(shell().querySelector('[data-test="cancel-loss-continue"]') as HTMLButtonElement).click()
await vi.waitFor(() => expect(shell().querySelector('[data-test="cancel-save-offer"]')).toBeTruthy())
expect(mocks.getCancelSaveOffer).toHaveBeenCalledWith('org_test', 'too_expensive')
expect(mocks.cancelSubscriptionWithReason).not.toHaveBeenCalled()
})
it('cancels when continue is clicked and no offer is available', async () => {
mocks.getCancelSaveOffer.mockResolvedValue({ offer: null })
mocks.cancelSubscriptionWithReason.mockResolvedValue({ subscription: null })
const onCanceled = vi.fn()
const root = document.createElement('div')
document.body.appendChild(root)
const app = createApp({
render: () => h(CancelPlanModal, {
orgId: 'org_test',
onCanceled,
}),
})
app.mount(root)
await pickReason()
await submitReason()
;(shell().querySelector('[data-test="cancel-loss-continue"]') as HTMLButtonElement).click()
await vi.waitFor(() => expect(onCanceled).toHaveBeenCalledOnce())
expect(mocks.cancelSubscriptionWithReason).toHaveBeenCalledWith('org_test', 'too_expensive', '', undefined)
})
it('still cancels when declining an eligible offer', async () => {
mocks.getCancelSaveOffer.mockResolvedValue({ offer })
mocks.cancelSubscriptionWithReason.mockResolvedValue({ subscription: { cancelAtPeriodEnd: true } })
const onCanceled = vi.fn()
const root = document.createElement('div')
document.body.appendChild(root)
const app = createApp({
render: () => h(CancelPlanModal, {
orgId: 'org_test',
onCanceled,
}),
})
app.mount(root)
await pickReason()
await submitReason()
;(shell().querySelector('[data-test="cancel-loss-continue"]') as HTMLButtonElement).click()
await vi.waitFor(() => expect(shell().querySelector('[data-test="cancel-save-offer"]')).toBeTruthy())
;(shell().querySelector('[data-test="cancel-offer-decline"]') as HTMLButtonElement).click()
await vi.waitFor(() => expect(onCanceled).toHaveBeenCalledOnce())
expect(mocks.cancelSubscriptionWithReason).toHaveBeenCalledWith('org_test', 'too_expensive', '', offer.id)
})
})