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();
});
});