Files
community-rule/tests/unit/siteMetadata.test.ts
T
adilalloandCursor 751f2e9343 Resolve share URLs from the instance host and ship 1200×630 Open Graph images.
Staging no longer advertises production canonicals, and Slack/social previews get a default hero card with template and article overrides.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-29 15:41:27 -06:00

223 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, it } from "vitest";
import metadataMessages from "../../messages/en/metadata.json";
import {
articleShareImages,
buildRobots,
metadataBaseUrl,
NO_INDEX_ROBOTS,
originFromHost,
PRODUCTION_SITE_ORIGIN,
resolveRequestHost,
robotsForPublishedRule,
routeMetadata,
sitemapUrl,
templatesShareImages,
} from "../../lib/siteMetadata";
const ORIGINAL_SITE_INDEXING = process.env.SITE_INDEXING;
const ORIGINAL_APP_ORIGIN = process.env.CLOUDRON_APP_ORIGIN;
afterEach(() => {
if (ORIGINAL_SITE_INDEXING === undefined) {
delete process.env.SITE_INDEXING;
} else {
process.env.SITE_INDEXING = ORIGINAL_SITE_INDEXING;
}
if (ORIGINAL_APP_ORIGIN === undefined) {
delete process.env.CLOUDRON_APP_ORIGIN;
} else {
process.env.CLOUDRON_APP_ORIGIN = ORIGINAL_APP_ORIGIN;
}
});
describe("routeMetadata", () => {
const defaultImage = {
url: "/assets/marketing/og-image.png",
width: 1200,
height: 630,
alt: metadataMessages.shareImage.defaultAlt,
};
it("sets a path canonical, stripping trailing slashes and query strings", () => {
expect(routeMetadata("/about").alternates?.canonical).toBe("/about");
expect(routeMetadata("/about/").alternates?.canonical).toBe("/about");
expect(routeMetadata("about").alternates?.canonical).toBe("/about");
expect(routeMetadata("/").alternates?.canonical).toBe("/");
expect(routeMetadata("/blog/post?x=1#top").alternates?.canonical).toBe(
"/blog/post",
);
});
it("sets og:url from the same path so metadataBase can add the host", () => {
expect(routeMetadata("/about").openGraph?.url).toBe("/about");
expect(routeMetadata("/").openGraph?.url).toBe("/");
});
it("ships a 1200×630 default share image on og and twitter", () => {
const meta = routeMetadata("/learn");
expect(meta.openGraph?.images).toEqual([defaultImage]);
expect(meta.twitter?.images).toEqual([defaultImage.url]);
});
it("keeps caller metadata while overriding a parent canonical", () => {
const meta = routeMetadata("/learn", {
title: "Learn",
description: "Copy",
});
expect(meta.title).toBe("Learn");
expect(meta.description).toBe("Copy");
expect(meta.alternates?.canonical).toBe("/learn");
expect(meta.openGraph?.url).toBe("/learn");
});
it("lets templates and articles replace the default share image", () => {
const templates = templatesShareImages();
const templateMeta = routeMetadata("/templates", {
openGraph: { images: templates },
});
expect(templateMeta.openGraph?.images).toEqual(templates);
expect(templateMeta.twitter?.images).toEqual([templates[0]?.url]);
const article = articleShareImages(
"how-chaos-concentrates-control",
"How Chaos Concentrates Control",
);
const articleMeta = routeMetadata("/blog/how-chaos-concentrates-control", {
openGraph: { images: article },
});
expect(articleMeta.openGraph?.images).toEqual([
{
url: "/content/blog/how-chaos-concentrates-control-og.png",
width: 1200,
height: 630,
alt: "How Chaos Concentrates Control",
},
]);
expect(articleMeta.twitter?.images).toEqual([
"/content/blog/how-chaos-concentrates-control-og.png",
]);
});
});
describe("sitemapUrl", () => {
it("resolves paths against the production origin", () => {
expect(sitemapUrl("/")).toBe(PRODUCTION_SITE_ORIGIN);
expect(sitemapUrl("/about")).toBe(`${PRODUCTION_SITE_ORIGIN}/about`);
});
});
describe("resolveRequestHost", () => {
it("prefers x-forwarded-host over Host", () => {
expect(
resolveRequestHost("staging.communityrule.info", "0.0.0.0:3000"),
).toBe("staging.communityrule.info");
});
it("uses CLOUDRON_APP_ORIGIN when Host is the standalone bind address", () => {
process.env.CLOUDRON_APP_ORIGIN = "https://staging.communityrule.info";
expect(resolveRequestHost(null, "0.0.0.0:3000")).toBe(
"staging.communityrule.info",
);
});
});
describe("originFromHost", () => {
it("uses CLOUDRON_APP_ORIGIN when no host is given", () => {
process.env.CLOUDRON_APP_ORIGIN = "https://staging.communityrule.info";
expect(originFromHost(null)).toBe("https://staging.communityrule.info");
expect(metadataBaseUrl().origin).toBe("https://staging.communityrule.info");
});
it("falls back to production when neither host nor app origin is set", () => {
delete process.env.CLOUDRON_APP_ORIGIN;
expect(originFromHost(null)).toBe(PRODUCTION_SITE_ORIGIN);
});
it("builds https origins from public hostnames", () => {
expect(originFromHost("staging.communityrule.info")).toBe(
"https://staging.communityrule.info",
);
expect(originFromHost("communityrule.com")).toBe(PRODUCTION_SITE_ORIGIN);
});
it("keeps http for localhost so local previews resolve", () => {
expect(originFromHost("localhost:3000")).toBe("http://localhost:3000");
});
});
describe("buildRobots", () => {
it("disallows crawlers on staging and unknown hosts", () => {
expect(buildRobots("staging.communityrule.info")).toEqual({
rules: { userAgent: "*", disallow: "/" },
});
expect(buildRobots("localhost:3000")).toEqual({
rules: { userAgent: "*", disallow: "/" },
});
});
it("allows production hosts and advertises the sitemap", () => {
expect(buildRobots("communityrule.info")).toEqual({
rules: { userAgent: "*", allow: "/" },
sitemap: `${PRODUCTION_SITE_ORIGIN}/sitemap.xml`,
});
expect(buildRobots("communityrule.com")).toEqual({
rules: { userAgent: "*", allow: "/" },
sitemap: `${PRODUCTION_SITE_ORIGIN}/sitemap.xml`,
});
});
it("honors SITE_INDEXING overrides", () => {
process.env.SITE_INDEXING = "false";
expect(buildRobots("communityrule.info").rules).toEqual({
userAgent: "*",
disallow: "/",
});
process.env.SITE_INDEXING = "true";
expect(buildRobots("staging.communityrule.info").sitemap).toBe(
`${PRODUCTION_SITE_ORIGIN}/sitemap.xml`,
);
});
});
describe("robotsForPublishedRule", () => {
it("leaves listed rules indexable and noindexes unlisted guest-claim pages", () => {
expect(robotsForPublishedRule(true)).toBeUndefined();
expect(robotsForPublishedRule(false)).toEqual(NO_INDEX_ROBOTS);
});
});
describe("page title segments", () => {
it("uses a shared em-dash template and does not bake a site suffix into page titles", () => {
expect(metadataMessages.titleTemplate).toBe("%s — CommunityRule");
const suffixes = [
" — CommunityRule",
" · CommunityRule",
" - CommunityRule",
];
const titles: string[] = [];
const walk = (value: unknown) => {
if (value && typeof value === "object") {
for (const [key, child] of Object.entries(
value as Record<string, unknown>,
)) {
if (key === "title" && typeof child === "string") {
titles.push(child);
} else {
walk(child);
}
}
}
};
walk(metadataMessages);
for (const title of titles) {
if (title === metadataMessages.home.title) continue;
for (const suffix of suffixes) {
expect(title.endsWith(suffix)).toBe(false);
}
}
});
});