diff --git a/package.json b/package.json
index 20ece96..68a4d1d 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "test": "vitest"
},
"keywords": [],
"author": "",
diff --git a/tests/contact.test.tsx b/tests/contact.test.tsx
new file mode 100644
index 0000000..cf3b522
--- /dev/null
+++ b/tests/contact.test.tsx
@@ -0,0 +1,97 @@
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
+import Contact from '../app/kontakt/page'
+
+// Mock fetch
+const fetchMock = vi.fn()
+global.fetch = fetchMock
+
+// Mock alert
+const alertMock = vi.fn()
+global.alert = alertMock
+
+describe('Contact Page', () => {
+ beforeEach(() => {
+ fetchMock.mockClear()
+ alertMock.mockClear()
+ })
+
+ it('renders the contact form correctly', () => {
+ render()
+
+ expect(screen.getByLabelText(/Name \*/i)).toBeInTheDocument()
+ expect(screen.getByLabelText(/Firma/i)).toBeInTheDocument()
+ expect(screen.getByLabelText(/E-Mail \*/i)).toBeInTheDocument()
+ expect(screen.getByLabelText(/Nachricht \*/i)).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: /Nachricht senden/i })).toBeInTheDocument()
+ })
+
+ it('submits the form successfully', async () => {
+ fetchMock.mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({ success: true }),
+ })
+
+ render()
+
+ fireEvent.change(screen.getByLabelText(/Name \*/i), { target: { value: 'John Doe' } })
+ fireEvent.change(screen.getByLabelText(/Firma/i), { target: { value: 'Acme Corp' } })
+ fireEvent.change(screen.getByLabelText(/E-Mail \*/i), { target: { value: 'john@example.com' } })
+ fireEvent.change(screen.getByLabelText(/Nachricht \*/i), { target: { value: 'This is a test message that is long enough.' } })
+
+ fireEvent.click(screen.getByRole('button', { name: /Nachricht senden/i }))
+
+ await waitFor(() => {
+ expect(fetchMock).toHaveBeenCalledTimes(1)
+ expect(fetchMock).toHaveBeenCalledWith('/api/contact', expect.objectContaining({
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ name: 'John Doe',
+ company: 'Acme Corp',
+ email: 'john@example.com',
+ message: 'This is a test message that is long enough.',
+ website: ''
+ }),
+ }))
+ })
+
+ expect(screen.getByText(/Nachricht gesendet/i)).toBeInTheDocument()
+ expect(screen.getByText(/Vielen Dank für Ihre Anfrage/i)).toBeInTheDocument()
+ })
+
+ it('handles submission errors', async () => {
+ fetchMock.mockResolvedValueOnce({
+ ok: false,
+ json: async () => ({ error: 'Server error' }),
+ })
+
+ render()
+
+ fireEvent.change(screen.getByLabelText(/Name \*/i), { target: { value: 'John Doe' } })
+ fireEvent.change(screen.getByLabelText(/E-Mail \*/i), { target: { value: 'john@example.com' } })
+ fireEvent.change(screen.getByLabelText(/Nachricht \*/i), { target: { value: 'This is a test message that is long enough.' } })
+
+ fireEvent.click(screen.getByRole('button', { name: /Nachricht senden/i }))
+
+ await waitFor(() => {
+ expect(alertMock).toHaveBeenCalledWith('Fehler: Server error')
+ })
+ })
+
+ it('handles network errors', async () => {
+ fetchMock.mockRejectedValueOnce(new Error('Network error'))
+
+ render()
+
+ fireEvent.change(screen.getByLabelText(/Name \*/i), { target: { value: 'John Doe' } })
+ fireEvent.change(screen.getByLabelText(/E-Mail \*/i), { target: { value: 'john@example.com' } })
+ fireEvent.change(screen.getByLabelText(/Nachricht \*/i), { target: { value: 'This is a test message that is long enough.' } })
+
+ fireEvent.click(screen.getByRole('button', { name: /Nachricht senden/i }))
+
+ await waitFor(() => {
+ expect(alertMock).toHaveBeenCalledWith('Es gab einen Fehler beim Senden Ihrer Nachricht.')
+ })
+ })
+})