mirror of
https://github.com/langgenius/dify.git
synced 2025-12-20 06:32:45 +00:00
chore: tests for billings (#29720)
This commit is contained in:
@@ -19,7 +19,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const useTranslation = () => ({
|
export const useTranslation = () => ({
|
||||||
t: (key: string) => key,
|
t: (key: string, options?: Record<string, unknown>) => {
|
||||||
|
if (options?.returnObjects)
|
||||||
|
return [`${key}-feature-1`, `${key}-feature-2`]
|
||||||
|
if (options)
|
||||||
|
return `${key}:${JSON.stringify(options)}`
|
||||||
|
return key
|
||||||
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
language: 'en',
|
language: 'en',
|
||||||
changeLanguage: jest.fn(),
|
changeLanguage: jest.fn(),
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import Button from './button'
|
||||||
|
import { Plan } from '../../../type'
|
||||||
|
|
||||||
|
describe('CloudPlanButton', () => {
|
||||||
|
describe('Disabled state', () => {
|
||||||
|
test('should disable button and hide arrow when plan is not available', () => {
|
||||||
|
const handleGetPayUrl = jest.fn()
|
||||||
|
// Arrange
|
||||||
|
render(
|
||||||
|
<Button
|
||||||
|
plan={Plan.team}
|
||||||
|
isPlanDisabled
|
||||||
|
btnText="Get started"
|
||||||
|
handleGetPayUrl={handleGetPayUrl}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: /Get started/i })
|
||||||
|
// Assert
|
||||||
|
expect(button).toBeDisabled()
|
||||||
|
expect(button.className).toContain('cursor-not-allowed')
|
||||||
|
expect(handleGetPayUrl).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Enabled state', () => {
|
||||||
|
test('should invoke handler and render arrow when plan is available', () => {
|
||||||
|
const handleGetPayUrl = jest.fn()
|
||||||
|
// Arrange
|
||||||
|
render(
|
||||||
|
<Button
|
||||||
|
plan={Plan.sandbox}
|
||||||
|
isPlanDisabled={false}
|
||||||
|
btnText="Start now"
|
||||||
|
handleGetPayUrl={handleGetPayUrl}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: /Start now/i })
|
||||||
|
// Act
|
||||||
|
fireEvent.click(button)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(handleGetPayUrl).toHaveBeenCalledTimes(1)
|
||||||
|
expect(button).not.toBeDisabled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
|
import CloudPlanItem from './index'
|
||||||
|
import { Plan } from '../../../type'
|
||||||
|
import { PlanRange } from '../../plan-switcher/plan-range-switcher'
|
||||||
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||||
|
import { fetchBillingUrl, fetchSubscriptionUrls } from '@/service/billing'
|
||||||
|
import Toast from '../../../../base/toast'
|
||||||
|
import { ALL_PLANS } from '../../../config'
|
||||||
|
|
||||||
|
jest.mock('../../../../base/toast', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
notify: jest.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/context/app-context', () => ({
|
||||||
|
useAppContext: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/service/billing', () => ({
|
||||||
|
fetchBillingUrl: jest.fn(),
|
||||||
|
fetchSubscriptionUrls: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/hooks/use-async-window-open', () => ({
|
||||||
|
useAsyncWindowOpen: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('../../assets', () => ({
|
||||||
|
Sandbox: () => <div>Sandbox Icon</div>,
|
||||||
|
Professional: () => <div>Professional Icon</div>,
|
||||||
|
Team: () => <div>Team Icon</div>,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockUseAppContext = useAppContext as jest.Mock
|
||||||
|
const mockUseAsyncWindowOpen = useAsyncWindowOpen as jest.Mock
|
||||||
|
const mockFetchBillingUrl = fetchBillingUrl as jest.Mock
|
||||||
|
const mockFetchSubscriptionUrls = fetchSubscriptionUrls as jest.Mock
|
||||||
|
const mockToastNotify = Toast.notify as jest.Mock
|
||||||
|
|
||||||
|
let assignedHref = ''
|
||||||
|
const originalLocation = window.location
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
get href() {
|
||||||
|
return assignedHref
|
||||||
|
},
|
||||||
|
set href(value: string) {
|
||||||
|
assignedHref = value
|
||||||
|
},
|
||||||
|
} as unknown as Location,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: originalLocation,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: true })
|
||||||
|
mockUseAsyncWindowOpen.mockReturnValue(jest.fn(async open => await open()))
|
||||||
|
mockFetchBillingUrl.mockResolvedValue({ url: 'https://billing.example' })
|
||||||
|
mockFetchSubscriptionUrls.mockResolvedValue({ url: 'https://subscription.example' })
|
||||||
|
assignedHref = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('CloudPlanItem', () => {
|
||||||
|
// Static content for each plan
|
||||||
|
describe('Rendering', () => {
|
||||||
|
test('should show plan metadata and free label for sandbox plan', () => {
|
||||||
|
render(
|
||||||
|
<CloudPlanItem
|
||||||
|
plan={Plan.sandbox}
|
||||||
|
currentPlan={Plan.sandbox}
|
||||||
|
planRange={PlanRange.monthly}
|
||||||
|
canPay
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByText('billing.plans.sandbox.name')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plans.sandbox.description')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plansCommon.free')).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('button', { name: 'billing.plansCommon.currentPlan' })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should display yearly pricing with discount when planRange is yearly', () => {
|
||||||
|
render(
|
||||||
|
<CloudPlanItem
|
||||||
|
plan={Plan.professional}
|
||||||
|
currentPlan={Plan.sandbox}
|
||||||
|
planRange={PlanRange.yearly}
|
||||||
|
canPay
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const professionalPlan = ALL_PLANS[Plan.professional]
|
||||||
|
expect(screen.getByText(`$${professionalPlan.price * 12}`)).toBeInTheDocument()
|
||||||
|
expect(screen.getByText(`$${professionalPlan.price * 10}`)).toBeInTheDocument()
|
||||||
|
expect(screen.getByText(/billing\.plansCommon\.priceTip.*billing\.plansCommon\.year/)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should disable CTA when workspace already on higher tier', () => {
|
||||||
|
render(
|
||||||
|
<CloudPlanItem
|
||||||
|
plan={Plan.professional}
|
||||||
|
currentPlan={Plan.team}
|
||||||
|
planRange={PlanRange.monthly}
|
||||||
|
canPay
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: 'billing.plansCommon.startBuilding' })
|
||||||
|
expect(button).toBeDisabled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Payment actions triggered from the CTA
|
||||||
|
describe('Plan purchase flow', () => {
|
||||||
|
test('should show toast when non-manager tries to buy a plan', () => {
|
||||||
|
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: false })
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CloudPlanItem
|
||||||
|
plan={Plan.professional}
|
||||||
|
currentPlan={Plan.sandbox}
|
||||||
|
planRange={PlanRange.monthly}
|
||||||
|
canPay
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'billing.plansCommon.startBuilding' }))
|
||||||
|
expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
type: 'error',
|
||||||
|
message: 'billing.buyPermissionDeniedTip',
|
||||||
|
}))
|
||||||
|
expect(mockFetchBillingUrl).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should open billing portal when upgrading current paid plan', async () => {
|
||||||
|
const openWindow = jest.fn(async (cb: () => Promise<string>) => await cb())
|
||||||
|
mockUseAsyncWindowOpen.mockReturnValue(openWindow)
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CloudPlanItem
|
||||||
|
plan={Plan.professional}
|
||||||
|
currentPlan={Plan.professional}
|
||||||
|
planRange={PlanRange.monthly}
|
||||||
|
canPay
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'billing.plansCommon.currentPlan' }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockFetchBillingUrl).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
expect(openWindow).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should redirect to subscription url when selecting a new paid plan', async () => {
|
||||||
|
render(
|
||||||
|
<CloudPlanItem
|
||||||
|
plan={Plan.professional}
|
||||||
|
currentPlan={Plan.sandbox}
|
||||||
|
planRange={PlanRange.monthly}
|
||||||
|
canPay
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'billing.plansCommon.startBuilding' }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockFetchSubscriptionUrls).toHaveBeenCalledWith(Plan.professional, 'month')
|
||||||
|
expect(assignedHref).toBe('https://subscription.example')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import List from './index'
|
||||||
|
import { Plan } from '../../../../type'
|
||||||
|
|
||||||
|
describe('CloudPlanItem/List', () => {
|
||||||
|
test('should show sandbox specific quotas', () => {
|
||||||
|
render(<List plan={Plan.sandbox} />)
|
||||||
|
|
||||||
|
expect(screen.getByText('billing.plansCommon.messageRequest.title:{"count":200}')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plansCommon.triggerEvents.sandbox:{"count":3000}')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plansCommon.startNodes.limited:{"count":2}')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should show professional monthly quotas and tooltips', () => {
|
||||||
|
render(<List plan={Plan.professional} />)
|
||||||
|
|
||||||
|
expect(screen.getByText('billing.plansCommon.messageRequest.titlePerMonth:{"count":5000}')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plansCommon.vectorSpaceTooltip')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plansCommon.workflowExecution.faster')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should show unlimited messaging details for team plan', () => {
|
||||||
|
render(<List plan={Plan.team} />)
|
||||||
|
|
||||||
|
expect(screen.getByText('billing.plansCommon.triggerEvents.unlimited')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plansCommon.workflowExecution.priority')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plansCommon.unlimitedApiRate')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
87
web/app/components/billing/pricing/plans/index.spec.tsx
Normal file
87
web/app/components/billing/pricing/plans/index.spec.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import Plans from './index'
|
||||||
|
import { Plan, type UsagePlanInfo } from '../../type'
|
||||||
|
import { PlanRange } from '../plan-switcher/plan-range-switcher'
|
||||||
|
|
||||||
|
jest.mock('./cloud-plan-item', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn(props => (
|
||||||
|
<div data-testid={`cloud-plan-${props.plan}`} data-current-plan={props.currentPlan}>
|
||||||
|
Cloud {props.plan}
|
||||||
|
</div>
|
||||||
|
)),
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('./self-hosted-plan-item', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn(props => (
|
||||||
|
<div data-testid={`self-plan-${props.plan}`}>
|
||||||
|
Self {props.plan}
|
||||||
|
</div>
|
||||||
|
)),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const buildPlan = (type: Plan) => {
|
||||||
|
const usage: UsagePlanInfo = {
|
||||||
|
buildApps: 0,
|
||||||
|
teamMembers: 0,
|
||||||
|
annotatedResponse: 0,
|
||||||
|
documentsUploadQuota: 0,
|
||||||
|
apiRateLimit: 0,
|
||||||
|
triggerEvents: 0,
|
||||||
|
vectorSpace: 0,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
usage,
|
||||||
|
total: usage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Plans', () => {
|
||||||
|
// Cloud plans visible only when currentPlan is cloud
|
||||||
|
describe('Cloud plan rendering', () => {
|
||||||
|
test('should render sandbox, professional, and team cloud plans when workspace is cloud', () => {
|
||||||
|
render(
|
||||||
|
<Plans
|
||||||
|
plan={buildPlan(Plan.enterprise)}
|
||||||
|
currentPlan="cloud"
|
||||||
|
planRange={PlanRange.monthly}
|
||||||
|
canPay
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByTestId('cloud-plan-sandbox')).toBeInTheDocument()
|
||||||
|
expect(screen.getByTestId('cloud-plan-professional')).toBeInTheDocument()
|
||||||
|
expect(screen.getByTestId('cloud-plan-team')).toBeInTheDocument()
|
||||||
|
|
||||||
|
const cloudPlanItem = jest.requireMock('./cloud-plan-item').default as jest.Mock
|
||||||
|
const firstCallProps = cloudPlanItem.mock.calls[0][0]
|
||||||
|
expect(firstCallProps.plan).toBe(Plan.sandbox)
|
||||||
|
// Enterprise should be normalized to team when passed down
|
||||||
|
expect(firstCallProps.currentPlan).toBe(Plan.team)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Self-hosted plans visible for self-managed workspaces
|
||||||
|
describe('Self-hosted plan rendering', () => {
|
||||||
|
test('should render all self-hosted plans when workspace type is self-hosted', () => {
|
||||||
|
render(
|
||||||
|
<Plans
|
||||||
|
plan={buildPlan(Plan.sandbox)}
|
||||||
|
currentPlan="self"
|
||||||
|
planRange={PlanRange.yearly}
|
||||||
|
canPay={false}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByTestId('self-plan-community')).toBeInTheDocument()
|
||||||
|
expect(screen.getByTestId('self-plan-premium')).toBeInTheDocument()
|
||||||
|
expect(screen.getByTestId('self-plan-enterprise')).toBeInTheDocument()
|
||||||
|
|
||||||
|
const selfPlanItem = jest.requireMock('./self-hosted-plan-item').default as jest.Mock
|
||||||
|
expect(selfPlanItem).toHaveBeenCalledTimes(3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import Button from './button'
|
||||||
|
import { SelfHostedPlan } from '../../../type'
|
||||||
|
import useTheme from '@/hooks/use-theme'
|
||||||
|
import { Theme } from '@/types/app'
|
||||||
|
|
||||||
|
jest.mock('@/hooks/use-theme')
|
||||||
|
|
||||||
|
jest.mock('@/app/components/base/icons/src/public/billing', () => ({
|
||||||
|
AwsMarketplaceLight: () => <div>AwsMarketplaceLight</div>,
|
||||||
|
AwsMarketplaceDark: () => <div>AwsMarketplaceDark</div>,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockUseTheme = useTheme as jest.MockedFunction<typeof useTheme>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
mockUseTheme.mockReturnValue({ theme: Theme.light } as unknown as ReturnType<typeof useTheme>)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('SelfHostedPlanButton', () => {
|
||||||
|
test('should invoke handler when clicked', () => {
|
||||||
|
const handleGetPayUrl = jest.fn()
|
||||||
|
render(
|
||||||
|
<Button
|
||||||
|
plan={SelfHostedPlan.community}
|
||||||
|
handleGetPayUrl={handleGetPayUrl}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'billing.plans.community.btnText' }))
|
||||||
|
expect(handleGetPayUrl).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should render AWS marketplace badge for premium plan in light theme', () => {
|
||||||
|
const handleGetPayUrl = jest.fn()
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Button
|
||||||
|
plan={SelfHostedPlan.premium}
|
||||||
|
handleGetPayUrl={handleGetPayUrl}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByText('AwsMarketplaceLight')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should switch to dark AWS badge in dark theme', () => {
|
||||||
|
mockUseTheme.mockReturnValue({ theme: Theme.dark } as unknown as ReturnType<typeof useTheme>)
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Button
|
||||||
|
plan={SelfHostedPlan.premium}
|
||||||
|
handleGetPayUrl={jest.fn()}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByText('AwsMarketplaceDark')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import SelfHostedPlanItem from './index'
|
||||||
|
import { SelfHostedPlan } from '../../../type'
|
||||||
|
import { contactSalesUrl, getStartedWithCommunityUrl, getWithPremiumUrl } from '../../../config'
|
||||||
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import Toast from '../../../../base/toast'
|
||||||
|
|
||||||
|
const featuresTranslations: Record<string, string[]> = {
|
||||||
|
'billing.plans.community.features': ['community-feature-1', 'community-feature-2'],
|
||||||
|
'billing.plans.premium.features': ['premium-feature-1'],
|
||||||
|
'billing.plans.enterprise.features': ['enterprise-feature-1'],
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string, options?: Record<string, unknown>) => {
|
||||||
|
if (options?.returnObjects)
|
||||||
|
return featuresTranslations[key] || []
|
||||||
|
return key
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Trans: ({ i18nKey }: { i18nKey: string }) => <span>{i18nKey}</span>,
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('../../../../base/toast', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
notify: jest.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/context/app-context', () => ({
|
||||||
|
useAppContext: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('../../assets', () => ({
|
||||||
|
Community: () => <div>Community Icon</div>,
|
||||||
|
Premium: () => <div>Premium Icon</div>,
|
||||||
|
Enterprise: () => <div>Enterprise Icon</div>,
|
||||||
|
PremiumNoise: () => <div>PremiumNoise</div>,
|
||||||
|
EnterpriseNoise: () => <div>EnterpriseNoise</div>,
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/app/components/base/icons/src/public/billing', () => ({
|
||||||
|
Azure: () => <div>Azure</div>,
|
||||||
|
GoogleCloud: () => <div>Google Cloud</div>,
|
||||||
|
AwsMarketplaceDark: () => <div>AwsMarketplaceDark</div>,
|
||||||
|
AwsMarketplaceLight: () => <div>AwsMarketplaceLight</div>,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockUseAppContext = useAppContext as jest.Mock
|
||||||
|
const mockToastNotify = Toast.notify as jest.Mock
|
||||||
|
|
||||||
|
let assignedHref = ''
|
||||||
|
const originalLocation = window.location
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
get href() {
|
||||||
|
return assignedHref
|
||||||
|
},
|
||||||
|
set href(value: string) {
|
||||||
|
assignedHref = value
|
||||||
|
},
|
||||||
|
} as unknown as Location,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: originalLocation,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: true })
|
||||||
|
assignedHref = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('SelfHostedPlanItem', () => {
|
||||||
|
// Copy rendering for each plan
|
||||||
|
describe('Rendering', () => {
|
||||||
|
test('should display community plan info', () => {
|
||||||
|
render(<SelfHostedPlanItem plan={SelfHostedPlan.community} />)
|
||||||
|
|
||||||
|
expect(screen.getByText('billing.plans.community.name')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plans.community.description')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plans.community.price')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plans.community.includesTitle')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('community-feature-1')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should show premium extras such as cloud provider notice', () => {
|
||||||
|
render(<SelfHostedPlanItem plan={SelfHostedPlan.premium} />)
|
||||||
|
|
||||||
|
expect(screen.getByText('billing.plans.premium.price')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('billing.plans.premium.comingSoon')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Azure')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Google Cloud')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// CTA behavior for each plan
|
||||||
|
describe('CTA interactions', () => {
|
||||||
|
test('should show toast when non-manager tries to proceed', () => {
|
||||||
|
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: false })
|
||||||
|
|
||||||
|
render(<SelfHostedPlanItem plan={SelfHostedPlan.premium} />)
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /billing\.plans\.premium\.btnText/ }))
|
||||||
|
|
||||||
|
expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
type: 'error',
|
||||||
|
message: 'billing.buyPermissionDeniedTip',
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should redirect to community url when community plan button clicked', () => {
|
||||||
|
render(<SelfHostedPlanItem plan={SelfHostedPlan.community} />)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'billing.plans.community.btnText' }))
|
||||||
|
expect(assignedHref).toBe(getStartedWithCommunityUrl)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should redirect to premium marketplace url when premium button clicked', () => {
|
||||||
|
render(<SelfHostedPlanItem plan={SelfHostedPlan.premium} />)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /billing\.plans\.premium\.btnText/ }))
|
||||||
|
expect(assignedHref).toBe(getWithPremiumUrl)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should redirect to contact sales form when enterprise button clicked', () => {
|
||||||
|
render(<SelfHostedPlanItem plan={SelfHostedPlan.enterprise} />)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'billing.plans.enterprise.btnText' }))
|
||||||
|
expect(assignedHref).toBe(contactSalesUrl)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import List from './index'
|
||||||
|
import { SelfHostedPlan } from '@/app/components/billing/type'
|
||||||
|
|
||||||
|
jest.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string, options?: Record<string, unknown>) => {
|
||||||
|
if (options?.returnObjects)
|
||||||
|
return ['Feature A', 'Feature B']
|
||||||
|
return key
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Trans: ({ i18nKey }: { i18nKey: string }) => <span>{i18nKey}</span>,
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('SelfHostedPlanItem/List', () => {
|
||||||
|
test('should render plan info', () => {
|
||||||
|
render(<List plan={SelfHostedPlan.community} />)
|
||||||
|
|
||||||
|
expect(screen.getByText('billing.plans.community.includesTitle')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Feature A')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Feature B')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import Item from './item'
|
||||||
|
|
||||||
|
describe('SelfHostedPlanItem/List/Item', () => {
|
||||||
|
test('should display provided feature label', () => {
|
||||||
|
const { container } = render(<Item label="Dedicated support" />)
|
||||||
|
|
||||||
|
expect(screen.getByText('Dedicated support')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('svg')).not.toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user