Files
sass-mohfarawati/tests/unit/portfolio-form-progress.test.ts
T
Moh e2e06be86e test: add comprehensive automated test coverage
- Vitest multi-project setup (unit / integration / component)
- Real-Postgres integration harness via in-process PGlite (TEST_DATABASE_URL
  override), migrations applied per worker; production code untouched
- Unit: routing, locale, validation/Zod schemas, metadata, mail, site-theme,
  marquee, media, portfolio helpers, plus architecture-rule tests
- Integration: Prisma data layer, API routes, and all server actions
- Component: UI primitives and form components (jsdom + Testing Library)
- 371 tests passing
2026-08-06 02:27:20 +02:00

135 lines
4.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
getFirstIncompleteWizardStep,
getPortfolioWizardProgress,
isPortfolioAssetReady,
isPortfolioSectionReady,
} from "@/lib/portfolio-form-progress";
const titles = { titleAr: "ع", titleEn: "en", titleDe: "de" };
function section(overrides: Record<string, unknown> = {}) {
return {
type: "RICH_TEXT" as const,
...titles,
bodyAr: "ب",
bodyEn: "body",
bodyDe: "koerper",
linkUrl: "",
mediaAssetId: "",
...overrides,
};
}
describe("isPortfolioSectionReady", () => {
it("requires titles in all languages", () => {
expect(isPortfolioSectionReady(section({ titleEn: "" }))).toBe(false);
});
it("RICH_TEXT/STATS/DELIVERABLES require body in all languages", () => {
for (const type of ["RICH_TEXT", "STATS", "DELIVERABLES"] as const) {
expect(isPortfolioSectionReady(section({ type }))).toBe(true);
expect(isPortfolioSectionReady(section({ type, bodyDe: "" }))).toBe(false);
}
});
it("GALLERY requires a media asset id", () => {
expect(isPortfolioSectionReady(section({ type: "GALLERY", mediaAssetId: "asset_1" }))).toBe(true);
expect(isPortfolioSectionReady(section({ type: "GALLERY", mediaAssetId: "" }))).toBe(false);
});
it("LINK requires a link url", () => {
expect(isPortfolioSectionReady(section({ type: "LINK", linkUrl: "https://x" }))).toBe(true);
expect(isPortfolioSectionReady(section({ type: "LINK", linkUrl: "" }))).toBe(false);
});
});
describe("isPortfolioAssetReady", () => {
it("requires media asset id and alt text in all languages", () => {
expect(isPortfolioAssetReady({ mediaAssetId: "a", altAr: "ع", altEn: "e", altDe: "d" })).toBe(true);
expect(isPortfolioAssetReady({ mediaAssetId: "", altAr: "ع", altEn: "e", altDe: "d" })).toBe(false);
expect(isPortfolioAssetReady({ mediaAssetId: "a", altAr: "ع", altEn: "", altDe: "d" })).toBe(false);
});
});
describe("getPortfolioWizardProgress", () => {
const completeInput = {
basics: {
categoryId: "cat_1",
slug: "case-study",
clientName: "Client",
projectYear: "2025",
sortOrder: "1",
viewMode: "GRID" as const,
},
content: {
titleAr: "ع", titleEn: "t", titleDe: "t",
serviceLabelAr: "خ", serviceLabelEn: "s", serviceLabelDe: "s",
summaryAr: "م", summaryEn: "sum", summaryDe: "zus",
},
sections: [section()],
assets: [{ mediaAssetId: "asset_1", altAr: "ع", altEn: "e", altDe: "d" }],
};
it("marks all steps complete for a fully filled project", () => {
const progress = getPortfolioWizardProgress(completeInput);
expect(progress.every((step) => step.complete)).toBe(true);
expect(getFirstIncompleteWizardStep(progress)).toBeNull();
});
it("rejects an invalid slug", () => {
const progress = getPortfolioWizardProgress({
...completeInput,
basics: { ...completeInput.basics, slug: "Invalid Slug" },
});
expect(progress.find((s) => s.key === "basics")?.complete).toBe(false);
expect(getFirstIncompleteWizardStep(progress)).toBe("basics");
});
it("enforces year bounds 2000..2100", () => {
const before = getPortfolioWizardProgress({
...completeInput,
basics: { ...completeInput.basics, projectYear: "1999" },
});
expect(before.find((s) => s.key === "basics")?.complete).toBe(false);
const after = getPortfolioWizardProgress({
...completeInput,
basics: { ...completeInput.basics, projectYear: "2101" },
});
expect(after.find((s) => s.key === "basics")?.complete).toBe(false);
});
it("enforces sortOrder bounds 0..9999", () => {
const progress = getPortfolioWizardProgress({
...completeInput,
basics: { ...completeInput.basics, sortOrder: "-1" },
});
expect(progress.find((s) => s.key === "basics")?.complete).toBe(false);
});
it("content is incomplete when a summary locale is missing", () => {
const progress = getPortfolioWizardProgress({
...completeInput,
content: { ...completeInput.content, summaryEn: "" },
});
expect(progress.find((s) => s.key === "content")?.complete).toBe(false);
expect(getFirstIncompleteWizardStep(progress)).toBe("content");
});
it("sections/assets steps require at least one ready entry", () => {
const noEntries = getPortfolioWizardProgress({ ...completeInput, sections: [], assets: [] });
expect(noEntries.find((s) => s.key === "sections")?.complete).toBe(false);
expect(noEntries.find((s) => s.key === "assets")?.complete).toBe(false);
});
it("summarizes section/asset readiness counts", () => {
const progress = getPortfolioWizardProgress({
...completeInput,
sections: [section(), section({ titleEn: "" })],
});
expect(progress.find((s) => s.key === "sections")?.summary).toBe("1/2 sections ready.");
expect(progress.find((s) => s.key === "sections")?.complete).toBe(false);
});
});