import { describe, it, expect, beforeEach, vi } from 'vitest'; import { screen } from '@testing-library/react'; import { renderWithProviders } from '../../test/utils'; vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); 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 NewAppointmentModal from './NewAppointmentModal'; const get = api.get as ReturnType; const slot = { start: 1_780_000_000, end: 1_780_001_800, start_time: '۰۹:۰۰', end_time: '۰۹:۳۰', doctor_uuid: 'doc-1', doctor_name: 'دکتر رضایی', }; beforeEach(() => { get.mockReset(); get.mockResolvedValue({ success: true, data: [] }); }); const props = (overrides: Record = {}) => ({ slot, onClose: vi.fn(), onSuccess: vi.fn(), ...overrides, }); /** عنوان مرحله‌های نوار بالای مودال. */ const stepLabels = () => Array.from(document.querySelectorAll('ol[aria-label="مراحل ثبت نوبت"] li')) .map(li => li.textContent?.replace(/^\d+/, '').trim()); const pickerProps = () => props({ serviceMode: true, date: '1405-05-18', services: [] }); describe('NewAppointmentModal — ویزارد', () => { /** * فرم قبلاً یک صفحهٔ بلند بود: خدمت و زمان، بیمار و هزینه با هم. هر بار یک مرحله * دیده می‌شود تا مودال از ارتفاع صفحه بلندتر نشود. */ it('حالت انتخابگر سه مرحله دارد و از مرحلهٔ اول شروع می‌شود', () => { renderWithProviders(); expect(stepLabels()).toEqual(['خدمت و زمان', 'بیمار', 'تأیید و ثبت']); expect(screen.getByRole('heading', { name: 'خدمت و زمان' })).toBeInTheDocument(); expect(screen.queryByRole('heading', { name: 'بیمار' })).not.toBeInTheDocument(); }); it('مرحله‌ای که داده‌اش از قبل معلوم است ساخته نمی‌شود', () => { // نوبت اسلاتی: زمان از خودِ اسلات می‌آید، پس مرحلهٔ «خدمت و زمان» بی‌معناست. renderWithProviders(); expect(stepLabels()).toEqual(['بیمار', 'تأیید و ثبت']); }); it('بیمارِ از پیش معلوم، مرحلهٔ بیمار را حذف می‌کند', () => { renderWithProviders(); // تنها مرحلهٔ باقی‌مانده «تأیید و ثبت» است، پس نوار مراحل هم لازم نیست. expect(document.querySelector('ol[aria-label="مراحل ثبت نوبت"]')).toBeNull(); expect(screen.getByRole('heading', { name: 'تأیید و ثبت' })).toBeInTheDocument(); }); it('تا وقتی مرحله کامل نشده، دکمهٔ بعدی غیرفعال است و دلیلش را می‌گوید', () => { renderWithProviders(); expect(screen.getByRole('button', { name: 'مرحلهٔ بعد' })).toBeDisabled(); expect(screen.getByText(/یک سرویس انتخاب کنید/)).toBeInTheDocument(); // دکمهٔ ثبت فقط در مرحلهٔ آخر وجود دارد. expect(screen.queryByRole('button', { name: 'ثبت نوبت' })).not.toBeInTheDocument(); }); it('در مرحلهٔ اول دکمهٔ «مرحلهٔ قبل» نیست، ولی «انصراف» همیشه هست', () => { renderWithProviders(); expect(screen.queryByRole('button', { name: 'مرحلهٔ قبل' })).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: 'انصراف' })).toBeInTheDocument(); }); it('مرحلهٔ آخر خلاصهٔ نوبت را نشان می‌دهد', () => { renderWithProviders(); expect(screen.getByText('علی محمدی')).toBeInTheDocument(); expect(screen.getByText('09123456789')).toBeInTheDocument(); }); it('دکمهٔ اصلی در فوتر ثابت است، نه داخل ناحیهٔ اسکرول', () => { renderWithProviders(); const next = screen.getByRole('button', { name: 'مرحلهٔ بعد' }); expect(next.closest('.modal-foot')).not.toBeNull(); expect(next.closest('.modal-body')).toBeNull(); }); });