import { describe, it, expect, beforeEach, vi } from 'vitest'; import { screen } 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 { useAuthStore } from '../stores/authStore'; import DashboardPage from './DashboardPage'; import { JALALI_MONTHS } from '../components/dashboard/TauriDashboardView'; // eslint-disable-next-line @typescript-eslint/no-var-requires const jalaali = require('jalaali-js') as { toJalaali: (d: Date) => { jy: number; jm: number } }; const get = api.get as ReturnType; const clinicPayload = { success: true, data: { clinic: { uuid: 'clinic-1', name: 'کلینیک نمونه', is_active: true, logo: null }, stats: { total_doctors: 2, today_appointments: 15, this_month_appointments: 40, pending_invitations: 0, total_patients: 151, revenue_period_rials: 5_600_002_000, today_payments_rials: 5_225_000, week_payments_rials: 12_000_000, }, charts: { revenue_by_day: [ { label: '۷ خرداد', amount_rials: 60_000 }, { label: '۸ خرداد', amount_rials: 160_000 }, { label: '۹ خرداد', amount_rials: 90_000 }, ], revenue_by_month: [ { label: 'فروردین', amount_rials: 60_000 }, { label: 'اردیبهشت', amount_rials: 160_000 }, { label: 'خرداد', amount_rials: 90_000 }, ], appointments_by_day: [ { label: '۱', count: 15 }, { label: '۲', count: 40 }, { label: '۳', count: 48 }, ], }, charts_period: { patients_year: 1405, patients_month: 4, revenue_year: 1405 }, today_appointments: [ { uuid: 'appt-1', patient_name: 'دنیا خلیلی', patient_mobile: '09136549874', doctor_name: 'حمیدی', service_name: 'ویزیت عمومی', slot_start: 1_718_000_000, slot_end: 1_718_001_800, status: 'completed', }, ], doctors: [], period: { from: 0, to: 0 }, }, }; describe('DashboardPage (ported clinic dashboard)', () => { beforeEach(() => { get.mockReset(); useAuthStore.setState({ primaryRole: 'clinic', dbUuid: 'clinic-1', context: null } as never); get.mockImplementation((url: string) => { if (url.startsWith('/api/v1/dashboard/clinic')) return Promise.resolve(clinicPayload); return Promise.resolve({ success: true, data: [] }); }); }); it('renders the ported stat cards, charts and new-appointments list', async () => { renderWithProviders(, { route: '/admin/dashboard' }); // stat card labels (ported 1:1) expect(await screen.findByText('تعداد کل مراجعین')).toBeInTheDocument(); expect(screen.getByText('کل پرداختی‌ها')).toBeInTheDocument(); expect(screen.getByText('پرداختی‌های امروز')).toBeInTheDocument(); expect(screen.getByText('تعداد نوبت‌های امروز')).toBeInTheDocument(); // chart titles expect(screen.getByText('نمودار تعداد بیماران')).toBeInTheDocument(); expect(screen.getByText('میزان درآمد')).toBeInTheDocument(); // new-appointments list + row data (mobile/service columns come from the extended API) expect(screen.getByText('لیست نوبت‌های جدید')).toBeInTheDocument(); expect(screen.getByText('دنیا خلیلی')).toBeInTheDocument(); expect(screen.getByText('09136549874')).toBeInTheDocument(); expect(screen.getByText('ویزیت عمومی')).toBeInTheDocument(); // ستون «وضعیت» هست، ولی در داشبورد فقط خواندنی (بدون queryKey → StatusPill نه dropdown) expect(screen.getByText('وضعیت')).toBeInTheDocument(); expect(screen.getByText('ویزیت شده').closest('button')).toBeNull(); // کارت «پزشکان کلینیک» از داشبورد حذف شده expect(screen.queryByText('پزشکان کلینیک')).not.toBeInTheDocument(); }); it('«مشاهده» به همان روزِ نوبت در لیست نوبت‌ها لینک می‌دهد', async () => { renderWithProviders(, { route: '/admin/dashboard' }); await screen.findByText('دنیا خلیلی'); const d = new Date(1_718_000_000 * 1000); const day = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; expect(screen.getByText('مشاهده').closest('a')).toHaveAttribute('href', `/admin/appointments?date=${day}`); }); it('سلول‌های جدول با هدرهایشان هم‌تراز هستند (همه text-start جز ستون عملیات)', async () => { const { container } = renderWithProviders(, { route: '/admin/dashboard' }); await screen.findByText('دنیا خلیلی'); const heads = Array.from(container.querySelectorAll('thead th')); const cells = Array.from(container.querySelectorAll('tbody td')); expect(heads).toHaveLength(cells.length); heads.slice(0, -1).forEach(th => expect(th.className).toContain('text-start')); cells.slice(0, -1).forEach(td => expect(td.className).toContain('text-start')); // ستون آخر «عملیات» در هر دو سطر وسط‌چین است expect(heads[heads.length - 1].className).toContain('text-center'); expect(cells[cells.length - 1].className).toContain('text-center'); // dir=ltr روی سلول نمی‌نشیند (جهت را برمی‌گرداند و هم‌ترازی را می‌شکند) cells.forEach(td => expect(td.getAttribute('dir')).toBeNull()); }); it('calls the real clinic dashboard endpoint', async () => { renderWithProviders(, { route: '/admin/dashboard' }); await screen.findByText('تعداد کل مراجعین'); expect(get).toHaveBeenCalledWith(expect.stringContaining('/api/v1/dashboard/clinic')); }); it('نمودارها را روی ماه و سال شمسی جاری درخواست می‌کند', async () => { renderWithProviders(, { route: '/admin/dashboard' }); await screen.findByText('تعداد کل مراجعین'); const url = get.mock.calls.map(c => String(c[0])).find(u => u.startsWith('/api/v1/dashboard/clinic'))!; const params = new URLSearchParams(url.split('?')[1]); const now = jalaali.toJalaali(new Date()); expect(params.get('patients_year')).toBe(String(now.jy)); expect(params.get('patients_month')).toBe(String(now.jm)); expect(params.get('revenue_year')).toBe(String(now.jy)); // سلکتورها همان دوره را نشان می‌دهند expect(screen.getByText(JALALI_MONTHS[now.jm - 1])).toBeInTheDocument(); expect( screen.getByText(`سال ${new Intl.NumberFormat('fa-IR', { useGrouping: false }).format(now.jy)}`) ).toBeInTheDocument(); }); });