Tailwind was configured with darkMode: "class" while next-themes writes data-theme="dark" on the public pages. Every dark: utility on the public site — 111 of them — compiled to a selector that never matched, which is why the booking flow stayed white in dark mode. The variant strategy now accepts both .dark (the panel) and [data-theme="dark"] (the public pages), so neither provider had to change and 66 dark rules now compile against the real attribute. The cancel and reschedule modals get tests, the first component tests in this repo. They pin the things that would be silently wrong: the penalty comes from the server before anything is cancelled, the free window says "no penalty" rather than showing a zero, confirming actually sends the request (the old dialog's confirm button only closed it), a failed preview does not block the cancellation, slots are requested with exclude_appointment_uuid so the patient's own hour is not shown as taken, and the reschedule sends only the start time because the server owns the duration. The amount assertion deliberately checks the number and unit rather than the digit shape — numberToArStyle uses the ar-AE locale and its output depends on the ICU data in the environment. lib/getStateInfo.test.js had been failing since before this work: an unknown host falls through to the representation API, so the test made a real network call and timed out after five seconds. It mocks lib/req now. The suite is fully green for the first time: 158 tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
106 lines
4.0 KiB
JavaScript
106 lines
4.0 KiB
JavaScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||
import { render, screen, waitFor } from '@testing-library/react';
|
||
import userEvent from '@testing-library/user-event';
|
||
|
||
vi.mock('@/services/response', () => ({
|
||
request: {
|
||
getServiceSlotsForReschedule: vi.fn(),
|
||
serviceReschedule: vi.fn(),
|
||
},
|
||
}));
|
||
|
||
import { request } from '@/services/response';
|
||
import RescheduleModal from './RescheduleModal';
|
||
|
||
const appointment = {
|
||
uuid: 'a-1',
|
||
doctor: { uuid: 'd-1' },
|
||
service_items: [{ uuid: 's-1' }, { uuid: 's-2' }],
|
||
};
|
||
|
||
const slots = {
|
||
data: {
|
||
total_duration_minutes: 30,
|
||
start_times: [
|
||
{ start: 1_900_000_000, end: 1_900_001_800, start_time: '09:00', end_time: '09:30' },
|
||
{ start: 1_900_003_600, end: 1_900_005_400, start_time: '10:00', end_time: '10:30' },
|
||
],
|
||
},
|
||
};
|
||
|
||
async function openAndPickFirstDay(user) {
|
||
render(<RescheduleModal appointment={appointment} />);
|
||
await user.click(screen.getByRole('button', { name: 'جابهجایی نوبت' }));
|
||
|
||
const days = screen.getAllByRole('button').filter((b) => /\d|[۰-۹]/.test(b.textContent));
|
||
await user.click(days[0]);
|
||
}
|
||
|
||
describe('مودال جابهجایی نوبت سایت', () => {
|
||
beforeEach(() => {
|
||
request.getServiceSlotsForReschedule.mockResolvedValue(slots);
|
||
request.serviceReschedule.mockResolvedValue({ data: {} });
|
||
});
|
||
|
||
/**
|
||
* ⭐ بدون `exclude_appointment_uuid`، بیمار ساعت خودش را «پر» میبیند و نمیتواند
|
||
* حتی به همان روز جابهجا شود.
|
||
*/
|
||
it('وقتها را با کنارگذاشتن نوبت فعلی میگیرد', async () => {
|
||
const user = userEvent.setup();
|
||
await openAndPickFirstDay(user);
|
||
|
||
await waitFor(() => expect(request.getServiceSlotsForReschedule).toHaveBeenCalled());
|
||
|
||
const [doctorUuid, , appointmentUuid, serviceUuids] =
|
||
request.getServiceSlotsForReschedule.mock.calls[0];
|
||
|
||
expect(doctorUuid).toBe('d-1');
|
||
expect(appointmentUuid).toBe('a-1');
|
||
expect(serviceUuids).toEqual(['s-1', 's-2']);
|
||
});
|
||
|
||
/** مدت را سرور حساب میکند؛ فرانت فقط زمان شروع میفرستد. */
|
||
it('فقط زمان شروع را میفرستد، نه مدت', async () => {
|
||
const onDone = vi.fn();
|
||
const user = userEvent.setup();
|
||
|
||
render(<RescheduleModal appointment={appointment} onDone={onDone} />);
|
||
await user.click(screen.getByRole('button', { name: 'جابهجایی نوبت' }));
|
||
|
||
const days = screen.getAllByRole('button').filter((b) => /\d|[۰-۹]/.test(b.textContent));
|
||
await user.click(days[0]);
|
||
|
||
const slot = await screen.findByRole('button', { name: '09:00' });
|
||
await user.click(slot);
|
||
await user.click(screen.getByRole('button', { name: 'جابهجا کن' }));
|
||
|
||
await waitFor(() =>
|
||
expect(request.serviceReschedule).toHaveBeenCalledWith('a-1', { start: 1_900_000_000 }),
|
||
);
|
||
await waitFor(() => expect(onDone).toHaveBeenCalled());
|
||
});
|
||
|
||
it('روزِ بدون وقت آزاد را صریح میگوید', async () => {
|
||
request.getServiceSlotsForReschedule.mockResolvedValue({ data: { start_times: [] } });
|
||
const user = userEvent.setup();
|
||
|
||
await openAndPickFirstDay(user);
|
||
|
||
expect(await screen.findByText(/در این روز وقت آزادی نیست/)).toBeInTheDocument();
|
||
});
|
||
|
||
/** ۴۰۹ یعنی همین لحظه کس دیگری گرفت — پیام سرور باید دیده شود. */
|
||
it('پیام خطای سرور را نشان میدهد', async () => {
|
||
request.serviceReschedule.mockRejectedValue(new Error('این بازه زمانی قبلاً رزرو شده است'));
|
||
const user = userEvent.setup();
|
||
|
||
await openAndPickFirstDay(user);
|
||
|
||
await user.click(await screen.findByRole('button', { name: '09:00' }));
|
||
await user.click(screen.getByRole('button', { name: 'جابهجا کن' }));
|
||
|
||
expect(await screen.findByText('این بازه زمانی قبلاً رزرو شده است')).toBeInTheDocument();
|
||
});
|
||
});
|