Files
hamed 54bebbd41a feat: Implement resource-based appointment booking flow
- Updated DoctorPage component to accept bookingResources prop for appointment list.
- Added serviceQuery function to serialize service item UUIDs for API requests.
- Introduced new API endpoints for fetching booking resources and resource slots.
- Enhanced tests for new resource-based booking functionality, including resource selection and service availability.
- Created ResourceSelect component for selecting appointment types, including doctor and resource options.
- Updated appointment submission logic to include resource_uuid in payload when applicable.
- Ensured UI reflects changes in booking flow without disrupting existing doctor-centric experience.
2026-08-09 10:45:52 +03:30

143 lines
5.9 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
// جریان نوبت به کوکی، تم، تقویم و لایه‌ی API وابسته است؛ اینجا فقط تصمیمِ
// «کدام مرحله رندر می‌شود» سنجیده می‌شود، پس بقیه mock می‌شوند.
vi.mock('@/components/layout', () => ({ default: ({ children }) => <div>{children}</div> }));
vi.mock('./Content', () => ({ default: ({ children }) => <div>{children}</div> }));
vi.mock('./date', () => ({ default: () => <div>مرحله-روز</div> }));
vi.mock('@/components/appointment/paying', () => ({ default: () => null }));
vi.mock('@/components/appointment/detail', () => ({ default: () => null }));
vi.mock('@/components/appointment/failedPay', () => ({ default: () => null }));
vi.mock('@/components/appointment/successPay', () => ({ default: () => null }));
vi.mock('@/components/register/LogInPage', () => ({ default: () => null }));
vi.mock('@/components/register/verificationPage', () => ({ default: () => null }));
vi.mock('@/components/register/LayoutRegister', () => ({ default: ({ children }) => <div>{children}</div> }));
import Container from './Container';
const RESOURCE = {
uuid: 'r1',
name: 'کندلا2021',
type: { name: 'دستگاه لیزر' },
services: [{ uuid: 's1', name: 'لیزر دست', duration_minutes: 20 }],
};
const LOCATION = { clinic_uuid: null, title: 'مطب شخصی', booking_mode: 'slot', services: [] };
const props = (over = {}) => ({
step: 0,
data: {},
doctor: { uuid: 'd1' },
setData: vi.fn(),
setStep: vi.fn(),
prevData: {},
matchedCity: {},
isForAnother: false,
setIsForAnother: vi.fn(),
disabledDates: [],
setNum: vi.fn(),
setUuid: vi.fn(),
setIsSendMsg: vi.fn(),
setSelectedSlot: vi.fn(),
setSelectedDate: vi.fn(),
setAppointmentId: vi.fn(),
setAppointmentExpiresAt: vi.fn(),
bookingMode: 'slot',
bookingServices: [],
selectedServiceUuids: [],
setSelectedServiceUuids: vi.fn(),
bookingLocations: [LOCATION],
selectedLocation: LOCATION,
locationConfirmed: true,
changeLocation: vi.fn(),
reopenLocationChoice: vi.fn(),
onDateChange: vi.fn(),
selectedClosedOnDate: false,
resources: [],
selectedResource: null,
resourceStepNeeded: false,
changeResource: vi.fn(),
reopenResourceChoice: vi.fn(),
...over,
});
describe('مرحلهٔ انتخاب نوع نوبت در جریان رزرو', () => {
beforeEach(() => vi.clearAllMocks());
it('پزشکِ بدون منبع → هیچ تغییری؛ مستقیم مرحلهٔ روز', () => {
render(<Container {...props()} />);
expect(screen.getByText('مرحله-روز')).toBeInTheDocument();
expect(screen.queryByText('انتخاب نوع نوبت')).not.toBeInTheDocument();
});
it('منبع هست و هنوز انتخاب نشده → مرحلهٔ نوع نوبت', () => {
render(<Container {...props({ resources: [RESOURCE], resourceStepNeeded: true })} />);
expect(screen.getByText('۱. انتخاب نوع نوبت')).toBeInTheDocument();
expect(screen.getByText('کندلا2021')).toBeInTheDocument();
expect(screen.queryByText('مرحله-روز')).not.toBeInTheDocument();
});
it('محلِ تأییدنشده مقدم بر مرحلهٔ منبع است', () => {
render(
<Container
{...props({
bookingLocations: [LOCATION, { ...LOCATION, clinic_uuid: 'c2', title: 'کلینیک' }],
locationConfirmed: false,
resources: [RESOURCE],
resourceStepNeeded: true,
})}
/>
);
expect(screen.getByText('۱. انتخاب محل نوبت‌دهی')).toBeInTheDocument();
expect(screen.queryByText('۱. انتخاب نوع نوبت')).not.toBeInTheDocument();
});
it('پزشکِ بدون برنامهٔ هفتگی ولی دارای منبع → مرحلهٔ منبع، نه پیام «فعال نیست»', () => {
render(
<Container
{...props({
bookingLocations: [],
selectedLocation: null,
resources: [{ ...RESOURCE, clinic_uuid: 'c5' }],
resourceStepNeeded: true,
})}
/>
);
expect(screen.getByText('۱. انتخاب نوع نوبت')).toBeInTheDocument();
expect(screen.getByText('کندلا2021')).toBeInTheDocument();
expect(
screen.queryByText('نوبت‌دهی آنلاین برای این پزشک فعال نیست.')
).not.toBeInTheDocument();
// ویزیت مستقیم وجود ندارد، پس کارتش هم نباید باشد.
expect(screen.queryByText('نوبت با پزشک')).not.toBeInTheDocument();
});
it('نه محل و نه منبع → همان پیام «فعال نیست»', () => {
render(<Container {...props({ bookingLocations: [], selectedLocation: null })} />);
expect(screen.getByText('نوبت‌دهی آنلاین برای این پزشک فعال نیست.')).toBeInTheDocument();
});
it('بعد از انتخاب منبع، مرحلهٔ سرویس با سرویس‌های همان منبع می‌آید', () => {
render(
<Container
{...props({
resources: [RESOURCE],
selectedResource: RESOURCE,
resourceStepNeeded: false,
bookingMode: 'service',
bookingServices: RESOURCE.services,
})}
/>
);
expect(screen.getByText('۱. انتخاب سرویس')).toBeInTheDocument();
expect(screen.getByText('لیزر دست')).toBeInTheDocument();
});
});