import { describe, it, expect, beforeEach, vi } from 'vitest'; import { screen, fireEvent, 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 {}, })); import { api } from '../lib/api'; import PatientRecordFormPage from './PatientRecordFormPage'; const get = api.get as ReturnType; const post = api.post as ReturnType; /** الگوی شمارهٔ پرونده؛ `enabled=false` یعنی رفتار قدیمیِ ورود دستی. */ function mockPattern(settings: Record) { get.mockImplementation((url: string) => url.includes('patient-record-number-settings') ? Promise.resolve({ success: true, data: { pattern: '{YY}-{SEQ:4}', reset_policy: 'none', counter: 0, next_preview: '05-0001', ...settings } }) : Promise.resolve({ success: true, data: [] }), ); } /** * پرکردن فیلدهای الزامیِ دیگر تا فقط شمارهٔ پرونده متغیر بماند. * * اول منتظر رسیدن تنظیمات می‌ماند: رندرِ بعد از پاسخ، منوی باز react-select را * می‌بندد و انتخاب جنسیت را می‌خورد. */ async function fillRequiredFields() { await waitFor(() => expect(get).toHaveBeenCalledWith(expect.stringContaining('patient-record-number-settings'))); await waitFor(() => expect(screen.getByRole('button', { name: /ثبت|ذخیره/ })).toBeEnabled()); fireEvent.change(await screen.findByPlaceholderText('نام و نام خانوادگی را وارد نمایید'), { target: { value: 'علی محمدی' } }); fireEvent.change(screen.getByPlaceholderText('کد ملی را وارد نمایید'), { target: { value: '0012345675' } }); fireEvent.change(screen.getByPlaceholderText('شماره تماس را وارد نمایید'), { target: { value: '09121234567' } }); const gender = document.getElementById('patient-gender-select') as HTMLInputElement; fireEvent.focus(gender); fireEvent.keyDown(gender, { key: 'ArrowDown' }); fireEvent.click(await screen.findByText('مرد')); } beforeEach(() => { get.mockReset(); post.mockReset(); post.mockResolvedValue({ success: true, data: { uuid: 'rec-new' } }); (api.patch as ReturnType).mockResolvedValue({ success: true, data: {} }); }); describe('PatientRecordFormPage — شمارهٔ پرونده', () => { /** ⚠️ مرزی: بدون الگو، فیلد الزامی می‌ماند (رفتار امروز). */ it('بدون الگو شماره پرونده الزامی است', async () => { mockPattern({ enabled: false, can_edit: true }); renderWithProviders(, { route: '/admin/patients/new' }); await fillRequiredFields(); fireEvent.click(screen.getByText('ثبت اطلاعات') ?? screen.getByRole('button', { name: /ثبت|ذخیره/ })); expect(await screen.findByText('شماره پرونده الزامی است')).toBeInTheDocument(); expect(post).not.toHaveBeenCalled(); }); /** ✅ موفق: با الگوی فعال و بدون اجازهٔ دستی، فیلد read-only است و ارسال نمی‌شود. */ it('با الگوی فعال و بدون can_edit، شماره فرستاده نمی‌شود', async () => { mockPattern({ enabled: true, can_edit: false }); renderWithProviders(, { route: '/admin/patients/new' }); expect(await screen.findByText('شماره از الگوی مجموعه ساخته می‌شود')).toBeInTheDocument(); await fillRequiredFields(); fireEvent.click(screen.getByRole('button', { name: /ثبت|ذخیره/ })); await waitFor(() => expect(post).toHaveBeenCalled()); expect(post.mock.calls[0][1]).not.toHaveProperty('record_number'); }); /** ✅ موفق: صاحب مجموعه می‌تواند خالی بگذارد تا سرور بسازد. */ it('صاحب مجموعه می‌تواند شماره را خالی بگذارد', async () => { mockPattern({ enabled: true, can_edit: true }); renderWithProviders(, { route: '/admin/patients/new' }); expect(await screen.findByText(/خالی بماند تا از الگو ساخته شود/)).toBeInTheDocument(); await fillRequiredFields(); fireEvent.click(screen.getByRole('button', { name: /ثبت|ذخیره/ })); await waitFor(() => expect(post).toHaveBeenCalled()); expect(post.mock.calls[0][1].record_number).toBe(''); }); });