dify/web/app/signup/components/input-mail.spec.tsx
Coding On Star 2c4d8ef098
fix(auth): preserve redirect URL across auth flows (#38905)
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
2026-07-14 07:47:34 +00:00

175 lines
5.4 KiB
TypeScript

import type { MockedFunction } from 'vitest'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
import { useLocale } from '@/context/i18n'
import { useSearchParams } from '@/next/navigation'
import { useSendMail } from '@/service/use-common'
import Form from './input-mail'
const mockSubmitMail = vi.fn()
const mockOnSuccess = vi.fn()
vi.mock('@/next/link', () => ({
default: ({
children,
href,
className,
target,
rel,
}: {
children: React.ReactNode
href: string
className?: string
target?: string
rel?: string
}) => (
<a href={href} className={className} target={target} rel={rel}>
{children}
</a>
),
}))
vi.mock('@/context/i18n', () => ({
useLocale: vi.fn(),
}))
vi.mock('@/next/navigation', () => ({
useSearchParams: vi.fn(),
}))
vi.mock('@/service/use-common', () => ({
useSendMail: vi.fn(),
}))
type UseSendMailResult = ReturnType<typeof useSendMail>
const mockUseLocale = useLocale as unknown as MockedFunction<typeof useLocale>
const mockUseSearchParams = useSearchParams as unknown as MockedFunction<typeof useSearchParams>
const mockUseSendMail = useSendMail as unknown as MockedFunction<typeof useSendMail>
const renderForm = ({
brandingEnabled = false,
isPending = false,
}: {
brandingEnabled?: boolean
isPending?: boolean
} = {}) => {
mockUseLocale.mockReturnValue('en-US')
mockUseSendMail.mockReturnValue({
mutateAsync: mockSubmitMail,
isPending,
} as unknown as UseSendMailResult)
return renderWithSystemFeatures(<Form onSuccess={mockOnSuccess} />, {
systemFeatures: { branding: { enabled: brandingEnabled } },
})
}
describe('InputMail Form', () => {
beforeEach(() => {
vi.clearAllMocks()
mockUseSearchParams.mockReturnValue(
new URLSearchParams() as unknown as ReturnType<typeof useSearchParams>,
)
mockSubmitMail.mockResolvedValue({ result: 'success', data: 'token' })
})
// Rendering baseline UI elements.
describe('Rendering', () => {
it('should render email input and submit button', () => {
renderForm()
expect(screen.getByLabelText('login.email')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'login.signup.verifyMail' })).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'login.signup.signIn' })).toBeInTheDocument()
})
})
// Prop-driven branding content visibility.
describe('Props', () => {
it('should show terms links when branding is disabled', () => {
renderForm({ brandingEnabled: false })
expect(screen.getByRole('link', { name: 'login.tos' })).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'login.pp' })).toBeInTheDocument()
})
it('should hide terms links when branding is enabled', () => {
renderForm({ brandingEnabled: true })
expect(screen.queryByRole('link', { name: 'login.tos' })).not.toBeInTheDocument()
expect(screen.queryByRole('link', { name: 'login.pp' })).not.toBeInTheDocument()
})
})
// Submission flow and mutation integration.
describe('User Interactions', () => {
it('should submit email and call onSuccess when mutation succeeds', async () => {
renderForm()
const input = screen.getByLabelText('login.email')
const button = screen.getByRole('button', { name: 'login.signup.verifyMail' })
fireEvent.change(input, { target: { value: 'test@example.com' } })
fireEvent.click(button)
expect(mockSubmitMail).toHaveBeenCalledWith({
email: 'test@example.com',
language: 'en-US',
})
await waitFor(() => {
expect(mockOnSuccess).toHaveBeenCalledWith('test@example.com', 'token')
})
})
})
// Navigation between registration and login keeps the original destination.
describe('Registration Navigation', () => {
it('should preserve the current query when navigating to sign in', () => {
mockUseSearchParams.mockReturnValue(
new URLSearchParams(
'redirect_url=%2Fapps%3Ftag%3Dworkflow&source=pricing',
) as unknown as ReturnType<typeof useSearchParams>,
)
renderForm()
expect(screen.getByRole('link', { name: 'login.signup.signIn' })).toHaveAttribute(
'href',
'/signin?redirect_url=%2Fapps%3Ftag%3Dworkflow&source=pricing',
)
})
})
// Validation and failure paths.
describe('Edge Cases', () => {
it('should block submission when email is invalid', () => {
const { container } = renderForm()
const form = container.querySelector('form')
const input = screen.getByLabelText('login.email')
fireEvent.change(input, { target: { value: 'invalid-email' } })
expect(form).not.toBeNull()
fireEvent.submit(form as HTMLFormElement)
expect(mockSubmitMail).not.toHaveBeenCalled()
expect(mockOnSuccess).not.toHaveBeenCalled()
})
it('should not call onSuccess when mutation does not succeed', async () => {
mockSubmitMail.mockResolvedValue({ result: 'failed', data: 'token' })
renderForm()
const input = screen.getByLabelText('login.email')
const button = screen.getByRole('button', { name: 'login.signup.verifyMail' })
fireEvent.change(input, { target: { value: 'test@example.com' } })
fireEvent.click(button)
await waitFor(() => {
expect(mockSubmitMail).toHaveBeenCalled()
})
expect(mockOnSuccess).not.toHaveBeenCalled()
})
})
})