Kuma/tests/onboarding/onboarding-steps.test.ts

155 lines
5.2 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
ONBOARDING_STEPS,
ONBOARDING_STEP_IDS,
WIZARD_STEP_IDS,
LINK_STEP_IDS,
countCompletedSteps,
firstOpenWizardStep,
getStepStates,
isOnboardingComplete,
isOnboardingStepId,
type OnboardingContext,
} from "@/src/modules/onboarding/lib/onboarding-steps";
/** Frisch registriertes Studio: nichts eingerichtet. */
const emptyContext: OnboardingContext = {
stripeAccountStatus: null,
slug: null,
isPublic: false,
hasBranding: false,
hasCancellationPolicy: false,
roomCount: 0,
employeeCount: 0,
courseCount: 0,
};
/** Alle Pflichtschritte erledigt, optionale Schritte offen. */
const requiredDoneContext: OnboardingContext = {
...emptyContext,
stripeAccountStatus: "active",
slug: "mein-studio",
isPublic: true,
roomCount: 1,
courseCount: 1,
};
describe("isOnboardingStepId", () => {
it("erkennt gültige Registry-Keys", () => {
expect(isOnboardingStepId("payments")).toBe(true);
expect(isOnboardingStepId("course")).toBe(true);
});
it("lehnt unbekannte Keys ab", () => {
expect(isOnboardingStepId("zahlungen")).toBe(false);
expect(isOnboardingStepId("")).toBe(false);
expect(isOnboardingStepId("__proto__")).toBe(false);
});
});
describe("Registry-Aufteilung", () => {
it("teilt alle Schritte überschneidungsfrei in Wizard- und Link-Schritte", () => {
expect([...WIZARD_STEP_IDS, ...LINK_STEP_IDS].sort()).toEqual([...ONBOARDING_STEP_IDS].sort());
expect(WIZARD_STEP_IDS.filter((id) => (LINK_STEP_IDS as string[]).includes(id))).toEqual([]);
});
it("hat mindestens einen Pflichtschritt", () => {
expect(ONBOARDING_STEP_IDS.some((id) => ONBOARDING_STEPS[id].required)).toBe(true);
});
it("verlinkt jeden Schritt auf einen app-internen Pfad", () => {
for (const id of ONBOARDING_STEP_IDS) {
expect(ONBOARDING_STEPS[id].href.startsWith("/")).toBe(true);
expect(ONBOARDING_STEPS[id].href.startsWith("//")).toBe(false);
}
});
});
describe("getStepStates", () => {
it("meldet im leeren Studio jeden Schritt als offen", () => {
const states = getStepStates(emptyContext);
expect(states).toHaveLength(ONBOARDING_STEP_IDS.length);
expect(states.every((s) => !s.done)).toBe(true);
});
it("behält die Registry-Reihenfolge bei", () => {
expect(getStepStates(emptyContext).map((s) => s.id)).toEqual(ONBOARDING_STEP_IDS);
});
});
describe("Ableitung der einzelnen Schritte", () => {
it("hakt Zahlungen nur bei aktivem Stripe-Konto ab", () => {
expect(
ONBOARDING_STEPS.payments.isDone({ ...emptyContext, stripeAccountStatus: "active" })
).toBe(true);
// "pending" und "restricted" reichen NICHT: in beiden Fällen kann das Studio
// kein Geld empfangen, der Schritt muss also offen bleiben.
expect(
ONBOARDING_STEPS.payments.isDone({ ...emptyContext, stripeAccountStatus: "pending" })
).toBe(false);
expect(
ONBOARDING_STEPS.payments.isDone({ ...emptyContext, stripeAccountStatus: "restricted" })
).toBe(false);
});
it("verlangt für die öffentliche Seite Slug UND Sichtbarkeit", () => {
expect(
ONBOARDING_STEPS.publicPage.isDone({ ...emptyContext, isPublic: true, slug: null })
).toBe(false);
expect(
ONBOARDING_STEPS.publicPage.isDone({ ...emptyContext, isPublic: false, slug: "s" })
).toBe(false);
expect(ONBOARDING_STEPS.publicPage.isDone({ ...emptyContext, isPublic: true, slug: "s" })).toBe(
true
);
});
});
describe("isOnboardingComplete", () => {
it("ist im leeren Studio nicht erfüllt", () => {
expect(isOnboardingComplete(emptyContext)).toBe(false);
});
it("ist erfüllt, sobald alle Pflichtschritte stehen - auch ohne die optionalen", () => {
expect(isOnboardingComplete(requiredDoneContext)).toBe(true);
expect(requiredDoneContext.hasBranding).toBe(false);
expect(requiredDoneContext.employeeCount).toBe(0);
});
it("fällt zurück, wenn ein Pflichtschritt wegbricht (z.B. Stripe wird restricted)", () => {
expect(
isOnboardingComplete({ ...requiredDoneContext, stripeAccountStatus: "restricted" })
).toBe(false);
});
});
describe("countCompletedSteps", () => {
it("zählt über ALLE Schritte, nicht nur die Pflichtschritte", () => {
expect(countCompletedSteps(emptyContext)).toBe(0);
expect(countCompletedSteps(requiredDoneContext)).toBe(
ONBOARDING_STEP_IDS.filter((id) => ONBOARDING_STEPS[id].required).length
);
expect(
countCompletedSteps({ ...requiredDoneContext, hasBranding: true, employeeCount: 2 })
).toBe(ONBOARDING_STEP_IDS.length - 1); // nur die Storno-Policy fehlt noch
});
});
describe("firstOpenWizardStep", () => {
it("liefert den ersten offenen Wizard-Schritt in Registry-Reihenfolge", () => {
expect(firstOpenWizardStep(emptyContext)).toBe(WIZARD_STEP_IDS[0]);
});
it("überspringt bereits erledigte Schritte", () => {
expect(firstOpenWizardStep({ ...emptyContext, hasBranding: true })).toBe(WIZARD_STEP_IDS[1]);
});
it("liefert null, wenn alle Wizard-Schritte erledigt sind", () => {
const allWizardDone: OnboardingContext = {
...requiredDoneContext,
hasBranding: true,
hasCancellationPolicy: true,
};
expect(firstOpenWizardStep(allWizardDone)).toBeNull();
});
});