import { PortableText } from "astro-portabletext"; /** * Renders WordPress-migrated image nodes through astro-portabletext using * the SAME dispatch production uses (type.image -> Image.astro), then pins * the local, provider, placeholder, and public Image component edge cases. */ import { experimental_AstroContainer as AstroContainer } from "astro/container"; import { describe, expect, test } from "vitest"; import EmDashImage from "../../src/components/EmDashImage.astro"; import Image from "../../src/components/Image.astro"; import OverrideImage from "./OverrideImage.astro"; const testMediaProviders = [ { id: "mock-images", name: "Mock Images", capabilities: { list: false, upload: false, delete: false, metadata: false }, createProvider: () => ({ id: "mock-images", name: "Mock Images", capabilities: { list: false, upload: false, delete: false, metadata: false }, getEmbed: (_value: unknown, options: { width?: number; height?: number } = {}) => ({ type: "image", src: `https://img.example.com/original?w=${options.width ?? "auto"}`, getSrc: ({ width, height }: { width?: number; height?: number } = {}) => `https://img.example.com/render?w=${width ?? "auto"}&h=${height ?? "auto"}`, }), }), }, ]; ( globalThis as typeof globalThis & { __emdashTestMediaProviders?: typeof testMediaProviders } ).__emdashTestMediaProviders = testMediaProviders; const node = { _type: "image", _key: "img66", asset: { _ref: "01KTRTJ5QSVC3TB57387DX445P", url: "/_emdash/api/media/file/01KTRTJ55S65SADEH9P9TSY89H.png", }, alt: "", alignment: "right", displayWidth: 136, displayHeight: 201, }; const value = [node]; const locals = { emdash: { getPublicMediaUrl: (k: string) => `/_emdash/api/media/file/${k}` }, }; const imgSrc = (html: string) => html.match(/]*\bsrc="([^"]*)"/)?.[1] ?? "(no )"; const imgTag = (html: string) => html.match(/]*>/)?.[0] ?? ""; const attr = (tag: string, name: string) => tag.match(new RegExp(`\\b${name}="([^"]*)"`))?.[1]?.replaceAll("&", "&"); const compact = (html: string) => html.replace(/\s+/g, " ").trim(); async function renderImage( renderNode: Record, renderLocals: typeof locals | undefined = locals, placeholder = true, ) { const c = await AstroContainer.create(); return c.renderToString(Image, { props: { node: renderNode, placeholder }, ...(renderLocals ? { locals: renderLocals } : {}), }); } async function renderEmDashImage(props: Record) { const c = await AstroContainer.create(); return c.renderToString(EmDashImage, { props, locals }); } describe("faithful render of migrated image node", () => { test("default type.image=Image.astro", async () => { const c = await AstroContainer.create(); const html = await c.renderToString(PortableText, { props: { value, components: { type: { image: Image } } }, locals, }); expect(imgSrc(html)).not.toBe("(no )"); // #1404 fix: alignment now rendered as a figure class expect(html).toContain("emdash-image--align-right"); }); test("delegating override -> emdash ", async () => { const c = await AstroContainer.create(); const html = await c.renderToString(PortableText, { props: { value, components: { type: { image: OverrideImage } } }, locals, }); expect(imgSrc(html)).not.toBe("(no )"); }); test("default render with locals.emdash ABSENT", async () => { const c = await AstroContainer.create(); const html = await c.renderToString(PortableText, { props: { value, components: { type: { image: Image } } }, }); expect(imgSrc(html)).not.toBe("(no )"); }); test("local image keeps wrapper, dimensions, alt, caption and alignment", async () => { const html = await renderImage({ ...node, alt: "Migrated image", caption: "A caption", alignment: "center", width: 1200, height: 800, displayWidth: 600, displayHeight: undefined, }); const tag = imgTag(html); expect(compact(html)).toContain('
{ const html = await renderImage({ ...node, asset: { _ref: "01LOCALONLY" }, width: 320, height: 180, displayWidth: undefined, displayHeight: undefined, }); expect(attr(imgTag(html), "src")).toContain("/_emdash/api/media/file/01LOCALONLY"); }); test("unknown dimensions still render without forcing Astro to infer remote size", async () => { const html = await renderImage({ ...node, width: undefined, height: undefined, displayWidth: undefined, displayHeight: undefined, }); const tag = imgTag(html); expect(attr(tag, "src")).toContain("/_emdash/api/media/file/01KTRTJ55S65SADEH9P9TSY89H.png"); expect(tag).not.toContain("srcset="); expect(tag).not.toContain("data-astro-image"); }); test("LQIP fields survive the image rendering branch", async () => { const html = await renderImage({ ...node, dominantColor: "rgb(10, 20, 30)", }); expect(attr(imgTag(html), "style")).toContain("background-color: rgb(10, 20, 30)"); }); test("legacy LQIP metadata is still used", async () => { const html = await renderImage({ ...node, asset: { ...node.asset, meta: { dominantColor: "#102030" }, }, }); expect(attr(imgTag(html), "style")).toContain("background-color: #102030"); }); test("LQIP inline background can be disabled for strict CSP", async () => { const html = await renderImage( { ...node, dominantColor: "rgb(10, 20, 30)", }, locals, false, ); expect(attr(imgTag(html), "style")).toBeUndefined(); }); test("external providers keep provider-generated responsive URLs", async () => { const html = await renderImage({ ...node, asset: { _ref: "provider-image", provider: "mock-images", }, width: 1200, height: 800, displayWidth: 600, displayHeight: undefined, }); const tag = imgTag(html); expect(attr(tag, "src")).toBe("https://img.example.com/original?w=600"); expect(attr(tag, "srcset")).toContain("https://img.example.com/render?w=640&h=427 640w"); expect(attr(tag, "srcset")).toContain("https://img.example.com/render?w=1080&h=720 1080w"); expect(attr(tag, "sizes")).toBe("(min-width: 600px) 600px, 100vw"); }); test("missing external provider falls back without crashing", async () => { const html = await renderImage({ ...node, asset: { _ref: "missing-provider-image", provider: "missing-provider", }, }); expect(attr(imgTag(html), "src")).toContain("/_emdash/api/media/file/missing-provider-image"); }); test("public EmDashImage preserves string URL fallback", async () => { const html = await renderEmDashImage({ image: "https://cdn.example.com/photo.jpg", alt: "CDN photo", width: 640, height: 360, }); const tag = imgTag(html); expect(attr(tag, "src")).toBe("https://cdn.example.com/photo.jpg"); expect(attr(tag, "alt")).toBe("CDN photo"); expect(attr(tag, "width")).toBe("640"); expect(attr(tag, "height")).toBe("360"); expect(attr(tag, "class")).toContain("emdash-image-media"); }); test("public EmDashImage uses Astro Image for same-origin local media", async () => { const html = await renderEmDashImage({ image: { id: "01CUSTOM", src: "/media/custom.jpg", alt: "Custom local media", width: 640, height: 360, }, }); const tag = imgTag(html); expect(attr(tag, "src")).toContain("/media/custom.jpg"); expect(attr(tag, "data-astro-image")).toBe("constrained"); expect(attr(tag, "srcset")).toBeTruthy(); expect(attr(tag, "loading")).toBe("lazy"); expect(attr(tag, "decoding")).toBe("async"); }); test("public EmDashImage preserves priority and passthrough attrs", async () => { const html = await renderEmDashImage({ image: { id: "01MEDIA", src: "/_emdash/api/media/file/01MEDIA.jpg", alt: "Stored media", width: 800, height: 600, }, priority: true, class: "hero-image", "data-testid": "hero", }); const tag = imgTag(html); expect(attr(tag, "src")).toContain("/_emdash/api/media/file/01MEDIA.jpg"); expect(attr(tag, "loading")).toBe("eager"); expect(attr(tag, "fetchpriority")).toBe("high"); expect(attr(tag, "class")).toBe("hero-image"); expect(attr(tag, "data-testid")).toBe("hero"); }); });