Files
inventory-barcode-system/__tests__/PrintableLayoutService.test.js

452 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const PrintableLayoutService = require('../services/PrintableLayoutService');
// Mock the CodeGenerationService
jest.mock('../services/CodeGenerationService', () => {
return jest.fn().mockImplementation(() => ({
generateBarcode: jest.fn().mockResolvedValue({
success: true,
data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
format: 'CODE128',
productCode: 'TEST123'
}),
generateQRCode: jest.fn().mockResolvedValue({
success: true,
data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
productCode: 'TEST123',
embeddedData: { code: 'TEST123', desc: 'Test Product' }
})
}));
});
describe('PrintableLayoutService', () => {
let layoutService;
let sampleProducts;
beforeEach(() => {
layoutService = new PrintableLayoutService();
sampleProducts = [
{
product_code: 'PROD001',
description: 'Test Product 1',
category: 'Electronics',
unit_of_measure: 'pcs'
},
{
product_code: 'PROD002',
description: 'Test Product 2',
category: 'Hardware',
unit_of_measure: 'pcs'
},
{
product_code: 'PROD003',
description: 'Test Product 3 with a very long description that should be truncated',
category: 'Software',
unit_of_measure: 'licenses'
}
];
});
describe('Constructor and Configuration', () => {
test('should initialize with default options', () => {
expect(layoutService.labelSizes).toHaveProperty('avery-5160');
expect(layoutService.labelSizes).toHaveProperty('avery-5161');
expect(layoutService.labelSizes).toHaveProperty('custom');
expect(layoutService.defaultLayoutOptions.labelSize).toBe('avery-5160');
expect(layoutService.defaultLayoutOptions.includeBarcode).toBe(true);
expect(layoutService.defaultLayoutOptions.includeQRCode).toBe(false);
});
test('should return available label sizes', () => {
const sizes = layoutService.getAvailableLabelSizes();
expect(sizes).toHaveProperty('avery-5160');
expect(sizes).toHaveProperty('avery-5161');
expect(sizes).toHaveProperty('custom');
expect(sizes['avery-5160']).toHaveProperty('width');
expect(sizes['avery-5160']).toHaveProperty('height');
expect(sizes['avery-5160']).toHaveProperty('columns');
expect(sizes['avery-5160']).toHaveProperty('rows');
});
});
describe('Layout Preview Generation', () => {
test('should generate layout preview with default options', async () => {
const result = await layoutService.generateLayoutPreview(sampleProducts);
expect(result.success).toBe(true);
expect(result.preview).toHaveProperty('labelSize', 'avery-5160');
expect(result.preview).toHaveProperty('dimensions');
expect(result.preview).toHaveProperty('labelsPerPage');
expect(result.preview).toHaveProperty('totalPages');
expect(result.preview.totalPages).toBe(1); // 3 products, 30 labels per page
expect(result.preview.includeBarcode).toBe(true);
expect(result.preview.includeQRCode).toBe(false);
});
test('should generate layout preview with custom options', async () => {
const options = {
labelSize: 'avery-5161',
includeQRCode: true,
includeBarcode: false
};
const result = await layoutService.generateLayoutPreview(sampleProducts, options);
expect(result.success).toBe(true);
expect(result.preview.labelSize).toBe('avery-5161');
expect(result.preview.includeBarcode).toBe(false);
expect(result.preview.includeQRCode).toBe(true);
});
test('should calculate correct number of pages', async () => {
// Create more products to test pagination
const manyProducts = Array.from({ length: 35 }, (_, i) => ({
product_code: `PROD${String(i + 1).padStart(3, '0')}`,
description: `Product ${i + 1}`,
category: 'Test',
unit_of_measure: 'pcs'
}));
const result = await layoutService.generateLayoutPreview(manyProducts);
expect(result.success).toBe(true);
// avery-5160 has 30 labels per page (3 columns × 10 rows)
// 35 products should require 2 pages
expect(result.preview.totalPages).toBe(2);
});
test('should handle empty products array', async () => {
const result = await layoutService.generateLayoutPreview([]);
expect(result.success).toBe(true);
expect(result.preview.totalPages).toBe(0);
});
});
describe('Custom Template Generation', () => {
test('should generate custom template with valid dimensions', async () => {
const templateOptions = {
width: 40,
height: 20,
columns: 4,
rows: 10,
name: 'my-custom-template'
};
const result = await layoutService.generateCustomTemplate(templateOptions);
expect(result.success).toBe(true);
expect(result.template.name).toBe('my-custom-template');
expect(result.template.width).toBe(40);
expect(result.template.height).toBe(20);
expect(result.template.columns).toBe(4);
expect(result.template.rows).toBe(10);
expect(result.template.totalLabels).toBe(40);
expect(result.validation.fitsOnPage).toBe(true);
});
test('should reject template that exceeds page width', async () => {
const templateOptions = {
width: 100,
height: 20,
columns: 5,
rows: 5
};
const result = await layoutService.generateCustomTemplate(templateOptions);
expect(result.success).toBe(false);
expect(result.error).toContain('Template width exceeds page width');
});
test('should reject template that exceeds page height', async () => {
const templateOptions = {
width: 30,
height: 50,
columns: 2,
rows: 10
};
const result = await layoutService.generateCustomTemplate(templateOptions);
expect(result.success).toBe(false);
expect(result.error).toContain('Template height exceeds page height');
});
test('should use default values for missing template options', async () => {
const result = await layoutService.generateCustomTemplate({});
expect(result.success).toBe(true);
expect(result.template.width).toBe(50);
expect(result.template.height).toBe(25);
expect(result.template.columns).toBe(4);
expect(result.template.rows).toBe(8);
expect(result.template.name).toBe('custom-template');
});
});
describe('PDF Layout Generation', () => {
test('should generate PDF with barcode layout', async () => {
const options = {
labelSize: 'avery-5160',
includeBarcode: true,
includeQRCode: false,
includeProductCode: true,
includeDescription: true
};
const result = await layoutService.generatePrintableLayout(sampleProducts, options);
expect(result.success).toBe(true);
expect(result.data).toBeInstanceOf(Buffer);
expect(result.metadata.totalProducts).toBe(3);
expect(result.metadata.totalPages).toBe(1);
expect(result.metadata.labelSize).toBe('avery-5160');
expect(result.metadata.format).toBe('PDF');
});
test('should generate PDF with QR code layout', async () => {
const options = {
labelSize: 'avery-5161',
includeBarcode: false,
includeQRCode: true,
includeProductCode: true,
includeDescription: false
};
const result = await layoutService.generatePrintableLayout(sampleProducts, options);
expect(result.success).toBe(true);
expect(result.data).toBeInstanceOf(Buffer);
expect(result.metadata.labelSize).toBe('avery-5161');
});
test('should generate PDF with both barcode and QR code', async () => {
const options = {
includeBarcode: true,
includeQRCode: true,
includeProductCode: true,
includeDescription: true
};
const result = await layoutService.generatePrintableLayout(sampleProducts, options);
expect(result.success).toBe(true);
expect(result.data).toBeInstanceOf(Buffer);
});
test('should handle empty products array', async () => {
const result = await layoutService.generatePrintableLayout([]);
expect(result.success).toBe(false);
expect(result.error).toContain('Products array is required and cannot be empty');
});
test('should handle null products parameter', async () => {
const result = await layoutService.generatePrintableLayout(null);
expect(result.success).toBe(false);
expect(result.error).toContain('Products array is required and cannot be empty');
});
test('should generate multiple pages for many products', async () => {
// Create enough products to span multiple pages
const manyProducts = Array.from({ length: 35 }, (_, i) => ({
product_code: `PROD${String(i + 1).padStart(3, '0')}`,
description: `Product ${i + 1}`,
category: 'Test',
unit_of_measure: 'pcs'
}));
const result = await layoutService.generatePrintableLayout(manyProducts);
expect(result.success).toBe(true);
expect(result.metadata.totalProducts).toBe(35);
expect(result.metadata.totalPages).toBe(2);
});
test('should use custom label size', async () => {
const options = {
labelSize: 'custom',
includeBarcode: true
};
const result = await layoutService.generatePrintableLayout(sampleProducts, options);
expect(result.success).toBe(true);
expect(result.metadata.labelSize).toBe('custom');
});
test('should handle code generation failures gracefully', async () => {
// Mock code generation service to return failure
layoutService.codeGenService.generateBarcode.mockResolvedValueOnce({
success: false,
error: 'Barcode generation failed'
});
const result = await layoutService.generatePrintableLayout(sampleProducts);
// Should still succeed but without the failed barcode
expect(result.success).toBe(true);
});
});
describe('Configuration Export/Import', () => {
test('should export layout configuration', () => {
const layoutConfig = {
labelSize: 'avery-5160',
includeBarcode: true,
includeQRCode: false,
fontSize: 10
};
const exported = layoutService.exportLayoutConfiguration(layoutConfig);
expect(exported).toHaveProperty('version', '1.0');
expect(exported).toHaveProperty('timestamp');
expect(exported.configuration).toEqual({
...layoutConfig,
availableSizes: Object.keys(layoutService.labelSizes)
});
});
test('should import valid layout configuration', () => {
const configData = {
version: '1.0',
timestamp: '2023-01-01T00:00:00.000Z',
configuration: {
labelSize: 'avery-5161',
includeBarcode: true,
includeQRCode: true,
fontSize: 12
}
};
const result = layoutService.importLayoutConfiguration(configData);
expect(result.success).toBe(true);
expect(result.configuration.labelSize).toBe('avery-5161');
expect(result.message).toBe('Configuration imported successfully');
});
test('should reject invalid configuration format', () => {
const invalidConfig = {
version: '1.0',
// missing configuration property
};
const result = layoutService.importLayoutConfiguration(invalidConfig);
expect(result.success).toBe(false);
expect(result.error).toBe('Invalid configuration format');
});
test('should reject configuration with missing required fields', () => {
const configData = {
configuration: {
includeBarcode: true
// missing labelSize
}
};
const result = layoutService.importLayoutConfiguration(configData);
expect(result.success).toBe(false);
expect(result.error).toContain('Missing required field: labelSize');
});
test('should reject configuration with unsupported label size', () => {
const configData = {
configuration: {
labelSize: 'unsupported-size',
includeBarcode: true
}
};
const result = layoutService.importLayoutConfiguration(configData);
expect(result.success).toBe(false);
expect(result.error).toContain('Unsupported label size: unsupported-size');
});
});
describe('Text Truncation', () => {
test('should truncate long text to fit width', () => {
// Create a mock PDF object for text measurement
const mockPdf = {
getTextWidth: jest.fn((text) => text.length * 2) // Simple mock: 2mm per character
};
const longText = 'This is a very long product description that should be truncated';
const maxWidth = 50; // 25 characters max at 2mm per character
const truncated = layoutService._truncateText(longText, maxWidth, mockPdf);
expect(truncated).toContain('...');
expect(mockPdf.getTextWidth(truncated)).toBeLessThanOrEqual(maxWidth);
});
test('should return original text if it fits', () => {
const mockPdf = {
getTextWidth: jest.fn((text) => text.length * 2)
};
const shortText = 'Short text';
const maxWidth = 50;
const result = layoutService._truncateText(shortText, maxWidth, mockPdf);
expect(result).toBe(shortText);
});
test('should handle empty text', () => {
const mockPdf = {
getTextWidth: jest.fn(() => 0)
};
const result = layoutService._truncateText('', 50, mockPdf);
expect(result).toBe('');
});
test('should handle null text', () => {
const mockPdf = {
getTextWidth: jest.fn(() => 0)
};
const result = layoutService._truncateText(null, 50, mockPdf);
expect(result).toBe('');
});
});
describe('Private Methods', () => {
test('should generate codes for products', async () => {
const options = {
includeBarcode: true,
includeQRCode: true,
barcodeFormat: 'CODE128'
};
const codes = await layoutService._generateCodesForProducts(sampleProducts, options);
expect(codes).toHaveLength(3);
expect(codes[0]).toHaveProperty('product');
expect(codes[0]).toHaveProperty('barcode');
expect(codes[0]).toHaveProperty('qrCode');
expect(codes[0].barcode.success).toBe(true);
expect(codes[0].qrCode.success).toBe(true);
});
test('should generate codes with selective options', async () => {
const options = {
includeBarcode: true,
includeQRCode: false
};
const codes = await layoutService._generateCodesForProducts(sampleProducts, options);
expect(codes[0].barcode).toBeTruthy();
expect(codes[0].qrCode).toBeNull();
});
});
});