Initial commit: Inventory Barcode System
This commit is contained in:
505
__tests__/frontend.barcode.test.js
Normal file
505
__tests__/frontend.barcode.test.js
Normal file
@ -0,0 +1,505 @@
|
||||
/**
|
||||
* 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user