From 817ee05710d9db39ccbd31ba3b3739dfc6ba17f9 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Wed, 11 Feb 2026 19:49:52 +0100 Subject: [PATCH] test: restore and fix tests broken by lazy-loading --- tests/contact.test.tsx | 129 +++++++++++++++++++++++++++++++++++++++++ tests/home.test.tsx | 37 ++++++++++++ tests/setup.tsx | 56 ++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 tests/contact.test.tsx create mode 100644 tests/home.test.tsx create mode 100644 tests/setup.tsx diff --git a/tests/contact.test.tsx b/tests/contact.test.tsx new file mode 100644 index 0000000..0b80622 --- /dev/null +++ b/tests/contact.test.tsx @@ -0,0 +1,129 @@ +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NextIntlClientProvider } from "next-intl"; +import messages from "../messages/de.json"; + +// Mocks MUST be defined before component import to ensure they are picked up +vi.mock("../components/Reveal", () => ({ + Reveal: ({ children }: any) => <>{children}, + Stagger: ({ children }: any) => <>{children}, +})); + +// Better FormData mock for happy-dom +global.FormData = class MockFormData { + private data = new Map(); + constructor(form?: HTMLFormElement) { + if (form) { + const elements = form.elements as any; + for (let i = 0; i < elements.length; i++) { + const item = elements.item(i); + if (item.name && item.value) { + this.data.set(item.name, item.value); + } + } + } + } + append(key: string, value: any) { + this.data.set(key, value); + } + get(key: string) { + return this.data.get(key); + } + entries() { + return Array.from(this.data.entries())[Symbol.iterator](); + } +} as any; + +// Mock alert +const alertMock = vi.fn(); +global.alert = alertMock; + +// Import component AFTER mocks +import Contact from "../components/ContactContent"; + +// Mock fetch +const fetchMock = vi.fn(); +global.fetch = fetchMock; + +const renderContact = () => { + return render( + + + , + ); +}; + +describe("Contact Page", () => { + beforeEach(() => { + vi.clearAllMocks(); + fetchMock.mockReset(); + fetchMock.mockResolvedValue({ + ok: true, + json: async () => ({ success: true }), + }); + }); + + it("renders the contact form correctly", () => { + renderContact(); + + 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(); + }); + + it("submits the form successfully", async () => { + renderContact(); + + 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." }, + }); + + const form = screen.getByRole("form"); + fireEvent.submit(form); + + await waitFor( + () => { + expect(fetchMock).toHaveBeenCalled(); + }, + { timeout: 2000 }, + ); + + expect( + (await screen.findAllByText(/Anfrage erfolgreich übermittelt/i)).length, + ).toBeGreaterThanOrEqual(1); + expect( + (await screen.findAllByText(/Ihr Anliegen wurde erfasst/i)).length, + ).toBeGreaterThanOrEqual(1); + }); + + it("handles submission errors", async () => { + fetchMock.mockResolvedValueOnce({ + ok: false, + json: async () => ({ error: "Server error" }), + }); + + renderContact(); + + 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." }, + }); + + const form = screen.getByRole("form"); + fireEvent.submit(form); + + expect(await screen.findByText(/Server error/i)).toBeInTheDocument(); + }); +}); diff --git a/tests/home.test.tsx b/tests/home.test.tsx new file mode 100644 index 0000000..212b687 --- /dev/null +++ b/tests/home.test.tsx @@ -0,0 +1,37 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import Home from "../components/HomeContent"; +import { NextIntlClientProvider } from "next-intl"; +import messages from "../messages/de.json"; + +const renderHome = () => { + return render( + + + , + ); +}; + +describe("Home Page", () => { + it("renders the hero section with correct title", () => { + renderHome(); + expect( + screen.getByRole("heading", { name: /Spezialisierter Partner/i }), + ).toBeInTheDocument(); + }); + + it("contains the CTA button", () => { + renderHome(); + const ctaButton = screen.getByRole("link", { name: /Projekt anfragen/i }); + expect(ctaButton).toBeInTheDocument(); + expect(ctaButton).toHaveAttribute("href", "/kontakt"); + }); + + it("renders the portfolio section", async () => { + renderHome(); + expect(await screen.findByText(/Unsere Leistungen/i)).toBeInTheDocument(); + // Use getAllByText because it appears in both hero description and card title + const elements = await screen.findAllByText(/Technische Beratung/i); + expect(elements.length).toBeGreaterThan(0); + }); +}); diff --git a/tests/setup.tsx b/tests/setup.tsx new file mode 100644 index 0000000..3f5541b --- /dev/null +++ b/tests/setup.tsx @@ -0,0 +1,56 @@ +import "@testing-library/jest-dom/vitest"; +import React from "react"; +import { vi } from "vitest"; + +// Mock next/navigation +vi.mock("next/navigation", () => ({ + usePathname: () => "/", + useRouter: () => ({ + push: vi.fn(), + replace: vi.fn(), + prefetch: vi.fn(), + back: vi.fn(), + }), + useSearchParams: () => new URLSearchParams(), +})); + +// Mock next-intl to avoid transitive next/server issues +vi.mock("next-intl/middleware", () => ({ + default: vi.fn(() => (req: any) => req), +})); + +vi.mock("next-intl/server", () => ({ + getRequestConfig: vi.fn(), +})); + +// Mock next/server +vi.mock("next/server", () => ({ + NextResponse: { + json: vi.fn(), + next: vi.fn(), + redirect: vi.fn(), + }, +})); + +// Mock next/dynamic to be synchronous in tests +vi.mock("next/dynamic", () => ({ + default: vi.fn((loader) => { + return (props: any) => { + const [Component, setComponent] = React.useState(null); + React.useEffect(() => { + loader().then((mod: any) => { + setComponent( + () => + mod.default || + mod.PortfolioSection || + mod.ExpertiseSection || + mod.TechnicalSpecsSection || + mod.CTASection || + mod, + ); + }); + }, []); + return Component ? : null; + }; + }), +}));