This commit is contained in:
@@ -12,6 +12,7 @@ const FormContact = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
const [successMessage, setSuccessMessage] = useState('');
|
||||||
|
|
||||||
// EventHandlers =======
|
// EventHandlers =======
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
@@ -22,8 +23,10 @@ const FormContact = (props) => {
|
|||||||
const errors = validate(formData);
|
const errors = validate(formData);
|
||||||
|
|
||||||
if (Object.keys(errors).length === 0) {
|
if (Object.keys(errors).length === 0) {
|
||||||
console.log('Form submitted ....');
|
console.log('Form Data', formData);
|
||||||
// TODO: send over AJAX/Fetch
|
// TODO: send over AJAX/Fetch
|
||||||
|
setSuccessMessage('Form submitted');
|
||||||
|
|
||||||
form.reset();
|
form.reset();
|
||||||
setFormData({
|
setFormData({
|
||||||
firstname: '', //
|
firstname: '', //
|
||||||
@@ -35,6 +38,7 @@ const FormContact = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
console.warn('Form submit error');
|
console.warn('Form submit error');
|
||||||
setErrors(errors);
|
setErrors(errors);
|
||||||
|
setSuccessMessage('');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,6 +155,12 @@ const FormContact = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{successMessage && (
|
||||||
|
<div data-testid="success-message" role="note" className="text-success alert alert-success">
|
||||||
|
{successMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,7 @@ const validate = (formData) => {
|
|||||||
if (validator.isEmpty(formData.firstname.trim())) {
|
if (validator.isEmpty(formData.firstname.trim())) {
|
||||||
errors.firstname = 'Vorname ist erforderlich.';
|
errors.firstname = 'Vorname ist erforderlich.';
|
||||||
} else if (!validator.isLength(formData.firstname.trim(), { min: 2, max: 60 })) {
|
} 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
|
// Lastname
|
||||||
|
|||||||
6
05_react/unterricht/tag45/meeting.md
Normal file
6
05_react/unterricht/tag45/meeting.md
Normal 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
|
||||||
Reference in New Issue
Block a user