Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions components/EmailLoginForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { EmailLoginForm } from './EmailLoginForm';

// Mock the toast hook
vi.mock('./ui/use-toast', () => ({
useToast: () => ({
toast: vi.fn()
})
}));

describe('EmailLoginForm', () => {
const mockOnLogin = vi.fn();

beforeEach(() => {
mockOnLogin.mockClear();
});

it('renders email input and login button', () => {
render(<EmailLoginForm onLogin={mockOnLogin} />);

expect(screen.getByTestId('email-input')).toBeInTheDocument();
expect(screen.getByTestId('login-button')).toBeInTheDocument();
});

it('validates email input', async () => {
render(<EmailLoginForm onLogin={mockOnLogin} />);

const emailInput = screen.getByTestId('email-input');
const loginButton = screen.getByTestId('login-button');

// Invalid email
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
expect(loginButton).toBeDisabled();

// Valid email
fireEvent.change(emailInput, { target: { value: '[email protected]' } });
expect(loginButton).not.toBeDisabled();
});

it('calls onLogin with email when form is submitted', async () => {
mockOnLogin.mockResolvedValue(undefined);

render(<EmailLoginForm onLogin={mockOnLogin} />);

const emailInput = screen.getByTestId('email-input');
const loginButton = screen.getByTestId('login-button');

fireEvent.change(emailInput, { target: { value: '[email protected]' } });
fireEvent.click(loginButton);

await waitFor(() => {
expect(mockOnLogin).toHaveBeenCalledWith('[email protected]');
});
});

it('handles login failure', async () => {
mockOnLogin.mockRejectedValue(new Error('Login failed'));

render(<EmailLoginForm onLogin={mockOnLogin} />);

const emailInput = screen.getByTestId('email-input');
const loginButton = screen.getByTestId('login-button');

fireEvent.change(emailInput, { target: { value: '[email protected]' } });
fireEvent.click(loginButton);

await waitFor(() => {
expect(mockOnLogin).toHaveBeenCalled();
});
});
});
74 changes: 74 additions & 0 deletions components/EmailLoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { Input } from './ui/input';
import { Button } from './ui/button';
import { Label } from './ui/label';
import { useToast } from './ui/use-toast';

interface EmailLoginFormProps {
onLogin: (email: string) => Promise<void>;
}

export const EmailLoginForm: React.FC<EmailLoginFormProps> = ({ onLogin }) => {
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { toast } = useToast();

const validateEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (!validateEmail(email)) {
toast({
title: 'Invalid Email',
description: 'Please enter a valid email address.',
variant: 'destructive'
});
return;
}

setIsLoading(true);
try {
await onLogin(email);
toast({
title: 'Login Successful',
description: 'You have been logged in successfully.'
});
} catch (error) {
toast({
title: 'Login Failed',
description: 'Unable to log in. Please try again.',
variant: 'destructive'
});
} finally {
setIsLoading(false);
}
};

return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
data-testid="email-input"
/>
</div>
<Button
type="submit"
disabled={!validateEmail(email) || isLoading}
data-testid="login-button"
>
{isLoading ? 'Logging in...' : 'Login'}
</Button>
</form>
);
};
Loading