import { describe, it, expect, beforeEach, vi } from 'vitest'; import { screen, fireEvent, waitFor } from '@testing-library/react'; import { renderWithProviders } from '../../test/utils'; vi.mock('../../lib/api', () => ({ api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() }, ApiError: class extends Error {}, })); import { api } from '../../lib/api'; import ConfirmAppointmentModal from './ConfirmAppointmentModal'; const get = api.get as ReturnType; const post = api.post as ReturnType; const appointment = { uuid: 'a1', version: 1, patient_name: 'محمد رضایی', visit_price_rials: 2_120_000, service_items: [{ uuid: 's1', name: 'لیزر', price_rials: 880_000 }], }; function render() { return renderWithProviders( {}} />, ); } beforeEach(() => { vi.clearAllMocks(); // payment-methods (pos / bank-accounts) و بقیهٔ GETها get.mockResolvedValue({ success: true, data: [] }); post.mockResolvedValue({ success: true, data: {} }); }); /** همهٔ فیلدهای مبلغ (تومان) در ردیف‌های پرداخت. */ function amountInputs() { return screen.getAllByPlaceholderText('0') as HTMLInputElement[]; } describe('ConfirmAppointmentModal', () => { it('بیمار، اقلام هزینه و جمع کل را نشان می‌دهد', () => { render(); expect(screen.getByText('محمد رضایی')).toBeInTheDocument(); expect(screen.getByText('ویزیت')).toBeInTheDocument(); expect(screen.getByText('لیزر')).toBeInTheDocument(); expect(screen.getByText('جمع کل')).toBeInTheDocument(); }); it('ردیفِ اول پیش‌فرض برابر کل هزینه است و وضعیت «تسویه کامل» می‌شود', () => { render(); // ۳٬۰۰۰٬۰۰۰ ریال = ۳۰۰٬۰۰۰ تومان expect(amountInputs()[0]).toHaveValue('۳۰۰٬۰۰۰'); expect(screen.getByText('تسویه کامل')).toBeInTheDocument(); }); it('با تغییر دستی مبلغ به کمتر از کل، وضعیت «پرداخت جزئی» می‌شود', () => { render(); fireEvent.change(amountInputs()[0], { target: { value: '100000' } }); expect(screen.getByText('پرداخت جزئی')).toBeInTheDocument(); }); it('دکمهٔ تأیید فقط با مجموعِ بیشتر از جمع کل غیرفعال می‌شود', () => { render(); const submit = screen.getByRole('button', { name: 'تأیید و قطعی کردن' }); expect(submit).not.toBeDisabled(); fireEvent.change(amountInputs()[0], { target: { value: '9000000' } }); expect(screen.getByText('مجموع پرداخت‌ها از جمع کل بیشتر است.')).toBeInTheDocument(); expect(submit).toBeDisabled(); }); it('پرداخت جزئی مجاز است و همان یک روش را ثبت می‌کند', async () => { render(); fireEvent.change(amountInputs()[0], { target: { value: '100000' } }); fireEvent.click(screen.getByRole('button', { name: 'تأیید و قطعی کردن' })); await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/appointment/a1/confirm', { version: 1, payments: [{ method: 'cash', amount_rials: 1_000_000 }], })); }); it('تقسیم پرداخت بین دو روش: مجموع ردیف‌ها به‌صورت آرایه ثبت می‌شود', async () => { render(); // ردیف اول را به ۲۰۰٬۰۰۰ تومان کم می‌کنیم fireEvent.change(amountInputs()[0], { target: { value: '200000' } }); // افزودن روش دوم — پیش‌فرض با باقی‌ماندهٔ ۱۰۰٬۰۰۰ تومان پر می‌شود fireEvent.click(screen.getByRole('button', { name: /افزودن روش/ })); const inputs = amountInputs(); expect(inputs).toHaveLength(2); expect(inputs[1]).toHaveValue('۱۰۰٬۰۰۰'); // مجموع = ۳۰۰٬۰۰۰ تومان = کل ⇒ تسویه کامل expect(screen.getByText('تسویه کامل')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'تأیید و قطعی کردن' })); await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/appointment/a1/confirm', { version: 1, payments: [ { method: 'cash', amount_rials: 2_000_000 }, { method: 'cash', amount_rials: 1_000_000 }, ], })); }); it('حذف ردیف اضافه‌شده مجموع را دوباره محاسبه می‌کند', () => { render(); fireEvent.click(screen.getByRole('button', { name: /افزودن روش/ })); expect(amountInputs()).toHaveLength(2); fireEvent.click(screen.getAllByLabelText('حذف روش پرداخت')[0]); expect(amountInputs()).toHaveLength(1); }); });