Files
admin/apps/web/tests/page-metadata.test.ts

164 lines
5.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { pageMetadata } from '#web-runtime/lib/page-metadata';
import type { PageDocument, PageRenderData, SiteSettings } from '#web-runtime/types';
const settings: SiteSettings = {
locale: 'en',
header: { menus: [], ctaItems: [] },
footer: { copyright: '', menus: [] },
legal: { privacyPath: '', termsPath: '' }
};
function page(overrides: Partial<PageDocument> = {}): PageDocument {
return {
id: '7',
status: 'published',
locale: 'en',
path: 'about',
groupKey: 'about',
layout: 'landing',
title: 'About us',
seo: { title: 'About us — Crux', description: 'Who we are' },
breadcrumbs: [],
blocks: [],
publishedAt: null,
updatedAt: null,
...overrides
};
}
function renderData(overrides: Partial<PageRenderData> = {}): PageRenderData {
return {
page: page(),
settings,
languageAlternatives: [],
...overrides
};
}
/** The site serves locale-prefixed paths; metadataBase turns these into absolute URLs. */
const publicPath = (locale: string, path: string) => `/${locale}${path ? `/${path}` : ''}`;
describe('title and description', () => {
it('prefers the editor SEO fields', () => {
const metadata = pageMetadata(renderData(), 'en', publicPath);
expect(metadata.title).toBe('About us — Crux');
expect(metadata.description).toBe('Who we are');
});
it('falls back to the page title, and omits an empty description entirely', () => {
const data = renderData({ page: page({ seo: { title: '', description: '' } }) });
const metadata = pageMetadata(data, 'en', publicPath);
expect(metadata.title).toBe('About us');
expect(metadata.description).toBeUndefined();
});
});
describe('canonical and hreflang', () => {
it('always sets a canonical for the page itself', () => {
expect(pageMetadata(renderData(), 'en', publicPath).alternates?.canonical).toBe('/en/about');
});
it('emits no language map for a page with no translations', () => {
const data = renderData({ languageAlternatives: [{ locale: 'en', path: 'about' }] });
expect(pageMetadata(data, 'en', publicPath).alternates?.languages).toBeUndefined();
});
it('lists every locale including this one, plus x-default', () => {
const data = renderData({
languageAlternatives: [
{ locale: 'de', path: 'ueber-uns' },
{ locale: 'en', path: 'about' },
{ locale: 'uk', path: 'pro-nas' }
]
});
expect(pageMetadata(data, 'en', publicPath).alternates?.languages).toEqual({
'de': '/de/ueber-uns',
'en': '/en/about',
'uk': '/uk/pro-nas',
'x-default': '/en/about'
});
});
it('points x-default at this page when the default locale has no translation', () => {
const data = renderData({
page: page({ locale: 'uk', path: 'pro-nas' }),
languageAlternatives: [
{ locale: 'uk', path: 'pro-nas' },
{ locale: 'de', path: 'ueber-uns' }
]
});
expect(pageMetadata(data, 'en', publicPath).alternates?.languages).toMatchObject({
'x-default': '/uk/pro-nas'
});
});
});
describe('share cards', () => {
const media = {
url: 'https://cdn.example.com/hero.jpg',
alt: 'Team photo',
width: 800,
height: 525
};
it('uses the hero image, with the dimensions crawlers lay out against', () => {
const data = renderData({ page: page({ hero: { type: 'middle', ctaItems: [], media } }) });
const metadata = pageMetadata(data, 'en', publicPath);
expect(metadata.openGraph?.images).toEqual([
{ url: media.url, alt: 'Team photo', width: 800, height: 525 }
]);
expect(metadata.twitter).toMatchObject({ card: 'summary_large_image' });
});
it('falls back to the page title when the image has no alt text', () => {
const data = renderData({
page: page({ hero: { type: 'middle', ctaItems: [], media: { ...media, alt: '' } } })
});
expect(pageMetadata(data, 'en', publicPath).openGraph?.images).toMatchObject([
{ alt: 'About us' }
]);
});
it('omits dimensions the backend did not provide rather than guessing', () => {
const data = renderData({
page: page({ hero: { type: 'middle', ctaItems: [], media: { url: media.url, alt: 'x' } } })
});
expect(pageMetadata(data, 'en', publicPath).openGraph?.images).toEqual([
{ url: media.url, alt: 'x' }
]);
});
it('drops to a small card when the page has no hero image', () => {
const metadata = pageMetadata(renderData(), 'en', publicPath);
expect(metadata.openGraph?.images).toBeUndefined();
expect(metadata.twitter).toMatchObject({ card: 'summary' });
});
});
describe('open graph type', () => {
it('marks an editorial page as an article and everything else as a page', () => {
const article = renderData({ page: page({ layout: 'blog-item' }) });
const landing = renderData({ page: page({ layout: 'landing' }) });
expect(pageMetadata(article, 'en', publicPath).openGraph).toMatchObject({ type: 'article' });
expect(pageMetadata(landing, 'en', publicPath).openGraph).toMatchObject({ type: 'website' });
});
it('carries the page locale and its own url', () => {
expect(pageMetadata(renderData(), 'en', publicPath).openGraph).toMatchObject({
locale: 'en',
url: '/en/about'
});
});
});