import { describe, it, expect } from 'vitest'; import { parseTranslation, processDocument, processPage, segmentId, validateTranslationPair, } from '../../scripts/content-segments.mjs'; interface Collected { id: string; markup: string; } function parse(body: string, head = ''): Document { return new DOMParser().parseFromString( `
${head}${body}`, 'text/html' ); } function collect(document: Document): Collected[] { const found: Collected[] = []; processDocument(document, { record: (id: string, markup: string): void => { found.push({ id, markup }); }, }); return found; } function translateWith( document: Document, dictionary: RecordRead the docs now.
'); expect(collect(doc).map((entry) => entry.markup)).toEqual([ 'Read <1>the docs1> now.', ]); }); it('keeps links intact and allows word order to change', () => { const doc = parse( 'Read the docs now.
' ); const result = translateWith(doc, { 'Read <1>the docs1> now.': 'Jetzt <1>die Doku1> lesen.', }); expect(result.translated).toBe(1); expect(doc.body.innerHTML).toBe( 'Jetzt die Doku lesen.
' ); }); it('treats code and icons as untouched placeholders', () => { const doc = parse( 'Use qpdf --check first
Zuerst qpdf --check nutzen
Read the docs now.
'); const result = translateWith(doc, { 'Read <1>the docs1> now.': 'Jetzt die Doku lesen.', }); expect(result.translated).toBe(0); expect(result.invalid).toHaveLength(1); expect(doc.body.innerHTML).toBe( 'Read the docs now.
' ); }); it('rejects unbalanced or foreign tags', () => { const doc = parse('One two three
'); const elements = { 1: doc.querySelector('b') as Element, }; expect(parseTranslation('Eins <1>zwei1> drei', elements)).not.toBeNull(); expect(parseTranslation('Eins <1>zwei drei', elements)).toBeNull(); expect(parseTranslation('Eins <2>zwei2> drei', elements)).toBeNull(); expect(parseTranslation('Eins ' ); translateWith(doc, { 'Is it free?<1/>': 'Ist es kostenlos?<1/>', 'Yes, always.': 'Ja, immer.', }); const data = JSON.parse( doc.querySelector('script[type="application/ld+json"]')?.textContent ?? '{}' ); expect(data.mainEntity[0].name).toBe('Ist es kostenlos?'); expect(data.mainEntity[0].acceptedAnswer.text).toBe('Ja, immer.'); }); it('validates source and translation pairs', () => { expect(validateTranslationPair('A <1>b1>', 'X <1>y1>')).toBeNull(); expect(validateTranslationPair('A <1>b1>', 'X y')).toBe( 'placeholders differ' ); expect(validateTranslationPair('A', '')).toBe('empty'); expect(validateTranslationPair('A', 'B c')).toBe( 'contains raw angle brackets' ); }); it('keeps segment ids stable so stored translations stay valid', () => { expect(segmentId('How to deskew PDF online')).toBe('001b11bf2ae1'); expect(segmentId('Merge PDF')).not.toBe(segmentId('Split PDF')); }); it('rejects translations that nest markers differently or use unknown entities', () => { expect( validateTranslationPair('A <1>b <2>c2>1>', 'X <1>y1> <2>z2>') ).toBe('placeholders nested differently'); expect(validateTranslationPair('A <1>b1>', 'X <1>y1><2>2>')).toBe( 'placeholders differ' ); expect(validateTranslationPair('A <1>b1>', 'X <1>y…1>')).toBe( 'unsupported entity …' ); expect(validateTranslationPair('A & B', 'X & Y')).toBeNull(); expect(validateTranslationPair('A <1/> b', '<1/> x')).toBeNull(); }); it('keeps translated markup-like text as plain text', () => { const doc = parse('Hello world
'); translateWith(doc, { 'Hello <1>world1>': 'Hallo <img onerror=alert(1)> <1>Welt1>', }); expect(doc.querySelector('img')).toBeNull(); expect(doc.querySelector('p')?.textContent).toBe( 'HalloTwo reasons:
Hello
' ); expect(collect(doc).map((entry) => entry.markup)).toEqual(['Hello']); }); });