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