Files
bentopdf/src/tests/docs-translations.test.ts
T

207 lines
6.9 KiB
TypeScript

// @vitest-environment node
import { describe, it, expect } from 'vitest';
import {
anchorMap,
brokenAnchors,
quoteFrontmatter,
removeGeneratedAnchors,
translateAnchors,
compareStructure,
localizeLinks,
summarize,
} from '../../scripts/docs-translations.mjs';
describe('localizeLinks', () => {
it('prefixes absolute docs links with the language', () => {
expect(localizeLinks('See [Docker](/self-hosting/docker#env).', 'de')).toBe(
'See [Docker](/de/self-hosting/docker#env).'
);
expect(localizeLinks('[Tools](/tools/)', 'zh-TW')).toBe(
'[Tools](/zh-TW/tools/)'
);
});
it('leaves images, files, external links, anchors and relative links alone', () => {
const text = [
'![Logo](/images/logo.png)',
'[PDF](/files/sample.pdf)',
'[Site](https://www.bentopdf.com/licensing)',
'[Mail](mailto:a@b.co)',
'[Top](#top)',
'[Next](./merge-pdf)',
'[Up](../index)',
].join('\n');
expect(localizeLinks(text, 'fr')).toBe(text);
});
it('does not touch code blocks and is safe to run twice', () => {
const text = '```md\n[x](/tools/)\n```\n\n[ok](/tools/)';
const once = localizeLinks(text, 'es');
expect(once).toBe('```md\n[x](/tools/)\n```\n\n[ok](/es/tools/)');
expect(localizeLinks(once, 'es')).toBe(once);
});
it('rewrites hero action links in frontmatter', () => {
const text = '---\nhero:\n actions:\n - link: /getting-started\n---';
expect(localizeLinks(text, 'ja')).toBe(
'---\nhero:\n actions:\n - link: /ja/getting-started\n---'
);
});
});
describe('compareStructure', () => {
const source = [
'---',
'title: Merge PDF',
'description: Combine files.',
'---',
'',
'# Merge PDF',
'',
'Read [the guide](/self-hosting/docker) first.',
'',
'```bash',
'npm run build',
'```',
'',
'| A | B |',
'|---|---|',
'| 1 | 2 |',
].join('\n');
it('accepts a faithful translation', () => {
const translated = [
'---',
'title: PDF zusammenführen',
'description: Dateien kombinieren.',
'---',
'',
'# PDF zusammenführen',
'',
'Lesen Sie zuerst [die Anleitung](/self-hosting/docker).',
'',
'```bash',
'npm run build',
'```',
'',
'| A | B |',
'|---|---|',
'| 1 | 2 |',
].join('\n');
expect(compareStructure(source, translated, 'de')).toEqual([]);
});
it('flags changed code, links, headings and em dashes', () => {
const translated = source
.replace('npm run build', 'npm ausführen')
.replace('/self-hosting/docker', '/other')
.replace('# Merge PDF', '## Merge PDF')
.replace('Read', 'Lesen —');
const problems = compareStructure(source, translated, 'de');
expect(problems).toContain('code blocks differ');
expect(problems).toContain('link targets differ');
expect(problems).toContain('heading levels differ');
expect(problems).toContain('contains an em dash');
});
it('treats localized absolute links as equal to the English ones', () => {
const translated = source.replace(
'/self-hosting/docker',
'/de/self-hosting/docker'
);
expect(compareStructure(source, translated, 'de')).not.toContain(
'link targets differ'
);
});
it('summarizes structure', () => {
const summary = summarize(source);
expect(summary.headings).toEqual([1]);
expect(summary.fences).toHaveLength(1);
expect(summary.tables).toEqual([3]);
expect(summary.frontmatterKeys).toEqual(['title', 'description']);
});
});
describe('anchors', () => {
const source =
'# Docker\n\n## Disabling Specific Tools\n\n### Disabling Specific Tools\n\n### 404 on Refresh\n\nSee [refresh](#_404-on-refresh) and [tools](/self-hosting/nginx#_502-bad-gateway).\n';
const translated =
'# Docker {#docker}\n\n## Werkzeuge deaktivieren {#disabling-specific-tools}\n\n### Werkzeuge deaktivieren\n\n### 404 beim Neuladen {#\\_404-on-refresh}\n\nSiehe [Neuladen](#_404-on-refresh) und [Werkzeuge](/de/self-hosting/nginx#_502-bad-gateway).\n';
it('removes generated heading ids, including ones Prettier escaped', () => {
const cleaned = removeGeneratedAnchors(source, translated);
expect(cleaned).not.toContain('{#');
expect(cleaned).toContain('### 404 beim Neuladen\n');
});
it('keeps explicit ids that the English page defines', () => {
expect(
removeGeneratedAnchors('## One {#custom}\n', '## Eins {#old}\n')
).toBe('## Eins {#custom}\n');
});
it('maps English heading ids to the translated ones, like VitePress', () => {
const cleaned = removeGeneratedAnchors(source, translated);
const { map } = anchorMap(source, cleaned);
expect(map.get('disabling-specific-tools')).toBe('werkzeuge-deaktivieren');
expect(map.get('disabling-specific-tools-1')).toBe(
'werkzeuge-deaktivieren-1'
);
expect(map.get('_404-on-refresh')).toBe('_404-beim-neuladen');
});
it('rewrites anchor links on the same page and on other pages', () => {
const cleaned = removeGeneratedAnchors(source, translated);
const nginxSource = '## 502 Bad Gateway\n';
const nginxTranslated = '## 502 Fehlerhaftes Gateway\n';
const anchors = new Map([
['self-hosting/vercel.md', anchorMap(source, cleaned)],
['self-hosting/nginx.md', anchorMap(nginxSource, nginxTranslated)],
]);
const result = translateAnchors(
cleaned,
'self-hosting/vercel.md',
'de',
anchors
);
expect(result).toContain('[Neuladen](#_404-beim-neuladen)');
expect(result).toContain(
'[Werkzeuge](/de/self-hosting/nginx#_502-fehlerhaftes-gateway)'
);
expect(
translateAnchors(result, 'self-hosting/vercel.md', 'de', anchors)
).toBe(result);
expect(
brokenAnchors(result, 'self-hosting/vercel.md', 'de', anchors)
).toEqual([]);
expect(
brokenAnchors('[x](#missing)', 'self-hosting/vercel.md', 'de', anchors)
).toEqual(['#missing']);
});
it('builds ids the same way VitePress does for code and punctuation', () => {
const { map } = anchorMap(
'## Step 3b: Response Headers Policy (Required)\n\n## `ROBOTS_NOINDEX` flag\n',
'## Schritt 3b: Antwort (Pflicht)\n\n## `ROBOTS_NOINDEX`-Schalter\n'
);
expect(map.get('step-3b-response-headers-policy-required')).toBe(
'schritt-3b-antwort-pflicht'
);
expect(map.get('robots-noindex-flag')).toBe('robots-noindex-schalter');
});
});
describe('quoteFrontmatter', () => {
it('quotes values that YAML would misread and leaves the rest alone', () => {
const input =
'---\ntitle: Merge PDF\ndescription: Entfernen: Autor, Titel\nnote: "already quoted: yes"\n---\n# Body: text\n';
expect(quoteFrontmatter(input)).toBe(
'---\ntitle: Merge PDF\ndescription: "Entfernen: Autor, Titel"\nnote: "already quoted: yes"\n---\n# Body: text\n'
);
expect(quoteFrontmatter(quoteFrontmatter(input))).toBe(
quoteFrontmatter(input)
);
});
});