From 18850491905fc1bf9e467b1d65c7f1709daf3c30 Mon Sep 17 00:00:00 2001 From: HiDeoo Date: Fri, 11 Apr 2025 11:31:58 +0200 Subject: Fix missing head tags (#3088) --- .changeset/orange-pianos-juggle.md | 5 +++ packages/starlight/__tests__/head/head.test.ts | 40 ++++++++++++++++++++-- packages/starlight/__tests__/head/vitest.config.ts | 1 + packages/starlight/__tests__/i18n/head.test.ts | 30 ++++++++++++++++ packages/starlight/utils/head.ts | 23 +++++++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 .changeset/orange-pianos-juggle.md create mode 100644 packages/starlight/__tests__/i18n/head.test.ts diff --git a/.changeset/orange-pianos-juggle.md b/.changeset/orange-pianos-juggle.md new file mode 100644 index 00000000..85fec920 --- /dev/null +++ b/.changeset/orange-pianos-juggle.md @@ -0,0 +1,5 @@ +--- +'@astrojs/starlight': patch +--- + +Fixes a regression in Starlight version `0.33.0` that caused the description and links to language alternates for multilingual websites to be missing from the` ` of the page. diff --git a/packages/starlight/__tests__/head/head.test.ts b/packages/starlight/__tests__/head/head.test.ts index 46205b89..8ed7e1a7 100644 --- a/packages/starlight/__tests__/head/head.test.ts +++ b/packages/starlight/__tests__/head/head.test.ts @@ -6,7 +6,17 @@ import type { HeadConfig } from '../../schemas/head'; vi.mock('astro:content', async () => (await import('../test-utils')).mockedAstroContent({ - docs: [['index.mdx', { title: 'Home Page' }]], + docs: [ + ['index.mdx', { title: 'Home Page' }], + [ + 'environmental-impact.md', + { + title: 'Eco-friendly docs', + description: + 'Learn how Starlight can help you build greener documentation sites and reduce your carbon footprint.', + }, + ], + ], }) ); @@ -23,6 +33,31 @@ test('includes custom tags defined in the Starlight configuration', () => { }); }); +test('includes description based on Starlight `description` configuration', () => { + const head = getTestHead(); + expect(head).toContainEqual({ + tag: 'meta', + attrs: { + name: 'description', + content: 'Docs with a custom head', + }, + content: '', + }); +}); + +test('includes description based on page `description` frontmatter field if provided', () => { + const head = getTestHead([], routes[1]!); + expect(head).toContainEqual({ + tag: 'meta', + attrs: { + name: 'description', + content: + 'Learn how Starlight can help you build greener documentation sites and reduce your carbon footprint.', + }, + content: '', + }); +}); + test('includes `twitter:site` based on Starlight `social` configuration', () => { const head = getTestHead(); expect(head).toContainEqual({ @@ -151,8 +186,7 @@ test('places the default favicon below any user provided icons', () => { expect(defaultFaviconIndex).toBeGreaterThan(userFaviconIndex); }); -function getTestHead(heads: HeadConfig = []): HeadConfig { - const route = routes[0]!; +function getTestHead(heads: HeadConfig = [], route = routes[0]!): HeadConfig { return generateRouteData({ props: { ...route, diff --git a/packages/starlight/__tests__/head/vitest.config.ts b/packages/starlight/__tests__/head/vitest.config.ts index b13fef59..23642b5a 100644 --- a/packages/starlight/__tests__/head/vitest.config.ts +++ b/packages/starlight/__tests__/head/vitest.config.ts @@ -2,6 +2,7 @@ import { defineVitestConfig } from '../test-config'; export default defineVitestConfig({ title: 'Docs With Custom Head', + description: 'Docs with a custom head', head: [ { tag: 'link', attrs: { rel: 'canonical', href: 'https://example.com/test' } }, { tag: 'link', attrs: { rel: 'stylesheet', href: 'primary.css' } }, diff --git a/packages/starlight/__tests__/i18n/head.test.ts b/packages/starlight/__tests__/i18n/head.test.ts new file mode 100644 index 00000000..dbf25aa3 --- /dev/null +++ b/packages/starlight/__tests__/i18n/head.test.ts @@ -0,0 +1,30 @@ +import { expect, test, vi } from 'vitest'; +import config from 'virtual:starlight/user-config'; +import { getRouteDataTestContext } from '../test-utils'; +import { generateRouteData } from '../../utils/routing/data'; +import { routes } from '../../utils/routing'; + +vi.mock('astro:content', async () => + (await import('../test-utils')).mockedAstroContent({ + docs: [['index.mdx', { title: 'Home Page' }]], + }) +); + +test('includes links to language alternates', () => { + const route = routes[0]!; + const { head } = generateRouteData({ + props: { ...route, headings: [] }, + context: getRouteDataTestContext(), + }); + for (const [locale, localeConfig] of Object.entries(config.locales!)) { + expect(head).toContainEqual({ + tag: 'link', + attrs: { + rel: 'alternate', + href: `https://example.com/${locale}/`, + hreflang: localeConfig?.lang, + }, + content: '', + }); + } +}); diff --git a/packages/starlight/utils/head.ts b/packages/starlight/utils/head.ts index 62b49038..2222e0b3 100644 --- a/packages/starlight/utils/head.ts +++ b/packages/starlight/utils/head.ts @@ -5,6 +5,7 @@ import { type HeadConfig, HeadConfigSchema, type HeadUserConfig } from '../schem import type { PageProps, RouteDataContext } from './routing/data'; import { fileWithBase } from './base'; import { formatCanonical } from './canonical'; +import { localizedUrl } from './localizedUrl'; const HeadSchema = HeadConfigSchema(); @@ -61,6 +62,28 @@ export function getHead( }, ]; + if (description) + headDefaults.push({ + tag: 'meta', + attrs: { name: 'description', content: description }, + }); + + // Link to language alternates. + if (canonical && config.isMultilingual) { + for (const locale in config.locales) { + const localeOpts = config.locales[locale]; + if (!localeOpts) continue; + headDefaults.push({ + tag: 'link', + attrs: { + rel: 'alternate', + hreflang: localeOpts.lang, + href: localizedUrl(canonical, locale, project.trailingSlash).href, + }, + }); + } + } + // Link to sitemap, but only when `site` is set. if (context.site) { headDefaults.push({ -- cgit