mirror of
https://github.com/langgenius/dify.git
synced 2026-04-18 04:16:28 +08:00
255 lines
8.1 KiB
TypeScript
255 lines
8.1 KiB
TypeScript
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants'
|
|
import { Plan, SelfHostedPlan } from '../../type'
|
|
import PlanComp from '../index'
|
|
|
|
let currentPath = '/billing'
|
|
|
|
const push = vi.fn()
|
|
|
|
vi.mock('@/next/navigation', () => ({
|
|
useRouter: () => ({ push }),
|
|
usePathname: () => currentPath,
|
|
}))
|
|
|
|
const setShowAccountSettingModalMock = vi.fn()
|
|
vi.mock('@/context/modal-context', () => ({
|
|
useModalContextSelector: (selector: (state: { setShowAccountSettingModal: typeof setShowAccountSettingModalMock }) => unknown) => selector({
|
|
setShowAccountSettingModal: setShowAccountSettingModalMock,
|
|
}),
|
|
}))
|
|
|
|
const providerContextMock = vi.fn()
|
|
vi.mock('@/context/provider-context', () => ({
|
|
useProviderContext: () => providerContextMock(),
|
|
}))
|
|
|
|
vi.mock('@/context/app-context', () => ({
|
|
useAppContext: () => ({
|
|
userProfile: { email: 'user@example.com' },
|
|
isCurrentWorkspaceManager: true,
|
|
}),
|
|
}))
|
|
|
|
const mutateAsyncMock = vi.fn()
|
|
let isPending = false
|
|
vi.mock('@/service/use-education', () => ({
|
|
useEducationVerify: () => ({
|
|
mutateAsync: mutateAsyncMock,
|
|
isPending,
|
|
}),
|
|
}))
|
|
|
|
const verifyStateModalMock = vi.fn(props => (
|
|
<div data-testid="verify-modal" data-is-show={props.isShow ? 'true' : 'false'}>
|
|
{props.isShow ? 'visible' : 'hidden'}
|
|
</div>
|
|
))
|
|
vi.mock('@/app/education-apply/verify-state-modal', () => ({
|
|
default: (props: { isShow: boolean, title?: string, content?: string, email?: string, showLink?: boolean, onConfirm?: () => void, onCancel?: () => void }) => verifyStateModalMock(props),
|
|
}))
|
|
|
|
vi.mock('../../upgrade-btn', () => ({
|
|
default: () => <button data-testid="plan-upgrade-btn" type="button">Upgrade</button>,
|
|
}))
|
|
|
|
describe('PlanComp', () => {
|
|
const planMock = {
|
|
type: Plan.professional,
|
|
usage: {
|
|
teamMembers: 4,
|
|
documentsUploadQuota: 3,
|
|
vectorSpace: 8,
|
|
annotatedResponse: 5,
|
|
triggerEvents: 60,
|
|
apiRateLimit: 100,
|
|
},
|
|
total: {
|
|
teamMembers: 10,
|
|
documentsUploadQuota: 20,
|
|
vectorSpace: 10,
|
|
annotatedResponse: 500,
|
|
triggerEvents: 100,
|
|
apiRateLimit: 200,
|
|
},
|
|
reset: {
|
|
triggerEvents: 2,
|
|
apiRateLimit: 1,
|
|
},
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
currentPath = '/billing'
|
|
isPending = false
|
|
providerContextMock.mockReturnValue({
|
|
plan: planMock,
|
|
enableEducationPlan: true,
|
|
allowRefreshEducationVerify: false,
|
|
isEducationAccount: false,
|
|
})
|
|
mutateAsyncMock.mockReset()
|
|
mutateAsyncMock.mockResolvedValue({ token: 'token' })
|
|
})
|
|
|
|
it('renders plan info and handles education verify success', async () => {
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(screen.getByText('billing.plans.professional.name')).toBeInTheDocument()
|
|
expect(screen.getByTestId('plan-upgrade-btn')).toBeInTheDocument()
|
|
|
|
const verifyBtn = screen.getByText('education.toVerified')
|
|
fireEvent.click(verifyBtn)
|
|
|
|
await waitFor(() => expect(mutateAsyncMock).toHaveBeenCalled())
|
|
await waitFor(() => expect(push).toHaveBeenCalledWith('/education-apply?token=token'))
|
|
expect(localStorage.removeItem).toHaveBeenCalledWith(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM)
|
|
})
|
|
|
|
it('shows modal when education verify fails', async () => {
|
|
mutateAsyncMock.mockRejectedValueOnce(new Error('boom'))
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
const verifyBtn = screen.getByText('education.toVerified')
|
|
fireEvent.click(verifyBtn)
|
|
|
|
await waitFor(() => expect(mutateAsyncMock).toHaveBeenCalled())
|
|
await waitFor(() => expect(screen.getByTestId('verify-modal').getAttribute('data-is-show')).toBe('true'))
|
|
})
|
|
|
|
it('resets modal context when on education apply path', () => {
|
|
currentPath = '/education-apply/setup'
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(setShowAccountSettingModalMock).toHaveBeenCalledWith(null)
|
|
})
|
|
|
|
it('does not trigger verify when isPending is true', async () => {
|
|
isPending = true
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
const verifyBtn = screen.getByText('education.toVerified')
|
|
fireEvent.click(verifyBtn)
|
|
|
|
await waitFor(() => expect(mutateAsyncMock).not.toHaveBeenCalled())
|
|
})
|
|
|
|
it('renders sandbox plan', () => {
|
|
providerContextMock.mockReturnValue({
|
|
plan: { ...planMock, type: Plan.sandbox },
|
|
enableEducationPlan: false,
|
|
allowRefreshEducationVerify: false,
|
|
isEducationAccount: false,
|
|
})
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(screen.getByText('billing.plans.sandbox.name')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders team plan', () => {
|
|
providerContextMock.mockReturnValue({
|
|
plan: { ...planMock, type: Plan.team },
|
|
enableEducationPlan: false,
|
|
allowRefreshEducationVerify: false,
|
|
isEducationAccount: false,
|
|
})
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(screen.getByText('billing.plans.team.name')).toBeInTheDocument()
|
|
})
|
|
|
|
it('shows verify button when education account is about to expire', () => {
|
|
providerContextMock.mockReturnValue({
|
|
plan: planMock,
|
|
enableEducationPlan: true,
|
|
allowRefreshEducationVerify: true,
|
|
isEducationAccount: true,
|
|
})
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(screen.getByText('education.toVerified')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders enterprise plan without upgrade button', () => {
|
|
providerContextMock.mockReturnValue({
|
|
plan: { ...planMock, type: SelfHostedPlan.enterprise },
|
|
enableEducationPlan: false,
|
|
allowRefreshEducationVerify: false,
|
|
isEducationAccount: false,
|
|
})
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(screen.getByText('billing.plans.enterprise.name')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('plan-upgrade-btn')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('shows apiRateLimit reset info for sandbox plan', () => {
|
|
providerContextMock.mockReturnValue({
|
|
plan: {
|
|
...planMock,
|
|
type: Plan.sandbox,
|
|
total: { ...planMock.total, apiRateLimit: 5000 },
|
|
reset: { ...planMock.reset, apiRateLimit: null },
|
|
},
|
|
enableEducationPlan: false,
|
|
allowRefreshEducationVerify: false,
|
|
isEducationAccount: false,
|
|
})
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
// Sandbox plan with finite apiRateLimit and null reset uses getDaysUntilEndOfMonth()
|
|
expect(screen.getByText('billing.plans.sandbox.name')).toBeInTheDocument()
|
|
})
|
|
|
|
it('shows apiRateLimit reset info when reset is a number', () => {
|
|
providerContextMock.mockReturnValue({
|
|
plan: {
|
|
...planMock,
|
|
type: Plan.professional,
|
|
total: { ...planMock.total, apiRateLimit: 5000 },
|
|
reset: { ...planMock.reset, apiRateLimit: 3 },
|
|
},
|
|
enableEducationPlan: false,
|
|
allowRefreshEducationVerify: false,
|
|
isEducationAccount: false,
|
|
})
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(screen.getByText('billing.plans.professional.name')).toBeInTheDocument()
|
|
})
|
|
|
|
it('does not show education verify when enableEducationPlan is false', () => {
|
|
providerContextMock.mockReturnValue({
|
|
plan: planMock,
|
|
enableEducationPlan: false,
|
|
allowRefreshEducationVerify: false,
|
|
isEducationAccount: false,
|
|
})
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
expect(screen.queryByText('education.toVerified')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('handles modal onConfirm and onCancel callbacks', async () => {
|
|
mutateAsyncMock.mockRejectedValueOnce(new Error('boom'))
|
|
render(<PlanComp loc="billing-page" />)
|
|
|
|
// Trigger verify to show modal
|
|
const verifyBtn = screen.getByText('education.toVerified')
|
|
fireEvent.click(verifyBtn)
|
|
|
|
await waitFor(() => expect(screen.getByTestId('verify-modal').getAttribute('data-is-show')).toBe('true'))
|
|
|
|
// Get the props passed to the modal and call onConfirm/onCancel
|
|
const lastCall = verifyStateModalMock.mock.calls[verifyStateModalMock.mock.calls.length - 1][0]
|
|
expect(lastCall.onConfirm).toBeDefined()
|
|
expect(lastCall.onCancel).toBeDefined()
|
|
|
|
// Call onConfirm to close modal
|
|
act(() => {
|
|
lastCall.onConfirm()
|
|
lastCall.onCancel()
|
|
})
|
|
})
|
|
})
|