import { describe, it, expect, vi, beforeEach } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import { renderWithProviders } from '../test/utils'; import { api } from '../lib/api'; import ClinicDoctorsManager from './ClinicDoctorsManager'; vi.mock('../lib/api', () => ({ api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn() } })); const DOCTOR = { id: '1', uuid: 'doc-1', name: 'پزشک دعوت‌شده', gender: null, degree: null, img: [], specialties: [], active: false, // نوبت‌دهی آنلاینِ خودِ پزشک خاموش است }; /** پاسخ‌ها بر اساس مسیر تفکیک می‌شوند، چون سه کوئری موازی می‌روند. */ function mockApi({ doctors = [DOCTOR], total = 1, permissions = [] as any[], failDoctors = false } = {}) { (api.get as any).mockImplementation((url: string) => { if (url.includes('/doctor-permissions')) return Promise.resolve({ data: permissions }); if (url.includes('/invitations')) return Promise.resolve({ data: [] }); if (url.includes('/doctor-list/')) { return failDoctors ? Promise.reject(new Error('boom')) : Promise.resolve({ data: doctors, meta: { totalRecords: total, totalPages: 1, currentPage: 1 } }); } return Promise.resolve({ data: [] }); }); } describe('ClinicDoctorsManager', () => { beforeEach(() => vi.clearAllMocks()); it('صفحه و سقف را به سرور می‌فرستد — وگرنه backend سر ۱۰ پزشک بی‌صدا می‌برد', async () => { mockApi(); renderWithProviders(); await waitFor(() => expect(api.get).toHaveBeenCalled()); const listCall = (api.get as any).mock.calls.find((c: any[]) => c[0].includes('/doctor-list/')); expect(listCall[0]).toContain('page=1'); expect(listCall[0]).toContain('limit=10'); }); it('شمارندهٔ تب از meta می‌آید، نه از طول صفحهٔ جاری', async () => { mockApi({ doctors: [DOCTOR], total: 25 }); renderWithProviders(); expect(await screen.findByText(/پزشکان \(۲۵\)/)).toBeInTheDocument(); }); it('«نوبت‌دهی آنلاین» و «دسترسی در کلینیک» دو ستون جدا هستند', async () => { mockApi(); renderWithProviders(); expect(await screen.findByText('دسترسی در کلینیک')).toBeInTheDocument(); expect(screen.getByText('نوبت‌دهی آنلاین')).toBeInTheDocument(); }); it('شمار مجوزها زیر همان بجِ دسترسی می‌آید، نه در ستون جدا', async () => { mockApi({ permissions: [{ doctor_uuid: 'doc-1', active: true, permissions: { resources: { appointments: { view: true, create: false } } }, }], }); renderWithProviders(); expect(await screen.findByText('۱ از ۲ مجوز')).toBeInTheDocument(); expect(screen.queryByText('مجوزها')).not.toBeInTheDocument(); }); it('پزشکِ بدون ردیف مجوز، «پیش‌فرض» و دسترسی فعال می‌گیرد', async () => { mockApi({ permissions: [] }); renderWithProviders(); expect(await screen.findByText('پیش‌فرض')).toBeInTheDocument(); }); it('ردیف مجوزِ خاموش، «غیرفعال» نشان می‌دهد و شمار مجوزها را می‌آورد', async () => { mockApi({ permissions: [{ doctor_uuid: 'doc-1', active: false, permissions: { resources: { appointments: { view: true, create: false } } }, }], }); renderWithProviders(); expect(await screen.findByText('۱ از ۲ مجوز')).toBeInTheDocument(); // دو «غیرفعال»: یکی دسترسیِ کلینیک، یکی نوبت‌دهی آنلاین. پیش از این یک بج جای // هر دو می‌نشست و همین ابهام، پزشکِ سالم را «غیرفعال» نشان می‌داد. expect(screen.getAllByText('غیرفعال')).toHaveLength(2); }); it('دسترسیِ فعال با نوبت‌دهیِ خاموش قاطی نمی‌شود', async () => { mockApi({ permissions: [{ doctor_uuid: 'doc-1', active: true, permissions: { resources: { appointments: { view: true } } }, }], }); renderWithProviders(); // دسترسی فعال است… expect(await screen.findByText('فعال')).toBeInTheDocument(); // …ولی نوبت‌دهی آنلاینِ خودِ پزشک همچنان خاموش. expect(screen.getByText('غیرفعال')).toBeInTheDocument(); }); it('خطای فهرست، پیام خطا می‌دهد نه حالت خالی', async () => { mockApi({ failDoctors: true }); renderWithProviders(); expect(await screen.findByText('فهرست پزشکان بارگذاری نشد')).toBeInTheDocument(); expect(screen.queryByText('هیچ پزشکی به این کلینیک متصل نیست')).not.toBeInTheDocument(); }); it('فهرست خالی، پیام خالی می‌دهد', async () => { mockApi({ doctors: [], total: 0 }); renderWithProviders(); expect(await screen.findByText('هیچ پزشکی به این کلینیک متصل نیست')).toBeInTheDocument(); }); });