Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, expect, it, vi } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'

import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { render, screen, waitFor } from '@testing-library/vue'

import enMessages from '@/locales/en/main.json' with { type: 'json' }

Expand Down Expand Up @@ -44,23 +45,28 @@ const mockSubscription = vi.hoisted(() => ({
value: null as { endDate: string | null } | null
}))

const mockCancelSubscription = vi.hoisted(() => vi.fn())
const mockFetchStatus = vi.hoisted(() => vi.fn())
const mockCloseDialog = vi.hoisted(() => vi.fn())
const mockToastAdd = vi.hoisted(() => vi.fn())

vi.mock('@/composables/billing/useBillingContext', () => ({
useBillingContext: vi.fn(() => ({
cancelSubscription: vi.fn(),
fetchStatus: vi.fn(),
cancelSubscription: mockCancelSubscription,
fetchStatus: mockFetchStatus,
subscription: mockSubscription
}))
}))

vi.mock('@/stores/dialogStore', () => ({
useDialogStore: vi.fn(() => ({
closeDialog: vi.fn()
closeDialog: mockCloseDialog
}))
}))

vi.mock('primevue/usetoast', () => ({
useToast: vi.fn(() => ({
add: vi.fn()
add: mockToastAdd
}))
}))

Expand All @@ -86,6 +92,54 @@ function renderComponent(props: { cancelAt?: string } = {}) {
}

describe('CancelSubscriptionDialogContent', () => {
beforeEach(() => {
vi.clearAllMocks()
})

describe('cancel flow', () => {
it('shows an error toast and keeps the dialog open when cancellation fails', async () => {
mockSubscription.value = null
mockCancelSubscription.mockRejectedValueOnce(
new Error('Subscription cancellation timed out')
)

renderComponent()
await userEvent.click(
screen.getByRole('button', { name: /^cancel subscription$/i })
)

await waitFor(() =>
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({
severity: 'error',
detail: 'Subscription cancellation timed out'
})
)
)
expect(mockCloseDialog).not.toHaveBeenCalled()
})

it('closes the dialog and shows a success toast when cancellation succeeds', async () => {
mockSubscription.value = null
mockCancelSubscription.mockResolvedValueOnce(undefined)

renderComponent()
await userEvent.click(
screen.getByRole('button', { name: /^cancel subscription$/i })
)

await waitFor(() =>
expect(mockCloseDialog).toHaveBeenCalledWith({
key: 'cancel-subscription'
})
)
expect(mockFetchStatus).toHaveBeenCalled()
expect(mockToastAdd).toHaveBeenCalledWith(
expect.objectContaining({ severity: 'success' })
)
})
})

describe('formattedEndDate fallbacks', () => {
it('uses the localized fallback when no cancel timestamp is available', () => {
mockSubscription.value = { endDate: null }
Expand Down
4 changes: 3 additions & 1 deletion src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2409,7 +2409,9 @@
"topupProcessing": "Processing payment — adding credits...",
"topupSuccess": "Credits added successfully",
"topupFailed": "Top-up failed",
"topupTimeout": "Top-up verification timed out"
"topupTimeout": "Top-up verification timed out",
"cancelFailed": "Failed to cancel subscription",
"cancelTimeout": "Subscription cancellation timed out"
},
"subscription": {
"plansForWorkspace": "Plans for {workspace}",
Expand Down
4 changes: 2 additions & 2 deletions src/platform/workspace/composables/useSubscriptionCheckout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,12 @@ export function useSubscriptionCheckout(emit: {
response.payment_method_url
) {
window.open(response.payment_method_url, '_blank')
billingOperationStore.startOperation(
void billingOperationStore.startOperation(
response.billing_op_id,
'subscription'
)
} else if (response.status === 'pending_payment') {
billingOperationStore.startOperation(
void billingOperationStore.startOperation(
response.billing_op_id,
'subscription'
)
Expand Down
Loading
Loading