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()
})
})