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() } })); vi.mock('../hooks/usePermissions', () => ({ usePermissions: () => ({ can: () => true }) })); import { Routes, Route } from 'react-router-dom'; import { api } from '../lib/api'; import ResourceCalendarPage from './ResourceCalendarPage'; const get = api.get as ReturnType; const emptyDays = (): Record => Object.fromEntries(Array.from({ length: 7 }, (_, d) => [String(d), [] as unknown[]])); function mockApi(days: Record, availabilityDays: unknown[]) { get.mockImplementation((path: string) => { if (path.endsWith('/calendar')) { return Promise.resolve({ success: true, data: { resource_uuid: 'r1', timezone: 'Asia/Tehran', defined: true, days }, }); } if (path.includes('/availability')) { return Promise.resolve({ success: true, data: { resource_uuid: 'r1', timezone: 'Asia/Tehran', days: availabilityDays }, }); } if (path.endsWith('/exceptions')) return Promise.resolve({ success: true, data: [] }); if (path.startsWith('/api/v1/resources')) return Promise.resolve({ success: true, data: [] }); return Promise.resolve({ success: true, data: [] }); }); } function renderPage() { return renderWithProviders( } /> , { route: '/admin/resources/r1/calendar' }, ); } describe('ResourceCalendarPage', () => { beforeEach(() => vi.clearAllMocks()); it('renders seven days and marks shiftless ones', async () => { mockApi(emptyDays(), []); renderPage(); await waitFor(() => expect(screen.getByText('شنبه')).toBeInTheDocument()); expect(screen.getByText('جمعه')).toBeInTheDocument(); expect(screen.getAllByText('بدون شیفت')).toHaveLength(7); }); it('shows stored shifts as times', async () => { const days = emptyDays(); days['0'] = [{ sequence: 0, start_minute: 540, end_minute: 1020, start_time: '09:00', end_time: '17:00', active: true }]; mockApi(days, []); renderPage(); await waitFor(() => expect(screen.getByDisplayValue('09:00')).toBeInTheDocument()); expect(screen.getByDisplayValue('17:00')).toBeInTheDocument(); }); /** * دلیلِ خالی بودن روز باید فارسی نشان داده شود؛ نشان دادن کلید خام سرور * («outside_branch_hours») به کاربر یعنی پیام بی‌معنا. */ it('translates every empty-day reason into Persian', async () => { mockApi(emptyDays(), [ { date: 1785529800, day_of_week: 0, intervals: [], total_minutes: 0, reasons: ['national_holiday'] }, { date: 1785616200, day_of_week: 1, intervals: [], total_minutes: 0, reasons: ['outside_branch_hours'] }, { date: 1785702600, day_of_week: 2, intervals: [], total_minutes: 0, reasons: ['exception'] }, ]); renderPage(); await waitFor(() => expect(screen.getByText('تعطیل رسمی')).toBeInTheDocument()); expect(screen.getByText('شیفت بیرون از ساعت کاری شعبه')).toBeInTheDocument(); expect(screen.getByText('مرخصی یا سرویس')).toBeInTheDocument(); expect(screen.queryByText('outside_branch_hours')).not.toBeInTheDocument(); }); it('shows free minutes for a day that has availability', async () => { mockApi(emptyDays(), [ { date: 1785529800, day_of_week: 0, intervals: [{ start: 1, end: 2 }], total_minutes: 480, reasons: [] }, ]); renderPage(); await waitFor(() => expect(screen.getByText('480 دقیقه')).toBeInTheDocument()); }); /** پیش‌نمایش نباید «وقت قابل رزرو» خوانده شود — نوبت‌ها هنوز کسر نشده‌اند. */ it('warns that the preview is raw availability', async () => { mockApi(emptyDays(), []); renderPage(); await waitFor(() => expect(screen.getByText(/نوبت‌های ثبت‌شده هنوز از آن کسر نشده‌اند/)).toBeInTheDocument()); }); });