import { describe, it, expect } from "vitest"; import { buildVideoObjectNodes } from "../jsonld/video.ts"; import { createJsonLdContext } from "../jsonld/context.ts"; import type { SemanticPageModel, VideoSeoData } from "../models.ts"; /* RFC-0912: Tests for buildVideoObjectNodes — the JSON-LD builder that emits VideoObject nodes from SemanticBlock.video data on opted-in content video blocks. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-1311: Compass header sweep — declares the jsonld suite's contract for test-file scaffold coverage. */ function makeMinimalPage(overrides: Partial = {}): SemanticPageModel { return { url: "https://example.com/uk/demo", type: "article", lang: "uk", title: "Demo Page", description: "A demo page", blocks: [], organization: { name: "Test Org", description: "Test", url: "https://example.com" }, ...overrides, } as unknown as SemanticPageModel; } const sampleVideoSeo: VideoSeoData = { seo: { name: "Demo Video", description: "A demonstration of the platform", uploadDate: "2026-01-15T00:00:00Z", }, manifest: { posterUrl: "https://example.com/_video/uk/demo/poster.webp", contentUrl: "https://example.com/_video/uk/demo/progressive.h264.mp4", durationSec: 120, }, }; describe("buildVideoObjectNodes", () => { it("returns empty array when no blocks have video data", () => { const page = makeMinimalPage({ blocks: [{ id: "block-1", heading: "Intro" }], }); const ctx = createJsonLdContext(page); const nodes = buildVideoObjectNodes(ctx); expect(nodes).toHaveLength(0); }); it("emits a VideoObject node for a block with video data", () => { const page = makeMinimalPage({ blocks: [{ id: "video-section", heading: "Demo", video: sampleVideoSeo }], }); const ctx = createJsonLdContext(page); const nodes = buildVideoObjectNodes(ctx); expect(nodes).toHaveLength(1); const node = nodes[0]!; expect(node["@type"]).toBe("VideoObject"); expect(node.name).toBe("Demo Video"); expect(node.description).toBe("A demonstration of the platform"); expect(node.uploadDate).toBe("2026-01-15T00:00:00Z"); expect(node.thumbnailUrl).toBe("https://example.com/_video/uk/demo/poster.webp"); expect(node.contentUrl).toBe("https://example.com/_video/uk/demo/progressive.h264.mp4"); }); it("includes ISO 8601 duration when durationSec is present", () => { const page = makeMinimalPage({ blocks: [{ id: "v1", heading: "Demo", video: sampleVideoSeo }], }); const ctx = createJsonLdContext(page); const nodes = buildVideoObjectNodes(ctx); expect(nodes[0]!.duration).toBe("PT2M0S"); }); it("omits duration when durationSec is absent", () => { const videoWithoutDuration: VideoSeoData = { seo: sampleVideoSeo.seo, manifest: { posterUrl: sampleVideoSeo.manifest.posterUrl, contentUrl: sampleVideoSeo.manifest.contentUrl, }, }; const page = makeMinimalPage({ blocks: [{ id: "v1", heading: "Demo", video: videoWithoutDuration }], }); const ctx = createJsonLdContext(page); const nodes = buildVideoObjectNodes(ctx); expect(nodes[0]!.duration).toBeUndefined(); }); it("emits multiple nodes for multiple video blocks", () => { const page = makeMinimalPage({ blocks: [ { id: "v1", heading: "Demo 1", video: sampleVideoSeo }, { id: "v2", heading: "Demo 2", video: { seo: { name: "Second", description: "Second video", uploadDate: "2026-02-01" }, manifest: { posterUrl: "https://example.com/_video/uk/demo2/poster.webp", contentUrl: "https://example.com/_video/uk/demo2/progressive.h264.mp4", durationSec: 3661, }, }, }, ], }); const ctx = createJsonLdContext(page); const nodes = buildVideoObjectNodes(ctx); expect(nodes).toHaveLength(2); expect(nodes[0]!.name).toBe("Demo Video"); expect(nodes[1]!.name).toBe("Second"); expect(nodes[1]!.duration).toBe("PT1H1M1S"); }); it("generates unique @id per video block", () => { const page = makeMinimalPage({ blocks: [ { id: "v1", heading: "Demo 1", video: sampleVideoSeo }, { id: "v2", heading: "Demo 2", video: { ...sampleVideoSeo, seo: { name: "Second", description: "d", uploadDate: "2026-02-01" }, }, }, ], }); const ctx = createJsonLdContext(page); const nodes = buildVideoObjectNodes(ctx); expect(nodes[0]!["@id"]).not.toBe(nodes[1]!["@id"]); expect(nodes[0]!["@id"]).toContain("v1"); expect(nodes[1]!["@id"]).toContain("v2"); }); });