260 lines
9.8 KiB
TypeScript
260 lines
9.8 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
tap.test('ENC-08: Mixed Content - should handle mixed text and element content', async () => {
|
|
console.log('Testing XML mixed content handling...\n');
|
|
|
|
// Test 1: Pure element content (structured only)
|
|
const testPureElementContent = async () => {
|
|
const einvoice = new EInvoice();
|
|
einvoice.id = 'MIXED-ELEMENT-TEST';
|
|
einvoice.issueDate = new Date(2025, 0, 25);
|
|
einvoice.subject = 'Pure element content test';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Test Company',
|
|
description: 'Testing pure element content',
|
|
address: {
|
|
streetName: 'Test 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: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: 'Test',
|
|
surname: 'Customer',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Test customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Test Product',
|
|
articleNumber: 'MIXED-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
// Generate XML and verify structure
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
// Check for proper element structure without mixed content
|
|
const hasProperStructure = xmlString.includes('<cbc:ID>MIXED-ELEMENT-TEST</cbc:ID>') &&
|
|
xmlString.includes('<cac:AccountingSupplierParty>') &&
|
|
xmlString.includes('<cac:Party>');
|
|
|
|
// Verify round-trip works
|
|
const newInvoice = new EInvoice();
|
|
await newInvoice.fromXmlString(xmlString);
|
|
|
|
const roundTripSuccess = (newInvoice.id === 'MIXED-ELEMENT-TEST' ||
|
|
newInvoice.invoiceId === 'MIXED-ELEMENT-TEST' ||
|
|
newInvoice.accountingDocId === 'MIXED-ELEMENT-TEST');
|
|
|
|
console.log(`Test 1 - Pure element content:`);
|
|
console.log(` Proper XML structure: ${hasProperStructure ? 'Yes' : 'No'}`);
|
|
console.log(` Round-trip successful: ${roundTripSuccess ? 'Yes' : 'No'}`);
|
|
|
|
return { hasProperStructure, roundTripSuccess };
|
|
};
|
|
|
|
// Test 2: Mixed content with text and elements
|
|
const testMixedContent = async () => {
|
|
// XML with mixed content (text + elements combined)
|
|
const mixedContentXml = `<?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:UBLVersionID>2.1</cbc:UBLVersionID>
|
|
<cbc:ID>MIXED-CONTENT-TEST</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Company Name with Text
|
|
<Element>nested element</Element> and more text
|
|
</cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:Note>This is a note with <strong>emphasis</strong> and additional text</cbc:Note>
|
|
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
|
<cac:Item>
|
|
<cbc:Name>Test Item</cbc:Name>
|
|
<cbc:Description>Item description with
|
|
<detail>detailed info</detail>
|
|
and more descriptive text
|
|
</cbc:Description>
|
|
</cac:Item>
|
|
</cac:InvoiceLine>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.fromXmlString(mixedContentXml);
|
|
|
|
// Check if mixed content is handled appropriately
|
|
const mixedContentHandled = einvoice.from?.name !== undefined &&
|
|
einvoice.items?.[0]?.name !== undefined;
|
|
|
|
console.log(`\nTest 2 - Mixed content parsing:`);
|
|
console.log(` Mixed content XML parsed: ${mixedContentHandled ? 'Yes' : 'No'}`);
|
|
console.log(` Supplier name extracted: ${einvoice.from?.name ? 'Yes' : 'No'}`);
|
|
console.log(` Item data extracted: ${einvoice.items?.[0]?.name ? 'Yes' : 'No'}`);
|
|
|
|
return { mixedContentHandled };
|
|
} catch (error) {
|
|
console.log(`\nTest 2 - Mixed content parsing:`);
|
|
console.log(` Mixed content parsing failed: ${error.message}`);
|
|
return { mixedContentHandled: false };
|
|
}
|
|
};
|
|
|
|
// Test 3: CDATA sections with mixed content
|
|
const testCDataMixedContent = async () => {
|
|
const cdataMixedXml = `<?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:UBLVersionID>2.1</cbc:UBLVersionID>
|
|
<cbc:ID>CDATA-MIXED-TEST</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name><![CDATA[Company & Co. with <special> chars]]></cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:Note><![CDATA[HTML content: <b>bold</b> and <i>italic</i> text]]></cbc:Note>
|
|
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
|
<cac:Item>
|
|
<cbc:Name>CDATA Test Item</cbc:Name>
|
|
<cbc:Description><![CDATA[
|
|
Multi-line description
|
|
with <XML> markup preserved
|
|
and "special" characters & symbols
|
|
]]></cbc:Description>
|
|
</cac:Item>
|
|
</cac:InvoiceLine>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.fromXmlString(cdataMixedXml);
|
|
|
|
const cdataHandled = einvoice.from?.name?.includes('&') &&
|
|
einvoice.from?.name?.includes('<') &&
|
|
einvoice.items?.[0]?.name === 'CDATA Test Item';
|
|
|
|
console.log(`\nTest 3 - CDATA mixed content:`);
|
|
console.log(` CDATA content preserved: ${cdataHandled ? 'Yes' : 'No'}`);
|
|
console.log(` Special characters handled: ${einvoice.from?.name?.includes('&') ? 'Yes' : 'No'}`);
|
|
|
|
return { cdataHandled };
|
|
} catch (error) {
|
|
console.log(`\nTest 3 - CDATA mixed content:`);
|
|
console.log(` CDATA parsing failed: ${error.message}`);
|
|
return { cdataHandled: false };
|
|
}
|
|
};
|
|
|
|
// Test 4: Whitespace handling in mixed content
|
|
const testWhitespaceHandling = async () => {
|
|
const whitespaceXml = `<?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:UBLVersionID>2.1</cbc:UBLVersionID>
|
|
<cbc:ID>WHITESPACE-TEST</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name> Company Name </cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
|
<cac:Item>
|
|
<cbc:Name>
|
|
Test Item
|
|
</cbc:Name>
|
|
</cac:Item>
|
|
</cac:InvoiceLine>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.fromXmlString(whitespaceXml);
|
|
|
|
// Check how whitespace is handled
|
|
const whitespacePreserved = einvoice.from?.name === ' Company Name ';
|
|
const whitespaceNormalized = einvoice.from?.name?.trim() === 'Company Name';
|
|
|
|
console.log(`\nTest 4 - Whitespace handling:`);
|
|
console.log(` Whitespace preserved: ${whitespacePreserved ? 'Yes' : 'No'}`);
|
|
console.log(` Whitespace normalized: ${whitespaceNormalized ? 'Yes' : 'No'}`);
|
|
console.log(` Company name value: "${einvoice.from?.name}"`);
|
|
|
|
return { whitespacePreserved, whitespaceNormalized };
|
|
} catch (error) {
|
|
console.log(`\nTest 4 - Whitespace handling:`);
|
|
console.log(` Whitespace test failed: ${error.message}`);
|
|
return { whitespacePreserved: false, whitespaceNormalized: false };
|
|
}
|
|
};
|
|
|
|
// Run all tests
|
|
const elementResult = await testPureElementContent();
|
|
const mixedResult = await testMixedContent();
|
|
const cdataResult = await testCDataMixedContent();
|
|
const whitespaceResult = await testWhitespaceHandling();
|
|
|
|
console.log(`\n=== XML Mixed Content Test Summary ===`);
|
|
console.log(`Pure element content: ${elementResult.hasProperStructure ? 'Working' : 'Issues'}`);
|
|
console.log(`Mixed content parsing: ${mixedResult.mixedContentHandled ? 'Working' : 'Issues'}`);
|
|
console.log(`CDATA mixed content: ${cdataResult.cdataHandled ? 'Working' : 'Issues'}`);
|
|
console.log(`Whitespace handling: ${whitespaceResult.whitespaceNormalized ? 'Working' : 'Issues'}`);
|
|
console.log(`Round-trip consistency: ${elementResult.roundTripSuccess ? 'Working' : 'Issues'}`);
|
|
|
|
// Test passes if basic element content and mixed content parsing work
|
|
expect(elementResult.hasProperStructure).toBeTrue();
|
|
expect(elementResult.roundTripSuccess).toBeTrue();
|
|
});
|
|
|
|
// Run the test
|
|
tap.start();
|