Kuma/tests/marketing/marketing-view.test.tsx
2026-10-02 17:44:59 +02:00

122 lines
4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { act, fireEvent, render, screen } from "@testing-library/react";
import type { ReactNode } from "react";
vi.mock("@/src/hooks/use-mobile", () => ({ useIsMobile: () => true }));
vi.mock("sonner", () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
vi.mock("@/src/modules/marketing/actions/editor-actions", () => ({
getMarketingEditor: vi.fn(),
setTriggerActive: vi.fn(),
getSendHistory: vi.fn(),
getChangeHistory: vi.fn(),
}));
// Radix-Select lässt sich in jsdom kaum bedienen - ein natives <select> reicht hier.
vi.mock("@/src/components/ui/select", () => ({
Select: ({
value,
onValueChange,
children,
}: {
value: string;
onValueChange: (v: string) => void;
children: ReactNode;
}) => (
<select aria-label="Business" value={value} onChange={(e) => onValueChange(e.target.value)}>
{children}
</select>
),
SelectTrigger: () => null,
SelectValue: () => null,
SelectContent: ({ children }: { children: ReactNode }) => <>{children}</>,
SelectItem: ({ value, children }: { value: string; children: ReactNode }) => (
<option value={value}>{children}</option>
),
}));
import { toast } from "sonner";
import MarketingView from "@/src/modules/marketing/ui/views/marketing-view";
import {
getMarketingEditor,
setTriggerActive,
type EditorData,
} from "@/src/modules/marketing/actions/editor-actions";
import { EMAIL_TEMPLATES } from "@/src/modules/marketing/lib/node-config";
function editorData(prefix: string): EditorData {
return {
consentingCustomers: 3,
nodes: [
{
id: `${prefix}-trigger`,
kind: "birthday",
config: {},
position: { x: 0, y: 0 },
emailNodeId: `${prefix}-mail`,
isActive: false,
sent: 0,
redeemed: 0,
activatedAt: null,
},
{
id: `${prefix}-mail`,
kind: "email",
config: { ...EMAIL_TEMPLATES.birthday, name: `Mail ${prefix}` },
position: { x: 300, y: 0 },
emailNodeId: null,
isActive: false,
sent: 0,
redeemed: 0,
activatedAt: null,
},
],
};
}
const businesses = [
{ id: "biz-a", name: "Studio A" },
{ id: "biz-b", name: "Studio B" },
];
beforeEach(() => {
vi.clearAllMocks();
});
describe("MarketingView - Studio wechseln", () => {
it("wechselt Studio und Daten erst zusammen, wenn das neue Studio geladen ist", async () => {
let resolve!: (value: Awaited<ReturnType<typeof getMarketingEditor>>) => void;
vi.mocked(getMarketingEditor).mockReturnValue(new Promise((r) => (resolve = r)));
vi.mocked(setTriggerActive).mockResolvedValue({ success: true, data: editorData("b") });
render(<MarketingView businesses={businesses} initialData={editorData("a")} />);
expect(screen.getByText(/Mail a/)).toBeInTheDocument();
fireEvent.change(screen.getByLabelText("Business"), { target: { value: "biz-b" } });
// Solange B lädt, bleibt alles bei A - auch die Auswahl.
expect(screen.getByText(/Mail a/)).toBeInTheDocument();
expect(screen.getByLabelText("Business")).toHaveValue("biz-a");
await act(async () => resolve({ success: true, data: editorData("b") }));
expect(screen.getByText(/Mail b/)).toBeInTheDocument();
expect(screen.getByLabelText("Business")).toHaveValue("biz-b");
await act(async () => {
fireEvent.click(screen.getByRole("switch"));
});
expect(setTriggerActive).toHaveBeenCalledWith("biz-b", "b-trigger", true);
});
it("bleibt beim alten Studio, wenn das neue nicht lädt", async () => {
vi.mocked(getMarketingEditor).mockResolvedValue({ success: false, error: "Kaputt" });
render(<MarketingView businesses={businesses} initialData={editorData("a")} />);
await act(async () => {
fireEvent.change(screen.getByLabelText("Business"), { target: { value: "biz-b" } });
});
expect(toast.error).toHaveBeenCalledWith("Kaputt");
expect(screen.getByLabelText("Business")).toHaveValue("biz-a");
expect(screen.getByText(/Mail a/)).toBeInTheDocument();
});
});