Просмотр исходного кода

test: add tests for users action dialog

satnaing 2 месяцев назад
Родитель
Сommit
cfb330e9c0
1 измененных файлов с 327 добавлено и 0 удалено
  1. 327 0
      src/features/users/components/users-action-dialog.test.tsx

+ 327 - 0
src/features/users/components/users-action-dialog.test.tsx

@@ -0,0 +1,327 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { render, type RenderResult } from 'vitest-browser-react'
+import { type UserEvent, userEvent } from 'vitest/browser'
+import { showSubmittedData } from '@/lib/show-submitted-data'
+import { type User } from '../data/schema'
+import { UsersActionDialog } from './users-action-dialog'
+
+const VALIDATION_MESSAGES = {
+  firstName: 'First Name is required.',
+  lastName: 'Last Name is required.',
+  username: 'Username is required.',
+  phoneNumber: 'Phone number is required.',
+  email: 'Email is required.',
+  role: 'Role is required.',
+  password: 'Password is required.',
+  passwordMismatch: "Passwords don't match.",
+  passwordLength: 'Password must be at least 8 characters long.',
+  passwordNumber: 'Password must contain at least one number.',
+  passwordLowercase: 'Password must contain at least one lowercase letter.',
+} as const
+
+const MOCK_USER: User = {
+  id: 'alex_uuid',
+  firstName: 'Alex',
+  lastName: 'Smith',
+  username: 'alex_smith',
+  email: 'alex@smith.com',
+  phoneNumber: '+19999999999',
+  status: 'active',
+  role: 'superadmin',
+  createdAt: new Date('2026-01-01'),
+  updatedAt: new Date('2026-02-02'),
+}
+
+vi.mock('@/lib/show-submitted-data', () => ({ showSubmittedData: vi.fn() }))
+
+describe('UsersActionDialog', () => {
+  beforeEach(() => vi.clearAllMocks())
+
+  describe('add user', () => {
+    it('renders title and description', async () => {
+      const { getByRole, getByText } = await render(
+        <UsersActionDialog open onOpenChange={vi.fn()} />
+      )
+
+      const title = getByRole('heading', {
+        level: 2,
+        name: /Add New User/i,
+      })
+      const description = getByText(
+        /Create new user here. Click save when you're done./i
+      )
+
+      expect(title).toBeInTheDocument()
+      expect(description).toBeInTheDocument()
+    })
+
+    it('shows validation messages when the form is submitted with empty fields', async () => {
+      const { getByRole, getByText } = await render(
+        <UsersActionDialog open onOpenChange={vi.fn()} />
+      )
+
+      const submitButton = getByRole('button', { name: /Save Changes/i })
+      await userEvent.click(submitButton)
+
+      expect(getByText(VALIDATION_MESSAGES.firstName)).toBeInTheDocument()
+      expect(getByText(VALIDATION_MESSAGES.lastName)).toBeInTheDocument()
+      expect(getByText(VALIDATION_MESSAGES.username)).toBeInTheDocument()
+      expect(getByText(VALIDATION_MESSAGES.phoneNumber)).toBeInTheDocument()
+      expect(getByText(VALIDATION_MESSAGES.email)).toBeInTheDocument()
+      expect(getByText(VALIDATION_MESSAGES.role)).toBeInTheDocument()
+      expect(getByText(VALIDATION_MESSAGES.password)).toBeInTheDocument()
+    })
+
+    it('keeps confirm password disabled until password field is touched', async () => {
+      const { getByRole } = await render(
+        <UsersActionDialog open onOpenChange={vi.fn()} />
+      )
+
+      const password = getByRole('textbox', { name: /^Password$/i })
+      const confirmPassword = getByRole('textbox', {
+        name: /Confirm Password/i,
+      })
+      expect(confirmPassword).toBeDisabled()
+
+      await userEvent.type(password, 'a')
+      expect(confirmPassword).toBeEnabled()
+    })
+
+    it('shows password validation messages when password is invalid', async () => {
+      const { getByRole, getByText } = await render(
+        <UsersActionDialog open onOpenChange={vi.fn()} />
+      )
+
+      const password = getByRole('textbox', { name: /^Password$/i })
+      const confirmPassword = getByRole('textbox', {
+        name: /Confirm Password/i,
+      })
+      await userEvent.type(password, 'a')
+      await userEvent.type(confirmPassword, 'b')
+      const submitButton = getByRole('button', { name: /Save Changes/i })
+
+      await userEvent.click(submitButton)
+
+      expect(
+        getByText(VALIDATION_MESSAGES.passwordMismatch)
+      ).toBeInTheDocument()
+
+      await userEvent.fill(password, 'short')
+
+      expect(getByText(VALIDATION_MESSAGES.passwordLength)).toBeInTheDocument()
+
+      await userEvent.fill(password, 'ONLYUPPERCASE')
+
+      expect(
+        getByText(VALIDATION_MESSAGES.passwordLowercase)
+      ).toBeInTheDocument()
+
+      await userEvent.fill(password, 'onlylowercase')
+
+      expect(getByText(VALIDATION_MESSAGES.passwordNumber)).toBeInTheDocument()
+
+      await userEvent.fill(password, 'S3cur3P@ssw0rd')
+      await userEvent.fill(confirmPassword, 'S3cur3P@ssw0rd')
+
+      expect(
+        getByText(VALIDATION_MESSAGES.passwordMismatch)
+      ).not.toBeInTheDocument()
+      expect(
+        getByText(VALIDATION_MESSAGES.passwordLength)
+      ).not.toBeInTheDocument()
+      expect(
+        getByText(VALIDATION_MESSAGES.passwordNumber)
+      ).not.toBeInTheDocument()
+    })
+
+    it('shows the submitted data when the form is submitted successfully', async () => {
+      const onOpenChange = vi.fn()
+
+      const screen = await render(
+        <UsersActionDialog open onOpenChange={onOpenChange} />
+      )
+
+      await fillRequiredProfileFields(userEvent, screen, MOCK_USER)
+
+      await fillPasswords(userEvent, screen, 'S3cur3P@ssw0rd', 'S3cur3P@ssw0rd')
+
+      const submitButton = screen.getByRole('button', { name: /Save Changes/i })
+      await userEvent.click(submitButton)
+
+      expect(onOpenChange).toHaveBeenCalledOnce()
+      expect(onOpenChange).toHaveBeenCalledWith(false)
+
+      expect(showSubmittedData).toHaveBeenCalledOnce()
+      expect(showSubmittedData).toHaveBeenCalledWith({
+        firstName: MOCK_USER.firstName,
+        lastName: MOCK_USER.lastName,
+        username: MOCK_USER.username,
+        email: MOCK_USER.email,
+        role: MOCK_USER.role,
+        phoneNumber: MOCK_USER.phoneNumber,
+        password: 'S3cur3P@ssw0rd',
+        confirmPassword: 'S3cur3P@ssw0rd',
+        isEdit: false,
+      })
+    })
+  })
+
+  describe('edit user', () => {
+    it('renders title and description', async () => {
+      const { getByRole, getByText } = await render(
+        <UsersActionDialog open onOpenChange={vi.fn()} currentRow={MOCK_USER} />
+      )
+
+      const title = getByRole('heading', {
+        level: 2,
+        name: /Edit User/i,
+      })
+      const description = getByText(
+        /Update the user here\. Click save when you're done\./i
+      )
+
+      expect(title).toBeInTheDocument()
+      expect(description).toBeInTheDocument()
+    })
+
+    it('submits without password changes', async () => {
+      const onOpenChange = vi.fn()
+      const screen = await render(
+        <UsersActionDialog
+          open
+          onOpenChange={onOpenChange}
+          currentRow={MOCK_USER}
+        />
+      )
+
+      const submitButton = screen.getByRole('button', { name: /Save Changes/i })
+      await userEvent.click(submitButton)
+
+      expect(onOpenChange).toHaveBeenCalledOnce()
+      expect(onOpenChange).toHaveBeenCalledWith(false)
+
+      expect(showSubmittedData).toHaveBeenCalledOnce()
+      expect(showSubmittedData).toHaveBeenCalledWith({
+        firstName: MOCK_USER.firstName,
+        lastName: MOCK_USER.lastName,
+        username: MOCK_USER.username,
+        email: MOCK_USER.email,
+        phoneNumber: MOCK_USER.phoneNumber,
+        role: MOCK_USER.role,
+        password: '',
+        confirmPassword: '',
+        isEdit: true,
+      })
+    })
+
+    it('requires confirm password when password is changed', async () => {
+      const { getByRole, getByText } = await render(
+        <UsersActionDialog open onOpenChange={vi.fn()} currentRow={MOCK_USER} />
+      )
+
+      const password = getByRole('textbox', { name: /^Password$/i })
+      const confirmPassword = getByRole('textbox', {
+        name: /Confirm Password/i,
+      })
+
+      await userEvent.fill(password, 'S3cur3P@ssw0rd')
+      expect(confirmPassword).toBeEnabled()
+
+      const submitButton = getByRole('button', { name: /Save Changes/i })
+      await userEvent.click(submitButton)
+
+      expect(
+        getByText(VALIDATION_MESSAGES.passwordMismatch)
+      ).toBeInTheDocument()
+    })
+
+    it('shows the submitted data when the form is submitted successfully', async () => {
+      const onOpenChange = vi.fn()
+      const screen = await render(
+        <UsersActionDialog
+          open
+          onOpenChange={onOpenChange}
+          currentRow={MOCK_USER}
+        />
+      )
+
+      const EDIT_SUCCESS_FIRST_NAME = 'John'
+      const EDIT_SUCCESS_PASSWORD = 'S3cur3P@ssw0rd'
+
+      await userEvent.fill(
+        screen.getByLabelText(/first name/i),
+        EDIT_SUCCESS_FIRST_NAME
+      )
+      await fillPasswords(
+        userEvent,
+        screen,
+        EDIT_SUCCESS_PASSWORD,
+        EDIT_SUCCESS_PASSWORD
+      )
+
+      const submitButton = screen.getByRole('button', { name: /Save Changes/i })
+      await userEvent.click(submitButton)
+
+      expect(onOpenChange).toHaveBeenCalledOnce()
+      expect(onOpenChange).toHaveBeenCalledWith(false)
+
+      expect(showSubmittedData).toHaveBeenCalledOnce()
+      expect(showSubmittedData).toHaveBeenCalledWith({
+        firstName: EDIT_SUCCESS_FIRST_NAME,
+        lastName: MOCK_USER.lastName,
+        username: MOCK_USER.username,
+        email: MOCK_USER.email,
+        phoneNumber: MOCK_USER.phoneNumber,
+        role: MOCK_USER.role,
+        password: EDIT_SUCCESS_PASSWORD,
+        confirmPassword: EDIT_SUCCESS_PASSWORD,
+        isEdit: true,
+      })
+    })
+  })
+})
+
+async function fillRequiredProfileFields(
+  user: UserEvent,
+  screen: RenderResult,
+  overrides?: {
+    firstName?: string
+    lastName?: string
+    username?: string
+    email?: string
+    roleOption?: RegExp
+    phoneNumber?: string
+  }
+) {
+  const entries = [
+    [/first name/i, overrides?.firstName ?? 'John'],
+    [/last name/i, overrides?.lastName ?? 'Doe'],
+    [/username/i, overrides?.username ?? 'john_doe'],
+    [/^email$/i, overrides?.email ?? 'a@b.co'],
+    [/phone number/i, overrides?.phoneNumber ?? '+19999999999'],
+  ] as const
+
+  for (const [label, value] of entries) {
+    const el = screen.getByLabelText(label)
+    expect(el).toBeInTheDocument()
+    await user.type(el, value)
+  }
+
+  const roleSelect = screen.getByRole('combobox', { name: /Role/i })
+  await user.click(roleSelect)
+  await user.click(
+    screen.getByRole('option', { name: overrides?.roleOption ?? 'Superadmin' })
+  )
+}
+
+async function fillPasswords(
+  user: UserEvent,
+  screen: RenderResult,
+  a: string,
+  b: string
+) {
+  const password = screen.getByLabelText(/^Password$/i)
+  const confirmPassword = screen.getByLabelText(/^Confirm Password$/i)
+  await user.fill(password, a)
+  await user.fill(confirmPassword, b)
+}