Files
sass-mohfarawati/tests/unit/portfolio-validation.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

206 lines
7.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
assetInputSchema,
categoryInputSchema,
projectInputSchema,
sectionInputSchema,
} from "@/lib/portfolio-validation";
function category(overrides: Record<string, unknown> = {}) {
return {
slug: "branding",
nameAr: "الهوية", nameEn: "Branding", nameDe: "Branding",
descriptionAr: "وصف", descriptionEn: "Description", descriptionDe: "Beschreibung",
sortOrder: 1,
isActive: true,
...overrides,
};
}
function baseMedia(overrides: Record<string, unknown> = {}) {
return { mode: "external", assetId: "", url: "https://x/y.png", label: "L", kind: "IMAGE", ...overrides };
}
function section(overrides: Record<string, unknown> = {}) {
return {
type: "RICH_TEXT",
titleAr: "ع", titleEn: "t", titleDe: "t",
bodyAr: "ب", bodyEn: "b", bodyDe: "b",
imagePath: "",
linkUrl: "",
sortOrder: 0,
...overrides,
};
}
function project(overrides: Record<string, unknown> = {}) {
return {
categoryId: "cat_1",
slug: "case-study",
viewMode: "GRID",
titleAr: "ع", titleEn: "T", titleDe: "T",
summaryAr: "م", summaryEn: "S", summaryDe: "S",
clientName: "Client",
projectYear: 2025,
serviceLabelAr: "خ", serviceLabelEn: "Svc", serviceLabelDe: "Svc",
previewUrl: "https://example.com",
currentCoverImagePath: "",
sortOrder: 1,
isFeatured: false,
isPublished: true,
sections: [],
assets: [],
...overrides,
};
}
describe("categoryInputSchema", () => {
it("accepts a valid payload", () => {
expect(categoryInputSchema.parse(category()).slug).toBe("branding");
});
it("requires lowercase hyphenated slugs", () => {
expect(() => categoryInputSchema.parse(category({ slug: "Not Valid" }))).toThrow(/slug/i);
expect(() => categoryInputSchema.parse(category({ slug: "-leading" }))).toThrow(/slug/i);
expect(categoryInputSchema.parse(category({ slug: "multi-word-slug" })).slug).toBe("multi-word-slug");
});
it("requires all name and description locales", () => {
expect(() => categoryInputSchema.parse(category({ nameEn: "" }))).toThrow(/nameEn/i);
expect(() => categoryInputSchema.parse(category({ descriptionDe: " " }))).toThrow(/descriptionDe/i);
});
it("coerces sortOrder and enforces its range", () => {
expect(categoryInputSchema.parse(category({ sortOrder: "5" })).sortOrder).toBe(5);
expect(() => categoryInputSchema.parse(category({ sortOrder: 10000 }))).toThrow();
expect(() => categoryInputSchema.parse(category({ sortOrder: -1 }))).toThrow();
});
});
describe("sectionInputSchema", () => {
it("accepts a RICH_TEXT section with body", () => {
expect(sectionInputSchema.parse(section()).type).toBe("RICH_TEXT");
});
it("requires body for RICH_TEXT, STATS and DELIVERABLES", () => {
for (const type of ["RICH_TEXT", "STATS", "DELIVERABLES"]) {
expect(() => sectionInputSchema.parse(section({ type, bodyEn: "" }))).toThrow(/body/i);
}
});
it("requires an image for GALLERY sections", () => {
expect(() =>
sectionInputSchema.parse(section({ type: "GALLERY", bodyAr: "", bodyEn: "", bodyDe: "", imagePath: "" })),
).toThrow(/image/i);
expect(
sectionInputSchema.parse(
section({ type: "GALLERY", bodyAr: "", bodyEn: "", bodyDe: "", imagePath: "/uploads/media/x.svg" }),
).type,
).toBe("GALLERY");
expect(
sectionInputSchema.parse(
section({ type: "GALLERY", bodyAr: "", bodyEn: "", bodyDe: "", media: baseMedia({ mode: "library", assetId: "a" }) }),
).type,
).toBe("GALLERY");
});
it("requires a link url for LINK sections", () => {
expect(() =>
sectionInputSchema.parse(section({ type: "LINK", bodyAr: "", bodyEn: "", bodyDe: "", linkUrl: "" })),
).toThrow(/link/i);
expect(
sectionInputSchema.parse(section({ type: "LINK", bodyAr: "", bodyEn: "", bodyDe: "", linkUrl: "https://x" })).linkUrl,
).toBe("https://x");
});
it("rejects malformed link urls", () => {
expect(() => sectionInputSchema.parse(section({ type: "LINK", linkUrl: "javascript:alert(1)" }))).toThrow(/absolute URL/i);
});
it("requires titles in all languages", () => {
expect(() => sectionInputSchema.parse(section({ titleAr: "" }))).toThrow(/titleAr/i);
});
});
describe("assetInputSchema", () => {
it("accepts a valid image asset", () => {
const parsed = assetInputSchema.parse({
kind: "IMAGE",
filePath: "/uploads/media/assets/x.svg",
fileFieldName: "",
media: baseMedia(),
altAr: "ع", altEn: "a", altDe: "a",
sortOrder: 0,
});
expect(parsed.kind).toBe("IMAGE");
});
it("only allows the IMAGE kind", () => {
expect(() =>
assetInputSchema.parse({ kind: "DOCUMENT", altAr: "ع", altEn: "a", altDe: "a", sortOrder: 0 }),
).toThrow();
});
it("requires alt text in all languages", () => {
expect(() =>
assetInputSchema.parse({ kind: "IMAGE", altAr: "ع", altEn: "", altDe: "a", sortOrder: 0 }),
).toThrow(/altEn/i);
});
it("rejects invalid embedded media urls", () => {
expect(() =>
assetInputSchema.parse({
kind: "IMAGE",
media: baseMedia({ url: "not-a-url" }),
altAr: "ع", altEn: "a", altDe: "a",
sortOrder: 0,
}),
).toThrow(/url/i);
});
});
describe("projectInputSchema", () => {
it("accepts a fully valid project", () => {
expect(projectInputSchema.parse(project()).slug).toBe("case-study");
});
it("defaults viewMode to GRID and accepts known modes", () => {
expect(projectInputSchema.parse(project({ viewMode: undefined })).viewMode).toBe("GRID");
expect(projectInputSchema.parse(project({ viewMode: "CASE_STUDY" })).viewMode).toBe("CASE_STUDY");
expect(() => projectInputSchema.parse(project({ viewMode: "WILD" }))).toThrow();
});
it("rejects invalid slugs", () => {
expect(() => projectInputSchema.parse(project({ slug: "Bad Slug" }))).toThrow(/slug/i);
});
it("coerces and bounds projectYear", () => {
expect(projectInputSchema.parse(project({ projectYear: "2025" })).projectYear).toBe(2025);
expect(() => projectInputSchema.parse(project({ projectYear: 1999 }))).toThrow();
expect(() => projectInputSchema.parse(project({ projectYear: 2101 }))).toThrow();
});
it("requires all localized content fields", () => {
expect(() => projectInputSchema.parse(project({ summaryDe: "" }))).toThrow(/summaryDe/i);
expect(() => projectInputSchema.parse(project({ serviceLabelAr: "" }))).toThrow(/serviceLabelAr/i);
expect(() => projectInputSchema.parse(project({ clientName: "" }))).toThrow(/clientName/i);
});
it("allows an empty preview url but rejects a relative one", () => {
expect(projectInputSchema.parse(project({ previewUrl: "" })).previewUrl).toBe("");
expect(() => projectInputSchema.parse(project({ previewUrl: "/relative" }))).toThrow(/absolute URL/i);
});
it("validates nested sections and assets", () => {
expect(() =>
projectInputSchema.parse(project({ sections: [section({ titleEn: "" })] })),
).toThrow(/titleEn/i);
expect(
projectInputSchema.parse(
project({ assets: [{ kind: "IMAGE", altAr: "ع", altEn: "a", altDe: "a", sortOrder: 0, media: baseMedia() }] }),
).assets.length,
).toBe(1);
});
});