Files
resolutionflow/frontend/src/components/layout/__tests__/TrialPill.test.tsx
Michael Chihlas f1be3abcc5
Some checks failed
CI / e2e (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / backend (push) Has been cancelled
Mirror to GitHub / mirror (push) Has been cancelled
feat: self-serve signup Phase 2 (frontend cutover) (#162)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com>
Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
2026-05-07 18:42:20 +00:00

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')
})
})