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");
});
});