Files
ComfyUI_frontend/src/platform/workspace/components/SubscriptionRequiredDialogContentWorkspace.test.ts
Christian Byrne 5cad2c952b refactor+test: extract useSubscriptionCheckout composable, rewrite tests (#11396)
## Summary

Adds 20 component tests for
`SubscriptionRequiredDialogContentWorkspace.vue` covering:

- **Initial rendering**: pricing table display, close/back button
visibility, out_of_credits reason message
- **Close button**: calls onClose callback
- **Subscribe click flow**: pricing→preview transitions (new
subscription & upgrade), error toasts for disallowed/missing/failed
previews, monthly billing cycle
- **Back button**: returns from preview to pricing step
- **Add credit card**: handles subscribed status (success toast +
close), needs_payment_method (opens Stripe URL), error state
- **Confirm transition**: success path with close emit, error toast on
failure
- **Resubscribe**: success path with toast + close, error toast on
failure

## Testing

```bash
pnpm test:unit -- src/platform/workspace/components/SubscriptionRequiredDialogContentWorkspace.test.ts
```

All 20 tests pass. Quality gates (typecheck, lint, format, knip) pass.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11396-test-add-component-tests-for-SubscriptionRequiredDialogContentWorkspace-3476d73d36508156a218dcb67a2a334e)
by [Unito](https://www.unito.io)
2026-05-02 04:50:44 +00:00

199 lines
6.6 KiB
TypeScript

import { createTestingPinia } from '@pinia/testing'
import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ref } from 'vue'
import { createI18n } from 'vue-i18n'
import type { SubscriptionDialogReason } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
import SubscriptionRequiredDialogContentWorkspace from './SubscriptionRequiredDialogContentWorkspace.vue'
const mockHandleSubscribeClick = vi.fn()
const mockHandleBackToPricing = vi.fn()
const mockHandleAddCreditCard = vi.fn()
const mockHandleConfirmTransition = vi.fn()
const mockHandleResubscribe = vi.fn()
const mockCheckoutStep = ref<'pricing' | 'preview'>('pricing')
const mockPreviewData = ref<{ transition_type: string } | null>(null)
vi.mock('@/platform/workspace/composables/useSubscriptionCheckout', () => ({
useSubscriptionCheckout: () => ({
checkoutStep: mockCheckoutStep,
isLoadingPreview: ref(false),
loadingTier: ref(null),
isSubscribing: ref(false),
isResubscribing: ref(false),
previewData: mockPreviewData,
selectedTierKey: ref('standard'),
selectedBillingCycle: ref('yearly'),
isPolling: ref(false),
handleSubscribeClick: mockHandleSubscribeClick,
handleBackToPricing: mockHandleBackToPricing,
handleAddCreditCard: mockHandleAddCreditCard,
handleConfirmTransition: mockHandleConfirmTransition,
handleResubscribe: mockHandleResubscribe
})
}))
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
g: { back: 'Back', close: 'Close' },
subscription: {
plansForWorkspace: 'Plans for {workspace}',
teamWorkspace: 'Team'
},
credits: {
topUp: {
insufficientTitle: 'Insufficient Credits',
insufficientMessage: 'You have run out of credits.'
}
}
}
}
})
const PricingTableStub = {
name: 'PricingTableWorkspace',
template: `<div data-testid="pricing-table">
<button data-testid="subscribe-btn" @click="$emit('subscribe', { tierKey: 'standard', billingCycle: 'yearly' })">Subscribe</button>
<button data-testid="resubscribe-btn" @click="$emit('resubscribe')">Resubscribe</button>
</div>`
}
const AddPaymentPreviewStub = {
name: 'SubscriptionAddPaymentPreviewWorkspace',
template: `<div data-testid="add-payment-preview">
<button data-testid="add-card-btn" @click="$emit('addCreditCard')">Add Card</button>
</div>`
}
const TransitionPreviewStub = {
name: 'SubscriptionTransitionPreviewWorkspace',
template: `<div data-testid="transition-preview">
<button data-testid="confirm-btn" @click="$emit('confirm')">Confirm</button>
</div>`
}
function renderComponent(
props: { onClose?: () => void; reason?: SubscriptionDialogReason } = {}
) {
return render(SubscriptionRequiredDialogContentWorkspace, {
props: {
onClose: props.onClose ?? vi.fn(),
...(props.reason ? { reason: props.reason } : {})
},
global: {
plugins: [
createTestingPinia({ createSpy: vi.fn, stubActions: false }),
i18n
],
stubs: {
PricingTableWorkspace: PricingTableStub,
SubscriptionAddPaymentPreviewWorkspace: AddPaymentPreviewStub,
SubscriptionTransitionPreviewWorkspace: TransitionPreviewStub
}
}
})
}
describe('SubscriptionRequiredDialogContentWorkspace', () => {
beforeEach(() => {
vi.clearAllMocks()
mockCheckoutStep.value = 'pricing'
mockPreviewData.value = null
})
it('shows pricing table on pricing step', () => {
renderComponent()
expect(screen.getByTestId('pricing-table')).toBeInTheDocument()
expect(screen.queryByTestId('add-payment-preview')).not.toBeInTheDocument()
expect(screen.queryByTestId('transition-preview')).not.toBeInTheDocument()
})
it('shows close button and hides back button on pricing step', () => {
renderComponent()
expect(screen.getByLabelText('Close')).toBeInTheDocument()
expect(screen.queryByLabelText('Back')).not.toBeInTheDocument()
})
it('calls onClose when close button is clicked', async () => {
const user = userEvent.setup()
const onClose = vi.fn()
renderComponent({ onClose })
await user.click(screen.getByLabelText('Close'))
expect(onClose).toHaveBeenCalledOnce()
})
it('shows back button on preview step', () => {
mockCheckoutStep.value = 'preview'
mockPreviewData.value = { transition_type: 'new_subscription' }
renderComponent()
expect(screen.getByLabelText('Back')).toBeInTheDocument()
})
it('shows insufficient credits message when reason is out_of_credits', () => {
renderComponent({ reason: 'out_of_credits' })
expect(screen.getByText('Insufficient Credits')).toBeInTheDocument()
expect(screen.getByText('You have run out of credits.')).toBeInTheDocument()
})
it('does not show insufficient credits message without reason', () => {
renderComponent()
expect(screen.queryByText('Insufficient Credits')).not.toBeInTheDocument()
})
it('shows new subscription preview when transition_type is new_subscription', () => {
mockCheckoutStep.value = 'preview'
mockPreviewData.value = { transition_type: 'new_subscription' }
renderComponent()
expect(screen.getByTestId('add-payment-preview')).toBeInTheDocument()
expect(screen.queryByTestId('transition-preview')).not.toBeInTheDocument()
})
it('shows transition preview when transition_type is upgrade', () => {
mockCheckoutStep.value = 'preview'
mockPreviewData.value = { transition_type: 'upgrade' }
renderComponent()
expect(screen.getByTestId('transition-preview')).toBeInTheDocument()
expect(screen.queryByTestId('add-payment-preview')).not.toBeInTheDocument()
})
it('wires subscribe event to handleSubscribeClick', async () => {
const user = userEvent.setup()
renderComponent()
await user.click(screen.getByTestId('subscribe-btn'))
expect(mockHandleSubscribeClick).toHaveBeenCalledWith({
tierKey: 'standard',
billingCycle: 'yearly'
})
})
it('wires resubscribe event to handleResubscribe', async () => {
const user = userEvent.setup()
renderComponent()
await user.click(screen.getByTestId('resubscribe-btn'))
expect(mockHandleResubscribe).toHaveBeenCalled()
})
it('wires back button to handleBackToPricing', async () => {
const user = userEvent.setup()
mockCheckoutStep.value = 'preview'
mockPreviewData.value = { transition_type: 'new_subscription' }
renderComponent()
await user.click(screen.getByLabelText('Back'))
expect(mockHandleBackToPricing).toHaveBeenCalled()
})
})