import { describe, it, expect, vi, beforeEach } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; 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 {}, })); vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); import { api } from '../lib/api'; import CancelAppointmentDialog from './CancelAppointmentDialog'; const get = api.get as ReturnType; const post = api.post as ReturnType; const preview = (over: Record = {}) => ({ data: { penalty_rials: 250_000, deposit_refundable: true, credit_refundable: true, within_free_window: false, notes: [], paid_rials: 1_000_000, ...over, }, }); describe('CancelAppointmentDialog', () => { beforeEach(() => { vi.clearAllMocks(); post.mockResolvedValue({ data: { waitlist_notified: 0 } }); }); /** ⭐ عددی که اپراتور می‌بیند باید همان باشد که کسر می‌شود. */ it('shows the penalty from the server before anything is cancelled', async () => { get.mockResolvedValue(preview()); renderWithProviders( {}} />, ); // مبلغ‌ها به تومان نمایش داده می‌شوند: ۲۵۰٬۰۰۰ ریال ⇒ ۲۵٬۰۰۰ تومان. await waitFor(() => expect(screen.getByText(/۲۵٬۰۰۰ تومان/)).toBeInTheDocument()); expect(post).not.toHaveBeenCalled(); }); it('says "بدون جریمه" inside the free window instead of showing a zero', async () => { get.mockResolvedValue(preview({ penalty_rials: 0, within_free_window: true })); renderWithProviders( {}} />, ); await waitFor(() => expect(screen.getByText('بدون جریمه')).toBeInTheDocument()); expect(screen.getByText('در بازهٔ لغو رایگان است.')).toBeInTheDocument(); }); it('sends the typed reason along with the cancellation', async () => { get.mockResolvedValue(preview()); const user = userEvent.setup(); renderWithProviders( {}} />, ); await waitFor(() => expect(screen.getByText(/مبلغ پرداخت‌شده/)).toBeInTheDocument()); await user.type(screen.getByLabelText(/دلیل لغو/), 'بیمار تماس گرفت'); await user.click(screen.getByRole('button', { name: 'لغو نوبت' })); await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/appointment/a-1/cancel', { by: 'doctor', reason: 'بیمار تماس گرفت', }), ); }); /** پیش‌نمایشی که نیامده نباید لغو را قفل کند — ولی باید صریح بگوید که نیامده. */ it('still allows cancelling when the preview fails, and says so', async () => { get.mockRejectedValue(new Error('down')); renderWithProviders( {}} />, ); await waitFor(() => expect(screen.getByText(/پیامد مالی لغو در دسترس نیست/)).toBeInTheDocument(), ); expect(screen.getByRole('button', { name: 'لغو نوبت' })).not.toBeDisabled(); }); });