// Sitemap entries are built from journal.routes, never from fixed paths. import { test } from 'node:test' import assert from 'node:assert/strict' import { buildJournalSitemapEntries } from '../app/utils/sitemap.ts' import { DEFAULT_JOURNAL_ROUTES, normalizeJournalRoutes } from '../app/utils/routes.ts' import type { JournalCategory, JournalPost } from '../app/utils/types.ts' function post(id: number, slug: string, extra: Partial = {}): JournalPost { return { id, title: slug, slug, excerpt: null, content: null, content_html: null, reading_time: 1, published: true, published_at: '2024-06-12T06:00:00+00:00', is_pinned: false, metadata: null, sources: null, author: null, categories: [], tags: [], featured_images: [], featured_image_url: null, translations: null, created_at: '2024-06-12T06:00:00+00:00', updated_at: '2024-06-13T06:00:00+00:00', ...extra, } } function category(id: number, slug: string, children?: JournalCategory[]): JournalCategory { const row: JournalCategory = { id, name: slug, slug, description: null, content: null, parent_id: null, nest_depth: 0, post_count: 0, } if (children) row.children = children return row } const POSTS = [post(1, 'blogbadania-nad-cordycepsem'), post(2, 'bez-daty', { updated_at: null })] const CATEGORIES = [category(1, 'cordyceps', [category(2, 'militaris')]), category(3, 'amanita-muscaria')] test('default routes give /blog/:slug and /blog/category/:category', () => { assert.deepEqual(buildJournalSitemapEntries(POSTS, CATEGORIES, { ...DEFAULT_JOURNAL_ROUTES }), [ { loc: '/blog/blogbadania-nad-cordycepsem', lastmod: '2024-06-13T06:00:00+00:00' }, { loc: '/blog/bez-daty' }, { loc: '/blog/category/cordyceps' }, { loc: '/blog/category/militaris' }, { loc: '/blog/category/amanita-muscaria' }, ]) }) test('a custom root category route gives /cordyceps', () => { const routes = normalizeJournalRoutes({ category: '/:category', tag: false }) const locs = buildJournalSitemapEntries(POSTS, CATEGORIES, routes).map((e) => e.loc) assert.ok(locs.includes('/cordyceps')) assert.ok(locs.includes('/militaris'), 'nested categories are flattened') assert.ok(locs.includes('/amanita-muscaria'), 'empty categories are listed') }) test('a disabled route contributes nothing', () => { const noCategories = normalizeJournalRoutes({ category: false }) assert.deepEqual( buildJournalSitemapEntries(POSTS, CATEGORIES, noCategories).map((e) => e.loc), ['/blog/blogbadania-nad-cordycepsem', '/blog/bez-daty'], ) const noPosts = normalizeJournalRoutes({ post: false }) assert.equal(buildJournalSitemapEntries(POSTS, CATEGORIES, noPosts).filter((e) => e.loc.startsWith('/blog/b')).length, 0) }) test('unpublished posts and empty slugs are skipped', () => { const rows = [post(1, 'draft', { published: false }), post(2, ''), post(3, 'live')] assert.deepEqual( buildJournalSitemapEntries(rows, [category(9, '')], { ...DEFAULT_JOURNAL_ROUTES }).map((e) => e.loc), ['/blog/live'], ) }) test('lastmod comes from updated_at', () => { const [entry] = buildJournalSitemapEntries([post(1, 'x', { updated_at: '2025-01-02T03:04:05+00:00' })], [], { ...DEFAULT_JOURNAL_ROUTES, }) assert.equal(entry?.lastmod, '2025-01-02T03:04:05+00:00') }) test('slugs are encoded per segment', () => { const [entry] = buildJournalSitemapEntries([post(1, 'a b/c')], [], { ...DEFAULT_JOURNAL_ROUTES }) assert.equal(entry?.loc, '/blog/a%20b/c') })