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

test(auth): add tests for sign up form

satnaing 2 месяцев назад
Родитель
Сommit
718626c62b

+ 78 - 0
src/features/auth/sign-up/components/sign-up-form.test.tsx

@@ -0,0 +1,78 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { render, type RenderResult } from 'vitest-browser-react'
+import { type Locator, userEvent } from 'vitest/browser'
+import { SignUpForm } from './sign-up-form'
+
+const FORM_MESSAGES = {
+  emailEmpty: 'Please enter your email.',
+  passwordEmpty: 'Please enter your password.',
+  confirmPasswordEmpty: 'Please confirm your password.',
+  passwordMismatch: "Passwords don't match.",
+} as const
+
+const toastPromise = vi.hoisted(() =>
+  vi.fn((p: Promise<unknown>, opts: { success?: () => unknown }) => {
+    p.then(() => opts.success?.())
+  })
+)
+
+vi.mock('sonner', () => ({ toast: { promise: toastPromise } }))
+
+describe('SignUpForm', () => {
+  let screen: RenderResult
+  let emailInput: Locator
+  let passwordInput: Locator
+  let confirmPasswordInput: Locator
+  let submitButton: Locator
+
+  beforeEach(async () => {
+    vi.clearAllMocks()
+
+    screen = await render(<SignUpForm />)
+    emailInput = screen.getByRole('textbox', { name: /^Email$/i })
+    passwordInput = screen.getByLabelText(/^Password$/i)
+    confirmPasswordInput = screen.getByLabelText(/^Confirm Password$/i)
+    submitButton = screen.getByRole('button', { name: /^Create Account$/i })
+  })
+
+  it('renders fields and submit button', async () => {
+    expect(emailInput).toBeInTheDocument()
+    expect(passwordInput).toBeInTheDocument()
+    expect(confirmPasswordInput).toBeInTheDocument()
+    expect(submitButton).toBeInTheDocument()
+  })
+
+  it('shows validation messages when submitting empty form', async () => {
+    await userEvent.click(submitButton)
+
+    expect(screen.getByText(FORM_MESSAGES.emailEmpty)).toBeInTheDocument()
+    expect(screen.getByText(FORM_MESSAGES.passwordEmpty)).toBeInTheDocument()
+    expect(
+      screen.getByText(FORM_MESSAGES.confirmPasswordEmpty)
+    ).toBeInTheDocument()
+  })
+
+  it('shows a mismatch error when passwords do not match', async () => {
+    await userEvent.fill(emailInput, 'a@b.com')
+    await userEvent.fill(passwordInput, '1234567')
+    await userEvent.fill(confirmPasswordInput, '7654321')
+
+    await userEvent.click(submitButton)
+    expect(screen.getByText(FORM_MESSAGES.passwordMismatch)).toBeInTheDocument()
+  })
+
+  it('disables submit while submitting and re-enables after timeout', async () => {
+    vi.useFakeTimers()
+
+    await userEvent.fill(emailInput, 'a@b.com')
+    await userEvent.fill(passwordInput, '1234567')
+    await userEvent.fill(confirmPasswordInput, '1234567')
+
+    await userEvent.click(submitButton)
+    expect(submitButton).toBeDisabled()
+
+    await vi.advanceTimersByTimeAsync(2000)
+    await vi.waitFor(() => expect(submitButton).toBeEnabled())
+    expect(toastPromise).toHaveBeenCalledOnce()
+  })
+})

+ 16 - 10
src/features/auth/sign-up/components/sign-up-form.tsx

@@ -2,8 +2,10 @@ import { useState } from 'react'
 import { z } from 'zod'
 import { useForm } from 'react-hook-form'
 import { zodResolver } from '@hookform/resolvers/zod'
+import { Loader2, UserPlus } from 'lucide-react'
+import { toast } from 'sonner'
 import { IconFacebook, IconGithub } from '@/assets/brand-icons'
-import { cn } from '@/lib/utils'
+import { sleep, cn } from '@/lib/utils'
 import { Button } from '@/components/ui/button'
 import {
   Form,
@@ -20,13 +22,13 @@ const formSchema = z
   .object({
     email: z.email({
       error: (iss) =>
-        iss.input === '' ? 'Please enter your email' : undefined,
+        iss.input === '' ? 'Please enter your email.' : undefined,
     }),
     password: z
       .string()
-      .min(1, 'Please enter your password')
-      .min(7, 'Password must be at least 7 characters long'),
-    confirmPassword: z.string().min(1, 'Please confirm your password'),
+      .min(1, 'Please enter your password.')
+      .min(7, 'Password must be at least 7 characters long.'),
+    confirmPassword: z.string().min(1, 'Please confirm your password.'),
   })
   .refine((data) => data.password === data.confirmPassword, {
     message: "Passwords don't match.",
@@ -50,12 +52,15 @@ export function SignUpForm({
 
   function onSubmit(data: z.infer<typeof formSchema>) {
     setIsLoading(true)
-    // eslint-disable-next-line no-console
-    console.log(data)
 
-    setTimeout(() => {
-      setIsLoading(false)
-    }, 3000)
+    toast.promise(sleep(2000), {
+      loading: 'Creating account...',
+      success: () => {
+        setIsLoading(false)
+        return `Account created for ${data.email}.`
+      },
+      error: 'Error',
+    })
   }
 
   return (
@@ -105,6 +110,7 @@ export function SignUpForm({
           )}
         />
         <Button className='mt-2' disabled={isLoading}>
+          {isLoading ? <Loader2 className='animate-spin' /> : <UserPlus />}
           Create Account
         </Button>