mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-09 09:04:18 +01:00
207 lines
6.9 KiB
TypeScript
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 = [
|
|
'',
|
|
'[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)
|
|
);
|
|
});
|
|
});
|