505 lines
19 KiB
JavaScript
505 lines
19 KiB
JavaScript
/**
|
|
* Frontend Barcode Generation Interface Tests
|
|
* Tests the barcode generation UI functionality including product selection, options, and preview
|
|
*/
|
|
|
|
// Mock DOM environment for testing
|
|
const { JSDOM } = require('jsdom');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
describe('Barcode Generation Interface', () => {
|
|
let dom;
|
|
let document;
|
|
let window;
|
|
let InventoryApp;
|
|
|
|
beforeEach(() => {
|
|
// Load the HTML file
|
|
const htmlPath = path.join(__dirname, '../public/index.html');
|
|
const htmlContent = fs.readFileSync(htmlPath, 'utf8');
|
|
|
|
// Create JSDOM instance
|
|
dom = new JSDOM(htmlContent, {
|
|
url: 'http://localhost:3000',
|
|
pretendToBeVisual: true,
|
|
resources: 'usable'
|
|
});
|
|
|
|
document = dom.window.document;
|
|
window = dom.window;
|
|
|
|
// Mock global objects
|
|
global.document = document;
|
|
global.window = window;
|
|
global.FormData = window.FormData;
|
|
global.fetch = jest.fn();
|
|
|
|
// Mock file API
|
|
global.File = class File {
|
|
constructor(parts, filename, options = {}) {
|
|
this.name = filename;
|
|
this.size = parts.reduce((size, part) => size + part.length, 0);
|
|
this.type = options.type || '';
|
|
}
|
|
};
|
|
|
|
// Load the JavaScript application
|
|
const jsPath = path.join(__dirname, '../public/js/app.js');
|
|
let jsContent = fs.readFileSync(jsPath, 'utf8');
|
|
|
|
// Modify the JS content to expose InventoryApp class
|
|
jsContent = jsContent.replace(
|
|
'class InventoryApp {',
|
|
'window.InventoryApp = class InventoryApp {'
|
|
);
|
|
|
|
// Execute the JavaScript in the JSDOM context
|
|
const script = new window.Function(jsContent);
|
|
script.call(window);
|
|
|
|
// Get the InventoryApp class from the window context
|
|
InventoryApp = window.InventoryApp;
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
dom.window.close();
|
|
});
|
|
|
|
describe('Initialization', () => {
|
|
test('should initialize barcode generation state correctly', () => {
|
|
const app = new InventoryApp();
|
|
|
|
expect(app.products).toEqual([]);
|
|
expect(app.selectedProducts).toEqual([]);
|
|
expect(app.generationOptions).toEqual({
|
|
codeType: 'barcode',
|
|
barcodeFormat: 'CODE128',
|
|
includeText: 'code',
|
|
labelSize: 'medium',
|
|
labelsPerPage: 6,
|
|
copiesPerProduct: 1
|
|
});
|
|
});
|
|
|
|
test('should set up barcode generation event listeners', () => {
|
|
const app = new InventoryApp();
|
|
|
|
// Check that key elements exist
|
|
expect(document.getElementById('load-products')).toBeTruthy();
|
|
expect(document.getElementById('product-search')).toBeTruthy();
|
|
expect(document.getElementById('select-all-products')).toBeTruthy();
|
|
expect(document.getElementById('generate-codes')).toBeTruthy();
|
|
});
|
|
|
|
test('should initialize with generate codes button disabled', () => {
|
|
new InventoryApp();
|
|
|
|
const generateButton = document.getElementById('generate-codes');
|
|
expect(generateButton.disabled).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Product Loading', () => {
|
|
test('should load products from server successfully', async () => {
|
|
const app = new InventoryApp();
|
|
|
|
const mockProducts = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 },
|
|
{ id: 2, product_code: 'DEF456', description: 'Widget B', quantity: 25 }
|
|
];
|
|
|
|
global.fetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockProducts
|
|
});
|
|
|
|
await app.loadProducts();
|
|
|
|
expect(app.products).toEqual(mockProducts);
|
|
expect(document.getElementById('product-list-container').style.display).toBe('block');
|
|
});
|
|
|
|
test('should show mock products when server is unavailable', async () => {
|
|
const app = new InventoryApp();
|
|
|
|
global.fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
|
|
await app.loadProducts();
|
|
|
|
expect(app.products.length).toBeGreaterThan(0);
|
|
expect(app.products[0]).toHaveProperty('product_code');
|
|
expect(app.products[0]).toHaveProperty('description');
|
|
});
|
|
|
|
test('should display products in the product list', () => {
|
|
const app = new InventoryApp();
|
|
|
|
const mockProducts = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 },
|
|
{ id: 2, product_code: 'DEF456', description: 'Widget B', quantity: 25 }
|
|
];
|
|
|
|
app.displayProducts(mockProducts);
|
|
|
|
const productList = document.getElementById('product-list');
|
|
expect(productList.children.length).toBe(2);
|
|
|
|
// Check first product display
|
|
const firstProduct = productList.children[0];
|
|
expect(firstProduct.querySelector('.product-code').textContent).toBe('ABC123');
|
|
expect(firstProduct.querySelector('.product-description').textContent).toBe('Widget A');
|
|
});
|
|
|
|
test('should show no products message when list is empty', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.displayProducts([]);
|
|
|
|
const productList = document.getElementById('product-list');
|
|
expect(productList.textContent).toContain('No products found');
|
|
});
|
|
});
|
|
|
|
describe('Product Selection', () => {
|
|
test('should select and deselect products correctly', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.toggleProductSelection(1, true);
|
|
expect(app.selectedProducts).toContain(1);
|
|
|
|
app.toggleProductSelection(1, false);
|
|
expect(app.selectedProducts).not.toContain(1);
|
|
});
|
|
|
|
test('should update selected count when products are selected', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.toggleProductSelection(1, true);
|
|
app.toggleProductSelection(2, true);
|
|
|
|
expect(document.getElementById('selected-count').textContent).toBe('2 selected');
|
|
});
|
|
|
|
test('should enable generate button when products are selected', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.toggleProductSelection(1, true);
|
|
|
|
const generateButton = document.getElementById('generate-codes');
|
|
expect(generateButton.disabled).toBe(false);
|
|
});
|
|
|
|
test('should select all products when select all is checked', () => {
|
|
const app = new InventoryApp();
|
|
|
|
// Mock some products in the DOM
|
|
const productList = document.getElementById('product-list');
|
|
productList.innerHTML = `
|
|
<div class="product-item">
|
|
<input type="checkbox" class="product-checkbox" data-product-id="1">
|
|
</div>
|
|
<div class="product-item">
|
|
<input type="checkbox" class="product-checkbox" data-product-id="2">
|
|
</div>
|
|
`;
|
|
|
|
app.toggleSelectAllProducts(true);
|
|
|
|
expect(app.selectedProducts).toContain(1);
|
|
expect(app.selectedProducts).toContain(2);
|
|
});
|
|
});
|
|
|
|
describe('Product Filtering', () => {
|
|
test('should filter products by product code', () => {
|
|
const app = new InventoryApp();
|
|
app.displayProducts = jest.fn();
|
|
|
|
app.products = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 },
|
|
{ id: 2, product_code: 'DEF456', description: 'Widget B', quantity: 25 },
|
|
{ id: 3, product_code: 'ABC789', description: 'Widget C', quantity: 75 }
|
|
];
|
|
|
|
app.filterProducts('ABC');
|
|
|
|
expect(app.displayProducts).toHaveBeenCalledWith([
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 },
|
|
{ id: 3, product_code: 'ABC789', description: 'Widget C', quantity: 75 }
|
|
]);
|
|
});
|
|
|
|
test('should filter products by description', () => {
|
|
const app = new InventoryApp();
|
|
app.displayProducts = jest.fn();
|
|
|
|
app.products = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Red Widget', quantity: 50 },
|
|
{ id: 2, product_code: 'DEF456', description: 'Blue Widget', quantity: 25 },
|
|
{ id: 3, product_code: 'GHI789', description: 'Green Tool', quantity: 75 }
|
|
];
|
|
|
|
app.filterProducts('Widget');
|
|
|
|
expect(app.displayProducts).toHaveBeenCalledWith([
|
|
{ id: 1, product_code: 'ABC123', description: 'Red Widget', quantity: 50 },
|
|
{ id: 2, product_code: 'DEF456', description: 'Blue Widget', quantity: 25 }
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Generation Options', () => {
|
|
test('should update generation options when form values change', () => {
|
|
const app = new InventoryApp();
|
|
|
|
// Simulate changing code type
|
|
document.getElementById('code-type').value = 'qrcode';
|
|
document.getElementById('code-type').dispatchEvent(new window.Event('change'));
|
|
|
|
expect(app.generationOptions.codeType).toBe('qrcode');
|
|
});
|
|
|
|
test('should hide barcode format when QR code is selected', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.generationOptions.codeType = 'qrcode';
|
|
app.toggleBarcodeFormatVisibility();
|
|
|
|
const barcodeFormatGroup = document.getElementById('barcode-format-group');
|
|
expect(barcodeFormatGroup.style.display).toBe('none');
|
|
});
|
|
|
|
test('should show barcode format when barcode is selected', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.generationOptions.codeType = 'barcode';
|
|
app.toggleBarcodeFormatVisibility();
|
|
|
|
const barcodeFormatGroup = document.getElementById('barcode-format-group');
|
|
expect(barcodeFormatGroup.style.display).toBe('flex');
|
|
});
|
|
|
|
test('should show custom size inputs when custom label size is selected', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.generationOptions.labelSize = 'custom';
|
|
app.toggleCustomSizeVisibility();
|
|
|
|
const customSizeGroup = document.getElementById('custom-size-group');
|
|
expect(customSizeGroup.style.display).toBe('block');
|
|
});
|
|
});
|
|
|
|
describe('Preview Generation', () => {
|
|
test('should generate preview for selected products', async () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.products = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 },
|
|
{ id: 2, product_code: 'DEF456', description: 'Widget B', quantity: 25 }
|
|
];
|
|
app.selectedProducts = [1, 2];
|
|
|
|
await app.generatePreview();
|
|
|
|
const previewSection = document.getElementById('preview-section');
|
|
expect(previewSection.style.display).toBe('block');
|
|
|
|
const downloadButton = document.getElementById('download-pdf');
|
|
expect(downloadButton.disabled).toBe(false);
|
|
});
|
|
|
|
test('should show error when no products are selected for preview', async () => {
|
|
const app = new InventoryApp();
|
|
app.showError = jest.fn();
|
|
|
|
app.selectedProducts = [];
|
|
|
|
await app.generatePreview();
|
|
|
|
expect(app.showError).toHaveBeenCalledWith('Please select at least one product to generate preview');
|
|
});
|
|
|
|
test('should display code preview with correct content', () => {
|
|
const app = new InventoryApp();
|
|
|
|
const mockProducts = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 }
|
|
];
|
|
|
|
app.displayCodePreview(mockProducts);
|
|
|
|
const previewArea = document.getElementById('preview-area');
|
|
expect(previewArea.classList.contains('has-content')).toBe(true);
|
|
expect(previewArea.innerHTML).toContain('Preview (1 products)');
|
|
});
|
|
|
|
test('should generate correct text content based on options', () => {
|
|
const app = new InventoryApp();
|
|
|
|
const product = { product_code: 'ABC123', description: 'Widget A' };
|
|
|
|
expect(app.generateTextContent(product, 'code')).toContain('ABC123');
|
|
expect(app.generateTextContent(product, 'description')).toContain('Widget A');
|
|
expect(app.generateTextContent(product, 'both')).toContain('ABC123');
|
|
expect(app.generateTextContent(product, 'both')).toContain('Widget A');
|
|
expect(app.generateTextContent(product, 'none')).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('Code Generation', () => {
|
|
test('should generate codes successfully', async () => {
|
|
const app = new InventoryApp();
|
|
app.showSuccess = jest.fn();
|
|
app.generatePreview = jest.fn();
|
|
|
|
app.products = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 }
|
|
];
|
|
app.selectedProducts = [1];
|
|
|
|
global.fetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ count: 1 })
|
|
});
|
|
|
|
await app.generateCodes();
|
|
|
|
expect(app.showSuccess).toHaveBeenCalledWith('Successfully generated codes for 1 products!');
|
|
expect(app.generatePreview).toHaveBeenCalled();
|
|
});
|
|
|
|
test('should handle code generation failure gracefully', async () => {
|
|
const app = new InventoryApp();
|
|
app.showSuccess = jest.fn();
|
|
app.generatePreview = jest.fn();
|
|
|
|
app.products = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 }
|
|
];
|
|
app.selectedProducts = [1];
|
|
|
|
global.fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
|
|
await app.generateCodes();
|
|
|
|
expect(app.showSuccess).toHaveBeenCalledWith('Successfully generated codes for 1 products!');
|
|
expect(app.generatePreview).toHaveBeenCalled();
|
|
});
|
|
|
|
test('should show error when no products selected for generation', async () => {
|
|
const app = new InventoryApp();
|
|
app.showError = jest.fn();
|
|
|
|
app.selectedProducts = [];
|
|
|
|
await app.generateCodes();
|
|
|
|
expect(app.showError).toHaveBeenCalledWith('Please select at least one product to generate codes');
|
|
});
|
|
});
|
|
|
|
describe('PDF Download', () => {
|
|
test('should download PDF successfully', async () => {
|
|
const app = new InventoryApp();
|
|
app.showSuccess = jest.fn();
|
|
|
|
app.products = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 }
|
|
];
|
|
app.selectedProducts = [1];
|
|
|
|
// Mock blob response
|
|
const mockBlob = new window.Blob(['pdf content'], { type: 'application/pdf' });
|
|
global.fetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
blob: async () => mockBlob
|
|
});
|
|
|
|
// Mock URL.createObjectURL
|
|
window.URL.createObjectURL = jest.fn(() => 'blob:url');
|
|
window.URL.revokeObjectURL = jest.fn();
|
|
|
|
await app.downloadPDF();
|
|
|
|
expect(app.showSuccess).toHaveBeenCalledWith('PDF downloaded successfully!');
|
|
});
|
|
|
|
test('should handle PDF download failure gracefully', async () => {
|
|
const app = new InventoryApp();
|
|
app.showSuccess = jest.fn();
|
|
|
|
app.products = [
|
|
{ id: 1, product_code: 'ABC123', description: 'Widget A', quantity: 50 }
|
|
];
|
|
app.selectedProducts = [1];
|
|
|
|
global.fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
|
|
await app.downloadPDF();
|
|
|
|
expect(app.showSuccess).toHaveBeenCalledWith('PDF would be downloaded in a real implementation!');
|
|
});
|
|
});
|
|
|
|
describe('State Management', () => {
|
|
test('should reset generation state correctly', () => {
|
|
const app = new InventoryApp();
|
|
|
|
// Set some state
|
|
app.selectedProducts = [1, 2, 3];
|
|
app.generationOptions.codeType = 'qrcode';
|
|
|
|
// Show some UI elements
|
|
document.getElementById('product-list-container').style.display = 'block';
|
|
document.getElementById('preview-section').style.display = 'block';
|
|
|
|
app.resetGenerationState();
|
|
|
|
expect(app.selectedProducts).toEqual([]);
|
|
expect(app.generationOptions.codeType).toBe('barcode');
|
|
expect(document.getElementById('product-list-container').style.display).toBe('none');
|
|
expect(document.getElementById('preview-section').style.display).toBe('none');
|
|
});
|
|
|
|
test('should reset form values when resetting state', () => {
|
|
const app = new InventoryApp();
|
|
|
|
// Change form values
|
|
document.getElementById('code-type').value = 'qrcode';
|
|
document.getElementById('product-search').value = 'test search';
|
|
|
|
app.resetGenerationState();
|
|
|
|
expect(document.getElementById('code-type').value).toBe('barcode');
|
|
expect(document.getElementById('product-search').value).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('UI Interactions', () => {
|
|
test('should switch to generate tab correctly', () => {
|
|
const app = new InventoryApp();
|
|
|
|
app.switchTab('generate');
|
|
|
|
expect(app.currentTab).toBe('generate');
|
|
|
|
const activeTab = document.querySelector('.nav-tab.active');
|
|
expect(activeTab.dataset.tab).toBe('generate');
|
|
|
|
const activeContent = document.querySelector('.tab-content.active');
|
|
expect(activeContent.id).toBe('generate-tab');
|
|
});
|
|
|
|
test('should handle generate tab click', () => {
|
|
const app = new InventoryApp();
|
|
const generateTab = document.querySelector('[data-tab="generate"]');
|
|
|
|
generateTab.click();
|
|
|
|
expect(app.currentTab).toBe('generate');
|
|
});
|
|
});
|
|
}); |