import { describe, it, expect } from 'vitest'; import { screen } from '@testing-library/react'; import { renderWithProviders } from '../../test/utils'; import ResourceTimeline from './ResourceTimeline'; import type { ResourceTimelineLane } from '../../hooks/useResourceTimeline'; /** نیمه‌شبِ یک روز ثابت، تا موقعیت بلوک‌ها به ساعت اجرای تست وابسته نباشد. */ const DAY_START = Math.floor(new Date(2026, 7, 5, 0, 0, 0).getTime() / 1000); const at = (hour: number, minute = 0) => DAY_START + hour * 3600 + minute * 60; function lane(overrides: Partial = {}): ResourceTimelineLane { return { uuid: 'r-1', name: 'لیزر دایود', type_name: 'دستگاه لیزر', address_name: 'شعبهٔ مرکزی', capacity: 1, shifts: [{ start_minute: 9 * 60, end_minute: 17 * 60 }], shift_minutes: 480, busy_minutes: 60, free_minutes: 420, items: [ { uuid: 'o-1', starts_at: at(10), ends_at: at(11), status: 'booked', segment_name: 'لیزر', appointment_id: 47, patient_name: 'زهرا احمدی', appointment_uuid: 'appt-1', appointment_status: 'confirmed', }, ], ...overrides, }; } describe('ResourceTimeline', () => { it('یک ردیف به‌ازای هر منبع با نام بیمار و بخش نشان می‌دهد', () => { renderWithProviders( , ); expect(screen.getByText('لیزر دایود')).toBeInTheDocument(); expect(screen.getByText('دستگاه لیزر')).toBeInTheDocument(); expect(screen.getByText('زهرا احمدی · لیزر')).toBeInTheDocument(); }); it('بلوک به جزئیات همان نوبت لینک می‌دهد', () => { renderWithProviders( , ); expect(screen.getByText('زهرا احمدی · لیزر').closest('a')) .toHaveAttribute('href', '/admin/appointments/appt-1'); }); /** * یک نوبت می‌تواند هم‌زمان اتاق و دستگاه را بگیرد؛ همین است که ظرفیت را تمام می‌کند و * نمای پزشک‌محور نشانش نمی‌دهد. */ it('یک نوبت روی چند منبع، روی هر ردیف دیده می‌شود', () => { const room = lane({ uuid: 'r-2', name: 'اتاق لیزر ۱', type_name: 'اتاق', items: [{ ...lane().items[0], uuid: 'o-2', segment_name: 'بی‌حسی موضعی' }], }); renderWithProviders( , ); expect(screen.getByText('زهرا احمدی · لیزر')).toBeInTheDocument(); expect(screen.getByText('زهرا احمدی · بی‌حسی موضعی')).toBeInTheDocument(); }); it('روزِ بدون شیفت و بدون نوبت را صریح می‌گوید', () => { renderWithProviders( , ); expect(screen.getByText('این روز شیفتی ندارد')).toBeInTheDocument(); }); it('بدون هیچ منبعی به صفحهٔ تعریف منبع راه می‌دهد', () => { renderWithProviders( , ); expect(screen.getByText('تنظیمات ← منابع').closest('a')).toHaveAttribute('href', '/admin/resources'); }); it('خطای سرور را نشان می‌دهد، نه تایم‌لاین خالی', () => { renderWithProviders( , ); expect(screen.getByText('خطای داخلی سرور')).toBeInTheDocument(); }); /** ظرفیت و وقت آزاد باید روی خودِ ردیف باشد، نه حدسِ کاربر از روی بلوک‌ها. */ it('وقت آزاد هر ردیف را نشان می‌دهد', () => { renderWithProviders( , ); expect(screen.getByText('۴۲۰ دقیقه آزاد')).toBeInTheDocument(); }); it('ردیفِ پرشده «تکمیل» می‌گوید، نه صفر دقیقه', () => { renderWithProviders( , ); expect(screen.getByText('تکمیل')).toBeInTheDocument(); }); it('ظرفیت بیش از یک را کنار نوع منبع می‌آورد', () => { renderWithProviders( , ); expect(screen.getByText(/ظرفیت ۳/)).toBeInTheDocument(); }); });