import type { ReactNode } from 'react' import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import NotificationRulesPanel from '@/app/(dashboard)/notifications/_components/NotificationRulesPanel' const axiosMocks = vi.hoisted(() => ({ get: vi.fn(), patch: vi.fn(), })) vi.mock('@/config/axios', () => ({ default: { get: axiosMocks.get, patch: axiosMocks.patch, }, })) vi.mock('@/lib/toast', () => ({ addToast: vi.fn() })) vi.mock('@/components/formElements/Input', () => ({ default: ({ description, generalType, label, selectOptions, value, onValueChange, }: { description?: ReactNode generalType?: string label: string selectOptions?: { code: string; name: string }[] value?: unknown onValueChange?: (value: unknown) => void }) => ( ), })) vi.mock('@/components/formElements/Button', () => ({ default: ({ children, disabled, onClick }: { children: ReactNode; disabled?: boolean; onClick?: () => void }) => ( ), })) const bookingRule = { eventKey: 'booking_confirmed_guest', displayName: 'تأیید رزرو برای مهمان', description: 'پس از قطعی‌شدن رزرو', enabled: true, channel: 'in_app', titleTemplate: 'رزرو شما تأیید شد', bodyTemplate: 'رزرو شما برای «{{eventTitle}}» با موفقیت تأیید شد.', actionUrlTemplate: '/bookings/{{bookingId}}', allowedVariables: ['eventTitle', 'bookingId'], sampleVariables: { eventTitle: 'دورهمی آخر هفته', bookingId: 'booking-sample' }, smsAllowed: true, updatedAt: '2026-08-18T00:00:00.000Z', } const chatRule = { eventKey: 'chat_message', displayName: 'پیام جدید چت', description: 'برای سایر اعضای گفتگو', enabled: true, channel: 'in_app', titleTemplate: 'پیام جدید', bodyTemplate: '{{messagePreview}}', actionUrlTemplate: '/chats/{{conversationId}}', allowedVariables: ['messagePreview', 'conversationId'], sampleVariables: { messagePreview: 'سلام، ساعت را هماهنگ کنیم؟', conversationId: 'chat-sample' }, smsAllowed: false, updatedAt: '2026-08-18T00:00:00.000Z', } describe('NotificationRulesPanel', () => { afterEach(() => { cleanup() }) beforeEach(() => { axiosMocks.get.mockReset() axiosMocks.patch.mockReset() axiosMocks.get.mockResolvedValue({ data: { success: true, data: [bookingRule, chatRule] }, }) }) it('shows allowed variables, sample preview, and keeps save disabled until dirty', async () => { render() expect(await screen.findByText('{{eventTitle}}')).toBeInTheDocument() expect(screen.getByText('رزرو شما برای «دورهمی آخر هفته» با موفقیت تأیید شد.')).toBeInTheDocument() expect(screen.getByText('/bookings/booking-sample')).toBeInTheDocument() expect(screen.queryByText('ذخیره نشده')).not.toBeInTheDocument() const saveButtons = screen.getAllByRole('button', { name: 'ذخیره' }) expect(saveButtons[0]).toBeDisabled() fireEvent.change(screen.getAllByLabelText('عنوان')[0], { target: { value: 'رزرو قطعی شد' }, }) expect(await screen.findByText('ذخیره نشده')).toBeInTheDocument() expect(screen.getByText('رزرو قطعی شد')).toBeInTheDocument() expect(screen.getAllByRole('button', { name: 'ذخیره' })[0]).not.toBeDisabled() }) it('hides SMS channels for chat and warns when SMS-only is selected', async () => { render() expect(await screen.findByText('پیام جدید چت')).toBeInTheDocument() expect(screen.getByText('پیامک برای پیام‌های چت ارسال نمی‌شود؛ فقط اعلان درون‌برنامه‌ای و پوش.')).toBeInTheDocument() const chatChannel = screen.getAllByLabelText('کانال ارسال')[1] expect(chatChannel.querySelector('option[value="sms"]')).toBeNull() expect(chatChannel.querySelector('option[value="in_app"]')).not.toBeNull() fireEvent.change(screen.getAllByLabelText('کانال ارسال')[0], { target: { value: 'sms' }, }) expect(await screen.findByText('با انتخاب فقط پیامک، اعلان درون‌برنامه‌ای و پوش ارسال نمی‌شود.')).toBeInTheDocument() expect(screen.getByText(/کاراکتر/)).toBeInTheDocument() }) })