- Refactor multiple admin pages (BlogsPage, ClinicsPage, DoctorsPage, etc.) to utilize the new useUrlState hook for managing pagination, search, and filter states via URL. - Ensure that the state persists in the URL, allowing users to return to the same state when navigating back from detail pages. - Update relevant components to handle state changes appropriately and maintain clean URLs by removing default values. - Add SlotPicker component for selecting appointment slots based on availability. - Create tests for useUrlState to validate its functionality and ensure correct behavior when interacting with the URL. - Update API documentation to reflect changes in appointment creation and slot selection processes.
161 lines
8.0 KiB
TypeScript
161 lines
8.0 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { screen, fireEvent, 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 {},
|
|
}));
|
|
|
|
import { api } from '../lib/api';
|
|
import AppointmentActionsMenu from './AppointmentActions';
|
|
import type { Appointment } from '../types';
|
|
|
|
const get = api.get as ReturnType<typeof vi.fn>;
|
|
const patch = api.patch as ReturnType<typeof vi.fn>;
|
|
const post = api.post as ReturnType<typeof vi.fn>;
|
|
|
|
const navigate = vi.fn();
|
|
vi.mock('react-router-dom', async () => ({
|
|
...(await vi.importActual<typeof import('react-router-dom')>('react-router-dom')),
|
|
useNavigate: () => navigate,
|
|
}));
|
|
|
|
const appt: Appointment = {
|
|
uuid: 'ap1', patient_name: 'مریم خلیلی', patient_mobile: '09136549874',
|
|
doctor_uuid: 'd1', doctor_name: 'دکتر احمدی',
|
|
slot_start: 1735639200, slot_end: 1735641900, // 45 min
|
|
appointment_date: '2024-12-31', appointment_time: '09:00', end_time: '09:45',
|
|
status: 'confirmed', version: 3, created_at: '',
|
|
service_section: { uuid: 's1', name: 'زیبایی' },
|
|
service_item: { uuid: 'i1', name: 'لیزر توتال' },
|
|
staff: { uuid: 'st1', full_name: 'دکتر حمیدی' },
|
|
};
|
|
|
|
beforeEach(() => {
|
|
get.mockReset(); patch.mockReset();
|
|
get.mockImplementation((url: string) => {
|
|
if (url.startsWith('/api/v1/patients?search=')) return Promise.resolve({ success: true, data: [{ uuid: 'rec1' }] });
|
|
if (url === '/api/v1/patient/rec1/wallet') return Promise.resolve({ success: true, data: { balance_rials: 500000, recent_transactions: [] } });
|
|
return Promise.resolve({ success: true, data: [] });
|
|
});
|
|
patch.mockResolvedValue({ success: true, data: {} });
|
|
post.mockReset();
|
|
navigate.mockReset();
|
|
});
|
|
|
|
function openMenu() {
|
|
renderWithProviders(<AppointmentActionsMenu appointment={appt} queryKey={['appts']} />);
|
|
fireEvent.click(screen.getByRole('button', { name: 'عملیات' }));
|
|
}
|
|
|
|
describe('AppointmentActionsMenu (عملیات نوبت)', () => {
|
|
it('lists all six actions from the Figma menu', () => {
|
|
openMenu();
|
|
for (const label of ['ویرایش', 'ثبت سرویس', 'مشاهده', 'جا به جایی نوبت', 'انتقال به لیست رزرو', 'جایگزینی نوبت']) {
|
|
expect(screen.getByText(label)).toBeInTheDocument();
|
|
}
|
|
});
|
|
|
|
it('«ثبت سرویس» پروندهٔ موجود بیمار را باز میکند', async () => {
|
|
openMenu();
|
|
fireEvent.click(screen.getByText('ثبت سرویس'));
|
|
|
|
await waitFor(() => expect(navigate).toHaveBeenCalledWith('/admin/patients/rec1/session/new'));
|
|
// مسیر جستجو باید همان endpoint واقعی باشد، وگرنه ۴۰۴ میگیرد و «خطا در یافتن پرونده» میدهد.
|
|
expect(get).toHaveBeenCalledWith(expect.stringContaining('/api/v1/patients?search='));
|
|
expect(post).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('«ثبت سرویس» برای بیمارِ بدون پرونده، اول پرونده میسازد', async () => {
|
|
get.mockImplementation((url: string) => {
|
|
if (url.startsWith('/api/v1/patients?search=')) return Promise.resolve({ success: true, data: [] });
|
|
return Promise.resolve({ success: true, data: [] });
|
|
});
|
|
post.mockResolvedValue({ success: true, data: { uuid: 'rec-new' } });
|
|
|
|
openMenu();
|
|
fireEvent.click(screen.getByText('ثبت سرویس'));
|
|
|
|
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/patient', expect.objectContaining({
|
|
mobile: '09136549874',
|
|
name: 'مریم خلیلی',
|
|
})));
|
|
expect(navigate).toHaveBeenCalledWith('/admin/patients/rec-new/session/new');
|
|
});
|
|
|
|
it('info modal shows appointment details and patient wallet balance', async () => {
|
|
openMenu();
|
|
fireEvent.click(screen.getByText('مشاهده'));
|
|
expect(await screen.findByText('ساعت شروع:')).toBeInTheDocument();
|
|
expect(screen.getByText('۴۵ دقیقه')).toBeInTheDocument();
|
|
expect(screen.getByText('لیزر توتال')).toBeInTheDocument();
|
|
expect(screen.getByText('دکتر حمیدی')).toBeInTheDocument();
|
|
// wallet resolved through record search → balance shown (rial → toman)
|
|
expect(await screen.findByText(/تومان/)).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: 'مشاهده پرونده' })).toBeInTheDocument();
|
|
});
|
|
|
|
it('move modal patches new slot times', async () => {
|
|
openMenu();
|
|
fireEvent.click(screen.getByText('جا به جایی نوبت'));
|
|
expect(await screen.findByText('اعمال تغییرات')).toBeInTheDocument();
|
|
fireEvent.change(screen.getByLabelText('ساعت شروع'), { target: { value: '15:00' } });
|
|
fireEvent.change(screen.getByLabelText('ساعت پایان'), { target: { value: '16:00' } });
|
|
fireEvent.click(screen.getByText('اعمال تغییرات'));
|
|
await waitFor(() => expect(patch).toHaveBeenCalledWith('/api/v1/appointment/ap1', expect.objectContaining({
|
|
slot_start: Math.floor(new Date('2024-12-31T15:00').getTime() / 1000),
|
|
slot_end: Math.floor(new Date('2024-12-31T16:00').getTime() / 1000),
|
|
version: 3,
|
|
})));
|
|
});
|
|
|
|
it('transfer modal flips is_reserve with a day-level slot', async () => {
|
|
openMenu();
|
|
fireEvent.click(screen.getByText('انتقال به لیست رزرو'));
|
|
expect(await screen.findByText(/به لیست نوبت های رزرو شده منتقل می شود/)).toBeInTheDocument();
|
|
fireEvent.click(screen.getByText('انتقال و حذف از لیست'));
|
|
const day = Math.floor(new Date('2024-12-31T00:00').getTime() / 1000);
|
|
await waitFor(() => expect(patch).toHaveBeenCalledWith('/api/v1/appointment/ap1', expect.objectContaining({
|
|
is_reserve: true, slot_start: day, slot_end: day, version: 3,
|
|
})));
|
|
});
|
|
|
|
it('replace modal swaps the patient and keeps the slot locked', async () => {
|
|
openMenu();
|
|
fireEvent.click(screen.getByText('جایگزینی نوبت'));
|
|
expect(await screen.findByPlaceholderText('نام و نام خانوادگی')).toBeInTheDocument();
|
|
// the original slot is shown read-only
|
|
expect(screen.getByDisplayValue('2024-12-31')).toBeDisabled();
|
|
expect(screen.getByDisplayValue('09:00')).toBeDisabled();
|
|
// prefilled from the appointment's current specs (react-select single value)
|
|
expect(screen.getByText('قطعی شده')).toBeInTheDocument();
|
|
|
|
fireEvent.change(screen.getByPlaceholderText('نام و نام خانوادگی'), { target: { value: 'ساغر صابری' } });
|
|
fireEvent.change(screen.getByPlaceholderText('شماره تماس'), { target: { value: '09356619438' } });
|
|
fireEvent.click(screen.getByText('ثبت نوبت'));
|
|
await waitFor(() => expect(patch).toHaveBeenCalledWith('/api/v1/appointment/ap1', expect.objectContaining({
|
|
patient_name: 'ساغر صابری', patient_mobile: '09356619438',
|
|
service_section_uuid: 's1', service_item_uuid: 'i1', staff_uuid: 'st1',
|
|
version: 3,
|
|
})));
|
|
});
|
|
|
|
it('replace modal picks an existing patient from the record search', async () => {
|
|
get.mockImplementation((url: string) => {
|
|
if (url.startsWith('/api/v1/patients?search=')) return Promise.resolve({ success: true, data: [
|
|
{ uuid: 'rec9', user_name: 'پریسا همتی', user_mobile: '09120009999' },
|
|
] });
|
|
return Promise.resolve({ success: true, data: [] });
|
|
});
|
|
openMenu();
|
|
fireEvent.click(screen.getByText('جایگزینی نوبت'));
|
|
fireEvent.change(await screen.findByPlaceholderText('جستجوی نام، شماره تماس، شماره پرونده...'), { target: { value: 'پریسا' } });
|
|
fireEvent.click(await screen.findByText('پریسا همتی'));
|
|
fireEvent.click(screen.getByText('ثبت نوبت'));
|
|
await waitFor(() => expect(patch).toHaveBeenCalledWith('/api/v1/appointment/ap1', expect.objectContaining({
|
|
patient_name: 'پریسا همتی', patient_mobile: '09120009999',
|
|
})));
|
|
});
|
|
});
|