added unit test framework

This commit is contained in:
Rajpreet0 2026-04-27 20:28:43 +02:00
parent d3791060d2
commit 3c9fc19a3c
22 changed files with 2206 additions and 15 deletions

163
tests/hooks/useAuth.test.ts Normal file
View file

@ -0,0 +1,163 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: mockRouterPush }),
}));
vi.mock("@/src/store/authStore", () => ({
useAuthStore: (selector: (s: { setUser: typeof mockSetUser }) => typeof mockSetUser) =>
selector({ setUser: mockSetUser }),
}));
vi.mock("@/src/lib/supabase/client", () => ({
createClient: () => ({ auth: { signInWithPassword: mockSupabaseSignIn } }),
}));
vi.mock("sonner", () => ({
toast: { success: vi.fn(), error: vi.fn() },
}));
const mockRouterPush = vi.fn();
const mockSetUser = vi.fn();
const mockSupabaseSignIn = vi.fn();
import { useAuth } from "@/src/hooks/useAuth";
beforeEach(() => {
mockRouterPush.mockReset();
mockSetUser.mockReset();
mockSupabaseSignIn.mockReset();
vi.stubGlobal("fetch", vi.fn());
});
describe("useAuth", () => {
describe("loginUser", () => {
it("gibt Zod-Validierungsfehler bei ungültiger E-Mail zurück", async () => {
const { result } = renderHook(() => useAuth());
let success: boolean;
await act(async () => {
success = await result.current.loginUser("keine-email", "passwort123", false);
});
expect(success!).toBe(false);
expect(result.current.errors.email).toBeDefined();
expect(mockSupabaseSignIn).not.toHaveBeenCalled();
});
it("setzt Fehler bei falschen Credentials", async () => {
mockSupabaseSignIn.mockResolvedValue({
data: { user: null, session: null },
error: { message: "Invalid credentials" },
});
const { result } = renderHook(() => useAuth());
let success: boolean;
await act(async () => {
success = await result.current.loginUser("test@example.com", "Falsch!123", false);
});
expect(success!).toBe(false);
expect(result.current.errors.email).toBeDefined();
expect(mockRouterPush).not.toHaveBeenCalled();
});
it("ruft setUser und router.push bei erfolgreichem Login auf", async () => {
const fakeUser = { id: "user-1", email: "test@example.com" };
mockSupabaseSignIn.mockResolvedValue({
data: { user: fakeUser, session: {} },
error: null,
});
const { result } = renderHook(() => useAuth());
let success: boolean;
await act(async () => {
success = await result.current.loginUser("test@example.com", "Richtig!123", false);
});
expect(success!).toBe(true);
expect(mockSetUser).toHaveBeenCalledWith(fakeUser);
expect(mockRouterPush).toHaveBeenCalledWith("/dashboard");
});
it("setzt loading auf false nach Abschluss", async () => {
mockSupabaseSignIn.mockResolvedValue({
data: { user: { id: "1" }, session: {} },
error: null,
});
const { result } = renderHook(() => useAuth());
await act(async () => {
await result.current.loginUser("test@example.com", "Richtig!123", false);
});
expect(result.current.loading).toBe(false);
});
});
describe("registerUser", () => {
const validFormData = {
fullName: "Max Mustermann",
email: "max@example.com",
password: "Sicher!123",
passwordConfirm: "Sicher!123",
birthday: "1990-05-15",
plz: "10115",
place: "Berlin",
street: "Musterstr. 1",
gender: "Men" as const,
};
it("gibt Zod-Fehler bei fehlenden Pflichtfeldern zurück", async () => {
const { result } = renderHook(() => useAuth());
let success: boolean;
await act(async () => {
success = await result.current.registerUser({
...validFormData,
email: "keine-email",
});
});
expect(success!).toBe(false);
expect(result.current.errors.email).toBeDefined();
});
it("gibt true bei erfolgreicher Registrierung zurück", async () => {
vi.mocked(fetch).mockResolvedValue({
ok: true,
json: async () => ({ user: { id: "new-1" } }),
} as Response);
const { result } = renderHook(() => useAuth());
let success: boolean;
await act(async () => {
success = await result.current.registerUser(validFormData);
});
expect(success!).toBe(true);
});
it("setzt Fehler bei API-Fehlerantwort", async () => {
vi.mocked(fetch).mockResolvedValue({
ok: false,
json: async () => ({ error: "Dieser Nutzer existiert bereits." }),
} as Response);
const { result } = renderHook(() => useAuth());
let success: boolean;
await act(async () => {
success = await result.current.registerUser(validFormData);
});
expect(success!).toBe(false);
expect(result.current.errors.email).toContain("existiert bereits");
});
});
});

View file

@ -0,0 +1,127 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useImageUpload } from "@/src/hooks/useImageUpload";
vi.mock("@/src/lib/supabase/storage", () => ({
uploadFile: vi.fn(),
deleteFile: vi.fn(),
generateFilePath: vi.fn().mockReturnValue("users/user-1/1234567890.jpg"),
}));
import { uploadFile, deleteFile } from "@/src/lib/supabase/storage";
const defaultOptions = { bucket: "avatars" as const };
function makeFile(name = "photo.jpg", type = "image/jpeg", sizeBytes = 1024): File {
return new File([new Uint8Array(sizeBytes)], name, { type });
}
beforeEach(() => {
vi.mocked(uploadFile).mockReset();
vi.mocked(deleteFile).mockReset();
});
describe("useImageUpload", () => {
it("gibt initialen State zurück", () => {
const { result } = renderHook(() => useImageUpload(defaultOptions));
expect(result.current.isUploading).toBe(false);
expect(result.current.error).toBeNull();
expect(result.current.progress).toBe(0);
});
it("lehnt ungültigen MIME-Type ab und setzt error", async () => {
const { result } = renderHook(() => useImageUpload(defaultOptions));
const pdfFile = new File(["pdf"], "doc.pdf", { type: "application/pdf" });
await act(async () => {
await expect(result.current.upload(pdfFile, "user-1")).rejects.toThrow();
});
expect(result.current.error).toContain("Ungültiger Dateityp");
expect(result.current.isUploading).toBe(false);
});
it("lehnt Datei über maximaler Größe ab und setzt error", async () => {
const { result } = renderHook(() => useImageUpload({ ...defaultOptions, maxSizeMB: 1 }));
const bigFile = makeFile("big.jpg", "image/jpeg", 2 * 1024 * 1024);
await act(async () => {
await expect(result.current.upload(bigFile, "user-1")).rejects.toThrow();
});
expect(result.current.error).toContain("groß");
expect(result.current.isUploading).toBe(false);
});
it("setzt isUploading auf false nach erfolgreichem Upload", async () => {
vi.mocked(uploadFile).mockResolvedValue({
path: "users/user-1/photo.jpg",
publicUrl: "https://example.com/photo.jpg",
});
const { result } = renderHook(() => useImageUpload(defaultOptions));
const file = makeFile();
let url: string;
await act(async () => {
url = await result.current.upload(file, "user-1");
});
expect(url!).toBe("https://example.com/photo.jpg");
expect(result.current.isUploading).toBe(false);
expect(result.current.error).toBeNull();
expect(result.current.progress).toBe(100);
});
it("setzt error bei fehlgeschlagenem Upload", async () => {
vi.mocked(uploadFile).mockRejectedValue(new Error("Netzwerkfehler"));
const { result } = renderHook(() => useImageUpload(defaultOptions));
await act(async () => {
await expect(result.current.upload(makeFile(), "user-1")).rejects.toThrow("Netzwerkfehler");
});
expect(result.current.error).toBe("Netzwerkfehler");
expect(result.current.isUploading).toBe(false);
});
it("ruft deleteFile mit korrektem Pfad auf", async () => {
vi.mocked(deleteFile).mockResolvedValue(undefined);
const { result } = renderHook(() => useImageUpload(defaultOptions));
await act(async () => {
await result.current.remove("users/user-1/old-photo.jpg");
});
expect(deleteFile).toHaveBeenCalledWith("avatars", "users/user-1/old-photo.jpg");
expect(result.current.error).toBeNull();
});
it("setzt error wenn delete fehlschlägt", async () => {
vi.mocked(deleteFile).mockRejectedValue(new Error("Löschen fehlgeschlagen"));
const { result } = renderHook(() => useImageUpload(defaultOptions));
await act(async () => {
await expect(result.current.remove("some/path.jpg")).rejects.toThrow();
});
expect(result.current.error).toBe("Löschen fehlgeschlagen");
});
it("akzeptiert konfigurierbare erlaubte MIME-Types", async () => {
const { result } = renderHook(() =>
useImageUpload({ ...defaultOptions, allowedTypes: ["image/png"] })
);
const jpegFile = makeFile("photo.jpg", "image/jpeg");
await act(async () => {
await expect(result.current.upload(jpegFile, "user-1")).rejects.toThrow();
});
expect(result.current.error).toContain("Ungültiger Dateityp");
});
});

View file

@ -0,0 +1,71 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useIsMobile } from "@/src/hooks/use-mobile";
const mockAddEventListener = vi.fn();
const mockRemoveEventListener = vi.fn();
function setWindowWidth(width: number) {
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: width,
});
Object.defineProperty(window, "matchMedia", {
writable: true,
configurable: true,
value: (query: string) => ({
matches: width < 768,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: mockAddEventListener,
removeEventListener: mockRemoveEventListener,
dispatchEvent: vi.fn(),
}),
});
}
beforeEach(() => {
mockAddEventListener.mockClear();
mockRemoveEventListener.mockClear();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("useIsMobile", () => {
it("gibt true zurück bei schmaler Viewport-Breite (<768px)", () => {
setWindowWidth(500);
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(true);
});
it("gibt false zurück bei breiter Viewport-Breite (>=768px)", () => {
setWindowWidth(1024);
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(false);
});
it("gibt false zurück genau bei 768px", () => {
setWindowWidth(768);
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(false);
});
it("registriert einen Event-Listener auf matchMedia", () => {
setWindowWidth(500);
renderHook(() => useIsMobile());
expect(mockAddEventListener).toHaveBeenCalledWith("change", expect.any(Function));
});
it("entfernt den Event-Listener beim Unmount", () => {
setWindowWidth(500);
const { unmount } = renderHook(() => useIsMobile());
unmount();
expect(mockRemoveEventListener).toHaveBeenCalledWith("change", expect.any(Function));
});
});