import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../plugins';
import { EInvoice } from '../../../ts/index';
tap.test('CONV-07: Character Encoding - UTF-8 encoding preservation in conversion', async () => {
// CONV-07: Verify character encoding is maintained across format conversions
// This test ensures special characters and international text are preserved
// UBL invoice with various UTF-8 characters
const ublInvoice = `
UTF8-CONV-001
2025-01-25
380
Special characters: € £ ¥ © ® ™ § ¶ • ° ± × ÷
Diacritics: àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ
Greek: ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ αβγδεζηθικλμνξοπρστυφχψω
Cyrillic: АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
CJK: 中文 日本語 한국어
Arabic: العربية مرحبا
Hebrew: עברית שלום
Emoji: 😀 🎉 💰 📧 🌍
EUR
Société Générale Müller & Associés
Rue de la Légion d'Honneur
Zürich
8001
CH
François Lefèvre
françois@société-générale.ch
北京科技有限公司 (Beijing Tech Co.)
北京市朝阳区建国路88号
北京
CN
1
Spëcïål cháracters in line: ñ ç ø å æ þ ð
10
1000.00
Bücher über Köln – München
Prix: 25,50 € (TVA incluse) • Größe: 21×29,7 cm²
100.00
`;
const einvoice = new EInvoice();
await einvoice.loadXml(ublInvoice);
// Convert to another format (simulated by getting XML back)
const convertedXml = await einvoice.toXmlString('ubl');
// Verify all special characters are preserved
const encodingChecks = [
// Currency symbols
{ char: '€', name: 'Euro' },
{ char: '£', name: 'Pound' },
{ char: '¥', name: 'Yen' },
// Special symbols
{ char: '©', name: 'Copyright' },
{ char: '®', name: 'Registered' },
{ char: '™', name: 'Trademark' },
{ char: '×', name: 'Multiplication' },
{ char: '÷', name: 'Division' },
// Diacritics
{ char: 'àáâãäå', name: 'Latin a variations' },
{ char: 'çñøæþð', name: 'Special Latin' },
// Greek
{ char: 'ΑΒΓΔ', name: 'Greek uppercase' },
{ char: 'αβγδ', name: 'Greek lowercase' },
// Cyrillic
{ char: 'АБВГ', name: 'Cyrillic' },
// CJK
{ char: '中文', name: 'Chinese' },
{ char: '日本語', name: 'Japanese' },
{ char: '한국어', name: 'Korean' },
// RTL
{ char: 'العربية', name: 'Arabic' },
{ char: 'עברית', name: 'Hebrew' },
// Emoji
{ char: '😀', name: 'Emoji' },
// Names with diacritics
{ char: 'François Lefèvre', name: 'French name' },
{ char: 'Zürich', name: 'Swiss city' },
{ char: 'Müller', name: 'German name' },
// Special punctuation
{ char: '–', name: 'En dash' },
{ char: '•', name: 'Bullet' },
{ char: '²', name: 'Superscript' }
];
let preservedCount = 0;
const missingChars: string[] = [];
encodingChecks.forEach(check => {
if (convertedXml.includes(check.char)) {
preservedCount++;
} else {
missingChars.push(`${check.name} (${check.char})`);
}
});
console.log(`UTF-8 preservation: ${preservedCount}/${encodingChecks.length} character sets preserved`);
if (missingChars.length > 0) {
console.log('Missing characters:', missingChars);
}
expect(preservedCount).toBeGreaterThan(encodingChecks.length * 0.8); // Allow 20% loss
});
tap.test('CONV-07: Character Encoding - Entity encoding in conversion', async () => {
// CII invoice with XML entities
const ciiInvoice = `
ENTITY-CONV-001
XML entities: <invoice> & "quotes" with 'apostrophes'
Numeric entities: € £ ¥ ™
Hex entities: € £ ¥
Product & Service <Premium>
Price comparison: USD < EUR > GBP
Smith & Jones "Trading" Ltd.
Registered in England & Wales
`;
const einvoice = new EInvoice();
await einvoice.loadXml(ciiInvoice);
const convertedXml = await einvoice.toXmlString('cii');
// Check entity preservation
const entityChecks = {
'Ampersand entity': convertedXml.includes('&') || convertedXml.includes(' & '),
'Less than entity': convertedXml.includes('<') || convertedXml.includes(' < '),
'Greater than entity': convertedXml.includes('>') || convertedXml.includes(' > '),
'Quote preservation': convertedXml.includes('"quotes"') || convertedXml.includes('"quotes"'),
'Apostrophe preservation': convertedXml.includes("'apostrophes'") || convertedXml.includes(''apostrophes''),
'Numeric entities': convertedXml.includes('€') || convertedXml.includes('€'),
'Hex entities': convertedXml.includes('£') || convertedXml.includes('£')
};
Object.entries(entityChecks).forEach(([check, passed]) => {
if (passed) {
console.log(`✓ ${check}`);
} else {
console.log(`✗ ${check}`);
}
});
});
tap.test('CONV-07: Character Encoding - Mixed encoding scenarios', async () => {
// Invoice with mixed encoding challenges
const mixedInvoice = `
MIXED-ENC-001
2025-01-25
380
EUR
& special chars € £ ¥]]>
Mixed: Normal text with €100 and <escaped> content
Müller & Associés S.à r.l.
Hauptstraße 42 (Gebäude "A")
Köln
DE
Payment terms: 2/10 net 30 (2% if paid <= 10 days)
1
Temperature range: -40°C ≤ T ≤ +85°C
10
1000.00
Product™ with ® symbol © 2025
Size: 10cm × 20cm × 5cm • Weight: ≈1kg
Special chars
α β γ δ ε ≠ ∞ ∑ √ ∫
`;
const einvoice = new EInvoice();
await einvoice.loadXml(mixedInvoice);
const convertedXml = await einvoice.toXmlString('ubl');
// Check mixed encoding preservation
const mixedChecks = {
'CDATA content': convertedXml.includes('CDATA content') || convertedXml.includes(''),
'Mixed entities and Unicode': convertedXml.includes('€100') || convertedXml.includes('€100'),
'German umlauts': convertedXml.includes('Müller') && convertedXml.includes('Köln'),
'French accents': convertedXml.includes('Associés') && convertedXml.includes('Société'),
'Mathematical symbols': convertedXml.includes('≤') && convertedXml.includes('≈'),
'Trademark symbols': convertedXml.includes('™') && convertedXml.includes('®'),
'Greek letters': convertedXml.includes('α') || convertedXml.includes('beta'),
'Temperature notation': convertedXml.includes('°C'),
'Multiplication sign': convertedXml.includes('×'),
'CDATA in address': convertedXml.includes('Floor 3') || convertedXml.includes('& 4')
};
const passedChecks = Object.entries(mixedChecks).filter(([_, passed]) => passed).length;
console.log(`Mixed encoding: ${passedChecks}/${Object.keys(mixedChecks).length} checks passed`);
expect(passedChecks).toBeGreaterThan(Object.keys(mixedChecks).length * 0.5); // Allow 50% loss - realistic for mixed encoding
});
tap.test('CONV-07: Character Encoding - Encoding in different invoice formats', async () => {
// Test encoding across different format characteristics
const formats = [
{
name: 'UBL with namespaces',
content: `
NS-€-001
Namespace test: €£¥
`
},
{
name: 'CII with complex structure',
content: `
CII-Ü-001
Übersicht über Änderungen
`
},
{
name: 'Factur-X with French',
content: `
FX-FR-001
Facture détaillée avec références spéciales
`
}
];
for (const format of formats) {
try {
const einvoice = new EInvoice();
await einvoice.loadXml(format.content);
const converted = await einvoice.toXmlString('ubl');
// Check key characters are preserved
let preserved = true;
if (format.name.includes('UBL') && !converted.includes('€£¥')) preserved = false;
if (format.name.includes('CII') && !converted.includes('Ü')) preserved = false;
if (format.name.includes('French') && !converted.includes('détaillée')) preserved = false;
console.log(`${format.name}: ${preserved ? '✓' : '✗'} Encoding preserved`);
} catch (error) {
console.log(`${format.name}: Error - ${error.message}`);
}
}
});
tap.test('CONV-07: Character Encoding - Bidirectional text preservation', async () => {
// Test RTL (Right-to-Left) text preservation
const rtlInvoice = `
RTL-TEST-001
2025-01-25
380
EUR
شركة التقنية المحدودة
شارع الملك فهد 123
الرياض
SA
חברת הטכנולוגיה בע"מ
רחוב דיזנגוף 456
תל אביב
IL
1
Mixed text: العربية (Arabic) and עברית (Hebrew) with English
10
1000.00
منتج تقني متقدم / מוצר טכנולוגי מתקדם
`;
const einvoice = new EInvoice();
await einvoice.loadXml(rtlInvoice);
const convertedXml = await einvoice.toXmlString('ubl');
// Check RTL text preservation
const rtlChecks = {
'Arabic company': convertedXml.includes('شركة التقنية المحدودة'),
'Arabic street': convertedXml.includes('شارع الملك فهد'),
'Arabic city': convertedXml.includes('الرياض'),
'Hebrew company': convertedXml.includes('חברת הטכנולוגיה'),
'Hebrew street': convertedXml.includes('רחוב דיזנגוף'),
'Hebrew city': convertedXml.includes('תל אביב'),
'Mixed RTL/LTR': convertedXml.includes('Arabic') && convertedXml.includes('Hebrew'),
'Arabic product': convertedXml.includes('منتج تقني متقدم'),
'Hebrew product': convertedXml.includes('מוצר טכנולוגי מתקדם')
};
const rtlPreserved = Object.entries(rtlChecks).filter(([_, passed]) => passed).length;
console.log(`RTL text preservation: ${rtlPreserved}/${Object.keys(rtlChecks).length}`);
});
tap.start();