202 lines
5.6 KiB
TypeScript
202 lines
5.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
getLeadBlacklistStatusLabel,
|
|
getLeadContactStatusLabel,
|
|
getLeadDuplicateStatusLabel,
|
|
getLeadPriorityLabel,
|
|
leadBlacklistStatusOptions,
|
|
leadContactStatusOptions,
|
|
leadDuplicateStatusOptions,
|
|
leadPriorityOptions,
|
|
dashboardKpis,
|
|
dashboardNavigation,
|
|
groupLeadFunnelCards,
|
|
leadFunnelStages,
|
|
pipelineStages,
|
|
toLeadFunnelCard,
|
|
reviewQueue,
|
|
} from "../lib/dashboard-model";
|
|
|
|
type NavigationItem = { label: string; href: string };
|
|
type PipelineStage = { title: string; count: number };
|
|
type ReviewQueueItem = { title: string };
|
|
|
|
test("dashboardNavigation contains the expected sidebar routes in order", () => {
|
|
assert.deepEqual(
|
|
dashboardNavigation.map((item: NavigationItem) => [item.label, item.href]),
|
|
[
|
|
["Übersicht", "/dashboard"],
|
|
["Kampagnen", "/dashboard/campaigns"],
|
|
["Leads", "/dashboard/leads"],
|
|
["Audits", "/dashboard/audits"],
|
|
["Review", "/dashboard/outreach"],
|
|
["Analytics", "/dashboard/analytics"],
|
|
["Sperrliste", "/dashboard/blacklist"],
|
|
["Einstellungen", "/dashboard/settings"],
|
|
],
|
|
);
|
|
});
|
|
|
|
test("pipelineStages keep the first-screen workflow focused on pipeline overview", () => {
|
|
assert.deepEqual(
|
|
pipelineStages.map((stage: PipelineStage) => stage.title),
|
|
["Kampagnen", "Lead-Recherche", "Audit-Freigabe", "Outreach"],
|
|
);
|
|
assert.equal(
|
|
pipelineStages.every((stage: PipelineStage) => stage.count >= 0),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("leadFunnelStages expose the agreed German funnel columns", () => {
|
|
assert.deepEqual(
|
|
leadFunnelStages.map((stage) => stage.title),
|
|
[
|
|
"Kontakt fehlt",
|
|
"Audit bereit",
|
|
"Freigabe offen",
|
|
"Kontaktiert",
|
|
"Follow-up",
|
|
"Zurückgestellt",
|
|
],
|
|
);
|
|
});
|
|
|
|
test("toLeadFunnelCard exposes scan data and derives missing contact next action", () => {
|
|
const card = toLeadFunnelCard({
|
|
id: "lead-1",
|
|
companyName: "Malerbetrieb Klein",
|
|
niche: "Maler",
|
|
city: "Freiburg",
|
|
postalCode: "79098",
|
|
priority: "high",
|
|
contactStatus: "missing_contact",
|
|
blacklistStatus: "clear",
|
|
});
|
|
|
|
assert.equal(card.stageId, "missing_contact");
|
|
assert.equal(card.company, "Malerbetrieb Klein");
|
|
assert.equal(card.niche, "Maler");
|
|
assert.equal(card.location, "79098 Freiburg");
|
|
assert.equal(card.priorityLabel, "Hoch");
|
|
assert.equal(card.contactStatusLabel, "Kontakt fehlt");
|
|
assert.equal(card.nextAction, "Kontaktquelle recherchieren");
|
|
});
|
|
|
|
test("groupLeadFunnelCards derives review, follow-up, and deferred columns without schema migration", () => {
|
|
const groups = groupLeadFunnelCards([
|
|
{
|
|
id: "lead-review",
|
|
companyName: "Physio am Park",
|
|
city: "Freiburg",
|
|
priority: "medium",
|
|
contactStatus: "outreach_ready",
|
|
blacklistStatus: "clear",
|
|
outreach: {
|
|
approvalStatus: "draft",
|
|
sendStatus: "not_sent",
|
|
responseStatus: "none",
|
|
salesStatus: "follow_up_planned",
|
|
},
|
|
},
|
|
{
|
|
id: "lead-follow-up",
|
|
companyName: "Tischlerei Weber",
|
|
city: "Emmendingen",
|
|
priority: "medium",
|
|
contactStatus: "contacted",
|
|
blacklistStatus: "clear",
|
|
outreach: {
|
|
approvalStatus: "approved",
|
|
sendStatus: "sent",
|
|
responseStatus: "follow_up_needed",
|
|
salesStatus: "follow_up_planned",
|
|
},
|
|
},
|
|
{
|
|
id: "lead-replied",
|
|
companyName: "Salon Licht",
|
|
city: "Freiburg",
|
|
priority: "low",
|
|
contactStatus: "replied",
|
|
blacklistStatus: "clear",
|
|
},
|
|
{
|
|
id: "lead-defer",
|
|
companyName: "Cafe Morgen",
|
|
city: "Basel",
|
|
priority: "defer",
|
|
contactStatus: "new",
|
|
blacklistStatus: "clear",
|
|
},
|
|
]);
|
|
|
|
assert.deepEqual(
|
|
groups.map((group) => [group.stage.id, group.cards.map((card) => card.id)]),
|
|
[
|
|
["missing_contact", []],
|
|
["audit_ready", []],
|
|
["review_open", ["lead-review"]],
|
|
["contacted", ["lead-replied"]],
|
|
["follow_up", ["lead-follow-up"]],
|
|
["deferred", ["lead-defer"]],
|
|
],
|
|
);
|
|
});
|
|
|
|
test("toLeadFunnelCard maps blocked priority to deferred stage with blocker label", () => {
|
|
const card = toLeadFunnelCard({
|
|
id: "lead-blocked",
|
|
companyName: "Sperr Beispiel",
|
|
city: "Freiburg",
|
|
priority: "blocked",
|
|
contactStatus: "new",
|
|
blacklistStatus: "blocked",
|
|
});
|
|
|
|
assert.equal(card.stageId, "deferred");
|
|
assert.equal(card.priorityLabel, "Gesperrt");
|
|
assert.equal(card.nextAction, "Zurückstellung prüfen");
|
|
});
|
|
|
|
test("dashboard-model exposes stable lead label helpers for UI mapping", () => {
|
|
assert.deepEqual(leadPriorityOptions, [
|
|
"high",
|
|
"medium",
|
|
"low",
|
|
"defer",
|
|
"blocked",
|
|
]);
|
|
assert.equal(getLeadPriorityLabel("high"), "Hoch");
|
|
assert.equal(getLeadContactStatusLabel("missing_contact"), "Kontakt fehlt");
|
|
assert.equal(getLeadBlacklistStatusLabel("blocked"), "Gesperrt");
|
|
});
|
|
|
|
test("dashboard-model exposes duplicate status options and labels", () => {
|
|
assert.deepEqual(leadDuplicateStatusOptions, [
|
|
"unchecked",
|
|
"unique",
|
|
"possible_duplicate",
|
|
"duplicate",
|
|
]);
|
|
assert.equal(getLeadDuplicateStatusLabel("duplicate"), "Duplikat");
|
|
});
|
|
|
|
test("dashboard-model exposes contact status options for lead review controls", () => {
|
|
assert.equal(leadContactStatusOptions[1], "missing_contact");
|
|
assert.equal(leadBlacklistStatusOptions.length, 2);
|
|
});
|
|
|
|
test("dashboardKpis and reviewQueue expose the above-the-fold dashboard summary", () => {
|
|
assert.equal(dashboardKpis.length, 4);
|
|
assert.equal(reviewQueue.length, 3);
|
|
assert.equal(
|
|
reviewQueue.some((item: ReviewQueueItem) =>
|
|
item.title.includes("Audit-Freigabe"),
|
|
),
|
|
true,
|
|
);
|
|
});
|