2025-05-25 19:45:37 +00:00
|
|
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
|
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
|
|
2025-05-27 19:30:07 +00:00
|
|
|
|
tap.test('ENC-05: Special Characters - should handle special XML characters correctly', async () => {
|
2025-05-28 14:46:32 +00:00
|
|
|
|
console.log('Testing special character handling in XML content...\n');
|
|
|
|
|
|
|
|
|
|
// Test 1: Unicode special characters
|
|
|
|
|
const testUnicodeSpecialChars = async () => {
|
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
|
einvoice.id = 'UNICODE-SPECIAL-TEST';
|
|
|
|
|
einvoice.date = Date.now();
|
|
|
|
|
einvoice.currency = 'EUR';
|
|
|
|
|
|
|
|
|
|
// Test various special Unicode characters
|
|
|
|
|
const specialChars = {
|
|
|
|
|
mathematical: '∑∏∆∇∂∞≠≤≥±∓×÷√∝∴∵∠∟⊥∥∦',
|
|
|
|
|
currency: '€£¥₹₽₩₪₨₫₡₢₣₤₥₦₧₨₩₪₫',
|
|
|
|
|
symbols: '™®©℗℠⁋⁌⁍⁎⁏⁐⁑⁒⁓⁔⁕⁖⁗⁘⁙⁚⁛⁜⁝⁞',
|
|
|
|
|
arrows: '←→↑↓↔↕↖↗↘↙↚↛↜↝↞↟↠↡↢↣↤↥',
|
|
|
|
|
punctuation: '‚„"«»‹›§¶†‡•‰‱′″‴‵‶‷‸‼⁇⁈⁉⁊⁋⁌⁍⁎⁏'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
einvoice.subject = `Unicode test: ${specialChars.mathematical.substring(0, 10)}`;
|
|
|
|
|
einvoice.notes = [
|
|
|
|
|
`Math: ${specialChars.mathematical}`,
|
|
|
|
|
`Currency: ${specialChars.currency}`,
|
|
|
|
|
`Symbols: ${specialChars.symbols}`,
|
|
|
|
|
`Arrows: ${specialChars.arrows}`,
|
|
|
|
|
`Punctuation: ${specialChars.punctuation}`
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
|
type: 'company',
|
|
|
|
|
name: 'Special Characters Inc ™',
|
|
|
|
|
description: 'Company with special symbols: ®©',
|
|
|
|
|
address: {
|
|
|
|
|
streetName: 'Unicode Street ←→',
|
|
|
|
|
houseNumber: '∞',
|
|
|
|
|
postalCode: '12345',
|
|
|
|
|
city: 'Symbol City ≤≥',
|
|
|
|
|
country: 'DE'
|
|
|
|
|
},
|
|
|
|
|
status: 'active',
|
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
|
registrationDetails: {
|
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
|
registrationName: 'Special Registry ™'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
einvoice.to = {
|
|
|
|
|
type: 'company',
|
|
|
|
|
name: 'Customer Ltd ©',
|
|
|
|
|
description: 'Customer with currency: €£¥',
|
|
|
|
|
address: {
|
|
|
|
|
streetName: 'Currency Ave',
|
|
|
|
|
houseNumber: '€1',
|
|
|
|
|
postalCode: '54321',
|
|
|
|
|
city: 'Money City',
|
|
|
|
|
country: 'DE'
|
|
|
|
|
},
|
|
|
|
|
status: 'active',
|
|
|
|
|
foundedDate: { year: 2019, month: 1, day: 1 },
|
|
|
|
|
registrationDetails: {
|
|
|
|
|
vatId: 'DE987654321',
|
|
|
|
|
registrationId: 'HRB 54321',
|
|
|
|
|
registrationName: 'Customer Registry'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
einvoice.items = [{
|
|
|
|
|
position: 1,
|
|
|
|
|
name: 'Product with symbols: ∑∏∆',
|
|
|
|
|
unitType: 'C62',
|
|
|
|
|
unitQuantity: 1,
|
|
|
|
|
unitNetPrice: 100,
|
|
|
|
|
vatPercentage: 19
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
|
|
|
|
|
|
// Check if special characters are preserved or properly encoded
|
|
|
|
|
const mathPreserved = specialChars.mathematical.split('').filter(char =>
|
|
|
|
|
xmlString.includes(char) ||
|
|
|
|
|
xmlString.includes(`&#${char.charCodeAt(0)};`) ||
|
|
|
|
|
xmlString.includes(`&#x${char.charCodeAt(0).toString(16)};`)
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
const currencyPreserved = specialChars.currency.split('').filter(char =>
|
|
|
|
|
xmlString.includes(char) ||
|
|
|
|
|
xmlString.includes(`&#${char.charCodeAt(0)};`) ||
|
|
|
|
|
xmlString.includes(`&#x${char.charCodeAt(0).toString(16)};`)
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
const symbolsPreserved = specialChars.symbols.split('').filter(char =>
|
|
|
|
|
xmlString.includes(char) ||
|
|
|
|
|
xmlString.includes(`&#${char.charCodeAt(0)};`) ||
|
|
|
|
|
xmlString.includes(`&#x${char.charCodeAt(0).toString(16)};`)
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
mathPreserved,
|
|
|
|
|
currencyPreserved,
|
|
|
|
|
symbolsPreserved,
|
|
|
|
|
totalMath: specialChars.mathematical.length,
|
|
|
|
|
totalCurrency: specialChars.currency.length,
|
|
|
|
|
totalSymbols: specialChars.symbols.length,
|
|
|
|
|
xmlString
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const unicodeResult = await testUnicodeSpecialChars();
|
|
|
|
|
console.log('Test 1 - Unicode special characters:');
|
|
|
|
|
console.log(` Mathematical symbols: ${unicodeResult.mathPreserved}/${unicodeResult.totalMath} preserved`);
|
|
|
|
|
console.log(` Currency symbols: ${unicodeResult.currencyPreserved}/${unicodeResult.totalCurrency} preserved`);
|
|
|
|
|
console.log(` Other symbols: ${unicodeResult.symbolsPreserved}/${unicodeResult.totalSymbols} preserved`);
|
|
|
|
|
|
|
|
|
|
// Test 2: Control characters and whitespace
|
|
|
|
|
const testControlCharacters = async () => {
|
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
|
einvoice.id = 'CONTROL-CHARS-TEST';
|
|
|
|
|
einvoice.date = Date.now();
|
|
|
|
|
einvoice.currency = 'EUR';
|
|
|
|
|
|
|
|
|
|
// Test various whitespace and control characters
|
|
|
|
|
einvoice.subject = 'Control chars test:\ttab\nnewline\rcarriage return';
|
|
|
|
|
einvoice.notes = [
|
|
|
|
|
'Tab separated:\tvalue1\tvalue2\tvalue3',
|
|
|
|
|
'Line break:\nSecond line\nThird line',
|
|
|
|
|
'Mixed whitespace: spaces \t tabs \r\n mixed'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
|
type: 'company',
|
|
|
|
|
name: 'Control\tCharacters\nCompany',
|
|
|
|
|
description: 'Company\twith\ncontrol\rcharacters',
|
|
|
|
|
address: {
|
|
|
|
|
streetName: 'Control Street',
|
|
|
|
|
houseNumber: '1',
|
|
|
|
|
postalCode: '12345',
|
|
|
|
|
city: 'Test City',
|
|
|
|
|
country: 'DE'
|
|
|
|
|
},
|
|
|
|
|
status: 'active',
|
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
|
registrationDetails: {
|
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
|
registrationName: 'Registry'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
einvoice.to = {
|
|
|
|
|
type: 'company',
|
|
|
|
|
name: 'Customer',
|
|
|
|
|
description: 'Normal customer',
|
|
|
|
|
address: {
|
|
|
|
|
streetName: 'Customer Street',
|
|
|
|
|
houseNumber: '2',
|
|
|
|
|
postalCode: '54321',
|
|
|
|
|
city: 'Customer City',
|
|
|
|
|
country: 'DE'
|
|
|
|
|
},
|
|
|
|
|
status: 'active',
|
|
|
|
|
foundedDate: { year: 2019, month: 1, day: 1 },
|
|
|
|
|
registrationDetails: {
|
|
|
|
|
vatId: 'DE987654321',
|
|
|
|
|
registrationId: 'HRB 54321',
|
|
|
|
|
registrationName: 'Customer Registry'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
einvoice.items = [{
|
|
|
|
|
position: 1,
|
|
|
|
|
name: 'Product\twith\ncontrol\rchars',
|
|
|
|
|
unitType: 'C62',
|
|
|
|
|
unitQuantity: 1,
|
|
|
|
|
unitNetPrice: 100,
|
|
|
|
|
vatPercentage: 19
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
|
|
|
|
|
|
// Check how control characters are handled
|
|
|
|
|
const hasTabHandling = xmlString.includes('	') || xmlString.includes('	') ||
|
|
|
|
|
xmlString.includes('\t') || !xmlString.includes('Control\tCharacters');
|
|
|
|
|
const hasNewlineHandling = xmlString.includes(' ') || xmlString.includes('
') ||
|
|
|
|
|
xmlString.includes('\n') || !xmlString.includes('Characters\nCompany');
|
|
|
|
|
const hasCarriageReturnHandling = xmlString.includes(' ') || xmlString.includes('
') ||
|
|
|
|
|
xmlString.includes('\r') || !xmlString.includes('control\rcharacters');
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
hasTabHandling,
|
|
|
|
|
hasNewlineHandling,
|
|
|
|
|
hasCarriageReturnHandling,
|
|
|
|
|
xmlString
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const controlResult = await testControlCharacters();
|
|
|
|
|
console.log('\nTest 2 - Control characters and whitespace:');
|
|
|
|
|
console.log(` Tab handling: ${controlResult.hasTabHandling ? 'Yes' : 'No'}`);
|
|
|
|
|
console.log(` Newline handling: ${controlResult.hasNewlineHandling ? 'Yes' : 'No'}`);
|
|
|
|
|
console.log(` Carriage return handling: ${controlResult.hasCarriageReturnHandling ? 'Yes' : 'No'}`);
|
|
|
|
|
|
|
|
|
|
// Test 3: Emojis and extended Unicode
|
|
|
|
|
const testEmojisAndExtended = async () => {
|
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
|
einvoice.id = 'EMOJI-TEST';
|
|
|
|
|
einvoice.date = Date.now();
|
|
|
|
|
einvoice.currency = 'EUR';
|
|
|
|
|
|
|
|
|
|
// Test emojis and extended Unicode
|
|
|
|
|
const emojis = '😀😃😄😁😆😅🤣😂🙂🙃😉😊😇🥰😍🤩😘😗☺😚😙🥲😋😛😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐😑😶😏😒🙄😬🤥😌😔😪🤤😴😷🤒🤕🤢🤮🤧🥵🥶🥴😵🤯🤠🥳🥸😎🤓🧐😕😟🙁☹😮😯😲😳🥺😦😧😨😰😥😢😭😱😖😣😞😓😩😫🥱😤😡😠🤬😈👿💀☠💩🤡👹👺👻👽👾🤖😺😸😹😻😼😽🙀😿😾🙈🙉🙊💋💌💘💝💖💗💓💞💕💟❣💔❤🧡💛💚💙💜🤎🖤🤍💯💢💥💫💦💨🕳💣💬👁🗨🗯💭💤';
|
|
|
|
|
|
|
|
|
|
einvoice.subject = `Emoji test: ${emojis.substring(0, 20)}`;
|
|
|
|
|
einvoice.notes = [
|
|
|
|
|
`Faces: ${emojis.substring(0, 50)}`,
|
|
|
|
|
`Hearts: 💋💌💘💝💖💗💓💞💕💟❣💔❤🧡💛💚💙💜🤎🖤🤍`,
|
|
|
|
|
`Objects: 💯💢💥💫💦💨🕳💣💬👁🗨🗯💭💤`
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
|
type: 'company',
|
|
|
|
|
name: 'Emoji Company 😊',
|
|
|
|
|
description: 'Company with emojis 🏢',
|
|
|
|
|
address: {
|
|
|
|
|
streetName: 'Happy Street 😃',
|
|
|
|
|
houseNumber: '1️⃣',
|
|
|
|
|
postalCode: '12345',
|
|
|
|
|
city: 'Emoji City 🌆',
|
|
|
|
|
country: 'DE'
|
|
|
|
|
},
|
|
|
|
|
status: 'active',
|
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
|
registrationDetails: {
|
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
|
registrationName: 'Registry 📝'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
einvoice.to = {
|
|
|
|
|
type: 'company',
|
|
|
|
|
name: 'Customer 🛍️',
|
|
|
|
|
description: 'Shopping customer',
|
|
|
|
|
address: {
|
|
|
|
|
streetName: 'Customer Street',
|
|
|
|
|
houseNumber: '2',
|
|
|
|
|
postalCode: '54321',
|
|
|
|
|
city: 'Customer City',
|
|
|
|
|
country: 'DE'
|
|
|
|
|
},
|
|
|
|
|
status: 'active',
|
|
|
|
|
foundedDate: { year: 2019, month: 1, day: 1 },
|
|
|
|
|
registrationDetails: {
|
|
|
|
|
vatId: 'DE987654321',
|
|
|
|
|
registrationId: 'HRB 54321',
|
|
|
|
|
registrationName: 'Customer Registry'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
einvoice.items = [{
|
|
|
|
|
position: 1,
|
|
|
|
|
name: 'Emoji Product 📦',
|
|
|
|
|
unitType: 'C62',
|
|
|
|
|
unitQuantity: 1,
|
|
|
|
|
unitNetPrice: 100,
|
|
|
|
|
vatPercentage: 19
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
|
|
|
|
|
|
// Check if emojis are preserved or encoded
|
|
|
|
|
const emojiCount = emojis.split('').filter(char => {
|
|
|
|
|
const codePoint = char.codePointAt(0);
|
|
|
|
|
return codePoint && codePoint > 0xFFFF; // Emojis are typically above the BMP
|
|
|
|
|
}).length;
|
|
|
|
|
|
|
|
|
|
const preservedEmojis = emojis.split('').filter(char => {
|
|
|
|
|
const codePoint = char.codePointAt(0);
|
|
|
|
|
if (!codePoint || codePoint <= 0xFFFF) return false;
|
|
|
|
|
return xmlString.includes(char) ||
|
|
|
|
|
xmlString.includes(`&#${codePoint};`) ||
|
|
|
|
|
xmlString.includes(`&#x${codePoint.toString(16)};`);
|
|
|
|
|
}).length;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
emojiCount,
|
|
|
|
|
preservedEmojis,
|
|
|
|
|
preservationRate: emojiCount > 0 ? (preservedEmojis / emojiCount) * 100 : 0
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const emojiResult = await testEmojisAndExtended();
|
|
|
|
|
console.log('\nTest 3 - Emojis and extended Unicode:');
|
|
|
|
|
console.log(` Emoji preservation: ${emojiResult.preservedEmojis}/${emojiResult.emojiCount} (${emojiResult.preservationRate.toFixed(1)}%)`);
|
|
|
|
|
|
|
|
|
|
// Test 4: XML predefined entities in content
|
|
|
|
|
const testXmlPredefinedEntities = async () => {
|
|
|
|
|
const xmlWithEntities = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
|
|
|
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
|
|
|
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
|
|
|
|
<cbc:ID>ENTITIES-TEST</cbc:ID>
|
|
|
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
|
|
|
<cbc:Note>Entities: & < > " '</cbc:Note>
|
|
|
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
|
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
|
|
|
<cac:AccountingSupplierParty>
|
|
|
|
|
<cac:Party>
|
|
|
|
|
<cac:PartyName>
|
|
|
|
|
<cbc:Name>Entity & Company</cbc:Name>
|
|
|
|
|
</cac:PartyName>
|
|
|
|
|
<cac:PostalAddress>
|
|
|
|
|
<cbc:StreetName><Special> Street</cbc:StreetName>
|
|
|
|
|
<cbc:CityName>Entity City</cbc:CityName>
|
|
|
|
|
<cbc:PostalZone>12345</cbc:PostalZone>
|
|
|
|
|
<cac:Country>
|
|
|
|
|
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
|
|
|
|
</cac:Country>
|
|
|
|
|
</cac:PostalAddress>
|
|
|
|
|
</cac:Party>
|
|
|
|
|
</cac:AccountingSupplierParty>
|
|
|
|
|
<cac:AccountingCustomerParty>
|
|
|
|
|
<cac:Party>
|
|
|
|
|
<cac:PartyName>
|
|
|
|
|
<cbc:Name>Customer "Quotes"</cbc:Name>
|
|
|
|
|
</cac:PartyName>
|
|
|
|
|
<cac:PostalAddress>
|
|
|
|
|
<cbc:StreetName>Customer Street</cbc:StreetName>
|
|
|
|
|
<cbc:CityName>Customer City</cbc:CityName>
|
|
|
|
|
<cbc:PostalZone>54321</cbc:PostalZone>
|
|
|
|
|
<cac:Country>
|
|
|
|
|
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
|
|
|
|
</cac:Country>
|
|
|
|
|
</cac:PostalAddress>
|
|
|
|
|
</cac:Party>
|
|
|
|
|
</cac:AccountingCustomerParty>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>1</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Item>
|
|
|
|
|
<cbc:Name>Product 'Apostrophe'</cbc:Name>
|
|
|
|
|
</cac:Item>
|
|
|
|
|
</cac:InvoiceLine>
|
2025-05-25 19:45:37 +00:00
|
|
|
|
</Invoice>`;
|
2025-05-28 14:46:32 +00:00
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const invoice = await EInvoice.fromXml(xmlWithEntities);
|
2025-05-27 19:30:07 +00:00
|
|
|
|
|
2025-05-28 14:46:32 +00:00
|
|
|
|
const supplierName = invoice.from?.name || '';
|
|
|
|
|
const customerName = invoice.to?.name || '';
|
|
|
|
|
const itemName = invoice.items?.[0]?.name || '';
|
2025-05-27 19:30:07 +00:00
|
|
|
|
|
2025-05-28 14:46:32 +00:00
|
|
|
|
const entitiesDecoded =
|
|
|
|
|
supplierName.includes('Entity & Company') &&
|
|
|
|
|
customerName.includes('Customer "Quotes"') &&
|
|
|
|
|
itemName.includes("Product 'Apostrophe'");
|
2025-05-27 19:30:07 +00:00
|
|
|
|
|
2025-05-28 14:46:32 +00:00
|
|
|
|
return {
|
|
|
|
|
success: invoice.id === 'ENTITIES-TEST',
|
|
|
|
|
entitiesDecoded,
|
|
|
|
|
supplierName,
|
|
|
|
|
customerName,
|
|
|
|
|
itemName
|
2025-05-27 19:30:07 +00:00
|
|
|
|
};
|
2025-05-28 14:46:32 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: error.message
|
2025-05-27 19:30:07 +00:00
|
|
|
|
};
|
2025-05-25 19:45:37 +00:00
|
|
|
|
}
|
2025-05-28 14:46:32 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const entitiesResult = await testXmlPredefinedEntities();
|
|
|
|
|
console.log('\nTest 4 - XML predefined entities:');
|
|
|
|
|
console.log(` Invoice parsed: ${entitiesResult.success ? 'Yes' : 'No'}`);
|
|
|
|
|
console.log(` Entities decoded: ${entitiesResult.entitiesDecoded ? 'Yes' : 'No'}`);
|
|
|
|
|
if (entitiesResult.error) {
|
|
|
|
|
console.log(` Error: ${entitiesResult.error}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-27 19:30:07 +00:00
|
|
|
|
// Summary
|
2025-05-28 14:46:32 +00:00
|
|
|
|
console.log('\n=== Special Characters Test Summary ===');
|
|
|
|
|
const unicodeScore = (unicodeResult.mathPreserved + unicodeResult.currencyPreserved + unicodeResult.symbolsPreserved) /
|
|
|
|
|
(unicodeResult.totalMath + unicodeResult.totalCurrency + unicodeResult.totalSymbols) * 100;
|
|
|
|
|
console.log(`Unicode symbols: ${unicodeScore.toFixed(1)}% preserved`);
|
|
|
|
|
console.log(`Control characters: ${controlResult.hasTabHandling && controlResult.hasNewlineHandling ? 'Handled' : 'Issues'}`);
|
|
|
|
|
console.log(`Emojis: ${emojiResult.preservationRate.toFixed(1)}% preserved`);
|
|
|
|
|
console.log(`XML entities: ${entitiesResult.success && entitiesResult.entitiesDecoded ? 'Working' : 'Issues'}`);
|
|
|
|
|
|
|
|
|
|
// Tests pass if basic functionality works
|
|
|
|
|
expect(unicodeScore).toBeGreaterThan(50); // At least 50% of Unicode symbols preserved
|
|
|
|
|
expect(entitiesResult.success).toEqual(true);
|
|
|
|
|
expect(entitiesResult.entitiesDecoded).toEqual(true);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
2025-05-28 14:46:32 +00:00
|
|
|
|
console.log('\n✓ Special characters test completed');
|
2025-05-25 19:45:37 +00:00
|
|
|
|
});
|
|
|
|
|
|
2025-05-28 14:46:32 +00:00
|
|
|
|
tap.start();
|