import { describe, it, expect, vi, beforeEach } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../test/utils'; vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); vi.mock('../hooks/usePermissions', () => ({ usePermissions: () => ({ can: () => true }) })); // ScheduleSection یک درختِ سنگین با کوئری‌های خودش است؛ اینجا فقط باید ثابت شود کدام // scope رندر می‌شود، نه اینکه برنامهٔ پزشک درست کار می‌کند — آن تست خودش را دارد. vi.mock('../components/schedule/ScheduleSection', () => ({ ScheduleSection: () =>
برنامهٔ پزشک
, })); vi.mock('../components/FreeVisitPrice', () => ({ default: () =>
قیمت ویزیت
})); vi.mock('../components/resources/ResourceWorkingHoursPanel', () => ({ default: ({ resourceUuid }: { resourceUuid?: string }) =>
ساعات کاری {resourceUuid}
, DAY_LABELS: [], })); vi.mock('../components/resources/ResourceExceptionsCard', () => ({ default: ({ resourceUuid }: { resourceUuid?: string }) =>
مرخصی {resourceUuid}
, })); vi.mock('../components/holidays/NationalHolidaysCard', () => ({ default: () =>
تعطیلات رسمی سال
, })); let resources: Array<{ uuid: string; name: string; type_name: string }> = []; vi.mock('../hooks/useResources', () => ({ useResources: () => ({ resources, loading: false }), })); 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 { useAuthStore } from '../stores/authStore'; import ClinicAppointmentSettingsPage from './ClinicAppointmentSettingsPage'; const get = api.get as ReturnType; const LASER = { uuid: 'r-1', name: 'لیزر دایود', type_name: 'دستگاه لیزر' }; const ROOM = { uuid: 'r-2', name: 'اتاق ۱', type_name: 'اتاق درمان' }; beforeEach(() => { vi.clearAllMocks(); resources = [LASER, ROOM]; useAuthStore.setState({ primaryRole: 'clinic', dbUuid: 'c-1', context: { type: 'clinic', db_uuid: 'c-1' } as never, availableContexts: [], }); get.mockResolvedValue({ success: true, data: { data: [{ uuid: 'd-1', name: 'دکتر مرادی' }] } }); }); describe('ClinicAppointmentSettingsPage', () => { it('پیش‌فرض روی پزشکان است', async () => { renderWithProviders(); await waitFor(() => expect(screen.getByText('برنامهٔ پزشک')).toBeInTheDocument()); expect(screen.queryByText(/ساعات کاری r-/)).not.toBeInTheDocument(); }); /** منبع همان‌جایی مدیریت می‌شود که برنامهٔ پزشک — نه در یک صفحهٔ جدا. */ it('تب منابع، برنامهٔ کاری منبع را می‌آورد', async () => { const user = userEvent.setup(); renderWithProviders(); await user.click(screen.getByRole('button', { name: 'منابع' })); await waitFor(() => expect(screen.getByText('ساعات کاری r-1')).toBeInTheDocument()); expect(screen.queryByText('برنامهٔ پزشک')).not.toBeInTheDocument(); }); /** * سه تب، نه سه بخشِ پشت‌سرهم: هر سه یک سؤال را جواب می‌دهند ولی هم‌زمان لازم * نیستند و چیدنشان زیر هم صفحه را سه برابر بلند می‌کرد. */ it('شیفت، تعطیلات و مرخصی سه تب مجزا هستند', async () => { const user = userEvent.setup(); renderWithProviders(, { route: '/admin/settings/appointment-settings?scope=resources', }); // پیش‌فرض روی شیفت هفتگی است — کارِ اصلی صفحه. await waitFor(() => expect(screen.getByText('ساعات کاری r-1')).toBeInTheDocument()); expect(screen.queryByText('تعطیلات رسمی سال')).not.toBeInTheDocument(); expect(screen.queryByText('مرخصی r-1')).not.toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'تعطیلات رسمی' })); expect(await screen.findByText('تعطیلات رسمی سال')).toBeInTheDocument(); expect(screen.queryByText('ساعات کاری r-1')).not.toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'مرخصی و سرویس' })); expect(await screen.findByText('مرخصی r-1')).toBeInTheDocument(); expect(screen.queryByText('تعطیلات رسمی سال')).not.toBeInTheDocument(); }); /** تب از URL خوانده می‌شود، پس رفرش و «بازگشت» همان نما را نگه می‌دارند. */ it('تب برنامهٔ کاری را از URL می‌خواند', async () => { renderWithProviders(, { route: '/admin/settings/appointment-settings?scope=resources&calendarTab=exceptions', }); expect(await screen.findByText('مرخصی r-1')).toBeInTheDocument(); expect(screen.queryByText('ساعات کاری r-1')).not.toBeInTheDocument(); }); /** پیش‌نمایش دو هفته در این صفحه کاربردی ندارد و فقط ارتفاع اضافه می‌کرد. */ it('پیش‌نمایش دو هفته را نشان نمی‌دهد', async () => { const user = userEvent.setup(); renderWithProviders(, { route: '/admin/settings/appointment-settings?scope=resources', }); await waitFor(() => expect(screen.getByText('ساعات کاری r-1')).toBeInTheDocument()); expect(screen.queryByText('پیش‌نمایش دو هفته')).not.toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'مرخصی و سرویس' })); await screen.findByText('مرخصی r-1'); expect(screen.queryByText('پیش‌نمایش دو هفته')).not.toBeInTheDocument(); }); it('بین منابع جابه‌جا می‌شود', async () => { const user = userEvent.setup(); renderWithProviders(); await user.click(screen.getByRole('button', { name: 'منابع' })); await user.click(await screen.findByRole('button', { name: 'اتاق ۱' })); await waitFor(() => expect(screen.getByText('ساعات کاری r-2')).toBeInTheDocument()); }); /** scope در URL می‌نشیند، وگرنه «بازگشت» و رفرش کاربر را به تب پزشکان می‌پراند. */ it('scope را از URL می‌خواند', async () => { renderWithProviders(, { route: '/admin/settings/appointment-settings?scope=resources', }); await waitFor(() => expect(screen.getByText('ساعات کاری r-1')).toBeInTheDocument()); }); it('کلینیک بدون منبع، حالت خالی با راه‌حل می‌دهد', async () => { resources = []; const user = userEvent.setup(); renderWithProviders(); await user.click(screen.getByRole('button', { name: 'منابع' })); expect(await screen.findByText('هنوز منبعی تعریف نشده است')).toBeInTheDocument(); expect(screen.getByText('تنظیمات ← منابع')).toHaveAttribute('href', '/admin/resources'); }); });