import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { AddBeneficiaryModal } from '../AddBeneficiaryModal'; import * as api from '../../lib/api'; jest.mock('../../lib/api'); const mockCreateBeneficiary = api.createBeneficiary as jest.MockedFunction< typeof api.createBeneficiary >; describe('AddBeneficiaryModal', () => { const defaultProps = { isOpen: true, onClose: jest.fn(), onSuccess: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); }); const getNameInput = () => screen.getByPlaceholderText('e.g., Grandma Julia'); const getAddressInput = () => screen.getByPlaceholderText('123 Main St, City, State'); const getPhoneInput = () => screen.getByPlaceholderText('+1 (555) 123-4567'); const getSubmitButton = () => screen.getByRole('button', { name: /Add Beneficiary/i }); const getCancelButton = () => screen.getByRole('button', { name: /Cancel/i }); it('does not render when isOpen is false', () => { render(); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); it('renders modal when isOpen is true', () => { render(); expect(screen.getByRole('dialog')).toBeInTheDocument(); expect(screen.getByText('Add New Beneficiary')).toBeInTheDocument(); }); it('renders all form fields', () => { render(); expect(getNameInput()).toBeInTheDocument(); expect(getAddressInput()).toBeInTheDocument(); expect(getPhoneInput()).toBeInTheDocument(); }); it('shows validation error when name is empty', async () => { render(); fireEvent.click(getSubmitButton()); await waitFor(() => { expect(screen.getByText('Please enter a name for the beneficiary')).toBeInTheDocument(); }); expect(mockCreateBeneficiary).not.toHaveBeenCalled(); expect(defaultProps.onSuccess).not.toHaveBeenCalled(); }); it('calls createBeneficiary with correct data on submit', async () => { mockCreateBeneficiary.mockResolvedValueOnce({ id: 123, name: 'Test Name' }); render(); fireEvent.change(getNameInput(), { target: { value: 'Grandma Julia' } }); fireEvent.change(getAddressInput(), { target: { value: '123 Main St' } }); fireEvent.change(getPhoneInput(), { target: { value: '+1 555-123-4567' } }); fireEvent.click(getSubmitButton()); await waitFor(() => { expect(mockCreateBeneficiary).toHaveBeenCalledWith({ name: 'Grandma Julia', address: '123 Main St', phone: '+1 555-123-4567', }); }); }); it('calls onSuccess and onClose after successful creation', async () => { mockCreateBeneficiary.mockResolvedValueOnce({ id: 456, name: 'Test Person' }); render(); await userEvent.type(getNameInput(), 'Test Person'); fireEvent.click(getSubmitButton()); await waitFor(() => { expect(defaultProps.onSuccess).toHaveBeenCalledWith({ id: 456, name: 'Test Person', }); expect(defaultProps.onClose).toHaveBeenCalled(); }); }); it('shows error message on API failure', async () => { mockCreateBeneficiary.mockRejectedValueOnce(new Error('Network error')); render(); await userEvent.type(getNameInput(), 'Test Name'); fireEvent.click(getSubmitButton()); await waitFor(() => { expect(screen.getByText('Network error')).toBeInTheDocument(); }); expect(defaultProps.onClose).not.toHaveBeenCalled(); expect(defaultProps.onSuccess).not.toHaveBeenCalled(); }); it('closes modal when Cancel button is clicked', () => { render(); fireEvent.click(getCancelButton()); expect(defaultProps.onClose).toHaveBeenCalled(); }); it('closes modal when clicking backdrop', () => { render(); const backdrop = screen.getByRole('dialog'); fireEvent.click(backdrop); expect(defaultProps.onClose).toHaveBeenCalled(); }); it('does not close modal when clicking inside the modal content', () => { render(); const modalTitle = screen.getByText('Add New Beneficiary'); fireEvent.click(modalTitle); expect(defaultProps.onClose).not.toHaveBeenCalled(); }); it('closes modal when Escape key is pressed', () => { render(); fireEvent.keyDown(document, { key: 'Escape' }); expect(defaultProps.onClose).toHaveBeenCalled(); }); it('disables form during submission', async () => { let resolvePromise: (value: { id: number; name: string }) => void; const promise = new Promise<{ id: number; name: string }>((resolve) => { resolvePromise = resolve; }); mockCreateBeneficiary.mockReturnValueOnce(promise); render(); await userEvent.type(getNameInput(), 'Test Name'); fireEvent.click(getSubmitButton()); await waitFor(() => { expect(getNameInput()).toBeDisabled(); expect(getCancelButton()).toBeDisabled(); }); resolvePromise!({ id: 1, name: 'Test Name' }); }); it('clears form when modal is reopened', async () => { const { rerender } = render(); await userEvent.type(getNameInput(), 'Some Name'); rerender(); rerender(); expect(getNameInput()).toHaveValue(''); }); it('trims whitespace from inputs', async () => { mockCreateBeneficiary.mockResolvedValueOnce({ id: 789, name: 'Trimmed Name' }); render(); await userEvent.type(getNameInput(), ' Trimmed Name '); fireEvent.click(getSubmitButton()); await waitFor(() => { expect(mockCreateBeneficiary).toHaveBeenCalledWith({ name: 'Trimmed Name', address: undefined, phone: undefined, }); }); }); it('dismisses error when user starts typing', async () => { render(); fireEvent.click(getSubmitButton()); await waitFor(() => { expect(screen.getByText('Please enter a name for the beneficiary')).toBeInTheDocument(); }); await userEvent.type(getNameInput(), 'N'); expect(screen.queryByText('Please enter a name for the beneficiary')).not.toBeInTheDocument(); }); });