PlanUpdateSidePanel.test.tsx186 lines · main
1import { screen } from '@testing-library/react'
2import { beforeEach, describe, expect, it, vi } from 'vitest'
3
4import { PlanUpdateSidePanel } from './PlanUpdateSidePanel'
5import { MANAGED_BY } from '@/lib/constants/infrastructure'
6import { createMockOrganization, render } from '@/tests/helpers'
7
8const mockSelectedOrganization = vi.hoisted(() => vi.fn())
9const mockPush = vi.hoisted(() => vi.fn())
10const mockPartnerManagedResource = vi.hoisted(() => vi.fn())
11
12vi.mock('common', async (importOriginal) => {
13 const original = (await importOriginal()) as typeof import('common')
14 return {
15 ...original,
16 useParams: () => ({ slug: 'stripe-org' }),
17 }
18})
19
20vi.mock('next/router', () => ({
21 useRouter: () => ({
22 pathname: '/org/[slug]/billing',
23 query: {},
24 push: mockPush,
25 }),
26}))
27
28vi.mock('shared-data/plans', () => ({
29 plans: [
30 { id: 'tier_free', planId: 'free', name: 'Free', costUnit: '/ month', features: [] },
31 { id: 'tier_pro', planId: 'pro', name: 'Pro', costUnit: '/ month', features: [] },
32 ],
33}))
34
35vi.mock('@/hooks/misc/useSelectedOrganization', () => ({
36 useSelectedOrganizationQuery: () => ({ data: mockSelectedOrganization() }),
37}))
38
39vi.mock('@/data/telemetry/send-event-mutation', () => ({
40 useSendEventMutation: () => ({ mutate: vi.fn() }),
41}))
42
43vi.mock('@/hooks/misc/useCheckPermissions', () => ({
44 useAsyncCheckPermissions: () => ({ can: true }),
45}))
46
47vi.mock('@/state/organization-settings', () => ({
48 useOrgSettingsPageStateSnapshot: () => ({
49 panelKey: 'subscriptionPlan',
50 setPanelKey: vi.fn(),
51 }),
52}))
53
54vi.mock('@/data/projects/org-projects-infinite-query', () => ({
55 useOrgProjectsInfiniteQuery: () => ({ data: undefined }),
56}))
57
58vi.mock('@/data/organizations/free-project-limit-check-query', () => ({
59 useFreeProjectLimitCheckQuery: () => ({ data: [] }),
60}))
61
62vi.mock('@/data/organizations/organization-query', () => ({
63 useOrganizationQuery: () => ({ data: { has_oriole_project: false } }),
64}))
65
66vi.mock('@/data/subscriptions/org-subscription-query', () => ({
67 useOrgSubscriptionQuery: () => ({
68 data: { plan: { id: 'free', name: 'Free' } },
69 isSuccess: true,
70 }),
71}))
72
73vi.mock('@/data/subscriptions/org-plans-query', () => ({
74 useOrgPlansQuery: () => ({
75 data: {
76 plans: [
77 { id: 'free', price: 0 },
78 { id: 'pro', price: 25 },
79 ],
80 },
81 isPending: false,
82 }),
83}))
84
85vi.mock('@/data/organizations/organization-billing-subscription-preview', () => ({
86 useOrganizationBillingSubscriptionPreview: () => ({}),
87}))
88
89vi.mock('@/components/ui/PartnerManagedResource', () => ({
90 default: (props: any) => {
91 mockPartnerManagedResource(props)
92 return <div data-testid="partner-managed-resource">{props.details}</div>
93 },
94}))
95
96vi.mock('./EnterpriseCard', () => ({
97 EnterpriseCard: () => <div>Enterprise</div>,
98}))
99
100vi.mock('./DowngradeModal', () => ({
101 default: () => null,
102}))
103
104vi.mock('./ExitSurveyModal', () => ({
105 ExitSurveyModal: () => null,
106}))
107
108vi.mock('./UpgradeModal', () => ({
109 default: () => null,
110}))
111
112vi.mock('./MembersExceedLimitModal', () => ({
113 default: () => null,
114}))
115
116vi.mock('./SubscriptionPlanUpdateDialog', () => ({
117 SubscriptionPlanUpdateDialog: () => null,
118}))
119
120describe('PlanUpdateSidePanel', () => {
121 beforeEach(() => {
122 vi.clearAllMocks()
123 mockSelectedOrganization.mockReturnValue(
124 createMockOrganization({
125 slug: 'stripe-org',
126 billing_partner: null,
127 integration_source: 'stripe_projects',
128 managed_by: MANAGED_BY.STRIPE_PROJECTS,
129 })
130 )
131 })
132
133 it('shows Stripe-managed messaging without treating Stripe orgs as partner-billed', () => {
134 render(<PlanUpdateSidePanel />)
135
136 expect(screen.getByTestId('partner-managed-resource')).toBeInTheDocument()
137 expect(screen.getByText('stripe projects upgrade briven/free')).toBeInTheDocument()
138 expect(screen.getByRole('button', { name: 'Upgrade to Pro' })).toBeInTheDocument()
139 })
140
141 it('uses the current plan in the Stripe Projects upgrade command', () => {
142 mockSelectedOrganization.mockReturnValue(
143 createMockOrganization({
144 slug: 'stripe-org',
145 billing_partner: null,
146 integration_source: 'stripe_projects',
147 managed_by: MANAGED_BY.STRIPE_PROJECTS,
148 plan: { id: 'pro', name: 'Pro' },
149 })
150 )
151
152 render(<PlanUpdateSidePanel />)
153
154 expect(screen.getByText('stripe projects upgrade briven/pro')).toBeInTheDocument()
155 })
156
157 it('uses the Stripe Projects downgrade command for team plans', () => {
158 mockSelectedOrganization.mockReturnValue(
159 createMockOrganization({
160 slug: 'stripe-org',
161 billing_partner: null,
162 integration_source: 'stripe_projects',
163 managed_by: MANAGED_BY.STRIPE_PROJECTS,
164 plan: { id: 'team', name: 'Team' },
165 })
166 )
167
168 render(<PlanUpdateSidePanel />)
169
170 expect(screen.getByText('stripe projects downgrade briven/team')).toBeInTheDocument()
171 })
172
173 it('still shows partner-managed messaging for billing-partner orgs', () => {
174 mockSelectedOrganization.mockReturnValue(
175 createMockOrganization({
176 slug: 'aws-org',
177 billing_partner: 'aws_marketplace',
178 managed_by: MANAGED_BY.AWS_MARKETPLACE,
179 })
180 )
181
182 render(<PlanUpdateSidePanel />)
183
184 expect(screen.getByTestId('partner-managed-resource')).toBeInTheDocument()
185 })
186})