mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 21:41:17 +00:00
added unit test framework
This commit is contained in:
parent
d3791060d2
commit
3c9fc19a3c
22 changed files with 2206 additions and 15 deletions
163
tests/hooks/useAuth.test.ts
Normal file
163
tests/hooks/useAuth.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
127
tests/hooks/useImageUpload.test.ts
Normal file
127
tests/hooks/useImageUpload.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
71
tests/hooks/useIsMobile.test.ts
Normal file
71
tests/hooks/useIsMobile.test.ts
Normal 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));
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue