diff --git a/web/app/components/billing/plan-upgrade-modal/index.spec.tsx b/web/app/components/billing/plan-upgrade-modal/index.spec.tsx new file mode 100644 index 0000000000..324043d439 --- /dev/null +++ b/web/app/components/billing/plan-upgrade-modal/index.spec.tsx @@ -0,0 +1,118 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import PlanUpgradeModal from './index' + +const mockSetShowPricingModal = jest.fn() + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +jest.mock('@/app/components/base/modal', () => { + const MockModal = ({ isShow, children }: { isShow: boolean; children: React.ReactNode }) => ( + isShow ?
{children}
: null + ) + return { + __esModule: true, + default: MockModal, + } +}) + +jest.mock('@/context/modal-context', () => ({ + useModalContext: () => ({ + setShowPricingModal: mockSetShowPricingModal, + }), +})) + +const baseProps = { + title: 'Upgrade Required', + description: 'You need to upgrade your plan.', + show: true, + onClose: jest.fn(), +} + +const renderComponent = (props: Partial> = {}) => { + const mergedProps = { ...baseProps, ...props } + return render() +} + +describe('PlanUpgradeModal', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + // Rendering and props-driven content + it('should render modal with provided content when visible', () => { + // Arrange + const extraInfoText = 'Additional upgrade details' + renderComponent({ + extraInfo:
{extraInfoText}
, + }) + + // Assert + expect(screen.getByText(baseProps.title)).toBeInTheDocument() + expect(screen.getByText(baseProps.description)).toBeInTheDocument() + expect(screen.getByText(extraInfoText)).toBeInTheDocument() + expect(screen.getByText('billing.triggerLimitModal.dismiss')).toBeInTheDocument() + expect(screen.getByText('billing.triggerLimitModal.upgrade')).toBeInTheDocument() + }) + + // Guard against rendering when modal is hidden + it('should not render content when show is false', () => { + // Act + renderComponent({ show: false }) + + // Assert + expect(screen.queryByText(baseProps.title)).not.toBeInTheDocument() + expect(screen.queryByText(baseProps.description)).not.toBeInTheDocument() + }) + + // User closes the modal from dismiss button + it('should call onClose when dismiss button is clicked', async () => { + // Arrange + const user = userEvent.setup() + const onClose = jest.fn() + renderComponent({ onClose }) + + // Act + await user.click(screen.getByText('billing.triggerLimitModal.dismiss')) + + // Assert + expect(onClose).toHaveBeenCalledTimes(1) + }) + + // Upgrade path uses provided callback over pricing modal + it('should call onUpgrade and onClose when upgrade button is clicked with onUpgrade provided', async () => { + // Arrange + const user = userEvent.setup() + const onClose = jest.fn() + const onUpgrade = jest.fn() + renderComponent({ onClose, onUpgrade }) + + // Act + await user.click(screen.getByText('billing.triggerLimitModal.upgrade')) + + // Assert + expect(onClose).toHaveBeenCalledTimes(1) + expect(onUpgrade).toHaveBeenCalledTimes(1) + expect(mockSetShowPricingModal).not.toHaveBeenCalled() + }) + + // Fallback upgrade path opens pricing modal when no onUpgrade is supplied + it('should open pricing modal when upgrade button is clicked without onUpgrade', async () => { + // Arrange + const user = userEvent.setup() + const onClose = jest.fn() + renderComponent({ onClose, onUpgrade: undefined }) + + // Act + await user.click(screen.getByText('billing.triggerLimitModal.upgrade')) + + // Assert + expect(onClose).toHaveBeenCalledTimes(1) + expect(mockSetShowPricingModal).toHaveBeenCalledTimes(1) + }) +})