Co-authored-by: Michael Chihlas <michael@resolutionflow.com> Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
156 lines
4.1 KiB
TypeScript
156 lines
4.1 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
import { render, screen } from '@testing-library/react'
|
|
import { MemoryRouter } from 'react-router-dom'
|
|
|
|
import { TrialPill } from '../TrialPill'
|
|
import { useBillingStore } from '@/store/billingStore'
|
|
import type { SubscriptionState, PlanBillingState } from '@/types/billing'
|
|
|
|
const FROZEN_NOW = new Date('2026-05-06T12:00:00Z')
|
|
|
|
function renderPill() {
|
|
return render(
|
|
<MemoryRouter>
|
|
<TrialPill />
|
|
</MemoryRouter>,
|
|
)
|
|
}
|
|
|
|
function setBilling(opts: {
|
|
subscription: SubscriptionState | null
|
|
planBilling?: PlanBillingState | null
|
|
}) {
|
|
useBillingStore.setState({
|
|
subscription: opts.subscription,
|
|
planBilling: opts.planBilling ?? null,
|
|
planLimits: {},
|
|
enabledFeatures: {},
|
|
isLoading: false,
|
|
error: null,
|
|
})
|
|
}
|
|
|
|
function isoDaysFromNow(days: number): string {
|
|
const d = new Date(FROZEN_NOW.getTime() + days * 24 * 60 * 60 * 1000)
|
|
return d.toISOString()
|
|
}
|
|
|
|
describe('TrialPill', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
vi.setSystemTime(FROZEN_NOW)
|
|
useBillingStore.setState({
|
|
subscription: null,
|
|
planBilling: null,
|
|
planLimits: {},
|
|
enabledFeatures: {},
|
|
isLoading: false,
|
|
error: null,
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('renders Pro trial · Nd for pristine stage', () => {
|
|
setBilling({
|
|
subscription: {
|
|
status: 'trialing',
|
|
plan: 'pro',
|
|
current_period_start: FROZEN_NOW.toISOString(),
|
|
current_period_end: isoDaysFromNow(12),
|
|
cancel_at_period_end: false,
|
|
seat_limit: 5,
|
|
has_pro_entitlement: true,
|
|
is_paid: false,
|
|
},
|
|
})
|
|
|
|
renderPill()
|
|
|
|
const pill = screen.getByTestId('trial-pill')
|
|
expect(pill).toHaveTextContent(/Pro trial · 12d/)
|
|
// Pristine uses info tone tokens.
|
|
expect(pill.className).toContain('text-info')
|
|
expect(pill.className).toContain('bg-info-dim')
|
|
})
|
|
|
|
it('renders Trial expired CTA for expired stage', () => {
|
|
setBilling({
|
|
subscription: {
|
|
status: 'trialing',
|
|
plan: 'pro',
|
|
current_period_start: isoDaysFromNow(-14),
|
|
current_period_end: isoDaysFromNow(-1), // already past
|
|
cancel_at_period_end: false,
|
|
seat_limit: 5,
|
|
has_pro_entitlement: false,
|
|
is_paid: false,
|
|
},
|
|
})
|
|
|
|
renderPill()
|
|
|
|
const pill = screen.getByTestId('trial-pill')
|
|
expect(pill).toHaveTextContent(/Trial expired — pick a plan/)
|
|
// Clickable: rendered as anchor/link.
|
|
expect(pill.tagName).toBe('A')
|
|
expect(pill.getAttribute('href')).toBe('/account/billing/select-plan')
|
|
})
|
|
|
|
it('renders Complimentary Pro tag for complimentary subscription', () => {
|
|
setBilling({
|
|
subscription: {
|
|
status: 'complimentary',
|
|
plan: 'pro',
|
|
current_period_start: null,
|
|
current_period_end: null,
|
|
cancel_at_period_end: false,
|
|
seat_limit: null,
|
|
has_pro_entitlement: true,
|
|
is_paid: true,
|
|
},
|
|
})
|
|
|
|
renderPill()
|
|
|
|
const pill = screen.getByTestId('trial-pill')
|
|
expect(pill).toHaveTextContent(/Complimentary Pro/)
|
|
// Friendly tag, not clickable.
|
|
expect(pill.tagName).toBe('SPAN')
|
|
expect(pill.className).toContain('text-accent')
|
|
})
|
|
|
|
it('is hidden when subscription is null', () => {
|
|
setBilling({ subscription: null })
|
|
|
|
const { container } = renderPill()
|
|
|
|
expect(screen.queryByTestId('trial-pill')).not.toBeInTheDocument()
|
|
expect(container.firstChild).toBeNull()
|
|
})
|
|
|
|
it('past_due variant is clickable and links to /account/billing', () => {
|
|
setBilling({
|
|
subscription: {
|
|
status: 'past_due',
|
|
plan: 'pro',
|
|
current_period_start: isoDaysFromNow(-30),
|
|
current_period_end: isoDaysFromNow(-2),
|
|
cancel_at_period_end: false,
|
|
seat_limit: 5,
|
|
has_pro_entitlement: false,
|
|
is_paid: true,
|
|
},
|
|
})
|
|
|
|
renderPill()
|
|
|
|
const pill = screen.getByTestId('trial-pill')
|
|
expect(pill).toHaveTextContent(/Payment failed — update card/)
|
|
expect(pill.tagName).toBe('A')
|
|
expect(pill.getAttribute('href')).toBe('/account/billing')
|
|
})
|
|
})
|