import assert from 'node:assert/strict';
import { after, before, test } from 'node:test';
import { cp, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import fg from 'fast-glob';
import matter from 'gray-matter';
import { load } from 'cheerio';
import { createLibraryCatalog, extractMarkdownHeadings } from '../src/utils/library-catalog.ts';
import { createLibraryPaginationPublications, libraryPageRoute, paginateLibraryItems } from '../src/utils/library-pagination.ts';
import { boundedSearchOffset, normalizeLibrarySearchText, parseLegacyLibrarySearchState, searchLibrary } from '../src/utils/library-search.ts';
import { createPublicationManifest, renderSitemap, robotsFor } from '../src/utils/publication-policy.ts';
import { createLibrarySearchPublication } from '../src/utils/search-publication.ts';
import { createAuthorResolver } from '../src/utils/authors.ts';
import { authorPageRoute, createAuthorArchivePublications } from '../src/utils/author-archives.ts';
import { redirectArtifacts, redirects as approvedRedirects, validateRedirects } from './generate-redirects.mjs';
import { resolveNavigationDestination, visitorLanguageChoices } from '../src/utils/visitor-navigation.ts';
const project = fileURLToPath(new URL('../', import.meta.url));
const site = JSON.parse(await readFile(path.join(project, 'src/data/site.json'), 'utf8')).url;
async function contentEntries(root = project) {
return Promise.all((await fg('src/content/{pages,blog}/**/*.{md,mdx}', { cwd: root })).sort().map(async (file) => {
const source = await readFile(path.join(root, file), 'utf8');
const parsed = matter(source);
const [, , collection, ...parts] = file.split('/');
return { id: parts.join('/').replace(/\.mdx?$/, ''), collection, body: parsed.content, data: parsed.data };
}));
}
async function authorEntries(root = project) {
return Promise.all((await fg('src/content/authors/**/*.{md,mdx}', { cwd: root })).sort().map(async (file) => {
const parsed = matter(await readFile(path.join(root, file), 'utf8'));
return { id: file.replace(/^src\/content\/authors\//, '').replace(/\.mdx?$/, ''), data: parsed.data, body: parsed.content };
}));
}
const entries = await contentEntries();
const manifest = createPublicationManifest(entries, site);
const authors = await authorEntries();
const catalog = createLibraryCatalog(manifest, authors);
const paginationPublications = createLibraryPaginationPublications(catalog, manifest, site);
const authorPublications = createAuthorArchivePublications(catalog, authors, [...manifest, ...paginationPublications], site);
const routeManifest = [...manifest, ...paginationPublications, ...authorPublications].sort((left, right) => left.route.localeCompare(right.route, 'en'));
function record(overrides = {}) {
return {
url: '/library/example', locale: 'en', title: 'Autism Support Guide', description: 'Practical family resources',
headings: ['Planning Daily Routines'], category: 'Library', tags: [], author: { name: 'Rula Diab', slug: 'rula-diab' },
publishedAt: '2026-01-01', ...overrides
};
}
test('actual catalog groups every eligible post by locale and preserves syndicated noindex records', () => {
assert.deepEqual(Object.fromEntries(Object.entries(catalog.byLocale).map(([locale, items]) => [locale, items.length])), { en: 46, es: 12, ar: 7 });
assert.equal(catalog.all.length, 65);
for (const slug of ['community-highlight-meet-rula-diab', 'new-aia-scottsdale-office']) {
assert.ok(catalog.byLocale.en.some(({ search }) => search.url === `/library/${slug}`));
}
assert.equal(new Set(catalog.all.map(({ route }) => route)).size, 65);
assert.ok(catalog.all.every(({ search }) => search.author.name === 'Rula Diab'));
});
test('catalog ordering is date descending with route ascending as the stable tie-breaker', () => {
for (let index = 1; index < catalog.all.length; index++) {
const previous = catalog.all[index - 1].search;
const current = catalog.all[index].search;
const dateOrder = Date.parse(previous.publishedAt) - Date.parse(current.publishedAt);
assert.ok(dateOrder > 0 || (dateOrder === 0 && previous.url.localeCompare(current.url, 'en') < 0));
}
});
test('author resolution is exact by locale and slug, keeps identity separate, and rejects invalid or ambiguous data', () => {
const resolve = createAuthorResolver(authors);
for (const locale of ['en', 'es', 'ar']) {
const author = resolve(locale, 'rula-diab');
assert.equal(author.name, 'Rula Diab');
assert.equal(author.displayName, authors.find(({ data }) => data.lang === locale).data.displayName);
assert.equal(author.archiveRoute, `${locale === 'en' ? '' : '/' + locale}/library/author/rula-diab`);
assert.equal(author.avatar, '/assets/images/rula-diab-avatar.jpg');
}
assert.throws(() => resolve('fr', 'rula-diab'), /Missing or ambiguous/);
assert.throws(() => resolve('en', 'Rula-Diab'), /Missing or ambiguous/);
assert.throws(() => createAuthorResolver([...authors, authors[0]]), /Missing or ambiguous/);
const source = authors[0];
for (const override of [{ name: ' ' }, { name: 4 }, { displayName: '' }, { displayName: ' \t' },
{ slug: '../other' }, { slug: 'name/page/2' }, { slug: '%72ula' }, { lang: 'fr' },
{ avatar: 'https://example.org/image.jpg' }, { description: 1 }]) {
assert.throws(() => createAuthorResolver([{ ...source, data: { ...source.data, ...override } }]), /Invalid author/);
}
const plain = { id: 'en/another', data: { lang: 'en', slug: 'another', name: 'Another Author' } };
assert.equal(createAuthorResolver([plain])('en', 'another').displayName, 'Another Author');
});
test('visitor language navigation resolves explicit content and author relationships without changing SEO translations', () => {
const englishAuthor = routeManifest.find(({ route }) => route === '/library/author/rula-diab');
const englishAuthorPage = routeManifest.find(({ route }) => route === '/library/author/rula-diab/page/5');
assert.deepEqual(englishAuthor.translations, []);
assert.deepEqual(visitorLanguageChoices(englishAuthor, routeManifest), [
{ lang: 'en', route: '/library/author/rula-diab', current: true, kind: 'equivalent' },
{ lang: 'es', route: '/es/library/author/rula-diab', current: false, kind: 'equivalent' },
{ lang: 'ar', route: '/ar/library/author/rula-diab', current: false, kind: 'equivalent' }
]);
assert.deepEqual(visitorLanguageChoices(englishAuthorPage, routeManifest), [
{ lang: 'en', route: '/library/author/rula-diab', current: true, kind: 'section-root' },
{ lang: 'es', route: '/es/library/author/rula-diab', current: false, kind: 'section-root' },
{ lang: 'ar', route: '/ar/library/author/rula-diab', current: false, kind: 'section-root' }
]);
const libraryPage = routeManifest.find(({ route }) => route === '/library/page/2');
assert.deepEqual(visitorLanguageChoices(libraryPage, routeManifest).map(({ route, kind }) => ({ route, kind })), [
{ route: '/library', kind: 'section-root' },
{ route: '/es/library', kind: 'section-root' },
{ route: '/ar/library', kind: 'section-root' }
]);
const untranslated = routeManifest.find(({ route }) => route === '/library/new-aia-scottsdale-office');
assert.deepEqual(visitorLanguageChoices(untranslated, routeManifest), []);
});
test('menu destinations prefer eligible locale publications and expose deliberate fallbacks', () => {
assert.deepEqual(resolveNavigationDestination('/library', 'ar', routeManifest), {
href: '/ar/library', lang: 'ar', fallback: false, source: 'translation'
});
assert.deepEqual(resolveNavigationDestination('/ar/library', 'ar', routeManifest), {
href: '/ar/library', lang: 'ar', fallback: false, source: 'exact'
});
assert.deepEqual(resolveNavigationDestination('/ar', 'ar', routeManifest), {
href: '/', lang: 'en', fallback: true, source: 'explicit-fallback'
});
assert.deepEqual(resolveNavigationDestination('/team', 'ar', routeManifest), {
href: '/team', lang: 'en', fallback: true, source: 'explicit-fallback'
});
assert.equal(resolveNavigationDestination('/missing', 'es', routeManifest), null);
assert.deepEqual(resolveNavigationDestination('/library', 'es', [{
...routeManifest.find(({ route }) => route === '/library'), translations: []
}]), { href: '/library', lang: 'en', fallback: true, source: 'explicit-fallback' },
'Missing explicit equivalence falls back to the known published source instead of guessing /es/library');
});
test('author archives automatically follow multiple authors, exact locale references, draft removal and growth', () => {
const author = (locale, slug) => ({ id: `${locale}/${slug}`, data: { lang: locale, slug, name: `${locale} ${slug}` } });
const sources = [author('en', 'one'), author('es', 'one'), author('en', 'two'), author('en', 'empty'), author('en', 'draft-only')];
const post = (slug, authorSlug, lang = 'en', draft = false) => ({
id: `${lang}/${slug}`, collection: 'blog',
data: { lang, slug, title: slug, date: '2026-01-01', author: authorSlug, draft }
});
const posts = [...Array.from({ length: 11 }, (_, i) => post(`post-${String(i).padStart(2, '0')}`, 'one')),
post('spanish', 'one', 'es'), post('another', 'two'), post('unpublished', 'draft-only', 'en', true)];
const generate = (records) => {
const m = createPublicationManifest(records, site);
return createAuthorArchivePublications(createLibraryCatalog(m, sources), sources, m, site);
};
const generated = generate(posts);
assert.deepEqual(generated.map(({ route }) => route), [
'/es/library/author/one', '/library/author/one', '/library/author/one/page/2', '/library/author/two'
]);
assert.equal(generated.find(({ route }) => route === '/library/author/one').authorPage.totalItems, 11);
assert.ok(!generate(posts.slice(1)).some(({ route }) => route.endsWith('/page/2')));
assert.ok(!generate(posts.map((p, i) => i === 0 ? { ...p, data: { ...p.data, draft: true } } : p))
.some(({ route }) => route.endsWith('/page/2')));
assert.equal(generate([]).length, 0);
assert.ok(generate([...posts, post('new-author-post', 'empty')]).some(({ route }) => route === '/library/author/empty'));
});
test('author descriptors reject reserved routes including drafts, duplicate canonical targets, and invalid page paths', () => {
const root = manifest.find(({ route }) => route === '/library');
for (const route of ['/library/author', '/library/author/rula-diab', '/library/author/rula-diab/page/02', '/es/library/author/unknown']) {
assert.throws(() => createAuthorArchivePublications(catalog, authors, [...manifest, { ...root, route, eligible: false }], site), /Reserved author archive route collision/);
}
assert.throws(() => createAuthorArchivePublications(catalog, authors, [
...manifest, { ...root, route: '/other', canonical: `${site}/library/author/rula-diab` }
], site), /Duplicate canonical/);
for (const page of [0, -1, 1.5, NaN, Infinity, Number.MAX_SAFE_INTEGER + 1, '02', '2']) {
assert.throws(() => authorPageRoute('/library/author/rula-diab', page), /Invalid author page/);
}
assert.throws(() => authorPageRoute('/library/author/../bad', 1), /Invalid author archive root/);
});
test('pagination handles empty, boundary and exact-multiple catalogs without empty numbered pages', () => {
for (const [count, totalPages, pageSizes] of [
[0, 0, [0]], [1, 1, [1]], [9, 1, [9]], [10, 1, [10]],
[11, 2, [10, 1]], [20, 2, [10, 10]]
]) {
const items = Array.from({ length: count }, (_, index) => index);
const root = paginateLibraryItems(items, 1);
assert.equal(root.totalItems, count);
assert.equal(root.totalPages, totalPages);
assert.equal(root.items.length, pageSizes[0]);
for (let page = 2; page <= totalPages; page++) {
assert.equal(paginateLibraryItems(items, page).items.length, pageSizes[page - 1]);
}
assert.equal(paginateLibraryItems(items, Math.max(2, totalPages + 1)), null);
}
for (const page of [0, -1, 1.5, Number.NaN, Number.POSITIVE_INFINITY, Number.MAX_SAFE_INTEGER + 1]) {
assert.equal(paginateLibraryItems([1], page), null);
}
assert.equal(paginateLibraryItems([1], 1, 0), null);
});
test('current catalogs generate only five numbered Library routes with expected slices', () => {
assert.deepEqual(paginationPublications.map(({ route }) => route), [
'/es/library/page/2', '/library/page/2', '/library/page/3', '/library/page/4', '/library/page/5'
]);
assert.deepEqual(Object.fromEntries(['en', 'es', 'ar'].map((locale) => {
const root = paginateLibraryItems(catalog.byLocale[locale], 1);
return [locale, { total: root.totalItems, pages: root.totalPages }];
})), {
en: { total: 46, pages: 5 }, es: { total: 12, pages: 2 }, ar: { total: 7, pages: 1 }
});
assert.deepEqual([1, 2, 3, 4, 5].map((page) => paginateLibraryItems(catalog.byLocale.en, page).items.length), [10, 10, 10, 10, 6]);
assert.deepEqual([1, 2].map((page) => paginateLibraryItems(catalog.byLocale.es, page).items.length), [10, 2]);
assert.equal(paginateLibraryItems(catalog.byLocale.ar, 1).items.length, 7);
for (const publication of paginationPublications) {
assert.equal(publication.canonical, site + publication.route);
assert.equal(publication.sitemapEligible, true);
assert.deepEqual(publication.translations, []);
}
});
test('generated page inventory follows catalog growth and removal without manual routes', () => {
const english = catalog.byLocale.en;
const withFiveMore = [...english, ...english.slice(0, 5)];
const grown = { ...catalog, byLocale: { ...catalog.byLocale, en: withFiveMore } };
assert.ok(createLibraryPaginationPublications(grown, manifest, site).some(({ route }) => route === '/library/page/6'));
const reduced = { ...catalog, byLocale: { ...catalog.byLocale, en: english.slice(0, 40) } };
assert.deepEqual(createLibraryPaginationPublications(reduced, manifest, site)
.filter(({ libraryPage }) => libraryPage.locale === 'en').map(({ route }) => route), [
'/library/page/2', '/library/page/3', '/library/page/4'
]);
});
test('page routes reject invalid numbers and generated descriptors reject reserved-family collisions', () => {
assert.equal(libraryPageRoute('en', 1), '/library');
assert.equal(libraryPageRoute('es', 2), '/es/library/page/2');
for (const page of [0, -1, 1.5, Number.NaN, Number.MAX_SAFE_INTEGER + 1]) {
assert.throws(() => libraryPageRoute('en', page), /Invalid Library page number/);
}
const root = manifest.find((page) => page.route === '/library');
assert.throws(() => createLibraryPaginationPublications(catalog, [
...manifest,
{ ...root, route: '/library/page/2', url: `${site}/library/page/2`, canonical: `${site}/library/page/2` }
], site), /Reserved Library pagination route collision/);
});
test('catalog fixtures include new eligible posts automatically and exclude drafts', () => {
const author = [{ id: 'en/author', data: { lang: 'en', slug: 'author', name: 'Example Author' } }];
const make = (slug, draft = false) => ({
id: `en/${slug}`, collection: 'blog', body: '## Searchable heading',
data: { lang: 'en', slug, title: slug, description: '', date: '2026-01-01', author: 'author', category: 'Library', tags: [], draft }
});
const fixtureManifest = createPublicationManifest([make('new-post'), make('draft-post', true)], site);
const fixture = createLibraryCatalog(fixtureManifest, author);
assert.deepEqual(fixture.all.map(({ route }) => route), ['/library/new-post']);
assert.deepEqual(fixture.all[0].search.headings, ['Searchable heading']);
assert.equal(paginateLibraryItems(fixture.byLocale.en, 1).totalItems, 1);
});
test('catalog rejects duplicate routes and missing or ambiguous locale authors', () => {
const source = {
id: 'en/example', collection: 'blog', body: '',
data: { lang: 'en', slug: 'example', title: 'Example', description: '', date: '2026-01-01', author: 'author' }
};
const publication = { route: '/library/example', eligible: true, entry: source };
assert.throws(() => createLibraryCatalog([publication, publication], [{ id: 'a', data: { lang: 'en', slug: 'author', name: 'Author' } }]), /Duplicate Library route/);
assert.throws(() => createLibraryCatalog([publication], []), /Missing or ambiguous article author/);
assert.throws(() => createLibraryCatalog([publication], [
{ id: 'a', data: { lang: 'en', slug: 'author', name: 'Author' } },
{ id: 'b', data: { lang: 'en', slug: 'author', name: 'Other' } }
]), /Missing or ambiguous article author/);
});
test('heading extraction ignores fenced code and strips Markdown/MDX presentation syntax', () => {
assert.deepEqual(extractMarkdownHeadings(`## Visible **heading**\n\n\`\`\`md\n## Hidden code\n\`\`\`\n## [Linked heading](/path)\n## \n## `), [
'Visible heading', 'Linked heading', 'Image heading'
]);
});
test('normalization handles case, whitespace, punctuation, Spanish accents, and Arabic marks', () => {
assert.equal(normalizeLibrarySearchText(' EVALUACIÓN—ABA ', 'es'), 'evaluacion aba');
assert.equal(normalizeLibrarySearchText('التَّـوحُّد', 'ar'), 'التوحد');
assert.doesNotThrow(() => normalizeLibrarySearchText('\ud800 🙂 ???', 'en'));
});
test('search handles empty, exact-title, headings, descriptions, authors, categories and multiple terms', () => {
const records = [
record(),
record({ url: '/library/second', title: 'Second article', description: 'Autism support', publishedAt: '2026-02-01' })
];
assert.deepEqual(searchLibrary(records, ''), []);
assert.deepEqual(searchLibrary(records, ' '), []);
assert.equal(searchLibrary(records, 'Autism Support Guide')[0].record.url, '/library/example');
assert.equal(searchLibrary([records[0]], 'daily routines')[0].record.url, '/library/example');
assert.equal(searchLibrary([records[0]], 'family resources')[0].record.url, '/library/example');
assert.equal(searchLibrary([records[0]], 'Rula Diab')[0].record.url, '/library/example');
assert.equal(searchLibrary([records[0]], 'Library')[0].record.url, '/library/example');
assert.equal(searchLibrary(records, 'autism resources').length, 1);
assert.equal(searchLibrary(records, 'nonexistent').length, 0);
});
test('search matches Spanish accents and Arabic diacritics without changing display text', () => {
const spanish = record({ locale: 'es', title: 'Evaluación del autismo', url: '/es/library/evaluacion' });
const arabic = record({ locale: 'ar', title: 'دليل التَّـوحُّد', url: '/ar/library/guide' });
assert.equal(searchLibrary([spanish], 'evaluacion')[0].record.title, 'Evaluación del autismo');
assert.equal(searchLibrary([arabic], 'التوحد')[0].record.title, 'دليل التَّـوحُّد');
});
test('equal search scores use publication date then normalized route', () => {
const records = [
record({ url: '/library/z', title: 'Shared title', publishedAt: '2025-01-01' }),
record({ url: '/library/b', title: 'Shared title', publishedAt: '2026-01-01' }),
record({ url: '/library/a', title: 'Shared title', publishedAt: '2026-01-01' })
];
assert.deepEqual(searchLibrary(records, 'shared').map(({ record: item }) => item.url), ['/library/a', '/library/b', '/library/z']);
});
test('legacy query parsing gives term precedence, preserves repeated types, and normalizes offset/language', () => {
assert.deepEqual(parseLegacyLibrarySearchState('?term=autism&q=ignored&type=BLOG_POST&type=SITE_PAGE&lang=es&offset=20'), {
query: 'autism', language: 'es', offset: 20, types: ['BLOG_POST', 'SITE_PAGE']
});
assert.deepEqual(parseLegacyLibrarySearchState('?q=%3Cscript%3E&lang=unknown&offset=-10'), {
query: '