This commit is contained in:
Philippe Torrel
2026-08-14 13:33:31 +02:00
parent c89478bacd
commit c56fd39868
4 changed files with 420 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ const FormContact = (props) => {
});
const [errors, setErrors] = useState({});
const [successMessage, setSuccessMessage] = useState('');
// EventHandlers =======
const handleSubmit = (e) => {
@@ -22,8 +23,10 @@ const FormContact = (props) => {
const errors = validate(formData);
if (Object.keys(errors).length === 0) {
console.log('Form submitted ....');
console.log('Form Data', formData);
// TODO: send over AJAX/Fetch
setSuccessMessage('Form submitted');
form.reset();
setFormData({
firstname: '', //
@@ -35,6 +38,7 @@ const FormContact = (props) => {
} else {
console.warn('Form submit error');
setErrors(errors);
setSuccessMessage('');
}
};
@@ -151,6 +155,12 @@ const FormContact = (props) => {
</div>
</div>
</div>
{successMessage && (
<div data-testid="success-message" role="note" className="text-success alert alert-success">
{successMessage}
</div>
)}
</form>
);
};

View File

@@ -0,0 +1,402 @@
import { render, screen } from '@testing-library/react';
import FormContact from '../components/forms/FormContact';
import userEvent from '@testing-library/user-event';
import { expect, vi, afterAll, describe, test } from 'vitest';
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); //
afterAll(() => {
consoleSpy.mockRestore();
});
// Test Suite
describe('FormContact', () => {
// Test Case
test('FormContact submits data object correctly', async () => {
render(<FormContact />);
const user = userEvent.setup();
// find the input
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
const emailInput = screen.getByLabelText('E-Mail');
const messageInput = screen.getByLabelText('Message');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(firstNameInput, 'John');
await user.type(lastNameInput, 'Wick');
await user.type(emailInput, 'john.wick@continentalhotel.com');
await user.type(messageInput, 'Hello, this is a test message');
await user.click(submitButton);
// const succesMessageDiv = screen.getByTestId('success-message'); // data-testid Attribut muss gesetzt werden
// const succesMessageDiv = screen.getByRole('note', { name: /Form submitted/i }); // data-testid Attribut muss gesetzt werden
// const succesMessageDiv = screen.getByText(/Form submitted/i); // data-testid Attribut muss gesetzt werden
// expect(succesMessageDiv).toHaveTextContent(/Form submitted/i);
expect(consoleSpy).toHaveBeenCalledWith('Form Data', {
firstname: 'John',
lastname: 'Wick',
email: 'john.wick@continentalhotel.com',
message: 'Hello, this is a test message',
});
});
// ===== FIRSTNAME FIELD TESTS =====
describe('Firstname field error messages', () => {
test('shows error message when firstname is empty on submit', async () => {
render(<FormContact />);
const user = userEvent.setup();
const lastNameInput = screen.getByLabelText('Lastname');
const emailInput = screen.getByLabelText('E-Mail');
const messageInput = screen.getByLabelText('Message');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(lastNameInput, 'Wick');
await user.type(emailInput, 'john@example.com');
await user.type(messageInput, 'This is a valid test message');
await user.click(submitButton);
expect(screen.getByText('Vorname ist erforderlich.')).toBeInTheDocument();
});
test('shows error message when firstname is too short (less than 2 chars)', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(firstNameInput, 'J');
await user.click(submitButton);
expect(
screen.getByText('Vorname muss mindestens 2 Zeichen und maximal 60 Zeichen lang sein.'),
).toBeInTheDocument();
});
test('shows error message when firstname is too long (more than 60 chars)', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
const longName = 'a'.repeat(61);
await user.type(firstNameInput, longName);
await user.click(submitButton);
expect(
screen.getByText('Vorname muss mindestens 2 Zeichen und maximal 60 Zeichen lang sein.'),
).toBeInTheDocument();
});
test('no error message when firstname is valid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
const emailInput = screen.getByLabelText('E-Mail');
const messageInput = screen.getByLabelText('Message');
await user.type(firstNameInput, 'John');
await user.type(lastNameInput, 'Wick');
await user.type(emailInput, 'john@example.com');
await user.type(messageInput, 'This is a valid test message');
// Trigger onBlur to validate
await user.tab();
expect(firstNameInput).not.toHaveClass('is-invalid');
});
});
// ===== LASTNAME FIELD TESTS =====
describe('Lastname field error messages', () => {
test('shows error message when lastname is empty on submit', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const emailInput = screen.getByLabelText('E-Mail');
const messageInput = screen.getByLabelText('Message');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(firstNameInput, 'John');
await user.type(emailInput, 'john@example.com');
await user.type(messageInput, 'This is a valid test message');
await user.click(submitButton);
expect(screen.getByText('Nachname ist erforderlich.')).toBeInTheDocument();
});
test('shows error message when lastname is too short (less than 2 chars)', async () => {
render(<FormContact />);
const user = userEvent.setup();
const lastNameInput = screen.getByLabelText('Lastname');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(lastNameInput, 'W');
await user.click(submitButton);
expect(
screen.getByText('Nachname muss mindestens 2 Zeichen und maximal 60 Zeichen lang sein.'),
).toBeInTheDocument();
});
test('shows error message when lastname is too long (more than 60 chars)', async () => {
render(<FormContact />);
const user = userEvent.setup();
const lastNameInput = screen.getByLabelText('Lastname');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
const longName = 'b'.repeat(61);
await user.type(lastNameInput, longName);
await user.click(submitButton);
expect(
screen.getByText('Nachname muss mindestens 2 Zeichen und maximal 60 Zeichen lang sein.'),
).toBeInTheDocument();
});
test('no error message when lastname is valid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
const emailInput = screen.getByLabelText('E-Mail');
const messageInput = screen.getByLabelText('Message');
await user.type(firstNameInput, 'John');
await user.type(lastNameInput, 'Wick');
await user.type(emailInput, 'john@example.com');
await user.type(messageInput, 'This is a valid test message');
// Trigger onBlur to validate
await user.tab();
expect(lastNameInput).not.toHaveClass('is-invalid');
});
});
// ===== EMAIL FIELD TESTS =====
describe('Email field error messages', () => {
test('shows error message when email is empty on submit', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
const messageInput = screen.getByLabelText('Message');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(firstNameInput, 'John');
await user.type(lastNameInput, 'Wick');
await user.type(messageInput, 'This is a valid test message');
await user.click(submitButton);
expect(screen.getByText('E-Mail ist erforderlich.')).toBeInTheDocument();
});
test('shows error message when email format is invalid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const emailInput = screen.getByLabelText('E-Mail');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(emailInput, 'invalidemail');
await user.click(submitButton);
expect(screen.getByText('Bitte gib eine gültige E-Mail-Adresse ein.')).toBeInTheDocument();
});
test('shows error message when email is missing @ symbol', async () => {
render(<FormContact />);
const user = userEvent.setup();
const emailInput = screen.getByLabelText('E-Mail');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(emailInput, 'johnexample.com');
await user.click(submitButton);
expect(screen.getByText('Bitte gib eine gültige E-Mail-Adresse ein.')).toBeInTheDocument();
});
test('shows error message when email is missing domain', async () => {
render(<FormContact />);
const user = userEvent.setup();
const emailInput = screen.getByLabelText('E-Mail');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(emailInput, 'john@');
await user.click(submitButton);
expect(screen.getByText('Bitte gib eine gültige E-Mail-Adresse ein.')).toBeInTheDocument();
});
test('no error message when email is valid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
const emailInput = screen.getByLabelText('E-Mail');
const messageInput = screen.getByLabelText('Message');
await user.type(firstNameInput, 'John');
await user.type(lastNameInput, 'Wick');
await user.type(emailInput, 'john@example.com');
await user.type(messageInput, 'This is a valid test message');
// Trigger onBlur to validate
await user.tab();
expect(emailInput).not.toHaveClass('is-invalid');
});
});
// ===== MESSAGE FIELD TESTS =====
describe('Message field error messages', () => {
test('shows error message when message is empty on submit', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
const emailInput = screen.getByLabelText('E-Mail');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(firstNameInput, 'John');
await user.type(lastNameInput, 'Wick');
await user.type(emailInput, 'john@example.com');
await user.click(submitButton);
expect(screen.getByText('Nachricht ist erforderlich.')).toBeInTheDocument();
});
test('shows error message when message is too short (less than 10 chars)', async () => {
render(<FormContact />);
const user = userEvent.setup();
const messageInput = screen.getByLabelText('Message');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(messageInput, 'short msg');
await user.click(submitButton);
expect(
screen.getByText('Die Nachricht muss mindestens 10 und maximal 1000 Zeichen lang sein.'),
).toBeInTheDocument();
});
test('shows error message when message is too long (more than 1000 chars)', async () => {
render(<FormContact />);
const user = userEvent.setup();
const messageInput = screen.getByLabelText('Message');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
const longMessage = 'a'.repeat(1001);
await user.type(messageInput, longMessage);
await user.click(submitButton);
expect(
screen.getByText('Die Nachricht muss mindestens 10 und maximal 1000 Zeichen lang sein.'),
).toBeInTheDocument();
});
test('no error message when message is valid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
const emailInput = screen.getByLabelText('E-Mail');
const messageInput = screen.getByLabelText('Message');
await user.type(firstNameInput, 'John');
await user.type(lastNameInput, 'Wick');
await user.type(emailInput, 'john@example.com');
await user.type(messageInput, 'This is a valid test message');
// Trigger onBlur to validate
await user.tab();
expect(messageInput).not.toHaveClass('is-invalid');
});
});
// ===== ONBLUR VALIDATION TESTS =====
describe('Form field onBlur validation', () => {
test('shows error on firstname field after blur when invalid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
await user.type(firstNameInput, 'J');
await user.click(screen.getByLabelText('Lastname')); // Trigger blur on firstname
expect(firstNameInput).toHaveClass('is-invalid');
});
test('clears error on firstname field after blur when valid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const firstNameInput = screen.getByLabelText('Firstname');
const lastNameInput = screen.getByLabelText('Lastname');
await user.type(firstNameInput, 'J');
await user.click(lastNameInput); // Trigger blur with invalid input
expect(firstNameInput).toHaveClass('is-invalid');
// Clear and type valid input
await user.clear(firstNameInput);
await user.type(firstNameInput, 'John');
await user.click(screen.getByLabelText('E-Mail')); // Trigger blur with valid input
expect(firstNameInput).not.toHaveClass('is-invalid');
});
test('shows error on email field after blur when invalid', async () => {
render(<FormContact />);
const user = userEvent.setup();
const emailInput = screen.getByLabelText('E-Mail');
await user.type(emailInput, 'invalidemail');
await user.click(screen.getByLabelText('Message')); // Trigger blur on email
expect(emailInput).toHaveClass('is-invalid');
});
test('shows error on message field after blur when too short', async () => {
render(<FormContact />);
const user = userEvent.setup();
const messageInput = screen.getByLabelText('Message');
const submitButton = screen.getByRole('button', { name: 'Send Message' });
await user.type(messageInput, 'short');
await user.click(submitButton); // Trigger blur on message
expect(messageInput).toHaveClass('is-invalid');
});
});
});

View File

@@ -7,7 +7,7 @@ const validate = (formData) => {
if (validator.isEmpty(formData.firstname.trim())) {
errors.firstname = 'Vorname ist erforderlich.';
} else if (!validator.isLength(formData.firstname.trim(), { min: 2, max: 60 })) {
errors.firstname = 'Vorname muss mindestens 2 Zeichen und maximal 60 Zeichen lang sein.';
errors.firstname = 'Vorname muss mindestens 2 Zeichen und maximal 60 Zeichen lang sein.';
}
// Lastname

View File

@@ -0,0 +1,6 @@
# Meeting Fr. 14.08.2026
- [ ] 14:10 - 14:20 Uhr Ersin
- [ ] 14:20 - 14:30 Uhr Kahleel
- [ ] 14:30 - 14:40 Uhr Adel
- [ ] 14:40 - 14:50 Uhr Andreas