|
|
@@ -0,0 +1,129 @@
|
|
|
+import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
+import { render, type RenderResult } from 'vitest-browser-react'
|
|
|
+import { type Locator, userEvent } from 'vitest/browser'
|
|
|
+import { UserAuthForm } from './user-auth-form'
|
|
|
+
|
|
|
+const FORM_MESSAGES = {
|
|
|
+ emailEmpty: 'Please enter your email.',
|
|
|
+ passwordEmpty: 'Please enter your password.',
|
|
|
+ passwordShort: 'Password must be at least 7 characters long.',
|
|
|
+} as const
|
|
|
+
|
|
|
+const navigate = vi.fn()
|
|
|
+const setUserMock = vi.fn()
|
|
|
+const setAccessTokenMock = vi.fn()
|
|
|
+
|
|
|
+vi.mock('@/stores/auth-store', () => ({
|
|
|
+ useAuthStore: () => ({
|
|
|
+ auth: {
|
|
|
+ setUser: setUserMock,
|
|
|
+ setAccessToken: setAccessTokenMock,
|
|
|
+ },
|
|
|
+ }),
|
|
|
+}))
|
|
|
+
|
|
|
+vi.mock('@tanstack/react-router', async (importOriginal) => {
|
|
|
+ const actual = await importOriginal<typeof import('@tanstack/react-router')>()
|
|
|
+ return {
|
|
|
+ ...actual,
|
|
|
+ useNavigate: () => navigate,
|
|
|
+ Link: ({
|
|
|
+ children,
|
|
|
+ to,
|
|
|
+ className,
|
|
|
+ ...rest
|
|
|
+ }: {
|
|
|
+ children?: React.ReactNode
|
|
|
+ to: string
|
|
|
+ className?: string
|
|
|
+ }) => (
|
|
|
+ <a href={to} className={className} {...rest}>
|
|
|
+ {children}
|
|
|
+ </a>
|
|
|
+ ),
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+vi.mock('@/lib/utils', async (orig) => ({
|
|
|
+ ...(await orig()),
|
|
|
+ sleep: vi.fn(() => Promise.resolve()),
|
|
|
+}))
|
|
|
+
|
|
|
+describe('UserAuthForm', () => {
|
|
|
+ describe('Rendering without redirectTo', () => {
|
|
|
+ let screen: RenderResult
|
|
|
+ let emailInput: Locator
|
|
|
+ let passwordInput: Locator
|
|
|
+ let signInButton: Locator
|
|
|
+ let forgotPasswordLink: Locator
|
|
|
+
|
|
|
+ beforeEach(async () => {
|
|
|
+ vi.clearAllMocks()
|
|
|
+ screen = await render(<UserAuthForm />)
|
|
|
+ emailInput = screen.getByRole('textbox', { name: /^Email$/i })
|
|
|
+ passwordInput = screen.getByLabelText(/^Password$/i)
|
|
|
+ signInButton = screen.getByRole('button', { name: /^Sign in$/i })
|
|
|
+ forgotPasswordLink = screen.getByText(/^Forgot password\?$/i)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('renders fields, submit button, and forgot password link', async () => {
|
|
|
+ expect(emailInput).toBeInTheDocument()
|
|
|
+ expect(passwordInput).toBeInTheDocument()
|
|
|
+ expect(signInButton).toBeInTheDocument()
|
|
|
+ expect(forgotPasswordLink).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows validation messages when submitting empty form', async () => {
|
|
|
+ await userEvent.click(signInButton)
|
|
|
+
|
|
|
+ expect(screen.getByText(FORM_MESSAGES.emailEmpty)).toBeInTheDocument()
|
|
|
+ expect(screen.getByText(FORM_MESSAGES.passwordEmpty)).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('authenticates and navigates to default route on success', async () => {
|
|
|
+ await userEvent.fill(emailInput, 'a@b.com')
|
|
|
+ await userEvent.fill(passwordInput, '1234567')
|
|
|
+
|
|
|
+ await userEvent.click(signInButton)
|
|
|
+
|
|
|
+ await vi.waitFor(() => expect(setUserMock).toHaveBeenCalledOnce())
|
|
|
+ expect(setUserMock).toHaveBeenCalledWith(
|
|
|
+ expect.objectContaining({
|
|
|
+ email: 'a@b.com',
|
|
|
+ accountNo: expect.any(String),
|
|
|
+ role: expect.any(Array),
|
|
|
+ exp: expect.any(Number),
|
|
|
+ })
|
|
|
+ )
|
|
|
+ expect(setAccessTokenMock).toHaveBeenCalledOnce()
|
|
|
+ expect(setAccessTokenMock).toHaveBeenCalledWith('mock-access-token')
|
|
|
+
|
|
|
+ await vi.waitFor(() =>
|
|
|
+ expect(navigate).toHaveBeenCalledWith({ to: '/', replace: true })
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('navigates to redirectTo when provided', async () => {
|
|
|
+ vi.clearAllMocks()
|
|
|
+
|
|
|
+ const { getByRole, getByLabelText } = await render(
|
|
|
+ <UserAuthForm redirectTo='/settings' />
|
|
|
+ )
|
|
|
+
|
|
|
+ await userEvent.fill(getByRole('textbox', { name: /Email/i }), 'a@b.com')
|
|
|
+ await userEvent.fill(getByLabelText('Password'), '1234567')
|
|
|
+
|
|
|
+ await userEvent.click(getByRole('button', { name: /Sign in/i }))
|
|
|
+
|
|
|
+ await vi.waitFor(() => expect(setUserMock).toHaveBeenCalledOnce())
|
|
|
+ expect(setAccessTokenMock).toHaveBeenCalledOnce()
|
|
|
+
|
|
|
+ await vi.waitFor(() =>
|
|
|
+ expect(navigate).toHaveBeenCalledWith({
|
|
|
+ to: '/settings',
|
|
|
+ replace: true,
|
|
|
+ })
|
|
|
+ )
|
|
|
+ })
|
|
|
+})
|