import { describe, expect, it } from "vitest"; import { assetInputSchema, categoryInputSchema, projectInputSchema, sectionInputSchema, } from "@/lib/portfolio-validation"; function category(overrides: Record = {}) { return { slug: "branding", nameAr: "الهوية", nameEn: "Branding", nameDe: "Branding", descriptionAr: "وصف", descriptionEn: "Description", descriptionDe: "Beschreibung", sortOrder: 1, isActive: true, ...overrides, }; } function baseMedia(overrides: Record = {}) { return { mode: "external", assetId: "", url: "https://x/y.png", label: "L", kind: "IMAGE", ...overrides }; } function section(overrides: Record = {}) { return { type: "RICH_TEXT", titleAr: "ع", titleEn: "t", titleDe: "t", bodyAr: "ب", bodyEn: "b", bodyDe: "b", imagePath: "", linkUrl: "", sortOrder: 0, ...overrides, }; } function project(overrides: Record = {}) { 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); }); });