Files
hamedandClaude Opus 5 f144401dd3 feat(holidays): one official calendar, inherited everywhere
The holiday model was already right — national holidays global, a per-tenant
override in both directions, per-doctor and per-resource exceptions — but
nothing could create a national holiday. The only writer was an import
command, so the calendar the whole product inherits from had no owner.

Three admin-only routes give it one. POST upserts, because `date` is unique
and re-sending a day should rename it rather than surface a raw database
error; PATCH takes only the title, because moving a date means a different
holiday. The system admin has no work environment, so the list endpoint now
returns the calendar with an empty `overrides` for that role instead of the
403 `pair()` would raise — the person who maintains the calendar has to be
able to read it.

Both holiday tabs — the doctor's and the resource's — now open with the
official calendar above their own exceptions, from one shared card rather
than two copies that would drift. Each row can be opted out of with a single
click, which is the existing holiday-override endpoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 15:39:48 +03:30

72 lines
3.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderWithProviders } from '../../test/utils';
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
const setOverride = { mutate: vi.fn(), isPending: false };
const removeOverride = { mutate: vi.fn(), isPending: false };
let holidays: Array<{ uuid: string; date: number; title: string }> = [];
let overrides: Array<{ uuid: string; date: number; is_working: boolean }> = [];
vi.mock('../../hooks/useResourceCalendar', () => ({
useHolidays: () => ({ holidays, overrides, loading: false, setOverride, removeOverride }),
}));
import NationalHolidaysCard from './NationalHolidaysCard';
const NOWRUZ = { uuid: 'h-1', date: 1774040400, title: 'نوروز' };
describe('NationalHolidaysCard', () => {
beforeEach(() => {
holidays = [NOWRUZ];
overrides = [];
vi.clearAllMocks();
});
it('تعطیلات رسمی سال را نشان می‌دهد', () => {
renderWithProviders(<NationalHolidaysCard canUpdate year={1405} />);
expect(screen.getByText(/نوروز/)).toBeInTheDocument();
expect(screen.getByText('تعطیل')).toBeInTheDocument();
});
/** محیطی که آن روز کار می‌کند، تعطیلیِ سراسری را برای خودش خنثی می‌کند. */
it('با یک کلیک روز را برای این محیط باز می‌کند', async () => {
const user = userEvent.setup();
renderWithProviders(<NationalHolidaysCard canUpdate year={1405} />);
await user.click(screen.getByRole('button', { name: 'این روز باز است' }));
expect(setOverride.mutate).toHaveBeenCalledWith({ date: NOWRUZ.date, is_working: true });
});
it('روزی که استثنا خورده، «باز» است و می‌شود دوباره تعطیلش کرد', async () => {
overrides = [{ uuid: 'o-1', date: NOWRUZ.date, is_working: true }];
const user = userEvent.setup();
renderWithProviders(<NationalHolidaysCard canUpdate year={1405} />);
expect(screen.getByText('باز')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'تعطیل کن' }));
expect(removeOverride.mutate).toHaveBeenCalledWith('o-1');
});
/** ساخت و حذفِ خودِ تعطیلی کارِ مدیر سیستم است؛ اینجا فقط استثنا زده می‌شود. */
it('بدون مجوز ویرایش، هیچ دکمه‌ای نمی‌دهد', () => {
renderWithProviders(<NationalHolidaysCard canUpdate={false} year={1405} />);
expect(screen.queryByRole('button')).not.toBeInTheDocument();
expect(screen.getByText('تنظیمات ← تعطیلات رسمی')).toHaveAttribute('href', '/admin/holidays');
});
it('سالِ بدون تعطیلی را صریح می‌گوید', async () => {
holidays = [];
renderWithProviders(<NationalHolidaysCard canUpdate year={1405} />);
await waitFor(() => expect(screen.getByText(/تعطیلی رسمی ثبت نشده است/)).toBeInTheDocument());
});
});