PronounsPage/test/locale/blog.test.ts
Valentyne Stigloher 10180aa6a3 (refactor) use #shared alias instead of ~~/shared
the #shared alias used by Nuxt cannot be easily disabled and to prevent breackage with jiti, we make use of it
2025-08-17 18:56:02 +02:00

105 lines
3.7 KiB
TypeScript

import fs from 'node:fs/promises';
import type { AsyncExpectationResult } from '@vitest/expect';
import { HtmlValidate } from 'html-validate/node';
import type { Result } from 'html-validate/node';
import marked from 'marked';
import { describe, expect, test } from 'vitest';
import { extractMetadata } from '#shared/blog/metadata.ts';
import parseMarkdown from '#shared/parseMarkdown.ts';
import { Translator } from '#shared/translator.ts';
import type { Config } from '~~/locale/config.ts';
import allLocales from '~~/locale/locales.ts';
import type { Translations } from '~~/locale/translations.ts';
import { loadSuml } from '~~/server/loader.ts';
const validator = new HtmlValidate({
extends: [
'html-validate:recommended',
],
rules: {
'attr-case': 'off',
'element-required-attributes': 'off',
'no-deprecated-attr': 'off',
'no-inline-style': 'off',
'prefer-tbody': 'off',
'wcag/h30': 'off',
'wcag/h63': 'off',
// these originate from the current markdown parser
'valid-id': 'off',
'no-trailing-whitespace': 'off',
},
});
async function toBeValidHTML(actual: string): AsyncExpectationResult {
const validatorReport = await validator.validateString(actual);
const results: Result[] = validatorReport.results;
const messages = results
.flatMap((result) => result.messages)
.map((message) => `${message.ruleId} at ${message.line}:${message.column} (${message.selector})\n\t${message.message}`);
if (messages.length > 0) {
return {
message: () => `expected to be valid HTML\n\n${messages.join('\n')}`,
pass: false,
};
} else {
return {
message: () => 'expected to be invalid HTML',
pass: true,
};
}
}
interface CustomMatchers<R> {
toBeValidHTML(): Promise<R>;
}
declare module 'vitest' {
interface Assertion<T> extends CustomMatchers<T> {}
}
expect.extend({ toBeValidHTML });
const baseTranslations = await loadSuml<Translations>('locale/_base/translations.suml');
describe.each(allLocales)('blog in $code', async ({ code }) => {
const config = await loadSuml<Config>(`locale/${code}/config.suml`);
const translations = await loadSuml<Translations>(`locale/${code}/translations.suml`);
const translator = new Translator(translations, baseTranslations, config);
const blogDirectory = `locale/${code}/blog`;
const outputDirectory = `test/output/locale/${code}/blog`;
const slugs = (await fs.readdir(blogDirectory))
.filter((file) => file.endsWith('.md'))
.map((file) => file.replace(/\.md$/, ''));
test('contains posts when enabled', () => {
if (config.links.blog) {
expect(slugs).not.toHaveLength(0);
}
});
describe.each(slugs)('post %s', (slug) => {
test('has valid metadata', async () => {
const content = await fs.readFile(`${blogDirectory}/${slug}.md`, 'utf-8');
const metadata = extractMetadata(config, content);
expect(metadata?.title).toBeTruthy();
expect(metadata?.authors.length).toBeGreaterThan(0);
expect(metadata?.date).toMatch(/\d\d\d\d-\d\d-\d\d/);
});
test('is valid HTML', async () => {
const content = await fs.readFile(`${blogDirectory}/${slug}.md`, 'utf-8');
const parsed = marked(content);
const blogEntry = await parseMarkdown(parsed, translator);
await fs.mkdir(outputDirectory, { recursive: true });
await fs.writeFile(`${outputDirectory}/${slug}.html`, blogEntry.content ?? '');
await expect(blogEntry.content).toBeValidHTML();
});
});
});