admin/app/(dashboard)/users/[id]/_components/UserMobileChangeModal.test.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

134 lines
3.9 KiB
TypeScript

import type { ReactNode } from 'react'
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import UserMobileChangeModal from '@/app/(dashboard)/users/[id]/_components/UserMobileChangeModal'
const changeMobile = vi.fn()
const addToast = vi.fn()
vi.mock('@/services/adminUsers', () => ({
ADMIN_CHANGE_USER_MOBILE: (...args: unknown[]) => changeMobile(...args),
}))
vi.mock('@/lib/toast', () => ({ addToast: (...args: unknown[]) => addToast(...args) }))
vi.mock('@/components/formElements/Input', () => ({
default: ({ label, value, onValueChange }: { label: string; value?: unknown; onValueChange?: (value: string) => void }) => (
<label>
{label}
<input
aria-label={label}
value={String(value ?? '')}
onChange={(event) => onValueChange?.(event.target.value)}
/>
</label>
),
}))
vi.mock('@/components/formElements/Button', () => ({
default: ({ children, onClick }: { children: ReactNode; onClick?: () => void }) => (
<button
type="button"
onClick={onClick}
>
{children}
</button>
),
}))
vi.mock('@/components/modals/Modal', () => ({
default: ({
isOpen,
title,
children,
acceptBtnText,
rejectBtnText,
acceptBtnDisabled,
onAccept,
onReject,
}: {
isOpen: boolean
title: string
children: ReactNode
acceptBtnText: string
rejectBtnText: string
acceptBtnDisabled?: boolean
onAccept?: () => void
onReject?: () => void
}) =>
isOpen ? (
<div
aria-label={title}
role="dialog"
>
{children}
<button
type="button"
onClick={onReject}
>
{rejectBtnText}
</button>
<button
disabled={acceptBtnDisabled}
type="button"
onClick={onAccept}
>
{acceptBtnText}
</button>
</div>
) : null,
}))
describe('UserMobileChangeModal', () => {
afterEach(cleanup)
beforeEach(() => {
vi.clearAllMocks()
changeMobile.mockResolvedValue({ ok: true, data: { id: 'user-1', mobile: '989121231231' } })
})
it('requires all three confirmation stages before changing the mobile', async () => {
const onSuccess = vi.fn()
const onOpenChange = vi.fn()
render(
<UserMobileChangeModal
isOpen
user={{ id: 'user-1', mobile: '989111111111', firstName: 'Test', lastName: 'User' }}
onOpenChange={onOpenChange}
onSuccess={onSuccess}
/>
)
const firstAccept = screen.getByRole('button', { name: 'بررسی شماره' })
expect(firstAccept).toBeDisabled()
fireEvent.change(screen.getByLabelText('شماره موبایل جدید'), { target: { value: '09121231231' } })
expect(firstAccept).toBeEnabled()
fireEvent.click(firstAccept)
const secondAccept = screen.getByRole('button', { name: 'تأیید و ادامه' })
expect(secondAccept).toBeDisabled()
fireEvent.change(screen.getByLabelText('برای تأیید، شماره فعلی کاربر را وارد کنید'), {
target: { value: '09111111111' },
})
expect(secondAccept).toBeEnabled()
fireEvent.click(secondAccept)
const finalAccept = screen.getByRole('button', { name: 'تغییر قطعی شماره' })
expect(finalAccept).toBeDisabled()
fireEvent.change(screen.getByLabelText('عبارت «تغییر شماره» را وارد کنید'), {
target: { value: 'تغییر شماره' },
})
expect(finalAccept).toBeEnabled()
fireEvent.click(finalAccept)
await waitFor(() => {
expect(changeMobile).toHaveBeenCalledWith('user-1', '989121231231')
})
expect(addToast).toHaveBeenCalledWith(expect.objectContaining({ color: 'success' }))
expect(onSuccess).toHaveBeenCalledTimes(1)
expect(onOpenChange).toHaveBeenCalledWith(false)
})
})