This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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())) {
|
||||
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
|
||||
|
||||
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