import { describe, it, expect, beforeEach, vi } from 'vitest'; import { screen, 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 {}, })); vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); import { api } from '../../lib/api'; import DoctorPermissionsModal from './DoctorPermissionsModal'; const get = api.get as ReturnType; const resource = (key: string, label: string, actions: [string, string][]) => ({ key, label, clinic_only: false, actions: actions.map(([k, l]) => ({ key: k, label: l })), }); const BASE_CATALOG = [ resource('appointments', 'مدیریت نوبت‌ها', [['view', 'مشاهده'], ['create', 'ایجاد']]), resource('patients', 'پرونده بیماران', [['view', 'مشاهده'], ['delete', 'حذف']]), resource('services', 'خدمات و تعرفه‌ها', [['view', 'مشاهده']]), ]; const permissionPayload = { uuid: 'p1', clinic_uuid: 'c1', doctor_uuid: 'd1', doctor_name: 'دکتر تست', active: true, permissions: { version: 1, resources: { appointments: { view: true, create: false } } }, }; function mockApi(catalog: unknown[]) { get.mockImplementation((url: string) => { if (url.includes('permission-catalog')) { return Promise.resolve({ success: true, data: { version: 1, resources: catalog } }); } return Promise.resolve({ success: true, data: permissionPayload }); }); } function render(catalog: unknown[] = BASE_CATALOG) { mockApi(catalog); return renderWithProviders( {}} />, ); } describe('DoctorPermissionsModal', () => { beforeEach(() => vi.clearAllMocks()); it('renders one row per catalog resource', async () => { render(); expect(await screen.findByText('مدیریت نوبت‌ها')).toBeInTheDocument(); expect(screen.getByText('پرونده بیماران')).toBeInTheDocument(); expect(screen.getByText('خدمات و تعرفه‌ها')).toBeInTheDocument(); }); /** * قلبِ «داینامیک بودن»: منبع تازه فقط به mock اضافه می‌شود و هیچ خطی از کد * کامپوننت عوض نمی‌شود. اگر این تست بشکند یعنی فهرست دوباره هاردکد شده. */ it('shows a resource added to the catalog with no code change', async () => { render([...BASE_CATALOG, resource('brand_new_page', 'صفحهٔ کاملاً تازه', [['view', 'مشاهده']])]); expect(await screen.findByText('صفحهٔ کاملاً تازه')).toBeInTheDocument(); }); it('renders a switch only for actions the resource actually has', async () => { render(); // patients در کاتالوگ view و delete دارد، ولی create ندارد. expect(await screen.findByLabelText('پرونده بیماران — حذف')).toBeInTheDocument(); expect(screen.queryByLabelText('پرونده بیماران — ایجاد')).not.toBeInTheDocument(); }); it('reflects stored values and defaults missing ones to off', async () => { render(); await waitFor(() => expect(screen.getByLabelText('مدیریت نوبت‌ها — مشاهده')).toBeChecked()); expect(screen.getByLabelText('مدیریت نوبت‌ها — ایجاد')).not.toBeChecked(); // services اصلاً در JSONِ ذخیره‌شده نیست expect(screen.getByLabelText('خدمات و تعرفه‌ها — مشاهده')).not.toBeChecked(); }); it('shows loading rather than an empty table while the catalog is in flight', () => { get.mockImplementation(() => new Promise(() => {})); renderWithProviders( {}} />, ); expect(screen.getByText('در حال بارگذاری...')).toBeInTheDocument(); }); });