Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
134 lines
3.9 KiB
TypeScript
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)
|
|
})
|
|
})
|