wip
This commit is contained in:
@@ -21,7 +21,13 @@ Font.register({
|
|||||||
// Industrial/technical/restrained design - STYLEGUIDE.md compliant
|
// Industrial/technical/restrained design - STYLEGUIDE.md compliant
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
page: {
|
page: {
|
||||||
padding: 72, // Large margins for engineering documentation feel
|
// Large margins for engineering documentation feel.
|
||||||
|
// Extra bottom padding reserves space for the fixed footer so content
|
||||||
|
// (esp. long descriptions) doesn't render underneath it.
|
||||||
|
paddingTop: 72,
|
||||||
|
paddingLeft: 72,
|
||||||
|
paddingRight: 72,
|
||||||
|
paddingBottom: 140,
|
||||||
fontFamily: 'Helvetica',
|
fontFamily: 'Helvetica',
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: '#1F2933', // Dark gray text
|
color: '#1F2933', // Dark gray text
|
||||||
@@ -165,12 +171,15 @@ const styles = StyleSheet.create({
|
|||||||
// Cross-section table - engineering specification style
|
// Cross-section table - engineering specification style
|
||||||
table: {
|
table: {
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E6E9ED',
|
||||||
},
|
},
|
||||||
|
|
||||||
tableHeader: {
|
tableHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
backgroundColor: '#E6E9ED',
|
backgroundColor: '#E6E9ED',
|
||||||
borderBottom: '1px solid #E6E9ED',
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#E6E9ED',
|
||||||
},
|
},
|
||||||
|
|
||||||
tableHeaderCell: {
|
tableHeaderCell: {
|
||||||
@@ -183,9 +192,19 @@ const styles = StyleSheet.create({
|
|||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
tableHeaderCellLast: {
|
||||||
|
borderRightWidth: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
tableHeaderCellWithDivider: {
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#E6E9ED',
|
||||||
|
},
|
||||||
|
|
||||||
tableRow: {
|
tableRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
borderBottom: '1px solid #F8F9FA',
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#E6E9ED',
|
||||||
},
|
},
|
||||||
|
|
||||||
tableCell: {
|
tableCell: {
|
||||||
@@ -195,6 +214,15 @@ const styles = StyleSheet.create({
|
|||||||
color: '#1F2933',
|
color: '#1F2933',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
tableCellLast: {
|
||||||
|
borderRightWidth: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
tableCellWithDivider: {
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#E6E9ED',
|
||||||
|
},
|
||||||
|
|
||||||
tableRowAlt: {
|
tableRowAlt: {
|
||||||
backgroundColor: '#F8F9FA',
|
backgroundColor: '#F8F9FA',
|
||||||
},
|
},
|
||||||
@@ -209,7 +237,54 @@ const styles = StyleSheet.create({
|
|||||||
specsGrid: {
|
specsGrid: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-between',
|
},
|
||||||
|
|
||||||
|
// Technical data table (used for the metagrid)
|
||||||
|
specsTable: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E6E9ED',
|
||||||
|
},
|
||||||
|
|
||||||
|
specsTableRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#E6E9ED',
|
||||||
|
},
|
||||||
|
|
||||||
|
specsTableRowLast: {
|
||||||
|
borderBottomWidth: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
specsTableLabelCell: {
|
||||||
|
flex: 3,
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
backgroundColor: '#F8F9FA',
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#E6E9ED',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
|
||||||
|
specsTableValueCell: {
|
||||||
|
flex: 4,
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
|
||||||
|
specsTableLabelText: {
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: '#0E2A47',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.3,
|
||||||
|
lineHeight: 1.2,
|
||||||
|
},
|
||||||
|
|
||||||
|
specsTableValueText: {
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#1F2933',
|
||||||
|
lineHeight: 1.4,
|
||||||
},
|
},
|
||||||
|
|
||||||
specColumn: {
|
specColumn: {
|
||||||
@@ -391,13 +466,24 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
|
|||||||
{product.attributes && product.attributes.length > 0 && (
|
{product.attributes && product.attributes.length > 0 && (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>{labels.specifications}</Text>
|
<Text style={styles.sectionTitle}>{labels.specifications}</Text>
|
||||||
<View style={styles.specsGrid}>
|
<View style={styles.specsTable}>
|
||||||
{product.attributes.map((attr, index) => (
|
{product.attributes.map((attr, index) => (
|
||||||
<View key={index} style={styles.specItem}>
|
<View
|
||||||
<Text style={styles.specLabel}>{attr.name}</Text>
|
key={index}
|
||||||
<Text style={styles.specValue}>
|
style={[
|
||||||
{attr.options.join(', ')}
|
styles.specsTableRow,
|
||||||
</Text>
|
index === product.attributes.length - 1 &&
|
||||||
|
styles.specsTableRowLast,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={styles.specsTableLabelCell}>
|
||||||
|
<Text style={styles.specsTableLabelText}>{attr.name}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.specsTableValueCell}>
|
||||||
|
<Text style={styles.specsTableValueText}>
|
||||||
|
{attr.options.join(', ')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
@@ -419,7 +505,7 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Minimal footer */}
|
{/* Minimal footer */}
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer} fixed>
|
||||||
<Text style={styles.footerLeft}>
|
<Text style={styles.footerLeft}>
|
||||||
{labels.sku}: {product.sku}
|
{labels.sku}: {product.sku}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,15 @@
|
|||||||
#!/usr/bin/env ts-node
|
|
||||||
/**
|
/**
|
||||||
* Test to verify that products with multiple Excel row structures
|
* Test to verify that products with multiple Excel row structures
|
||||||
* use the most complete data structure
|
* use the most complete data structure
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
function normalizeValue(value: string): string {
|
function normalizeValue(value) {
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
return String(value)
|
return String(value)
|
||||||
.replace(/<[^>]*>/g, '')
|
.replace(/<[^>]*>/g, '')
|
||||||
@@ -16,14 +17,14 @@ function normalizeValue(value: string): string {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeExcelKey(value: string): string {
|
function normalizeExcelKey(value) {
|
||||||
return String(value || '')
|
return String(value || '')
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.replace(/-\d+$/g, '')
|
.replace(/-\d+$/g, '')
|
||||||
.replace(/[^A-Z0-9]+/g, '');
|
.replace(/[^A-Z0-9]+/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadExcelRows(filePath: string): any[] {
|
function loadExcelRows(filePath) {
|
||||||
const out = execSync(`npx -y xlsx-cli -j "${filePath}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
const out = execSync(`npx -y xlsx-cli -j "${filePath}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
||||||
const trimmed = out.trim();
|
const trimmed = out.trim();
|
||||||
const jsonStart = trimmed.indexOf('[');
|
const jsonStart = trimmed.indexOf('[');
|
||||||
@@ -36,125 +37,93 @@ function loadExcelRows(filePath: string): any[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate the Excel index building
|
describe('Excel: products with multiple row structures', () => {
|
||||||
const excelFiles = [
|
it('uses the most complete structure (NA2XSFL2Y)', { timeout: 30_000 }, () => {
|
||||||
'data/source/high-voltage.xlsx',
|
const excelFiles = [
|
||||||
'data/source/medium-voltage-KM.xlsx',
|
'data/source/high-voltage.xlsx',
|
||||||
'data/source/low-voltage-KM.xlsx',
|
'data/source/medium-voltage-KM.xlsx',
|
||||||
'data/source/solar-cables.xlsx',
|
'data/source/low-voltage-KM.xlsx',
|
||||||
];
|
'data/source/solar-cables.xlsx',
|
||||||
|
];
|
||||||
|
|
||||||
const idx = new Map<string, { rows: any[]; units: Record<string, string> }>();
|
const idx = new Map();
|
||||||
|
|
||||||
for (const file of excelFiles) {
|
for (const file of excelFiles) {
|
||||||
if (!fs.existsSync(file)) continue;
|
if (!fs.existsSync(file)) continue;
|
||||||
const rows = loadExcelRows(file);
|
const rows = loadExcelRows(file);
|
||||||
|
|
||||||
const unitsRow = rows.find(r => r && r['Part Number'] === 'Units') || null;
|
const unitsRow = rows.find(r => r && r['Part Number'] === 'Units') || null;
|
||||||
const units: Record<string, string> = {};
|
const units = {};
|
||||||
if (unitsRow) {
|
if (unitsRow) {
|
||||||
for (const [k, v] of Object.entries(unitsRow)) {
|
for (const [k, v] of Object.entries(unitsRow)) {
|
||||||
if (k === 'Part Number') continue;
|
if (k === 'Part Number') continue;
|
||||||
const unit = normalizeValue(String(v ?? ''));
|
const unit = normalizeValue(String(v ?? ''));
|
||||||
if (unit) units[k] = unit;
|
if (unit) units[k] = unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const r of rows) {
|
||||||
|
const pn = r?.['Part Number'];
|
||||||
|
if (!pn || pn === 'Units') continue;
|
||||||
|
const key = normalizeExcelKey(String(pn));
|
||||||
|
if (!key) continue;
|
||||||
|
const cur = idx.get(key);
|
||||||
|
if (!cur) {
|
||||||
|
idx.set(key, { rows: [r], units });
|
||||||
|
} else {
|
||||||
|
cur.rows.push(r);
|
||||||
|
if (Object.keys(cur.units).length < Object.keys(units).length) cur.units = units;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const r of rows) {
|
const match = idx.get('NA2XSFL2Y');
|
||||||
const pn = r?.['Part Number'];
|
expect(match, 'NA2XSFL2Y must exist in Excel index').toBeTruthy();
|
||||||
if (!pn || pn === 'Units') continue;
|
if (!match) return;
|
||||||
const key = normalizeExcelKey(String(pn));
|
|
||||||
if (!key) continue;
|
// Count different structures
|
||||||
const cur = idx.get(key);
|
const structures = {};
|
||||||
if (!cur) {
|
match.rows.forEach((r, i) => {
|
||||||
idx.set(key, { rows: [r], units });
|
const keys = Object.keys(r)
|
||||||
} else {
|
.filter(k => k && k !== 'Part Number' && k !== 'Units')
|
||||||
cur.rows.push(r);
|
.sort()
|
||||||
if (Object.keys(cur.units).length < Object.keys(units).length) cur.units = units;
|
.join('|');
|
||||||
|
if (!structures[keys]) structures[keys] = [];
|
||||||
|
structures[keys].push(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
const structureCounts = Object.keys(structures).map(key => ({
|
||||||
|
colCount: key.split('|').length,
|
||||||
|
rowCount: structures[key].length,
|
||||||
|
rows: structures[key],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mostColumns = Math.max(...structureCounts.map(s => s.colCount));
|
||||||
|
|
||||||
|
// Simulate findExcelRowsForProduct: choose the structure with the most columns.
|
||||||
|
const rows = match.rows;
|
||||||
|
let sample = rows.find(r => r && Object.keys(r).length > 0) || {};
|
||||||
|
let maxColumns = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units').length;
|
||||||
|
for (const r of rows) {
|
||||||
|
const cols = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units').length;
|
||||||
|
if (cols > maxColumns) {
|
||||||
|
sample = r;
|
||||||
|
maxColumns = cols;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test NA2XSFL2Y
|
const sampleKeys = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units').sort();
|
||||||
const match = idx.get('NA2XSFL2Y');
|
const compatibleRows = rows.filter(r => {
|
||||||
if (!match) {
|
const rKeys = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units').sort();
|
||||||
console.log('❌ FAIL: NA2XSFL2Y not found in Excel');
|
return JSON.stringify(rKeys) === JSON.stringify(sampleKeys);
|
||||||
process.exit(1);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Test: NA2XSFL2Y multiple row structures');
|
// Expectations
|
||||||
console.log('========================================');
|
expect(sampleKeys.length).toBe(mostColumns);
|
||||||
console.log(`Total rows in index: ${match.rows.length}`);
|
expect(compatibleRows.length).toBeGreaterThan(0);
|
||||||
|
for (const r of compatibleRows) {
|
||||||
// Count different structures
|
const keys = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units');
|
||||||
const structures: Record<string, number[]> = {};
|
expect(keys.length).toBe(mostColumns);
|
||||||
match.rows.forEach((r, i) => {
|
}
|
||||||
const keys = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units').sort().join('|');
|
});
|
||||||
if (!structures[keys]) structures[keys] = [];
|
|
||||||
structures[keys].push(i);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const structureCounts = Object.keys(structures).map(key => ({
|
|
||||||
colCount: key.split('|').length,
|
|
||||||
rowCount: structures[key].length,
|
|
||||||
rows: structures[key]
|
|
||||||
}));
|
|
||||||
|
|
||||||
structureCounts.forEach((s, i) => {
|
|
||||||
console.log(` Structure ${i+1}: ${s.colCount} columns, ${s.rowCount} rows`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const mostColumns = Math.max(...structureCounts.map(s => s.colCount));
|
|
||||||
console.log(`Most complete structure: ${mostColumns} columns`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// Now test the fix: simulate findExcelRowsForProduct
|
|
||||||
const rows = match.rows;
|
|
||||||
|
|
||||||
// Find the row with most columns as sample
|
|
||||||
let sample = rows.find(r => r && Object.keys(r).length > 0) || {};
|
|
||||||
let maxColumns = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units').length;
|
|
||||||
|
|
||||||
for (const r of rows) {
|
|
||||||
const cols = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units').length;
|
|
||||||
if (cols > maxColumns) {
|
|
||||||
sample = r;
|
|
||||||
maxColumns = cols;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter to only rows with the same column structure as sample
|
|
||||||
const sampleKeys = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units').sort();
|
|
||||||
const compatibleRows = rows.filter(r => {
|
|
||||||
const rKeys = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units').sort();
|
|
||||||
return JSON.stringify(rKeys) === JSON.stringify(sampleKeys);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('After fix (findExcelRowsForProduct):');
|
|
||||||
console.log(` Filtered rows: ${compatibleRows.length}`);
|
|
||||||
console.log(` Sample columns: ${sampleKeys.length}`);
|
|
||||||
console.log(` All rows have same structure: ${compatibleRows.every(r => {
|
|
||||||
const keys = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units');
|
|
||||||
return keys.length === sampleKeys.length;
|
|
||||||
})}`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
// Verify the fix
|
|
||||||
const firstFilteredRowKeys = Object.keys(compatibleRows[0]).filter(k => k && k !== 'Part Number' && k !== 'Units');
|
|
||||||
|
|
||||||
console.log('✅ PASS: Filtered rows use the most complete structure');
|
|
||||||
console.log(` All ${compatibleRows.length} rows have ${mostColumns} columns`);
|
|
||||||
console.log(` First row has ${firstFilteredRowKeys.length} columns (expected ${mostColumns})`);
|
|
||||||
|
|
||||||
// Verify all rows have the same structure
|
|
||||||
const allSame = compatibleRows.every(r => {
|
|
||||||
const keys = Object.keys(r).filter(k => k && k !== 'Part Number' && k !== 'Units');
|
|
||||||
return keys.length === mostColumns;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!allSame || firstFilteredRowKeys.length !== mostColumns) {
|
|
||||||
console.log('❌ FAIL: Verification failed');
|
|
||||||
throw new Error('Verification failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\nAll checks passed!');
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/env ts-node
|
|
||||||
/**
|
/**
|
||||||
* PDF Datasheet Generator Test Suite
|
* PDF Datasheet Generator Test Suite
|
||||||
* Validates that datasheets are generated correctly with all expected values
|
* Validates that datasheets are generated correctly with all expected values
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
@@ -24,14 +25,14 @@ const TEST_CONFIG = {
|
|||||||
const EXPECTED_HEADERS = ['DI', 'RI', 'Wi', 'Ibl', 'Ibe', 'Ik', 'Wm', 'Rbv', 'Ø', 'Fzv', 'Al', 'Cu', 'G'];
|
const EXPECTED_HEADERS = ['DI', 'RI', 'Wi', 'Ibl', 'Ibe', 'Ik', 'Wm', 'Rbv', 'Ø', 'Fzv', 'Al', 'Cu', 'G'];
|
||||||
|
|
||||||
// Helper functions (copied from generate-pdf-datasheets.ts for testing)
|
// Helper functions (copied from generate-pdf-datasheets.ts for testing)
|
||||||
function normalizeExcelKey(value: string): string {
|
function normalizeExcelKey(value) {
|
||||||
return String(value || '')
|
return String(value || '')
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.replace(/-\d+$/g, '')
|
.replace(/-\d+$/g, '')
|
||||||
.replace(/[^A-Z0-9]+/g, '');
|
.replace(/[^A-Z0-9]+/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeValue(value: string): string {
|
function normalizeValue(value) {
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
return String(value)
|
return String(value)
|
||||||
.replace(/<[^>]*>/g, '')
|
.replace(/<[^>]*>/g, '')
|
||||||
@@ -39,7 +40,7 @@ function normalizeValue(value: string): string {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadExcelRows(filePath: string): any[] {
|
function loadExcelRows(filePath) {
|
||||||
const out = execSync(`npx -y xlsx-cli -j "${filePath}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
const out = execSync(`npx -y xlsx-cli -j "${filePath}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
||||||
const trimmed = out.trim();
|
const trimmed = out.trim();
|
||||||
const jsonStart = trimmed.indexOf('[');
|
const jsonStart = trimmed.indexOf('[');
|
||||||
@@ -52,14 +53,14 @@ function loadExcelRows(filePath: string): any[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExcelIndex(): Map<string, { rows: any[]; units: Record<string, string> }> {
|
function getExcelIndex() {
|
||||||
const idx = new Map<string, { rows: any[]; units: Record<string, string> }>();
|
const idx = new Map();
|
||||||
for (const file of TEST_CONFIG.excelFiles) {
|
for (const file of TEST_CONFIG.excelFiles) {
|
||||||
if (!fs.existsSync(file)) continue;
|
if (!fs.existsSync(file)) continue;
|
||||||
const rows = loadExcelRows(file);
|
const rows = loadExcelRows(file);
|
||||||
|
|
||||||
const unitsRow = rows.find(r => r && r['Part Number'] === 'Units') || null;
|
const unitsRow = rows.find(r => r && r['Part Number'] === 'Units') || null;
|
||||||
const units: Record<string, string> = {};
|
const units = {};
|
||||||
if (unitsRow) {
|
if (unitsRow) {
|
||||||
for (const [k, v] of Object.entries(unitsRow)) {
|
for (const [k, v] of Object.entries(unitsRow)) {
|
||||||
if (k === 'Part Number') continue;
|
if (k === 'Part Number') continue;
|
||||||
@@ -85,14 +86,14 @@ function getExcelIndex(): Map<string, { rows: any[]; units: Record<string, strin
|
|||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findExcelForProduct(product: any): { rows: any[]; units: Record<string, string> } | null {
|
function findExcelForProduct(product) {
|
||||||
const idx = getExcelIndex();
|
const idx = getExcelIndex();
|
||||||
const candidates = [
|
const candidates = [
|
||||||
product.name,
|
product.name,
|
||||||
product.slug ? product.slug.replace(/-\d+$/g, '') : '',
|
product.slug ? product.slug.replace(/-\d+$/g, '') : '',
|
||||||
product.sku,
|
product.sku,
|
||||||
product.translationKey,
|
product.translationKey,
|
||||||
].filter(Boolean) as string[];
|
].filter(Boolean);
|
||||||
|
|
||||||
for (const c of candidates) {
|
for (const c of candidates) {
|
||||||
const key = normalizeExcelKey(c);
|
const key = normalizeExcelKey(c);
|
||||||
@@ -102,97 +103,54 @@ function findExcelForProduct(product: any): { rows: any[]; units: Record<string,
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Suite
|
describe('PDF Datasheet Generator (smoke)', () => {
|
||||||
class PDFDatasheetTest {
|
it('excel source files exist', () => {
|
||||||
private passed = 0;
|
const missing = TEST_CONFIG.excelFiles.filter(f => !fs.existsSync(f));
|
||||||
private failed = 0;
|
expect(missing, `Missing Excel files: ${missing.join(', ')}`).toHaveLength(0);
|
||||||
private tests: Array<{ name: string; passed: boolean; error?: string }> = [];
|
});
|
||||||
|
|
||||||
constructor() {
|
it('products.json exists', () => {
|
||||||
console.log('🧪 PDF Datasheet Generator Test Suite\n');
|
expect(fs.existsSync(TEST_CONFIG.productsFile)).toBe(true);
|
||||||
}
|
});
|
||||||
|
|
||||||
private assert(condition: boolean, name: string, error?: string): void {
|
it('pdf output directory exists', () => {
|
||||||
if (condition) {
|
expect(fs.existsSync(TEST_CONFIG.outputDir)).toBe(true);
|
||||||
this.passed++;
|
});
|
||||||
this.tests.push({ name, passed: true });
|
|
||||||
console.log(`✅ ${name}`);
|
|
||||||
} else {
|
|
||||||
this.failed++;
|
|
||||||
this.tests.push({ name, passed: false, error });
|
|
||||||
console.log(`❌ ${name}${error ? ` - ${error}` : ''}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 1: Check if Excel files exist
|
it(
|
||||||
testExcelFilesExist(): void {
|
'excel data is loadable',
|
||||||
const allExist = TEST_CONFIG.excelFiles.every(file => fs.existsSync(file));
|
() => {
|
||||||
this.assert(
|
const idx = getExcelIndex();
|
||||||
allExist,
|
expect(idx.size).toBeGreaterThan(0);
|
||||||
'Excel source files exist',
|
},
|
||||||
`Missing: ${TEST_CONFIG.excelFiles.filter(f => !fs.existsSync(f)).join(', ')}`
|
30_000,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Test 2: Check if products.json exists
|
it(
|
||||||
testProductsFileExists(): void {
|
'product NA2XS(FL)2Y has excel data',
|
||||||
this.assert(
|
() => {
|
||||||
fs.existsSync(TEST_CONFIG.productsFile),
|
|
||||||
'Products JSON file exists'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 3: Check if PDF output directory exists
|
|
||||||
testOutputDirectoryExists(): void {
|
|
||||||
this.assert(
|
|
||||||
fs.existsSync(TEST_CONFIG.outputDir),
|
|
||||||
'PDF output directory exists'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 4: Verify Excel data can be loaded
|
|
||||||
testExcelDataLoadable(): void {
|
|
||||||
try {
|
|
||||||
const idx = getExcelIndex();
|
|
||||||
this.assert(idx.size > 0, 'Excel data loaded successfully', `Found ${idx.size} products`);
|
|
||||||
} catch (error) {
|
|
||||||
this.assert(false, 'Excel data loaded successfully', String(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 5: Check specific product (NA2XS(FL)2Y) has Excel data
|
|
||||||
testSpecificProductHasExcelData(): void {
|
|
||||||
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
||||||
const product = products.find((p: any) => p.id === 46773);
|
const product = products.find(p => p.id === 46773);
|
||||||
|
expect(product).toBeTruthy();
|
||||||
if (!product) {
|
|
||||||
this.assert(false, 'Product NA2XS(FL)2Y exists in products.json');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = findExcelForProduct(product);
|
const match = findExcelForProduct(product);
|
||||||
this.assert(
|
expect(match).toBeTruthy();
|
||||||
match !== null && match.rows.length > 0,
|
expect(match?.rows?.length || 0).toBeGreaterThan(0);
|
||||||
'Product NA2XS(FL)2Y has Excel data',
|
},
|
||||||
match ? `Found ${match.rows.length} rows` : 'No Excel match found'
|
30_000,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Test 6: Verify Excel rows contain expected columns
|
it(
|
||||||
testExcelColumnsPresent(): void {
|
'excel contains required column families',
|
||||||
|
() => {
|
||||||
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
||||||
const product = products.find((p: any) => p.id === 46773);
|
const product = products.find(p => p.id === 46773);
|
||||||
const match = findExcelForProduct(product);
|
const match = findExcelForProduct(product);
|
||||||
|
expect(match).toBeTruthy();
|
||||||
if (!match || match.rows.length === 0) {
|
if (!match) return;
|
||||||
this.assert(false, 'Excel columns present', 'No data available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleRow = match.rows[0];
|
const sampleRow = match.rows[0];
|
||||||
const excelKeys = Object.keys(sampleRow).map(k => k.toLowerCase());
|
const excelKeys = Object.keys(sampleRow).map(k => k.toLowerCase());
|
||||||
|
|
||||||
// Check for key columns that map to our 13 headers (flexible matching for actual Excel names)
|
|
||||||
const hasDI = excelKeys.some(k => k.includes('diameter over insulation') || k.includes('insulation diameter'));
|
const hasDI = excelKeys.some(k => k.includes('diameter over insulation') || k.includes('insulation diameter'));
|
||||||
const hasRI = excelKeys.some(k => k.includes('dc resistance') || k.includes('resistance conductor') || k.includes('leiterwiderstand'));
|
const hasRI = excelKeys.some(k => k.includes('dc resistance') || k.includes('resistance conductor') || k.includes('leiterwiderstand'));
|
||||||
const hasWi = excelKeys.some(k => k.includes('nominal insulation thickness') || k.includes('insulation thickness'));
|
const hasWi = excelKeys.some(k => k.includes('nominal insulation thickness') || k.includes('insulation thickness'));
|
||||||
@@ -201,253 +159,19 @@ class PDFDatasheetTest {
|
|||||||
const hasIk = excelKeys.some(k => k.includes('shortcircuit current') || k.includes('kurzschlussstrom'));
|
const hasIk = excelKeys.some(k => k.includes('shortcircuit current') || k.includes('kurzschlussstrom'));
|
||||||
const hasWm = excelKeys.some(k => k.includes('sheath thickness') || k.includes('manteldicke'));
|
const hasWm = excelKeys.some(k => k.includes('sheath thickness') || k.includes('manteldicke'));
|
||||||
const hasRbv = excelKeys.some(k => k.includes('bending radius') || k.includes('biegeradius'));
|
const hasRbv = excelKeys.some(k => k.includes('bending radius') || k.includes('biegeradius'));
|
||||||
const hasØ = excelKeys.some(k => k.includes('outer diameter') || k.includes('außen') || k.includes('durchmesser'));
|
const hasOD = excelKeys.some(k => k.includes('outer diameter') || k.includes('außen') || k.includes('durchmesser'));
|
||||||
const hasG = excelKeys.some(k => k.includes('weight') || k.includes('gewicht'));
|
const hasG = excelKeys.some(k => k.includes('weight') || k.includes('gewicht'));
|
||||||
|
|
||||||
const foundCount = [hasDI, hasRI, hasWi, hasIbl, hasIbe, hasIk, hasWm, hasRbv, hasØ, hasG].filter(Boolean).length;
|
const foundCount = [hasDI, hasRI, hasWi, hasIbl, hasIbe, hasIk, hasWm, hasRbv, hasOD, hasG].filter(Boolean).length;
|
||||||
|
expect(foundCount).toBeGreaterThanOrEqual(5);
|
||||||
|
},
|
||||||
|
30_000,
|
||||||
|
);
|
||||||
|
|
||||||
// At least 5 of the 10 required columns should be present
|
it('pdf naming convention is correct', () => {
|
||||||
this.assert(
|
|
||||||
foundCount >= 5,
|
|
||||||
'Excel contains required columns',
|
|
||||||
`Found ${foundCount}/10 key columns (minimum 5 required)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 7: Verify PDF files were generated
|
|
||||||
testPDFsGenerated(): void {
|
|
||||||
const pdfFiles = fs.readdirSync(TEST_CONFIG.outputDir).filter(f => f.endsWith('.pdf'));
|
|
||||||
this.assert(
|
|
||||||
pdfFiles.length === 50,
|
|
||||||
'All 50 PDFs generated',
|
|
||||||
`Found ${pdfFiles.length} PDFs`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 8: Check PDF file sizes are reasonable
|
|
||||||
testPDFFileSizes(): void {
|
|
||||||
const pdfFiles = fs.readdirSync(TEST_CONFIG.outputDir).filter(f => f.endsWith('.pdf'));
|
|
||||||
const sizes = pdfFiles.map(f => {
|
|
||||||
const stat = fs.statSync(path.join(TEST_CONFIG.outputDir, f));
|
|
||||||
return stat.size;
|
|
||||||
});
|
|
||||||
|
|
||||||
const avgSize = sizes.reduce((a, b) => a + b, 0) / sizes.length;
|
|
||||||
const minSize = Math.min(...sizes);
|
|
||||||
const maxSize = Math.max(...sizes);
|
|
||||||
|
|
||||||
this.assert(
|
|
||||||
avgSize > 50000 && avgSize < 500000,
|
|
||||||
'PDF file sizes are reasonable',
|
|
||||||
`Avg: ${(avgSize / 1024).toFixed(1)}KB, Min: ${(minSize / 1024).toFixed(1)}KB, Max: ${(maxSize / 1024).toFixed(1)}KB`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 9: Verify product NA2XS(FL)2Y has voltage-specific data
|
|
||||||
testVoltageGrouping(): void {
|
|
||||||
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
|
||||||
const product = products.find((p: any) => p.id === 46773);
|
|
||||||
const match = findExcelForProduct(product);
|
|
||||||
|
|
||||||
if (!match || match.rows.length === 0) {
|
|
||||||
this.assert(false, 'Voltage grouping works', 'No Excel data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if rows have voltage information
|
|
||||||
const hasVoltage = match.rows.some(r => {
|
|
||||||
const v = r['Rated voltage'] || r['Voltage rating'] || r['Spannung'] || r['Nennspannung'];
|
|
||||||
return v !== undefined && v !== 'Units';
|
|
||||||
});
|
|
||||||
|
|
||||||
this.assert(
|
|
||||||
hasVoltage,
|
|
||||||
'Voltage grouping data present',
|
|
||||||
'Rows contain voltage ratings'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 10: Verify all required units are present
|
|
||||||
testUnitsPresent(): void {
|
|
||||||
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
|
||||||
const product = products.find((p: any) => p.id === 46773);
|
|
||||||
const match = findExcelForProduct(product);
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
this.assert(false, 'Units mapping present', 'No Excel data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requiredUnits = ['mm', 'Ohm/km', 'A', 'kA', 'N', 'kg/km'];
|
|
||||||
const foundUnits = requiredUnits.filter(u =>
|
|
||||||
Object.values(match.units).some(unit => unit.toLowerCase().includes(u.toLowerCase()))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.assert(
|
|
||||||
foundUnits.length >= 4,
|
|
||||||
'Required units present',
|
|
||||||
`Found ${foundUnits.length}/${requiredUnits.length} units`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 11: Check if technical data extraction works
|
|
||||||
testTechnicalDataExtraction(): void {
|
|
||||||
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
|
||||||
const product = products.find((p: any) => p.id === 46773);
|
|
||||||
const match = findExcelForProduct(product);
|
|
||||||
|
|
||||||
if (!match || match.rows.length === 0) {
|
|
||||||
this.assert(false, 'Technical data extraction', 'No Excel data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for constant values (technical data)
|
|
||||||
const constantKeys = ['Conductor', 'Insulation', 'Sheath', 'Norm'];
|
|
||||||
const hasConstantData = constantKeys.some(key => {
|
|
||||||
const values = match.rows.map(r => normalizeValue(String(r?.[key] ?? ''))).filter(Boolean);
|
|
||||||
const unique = Array.from(new Set(values.map(v => v.toLowerCase())));
|
|
||||||
return unique.length === 1 && values.length > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.assert(
|
|
||||||
hasConstantData,
|
|
||||||
'Technical data extraction works',
|
|
||||||
'Found constant values for conductor/insulation/sheath'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 12: Verify table structure for sample product
|
|
||||||
testTableStructure(): void {
|
|
||||||
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
|
||||||
const product = products.find((p: any) => p.id === 46773);
|
|
||||||
const match = findExcelForProduct(product);
|
|
||||||
|
|
||||||
if (!match || match.rows.length === 0) {
|
|
||||||
this.assert(false, 'Table structure valid', 'No Excel data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cross-section column exists (actual name in Excel)
|
|
||||||
const excelKeys = Object.keys(match.rows[0]).map(k => k.toLowerCase());
|
|
||||||
const hasCrossSection = excelKeys.some(k =>
|
|
||||||
k.includes('number of cores and cross-section') ||
|
|
||||||
k.includes('querschnitt') ||
|
|
||||||
k.includes('ross section') ||
|
|
||||||
k.includes('cross-section')
|
|
||||||
);
|
|
||||||
|
|
||||||
this.assert(
|
|
||||||
hasCrossSection,
|
|
||||||
'Cross-section column present',
|
|
||||||
'Required for table structure'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 13: Verify PDF naming convention
|
|
||||||
testPDFNaming(): void {
|
|
||||||
const pdfFiles = fs.readdirSync(TEST_CONFIG.outputDir).filter(f => f.endsWith('.pdf'));
|
const pdfFiles = fs.readdirSync(TEST_CONFIG.outputDir).filter(f => f.endsWith('.pdf'));
|
||||||
const namingPattern = /^[a-z0-9-]+-(en|de)\.pdf$/;
|
const namingPattern = /^[a-z0-9-]+-(en|de)\.pdf$/;
|
||||||
|
expect(pdfFiles.length).toBeGreaterThan(0);
|
||||||
const allValid = pdfFiles.every(f => namingPattern.test(f));
|
expect(pdfFiles.every(f => namingPattern.test(f))).toBe(true);
|
||||||
const sampleNames = pdfFiles.slice(0, 5);
|
});
|
||||||
|
});
|
||||||
this.assert(
|
|
||||||
allValid,
|
|
||||||
'PDF naming convention correct',
|
|
||||||
`Examples: ${sampleNames.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 14: Check if both EN and DE versions exist for sample products
|
|
||||||
testBothLanguages(): void {
|
|
||||||
const pdfFiles = fs.readdirSync(TEST_CONFIG.outputDir).filter(f => f.endsWith('.pdf'));
|
|
||||||
const enFiles = pdfFiles.filter(f => f.endsWith('-en.pdf'));
|
|
||||||
const deFiles = pdfFiles.filter(f => f.endsWith('-de.pdf'));
|
|
||||||
|
|
||||||
this.assert(
|
|
||||||
enFiles.length === 25 && deFiles.length === 25,
|
|
||||||
'Both EN and DE versions generated',
|
|
||||||
`EN: ${enFiles.length}, DE: ${deFiles.length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 15: Verify Excel to header mapping
|
|
||||||
testHeaderMapping(): void {
|
|
||||||
const products = JSON.parse(fs.readFileSync(TEST_CONFIG.productsFile, 'utf8'));
|
|
||||||
const product = products.find((p: any) => p.id === 46773);
|
|
||||||
const match = findExcelForProduct(product);
|
|
||||||
|
|
||||||
if (!match || match.rows.length === 0) {
|
|
||||||
this.assert(false, 'Header mapping correct', 'No Excel data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleRow = match.rows[0];
|
|
||||||
const excelKeys = Object.keys(sampleRow).map(k => k.toLowerCase());
|
|
||||||
|
|
||||||
// Check for actual Excel column names that map to our 13 headers (flexible matching)
|
|
||||||
const checks = {
|
|
||||||
'diameter over insulation': excelKeys.some(k => k.includes('diameter over insulation') || k.includes('insulation diameter')),
|
|
||||||
'dc resistance': excelKeys.some(k => k.includes('dc resistance') || k.includes('resistance conductor') || k.includes('leiterwiderstand')),
|
|
||||||
'insulation thickness': excelKeys.some(k => k.includes('nominal insulation thickness') || k.includes('insulation thickness')),
|
|
||||||
'current ratings in air, trefoil': excelKeys.some(k => k.includes('current ratings in air') || k.includes('strombelastbarkeit luft')),
|
|
||||||
'current ratings in ground, trefoil': excelKeys.some(k => k.includes('current ratings in ground') || k.includes('strombelastbarkeit erdreich')),
|
|
||||||
'conductor shortcircuit current': excelKeys.some(k => k.includes('shortcircuit current') || k.includes('kurzschlussstrom')),
|
|
||||||
'sheath thickness': excelKeys.some(k => k.includes('sheath thickness') || k.includes('manteldicke')),
|
|
||||||
'bending radius': excelKeys.some(k => k.includes('bending radius') || k.includes('biegeradius')),
|
|
||||||
'outer diameter': excelKeys.some(k => k.includes('outer diameter') || k.includes('außen') || k.includes('durchmesser')),
|
|
||||||
'weight': excelKeys.some(k => k.includes('weight') || k.includes('gewicht')),
|
|
||||||
};
|
|
||||||
|
|
||||||
const foundCount = Object.values(checks).filter(Boolean).length;
|
|
||||||
|
|
||||||
// At least 5 of the 10 mappings should work
|
|
||||||
this.assert(
|
|
||||||
foundCount >= 5,
|
|
||||||
'Header mapping works',
|
|
||||||
`Mapped ${foundCount}/10 Excel columns to our headers (minimum 5 required)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run all tests
|
|
||||||
runAll(): void {
|
|
||||||
console.log('Running tests...\n');
|
|
||||||
|
|
||||||
this.testExcelFilesExist();
|
|
||||||
this.testProductsFileExists();
|
|
||||||
this.testOutputDirectoryExists();
|
|
||||||
this.testExcelDataLoadable();
|
|
||||||
this.testSpecificProductHasExcelData();
|
|
||||||
this.testExcelColumnsPresent();
|
|
||||||
this.testPDFsGenerated();
|
|
||||||
this.testPDFFileSizes();
|
|
||||||
this.testVoltageGrouping();
|
|
||||||
this.testUnitsPresent();
|
|
||||||
this.testTechnicalDataExtraction();
|
|
||||||
this.testTableStructure();
|
|
||||||
this.testPDFNaming();
|
|
||||||
this.testBothLanguages();
|
|
||||||
this.testHeaderMapping();
|
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(60));
|
|
||||||
console.log(`RESULTS: ${this.passed} passed, ${this.failed} failed`);
|
|
||||||
console.log('='.repeat(60));
|
|
||||||
|
|
||||||
if (this.failed > 0) {
|
|
||||||
console.log('\n❌ Failed tests:');
|
|
||||||
this.tests.filter(t => !t.passed).forEach(t => {
|
|
||||||
console.log(` - ${t.name}${t.error ? `: ${t.error}` : ''}`);
|
|
||||||
});
|
|
||||||
// Don't call process.exit in test environment
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
console.log('\n✅ All tests passed!');
|
|
||||||
// Don't call process.exit in test environment
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run tests
|
|
||||||
const testSuite = new PDFDatasheetTest();
|
|
||||||
testSuite.runAll();
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user