From 8f3f56a12c09142a00569377e526aefabca48105 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Wed, 11 Feb 2026 11:58:29 +0100 Subject: [PATCH] fix: harmonize Zod versions to v3.24.1, restore build, and update tests --- components/ContactContent.tsx | 1 + package.json | 2 +- pnpm-lock.yaml | 12 +-- tests/contact.test.tsx | 129 ++++++++++++++++++++++++++ {tests_bak => tests}/home.test.tsx | 20 +++- tests_bak/setup.ts => tests/setup.tsx | 4 +- tests_bak/contact.test.tsx | 126 ------------------------- vitest.config.mts | 1 + 8 files changed, 156 insertions(+), 139 deletions(-) create mode 100644 tests/contact.test.tsx rename {tests_bak => tests}/home.test.tsx (64%) rename tests_bak/setup.ts => tests/setup.tsx (65%) delete mode 100644 tests_bak/contact.test.tsx diff --git a/components/ContactContent.tsx b/components/ContactContent.tsx index 41496cc..f1207c5 100644 --- a/components/ContactContent.tsx +++ b/components/ContactContent.tsx @@ -197,6 +197,7 @@ export default function Contact() { ) : (
diff --git a/package.json b/package.json index 8be26b5..0de4e56 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "pino": "^10.3.0", "react": "^19.2.4", "react-dom": "^19.2.4", - "zod": "^4.3.6" + "zod": "^3.24.1" }, "devDependencies": { "@commitlint/cli": "^20.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc74694..1ada8b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,8 +42,8 @@ importers: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) zod: - specifier: ^4.3.6 - version: 4.3.6 + specifier: ^3.24.1 + version: 3.24.1 devDependencies: '@commitlint/cli': specifier: ^20.4.0 @@ -4312,8 +4312,8 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -4964,7 +4964,7 @@ snapshots: '@directus/sdk': 21.0.0 next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-intl: 4.8.2(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3) - zod: 3.25.76 + zod: 3.24.1 transitivePeerDependencies: - '@babel/core' - '@opentelemetry/api' @@ -8863,6 +8863,6 @@ snapshots: dependencies: zod: 4.3.6 - zod@3.25.76: {} + zod@3.24.1: {} zod@4.3.6: {} diff --git a/tests/contact.test.tsx b/tests/contact.test.tsx new file mode 100644 index 0000000..bf85bde --- /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.findByText(/Anfrage erfolgreich übermittelt/i), + ).toBeInTheDocument(); + expect( + await screen.findByText(/Ihr Anliegen wurde erfasst/i), + ).toBeInTheDocument(); + }); + + 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_bak/home.test.tsx b/tests/home.test.tsx similarity index 64% rename from tests_bak/home.test.tsx rename to tests/home.test.tsx index eeea4ef..ddfa458 100644 --- a/tests_bak/home.test.tsx +++ b/tests/home.test.tsx @@ -1,24 +1,34 @@ import { render, screen } from "@testing-library/react"; import { describe, it, expect } from "vitest"; -import Home from "../app/page"; +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", () => { - render(); + renderHome(); expect( - screen.getByText(/Spezialisierter Partner für Energiekabelprojekte/i), + screen.getByRole("heading", { name: /Spezialisierter Partner/i }), ).toBeInTheDocument(); }); it("contains the CTA button", () => { - render(); + renderHome(); const ctaButton = screen.getByRole("link", { name: /Projekt anfragen/i }); expect(ctaButton).toBeInTheDocument(); expect(ctaButton).toHaveAttribute("href", "/kontakt"); }); it("renders the portfolio section", () => { - render(); + renderHome(); expect(screen.getByText(/Unsere Leistungen/i)).toBeInTheDocument(); // Use getAllByText because it appears in both hero description and card title const elements = screen.getAllByText(/Technische Beratung/i); diff --git a/tests_bak/setup.ts b/tests/setup.tsx similarity index 65% rename from tests_bak/setup.ts rename to tests/setup.tsx index 4a6d2e3..67c2039 100644 --- a/tests_bak/setup.ts +++ b/tests/setup.tsx @@ -1,4 +1,4 @@ -import "@testing-library/jest-dom"; +import "@testing-library/jest-dom/vitest"; import { vi } from "vitest"; // Mock next/navigation @@ -8,5 +8,7 @@ vi.mock("next/navigation", () => ({ push: vi.fn(), replace: vi.fn(), prefetch: vi.fn(), + back: vi.fn(), }), + useSearchParams: () => new URLSearchParams(), })); diff --git a/tests_bak/contact.test.tsx b/tests_bak/contact.test.tsx deleted file mode 100644 index 7b5ea77..0000000 --- a/tests_bak/contact.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -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.", - ); - }); - }); -}); diff --git a/vitest.config.mts b/vitest.config.mts index 768290a..54f4d9d 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -6,6 +6,7 @@ export default defineConfig({ test: { environment: 'happy-dom', globals: true, + setupFiles: ['./tests/setup.tsx'], alias: { 'next/server': 'next/dist/server/web/exports/next-server.js', },