Skip to content

Commit ef79de5

Browse files
authored
chore(clerk-js): Add unit test around "billing" tab visibility (#7013)
1 parent 160dc93 commit ef79de5

3 files changed

Lines changed: 431 additions & 7 deletions

File tree

.changeset/thin-turtles-search.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx

Lines changed: 328 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import type { CustomPage } from '@clerk/types';
2-
import React from 'react';
3-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { describe, expect, it } from 'vitest';
43

54
import { bindCreateFixtures } from '@/test/create-fixtures';
6-
import { render } from '@/test/utils';
5+
import { render, screen, waitFor } from '@/test/utils';
76

87
import { OrganizationProfile } from '../';
98

109
const { createFixtures } = bindCreateFixtures('OrganizationProfile');
1110

1211
describe('OrganizationProfile', () => {
13-
beforeEach(() => vi.useFakeTimers());
14-
afterEach(() => vi.useRealTimers());
1512
it('includes buttons for the bigger sections', async () => {
1613
const { wrapper } = await createFixtures(f => {
1714
f.withOrganizations();
@@ -55,6 +52,332 @@ describe('OrganizationProfile', () => {
5552
expect(getByText('ExternalLink')).toBeDefined();
5653
});
5754

55+
describe('Billing visibility', () => {
56+
it('does not include Billing when missing billing permission even with non-free subscription', async () => {
57+
const { wrapper, fixtures } = await createFixtures(f => {
58+
f.withOrganizations();
59+
f.withUser({
60+
email_addresses: ['test@clerk.com'],
61+
organization_memberships: [
62+
{
63+
name: 'Org1',
64+
permissions: [],
65+
},
66+
],
67+
});
68+
});
69+
70+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
71+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = false;
72+
73+
fixtures.clerk.billing.getSubscription.mockResolvedValue({
74+
id: 'sub_top',
75+
subscriptionItems: [
76+
{
77+
id: 'sub_item_1',
78+
plan: { hasBaseFee: true },
79+
},
80+
],
81+
} as any);
82+
83+
render(<OrganizationProfile />, { wrapper });
84+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
85+
expect(fixtures.clerk.billing.getSubscription).toHaveBeenCalled();
86+
expect(fixtures.clerk.billing.getStatements).toHaveBeenCalled();
87+
});
88+
89+
it('does not include Billing when missing billing permission even with paid plans', async () => {
90+
const { wrapper, fixtures } = await createFixtures(f => {
91+
f.withOrganizations();
92+
f.withUser({
93+
email_addresses: ['test@clerk.com'],
94+
organization_memberships: [
95+
{
96+
name: 'Org1',
97+
permissions: [],
98+
},
99+
],
100+
});
101+
});
102+
103+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
104+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = true;
105+
106+
render(<OrganizationProfile />, { wrapper });
107+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
108+
});
109+
it('does not include Billing when organization billing is disabled', async () => {
110+
const { wrapper, fixtures } = await createFixtures(f => {
111+
f.withOrganizations();
112+
f.withUser({
113+
email_addresses: ['test@clerk.com'],
114+
organization_memberships: [
115+
{
116+
name: 'Org1',
117+
permissions: ['org:sys_billing:read'],
118+
},
119+
],
120+
});
121+
});
122+
123+
fixtures.environment.commerceSettings.billing.organization.enabled = false;
124+
125+
render(<OrganizationProfile />, { wrapper });
126+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
127+
expect(fixtures.clerk.billing.getSubscription).not.toHaveBeenCalled();
128+
expect(fixtures.clerk.billing.getStatements).not.toHaveBeenCalled();
129+
});
130+
131+
it('does not include Billing when disabled even if instance has paid plans', async () => {
132+
const { wrapper, fixtures } = await createFixtures(f => {
133+
f.withOrganizations();
134+
f.withUser({
135+
email_addresses: ['test@clerk.com'],
136+
organization_memberships: [
137+
{
138+
name: 'Org1',
139+
permissions: ['org:sys_billing:read'],
140+
},
141+
],
142+
});
143+
});
144+
145+
fixtures.environment.commerceSettings.billing.organization.enabled = false;
146+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = true;
147+
148+
render(<OrganizationProfile />, { wrapper });
149+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
150+
expect(fixtures.clerk.billing.getSubscription).not.toHaveBeenCalled();
151+
expect(fixtures.clerk.billing.getStatements).not.toHaveBeenCalled();
152+
});
153+
154+
it('includes Billing when enabled and instance has paid plans', async () => {
155+
const { wrapper, fixtures } = await createFixtures(f => {
156+
f.withOrganizations();
157+
f.withUser({
158+
email_addresses: ['test@clerk.com'],
159+
organization_memberships: [
160+
{
161+
name: 'Org1',
162+
permissions: ['org:sys_billing:read'],
163+
},
164+
],
165+
});
166+
});
167+
168+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
169+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = true;
170+
171+
render(<OrganizationProfile />, { wrapper });
172+
expect(await screen.findByText('Billing')).toBeDefined();
173+
});
174+
175+
it('does not include Billing in organization when user billing has paid plans but organization billing is disabled', async () => {
176+
const { wrapper, fixtures } = await createFixtures(f => {
177+
f.withOrganizations();
178+
f.withUser({
179+
email_addresses: ['test@clerk.com'],
180+
organization_memberships: [
181+
{
182+
name: 'Org1',
183+
permissions: ['org:sys_billing:read'],
184+
},
185+
],
186+
});
187+
});
188+
189+
fixtures.environment.commerceSettings.billing.organization.enabled = false;
190+
fixtures.environment.commerceSettings.billing.user.enabled = true;
191+
fixtures.environment.commerceSettings.billing.user.hasPaidPlans = true;
192+
193+
render(<OrganizationProfile />, { wrapper });
194+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
195+
expect(fixtures.clerk.billing.getSubscription).not.toHaveBeenCalled();
196+
expect(fixtures.clerk.billing.getStatements).not.toHaveBeenCalled();
197+
});
198+
199+
it('includes Billing when enabled and organization has a non-free subscription', async () => {
200+
const { wrapper, fixtures } = await createFixtures(f => {
201+
f.withOrganizations();
202+
f.withUser({
203+
email_addresses: ['test@clerk.com'],
204+
organization_memberships: [
205+
{
206+
name: 'Org1',
207+
permissions: ['org:sys_billing:read'],
208+
},
209+
],
210+
});
211+
});
212+
213+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
214+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = false;
215+
216+
fixtures.clerk.billing.getSubscription.mockResolvedValue({
217+
id: 'sub_top',
218+
subscriptionItems: [
219+
{
220+
id: 'sub_item_1',
221+
plan: { hasBaseFee: true },
222+
},
223+
],
224+
} as any);
225+
226+
render(<OrganizationProfile />, { wrapper });
227+
expect(await screen.findByText('Billing')).toBeDefined();
228+
expect(fixtures.clerk.billing.getSubscription).toHaveBeenCalled();
229+
expect(fixtures.clerk.billing.getStatements).toHaveBeenCalled();
230+
});
231+
232+
it('includes Billing when enabled and organization has past statements', async () => {
233+
const { wrapper, fixtures } = await createFixtures(f => {
234+
f.withOrganizations();
235+
f.withUser({
236+
email_addresses: ['test@clerk.com'],
237+
organization_memberships: [
238+
{
239+
name: 'Org1',
240+
permissions: ['org:sys_billing:read'],
241+
},
242+
],
243+
});
244+
});
245+
246+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
247+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = false;
248+
249+
fixtures.clerk.billing.getSubscription.mockResolvedValue({
250+
id: 'sub_top',
251+
subscriptionItems: [],
252+
} as any);
253+
fixtures.clerk.billing.getStatements.mockResolvedValue({ data: [{}], total_count: 1 } as any);
254+
255+
render(<OrganizationProfile />, { wrapper });
256+
expect(await screen.findByText('Billing')).toBeDefined();
257+
expect(fixtures.clerk.billing.getSubscription).toHaveBeenCalled();
258+
expect(fixtures.clerk.billing.getStatements).toHaveBeenCalled();
259+
});
260+
261+
it('does not include Billing when enabled but no paid plans, no subscription, and no statements', async () => {
262+
const { wrapper, fixtures } = await createFixtures(f => {
263+
f.withOrganizations();
264+
f.withUser({
265+
email_addresses: ['test@clerk.com'],
266+
organization_memberships: [
267+
{
268+
name: 'Org1',
269+
permissions: ['org:sys_billing:read'],
270+
},
271+
],
272+
});
273+
});
274+
275+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
276+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = false;
277+
278+
fixtures.clerk.billing.getSubscription.mockResolvedValue({
279+
id: 'sub_top',
280+
subscriptionItems: [],
281+
} as any);
282+
fixtures.clerk.billing.getStatements.mockResolvedValue({ data: [], total_count: 0 } as any);
283+
284+
render(<OrganizationProfile />, { wrapper });
285+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
286+
expect(fixtures.clerk.billing.getSubscription).toHaveBeenCalled();
287+
expect(fixtures.clerk.billing.getStatements).toHaveBeenCalled();
288+
});
289+
290+
it('does not include Billing in organization when organization has no paid plans, no subscription, and no statements even if user billing has paid plans', async () => {
291+
const { wrapper, fixtures } = await createFixtures(f => {
292+
f.withOrganizations();
293+
f.withUser({
294+
email_addresses: ['test@clerk.com'],
295+
organization_memberships: [
296+
{
297+
name: 'Org1',
298+
permissions: ['org:sys_billing:read'],
299+
},
300+
],
301+
});
302+
});
303+
304+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
305+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = false;
306+
fixtures.environment.commerceSettings.billing.user.enabled = true;
307+
fixtures.environment.commerceSettings.billing.user.hasPaidPlans = true;
308+
309+
fixtures.clerk.billing.getSubscription.mockResolvedValue({
310+
id: 'sub_top',
311+
subscriptionItems: [],
312+
} as any);
313+
fixtures.clerk.billing.getStatements.mockResolvedValue({ data: [], total_count: 0 } as any);
314+
315+
render(<OrganizationProfile />, { wrapper });
316+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
317+
expect(fixtures.clerk.billing.getSubscription).toHaveBeenCalled();
318+
expect(fixtures.clerk.billing.getStatements).toHaveBeenCalled();
319+
});
320+
321+
it('does not include Billing when enabled, no paid plans, subscription is null, and no statements', async () => {
322+
const { wrapper, fixtures } = await createFixtures(f => {
323+
f.withOrganizations();
324+
f.withUser({
325+
email_addresses: ['test@clerk.com'],
326+
organization_memberships: [
327+
{
328+
name: 'Org1',
329+
permissions: ['org:sys_billing:read'],
330+
},
331+
],
332+
});
333+
});
334+
335+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
336+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = false;
337+
338+
(fixtures.clerk.billing.getSubscription as any).mockResolvedValue(null);
339+
(fixtures.clerk.billing.getStatements as any).mockResolvedValue({ data: [], total_count: 0 } as any);
340+
341+
render(<OrganizationProfile />, { wrapper });
342+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
343+
expect(fixtures.clerk.billing.getSubscription).toHaveBeenCalled();
344+
expect(fixtures.clerk.billing.getStatements).toHaveBeenCalled();
345+
});
346+
347+
it('does not include Billing when enabled, no paid plans, subscription is free-only, and no statements', async () => {
348+
const { wrapper, fixtures } = await createFixtures(f => {
349+
f.withOrganizations();
350+
f.withUser({
351+
email_addresses: ['test@clerk.com'],
352+
organization_memberships: [
353+
{
354+
name: 'Org1',
355+
permissions: ['org:sys_billing:read'],
356+
},
357+
],
358+
});
359+
});
360+
361+
fixtures.environment.commerceSettings.billing.organization.enabled = true;
362+
fixtures.environment.commerceSettings.billing.organization.hasPaidPlans = false;
363+
364+
fixtures.clerk.billing.getSubscription.mockResolvedValue({
365+
id: 'sub_top',
366+
subscriptionItems: [
367+
{
368+
id: 'sub_item_1',
369+
plan: { hasBaseFee: false },
370+
},
371+
],
372+
} as any);
373+
fixtures.clerk.billing.getStatements.mockResolvedValue({ data: [], total_count: 0 } as any);
374+
375+
render(<OrganizationProfile />, { wrapper });
376+
await waitFor(() => expect(screen.queryByText('Billing')).toBeNull());
377+
expect(fixtures.clerk.billing.getSubscription).toHaveBeenCalled();
378+
expect(fixtures.clerk.billing.getStatements).toHaveBeenCalled();
379+
});
380+
});
58381
it('removes member nav item if user is lacking permissions', async () => {
59382
const { wrapper } = await createFixtures(f => {
60383
f.withOrganizations();

0 commit comments

Comments
 (0)