|
@@ -0,0 +1,158 @@
|
|
|
|
|
+/* @vitest-environment node */
|
|
|
|
|
+
|
|
|
|
|
+import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
|
+
|
|
|
|
|
+vi.mock("sonner", () => {
|
|
|
|
|
+ const api = {
|
|
|
|
|
+ success: vi.fn(() => "toast-success-id"),
|
|
|
|
|
+ error: vi.fn(() => "toast-error-id"),
|
|
|
|
|
+ info: vi.fn(() => "toast-info-id"),
|
|
|
|
|
+ warning: vi.fn(() => "toast-warning-id"),
|
|
|
|
|
+ loading: vi.fn(() => "toast-loading-id"),
|
|
|
|
|
+ dismiss: vi.fn(() => undefined),
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return { toast: api };
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+import { toast } from "sonner";
|
|
|
|
|
+import { ApiClientError } from "@/lib/frontend/apiClient";
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ notifySuccess,
|
|
|
|
|
+ notifyError,
|
|
|
|
|
+ notifyInfo,
|
|
|
|
|
+ notifyWarning,
|
|
|
|
|
+ notifyLoading,
|
|
|
|
|
+ dismissToast,
|
|
|
|
|
+ mapApiErrorToToast,
|
|
|
|
|
+ notifyApiError,
|
|
|
|
|
+} from "./toast.js";
|
|
|
|
|
+
|
|
|
|
|
+describe("lib/frontend/ui/toast", () => {
|
|
|
|
|
+ beforeEach(() => {
|
|
|
|
|
+ vi.clearAllMocks();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("notifySuccess calls toast.success with title + description", () => {
|
|
|
|
|
+ const id = notifySuccess({
|
|
|
|
|
+ title: "Gespeichert",
|
|
|
|
|
+ description: "Änderungen wurden übernommen.",
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ expect(id).toBe("toast-success-id");
|
|
|
|
|
+ expect(toast.success).toHaveBeenCalledWith("Gespeichert", {
|
|
|
|
|
+ description: "Änderungen wurden übernommen.",
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("notifyError calls toast.error", () => {
|
|
|
|
|
+ const id = notifyError({ title: "Fehler", description: "Oh nein." });
|
|
|
|
|
+
|
|
|
|
|
+ expect(id).toBe("toast-error-id");
|
|
|
|
|
+ expect(toast.error).toHaveBeenCalledWith("Fehler", {
|
|
|
|
|
+ description: "Oh nein.",
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("notifyInfo / notifyWarning / notifyLoading forward correctly", () => {
|
|
|
|
|
+ notifyInfo({ title: "Info", description: "Hinweis" });
|
|
|
|
|
+ expect(toast.info).toHaveBeenCalledWith("Info", { description: "Hinweis" });
|
|
|
|
|
+
|
|
|
|
|
+ notifyWarning({ title: "Warnung" });
|
|
|
|
|
+ expect(toast.warning).toHaveBeenCalledWith("Warnung", {});
|
|
|
|
|
+
|
|
|
|
|
+ notifyLoading({ title: "Lädt…" });
|
|
|
|
|
+ expect(toast.loading).toHaveBeenCalledWith("Lädt…", {});
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("dismissToast forwards to toast.dismiss", () => {
|
|
|
|
|
+ dismissToast("x");
|
|
|
|
|
+ expect(toast.dismiss).toHaveBeenCalledWith("x");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("mapApiErrorToToast maps network errors", () => {
|
|
|
|
|
+ const err = new ApiClientError({
|
|
|
|
|
+ status: 0,
|
|
|
|
|
+ code: "CLIENT_NETWORK_ERROR",
|
|
|
|
|
+ message: "Network error",
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ expect(mapApiErrorToToast(err)).toEqual({
|
|
|
|
|
+ title: "Netzwerkfehler",
|
|
|
|
|
+ description:
|
|
|
|
|
+ "Bitte prüfen Sie Ihre Verbindung und versuchen Sie es erneut.",
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("mapApiErrorToToast maps unauthenticated", () => {
|
|
|
|
|
+ const err = new ApiClientError({
|
|
|
|
|
+ status: 401,
|
|
|
|
|
+ code: "AUTH_UNAUTHENTICATED",
|
|
|
|
|
+ message: "Unauthorized",
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ expect(mapApiErrorToToast(err)).toEqual({
|
|
|
|
|
+ title: "Sitzung abgelaufen",
|
|
|
|
|
+ description: "Bitte melden Sie sich erneut an.",
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("mapApiErrorToToast maps validation errors generically", () => {
|
|
|
|
|
+ const err = new ApiClientError({
|
|
|
|
|
+ status: 400,
|
|
|
|
|
+ code: "VALIDATION_WEAK_PASSWORD",
|
|
|
|
|
+ message: "Weak password",
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ expect(mapApiErrorToToast(err)).toEqual({
|
|
|
|
|
+ title: "Ungültige Eingabe",
|
|
|
|
|
+ description:
|
|
|
|
|
+ "Bitte prüfen Sie Ihre Eingaben und versuchen Sie es erneut.",
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("mapApiErrorToToast supports overrides by error code", () => {
|
|
|
|
|
+ const err = new ApiClientError({
|
|
|
|
|
+ status: 401,
|
|
|
|
|
+ code: "AUTH_INVALID_CREDENTIALS",
|
|
|
|
|
+ message: "Invalid credentials",
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const mapped = mapApiErrorToToast(err, {
|
|
|
|
|
+ overrides: {
|
|
|
|
|
+ AUTH_INVALID_CREDENTIALS: {
|
|
|
|
|
+ title: "Aktuelles Passwort ist falsch.",
|
|
|
|
|
+ description: null,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ expect(mapped).toEqual({
|
|
|
|
|
+ title: "Aktuelles Passwort ist falsch.",
|
|
|
|
|
+ description: null,
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("notifyApiError uses toast.error with mapped copy", () => {
|
|
|
|
|
+ const err = new ApiClientError({
|
|
|
|
|
+ status: 0,
|
|
|
|
|
+ code: "CLIENT_NETWORK_ERROR",
|
|
|
|
|
+ message: "Network error",
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const id = notifyApiError(err);
|
|
|
|
|
+
|
|
|
|
|
+ expect(id).toBe("toast-error-id");
|
|
|
|
|
+ expect(toast.error).toHaveBeenCalledWith("Netzwerkfehler", {
|
|
|
|
|
+ description:
|
|
|
|
|
+ "Bitte prüfen Sie Ihre Verbindung und versuchen Sie es erneut.",
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("notifySuccess returns null and does not toast when title is missing", () => {
|
|
|
|
|
+ const id = notifySuccess({ title: " " });
|
|
|
|
|
+ expect(id).toBe(null);
|
|
|
|
|
+ expect(toast.success).not.toHaveBeenCalled();
|
|
|
|
|
+ });
|
|
|
|
|
+});
|