mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
feat(shared,crm): add Code 128 barcode generation library Implements new @isa/shared/barcode library with directive and component for generating Code 128 barcodes using JsBarcode. Features: - Standalone Angular directive (svg[sharedBarcode]) - Standalone Angular component (<shared-barcode>) - Signal-based reactive inputs - SVG-based vector rendering - Customizable colors, size, margins, fonts - Comprehensive Vitest test coverage (39 tests) - Storybook stories for both directive and component - Integrated into customer loyalty card component Changes: - Created @isa/shared/barcode library with directive and component - Added JsBarcode dependency (v3.12.1) - Integrated barcode into customer loyalty card display - Added Storybook stories for interactive documentation - Fixed ui-switch story component reference - Updated library reference documentation Refs #5496 Related work items: #5496
244 lines
6.7 KiB
TypeScript
244 lines
6.7 KiB
TypeScript
import { Component } from '@angular/core';
|
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { BarcodeDirective } from './barcode.directive';
|
|
|
|
// Mock JsBarcode
|
|
vi.mock('jsbarcode', () => ({
|
|
default: vi.fn((element, value, options) => {
|
|
// Simulate JsBarcode by adding a rect element to the SVG
|
|
if (element && element.tagName === 'svg') {
|
|
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
rect.setAttribute('data-value', value);
|
|
rect.setAttribute('data-format', options?.format || 'CODE128');
|
|
element.appendChild(rect);
|
|
|
|
// Call valid callback if provided
|
|
if (options?.valid) {
|
|
const isValid = value && value.length > 0;
|
|
options.valid(isValid);
|
|
}
|
|
}
|
|
}),
|
|
}));
|
|
|
|
@Component({
|
|
standalone: true,
|
|
imports: [BarcodeDirective],
|
|
template: `
|
|
<svg
|
|
sharedBarcode
|
|
[value]="barcodeValue"
|
|
[format]="format"
|
|
[width]="width"
|
|
[height]="height"
|
|
[displayValue]="displayValue"
|
|
></svg>
|
|
`,
|
|
})
|
|
class TestHostComponent {
|
|
barcodeValue = '123456789';
|
|
format = 'CODE128';
|
|
width = 2;
|
|
height = 100;
|
|
displayValue = true;
|
|
}
|
|
|
|
describe('BarcodeDirective', () => {
|
|
let fixture: ComponentFixture<TestHostComponent>;
|
|
let component: TestHostComponent;
|
|
let svgElement: SVGElement;
|
|
|
|
beforeEach(async () => {
|
|
await TestBed.configureTestingModule({
|
|
imports: [TestHostComponent],
|
|
}).compileComponents();
|
|
|
|
fixture = TestBed.createComponent(TestHostComponent);
|
|
component = fixture.componentInstance;
|
|
svgElement = fixture.nativeElement.querySelector('svg');
|
|
fixture.detectChanges();
|
|
});
|
|
|
|
describe('Initialization', () => {
|
|
it('should create the directive', () => {
|
|
expect(component).toBeTruthy();
|
|
});
|
|
|
|
it('should apply to SVG element', () => {
|
|
expect(svgElement).toBeTruthy();
|
|
expect(svgElement.tagName).toBe('svg');
|
|
});
|
|
|
|
it('should render barcode on initialization', () => {
|
|
// JsBarcode adds child elements to the SVG
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Barcode Rendering', () => {
|
|
it('should render barcode with default CODE128 format', () => {
|
|
const rect = svgElement.querySelector('rect');
|
|
expect(rect).toBeTruthy();
|
|
});
|
|
|
|
it('should render barcode with custom value', () => {
|
|
component.barcodeValue = '987654321';
|
|
fixture.detectChanges();
|
|
|
|
// SVG should be updated with new value
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should update barcode when value changes', () => {
|
|
component.barcodeValue = 'NEWVALUE123';
|
|
fixture.detectChanges();
|
|
|
|
// Barcode should be re-rendered (may have different number of bars)
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should render with custom width', () => {
|
|
component.width = 4;
|
|
fixture.detectChanges();
|
|
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should render with custom height', () => {
|
|
component.height = 200;
|
|
fixture.detectChanges();
|
|
|
|
const rect = svgElement.querySelector('rect');
|
|
expect(rect).toBeTruthy();
|
|
});
|
|
|
|
it('should render without display value when disabled', () => {
|
|
component.displayValue = false;
|
|
fixture.detectChanges();
|
|
|
|
// Check that SVG is still rendered
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Input Changes', () => {
|
|
it('should re-render when format changes', () => {
|
|
component.format = 'CODE128';
|
|
fixture.detectChanges();
|
|
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should handle multiple rapid value changes', () => {
|
|
component.barcodeValue = 'VALUE1';
|
|
fixture.detectChanges();
|
|
|
|
component.barcodeValue = 'VALUE2';
|
|
fixture.detectChanges();
|
|
|
|
component.barcodeValue = 'VALUE3';
|
|
fixture.detectChanges();
|
|
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('should handle empty value gracefully', () => {
|
|
// JsBarcode should handle this or log warning
|
|
component.barcodeValue = '';
|
|
fixture.detectChanges();
|
|
|
|
// Directive should not crash
|
|
expect(component).toBeTruthy();
|
|
});
|
|
|
|
it('should handle very long values', () => {
|
|
component.barcodeValue = '1'.repeat(100);
|
|
fixture.detectChanges();
|
|
|
|
expect(svgElement.children.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Customization Options', () => {
|
|
it('should render with custom line color', () => {
|
|
@Component({
|
|
standalone: true,
|
|
imports: [BarcodeDirective],
|
|
template: `
|
|
<svg
|
|
sharedBarcode
|
|
[value]="'123456'"
|
|
[lineColor]="'#FF0000'"
|
|
></svg>
|
|
`,
|
|
})
|
|
class ColorTestComponent {}
|
|
|
|
const colorFixture = TestBed.createComponent(ColorTestComponent);
|
|
colorFixture.detectChanges();
|
|
|
|
const svg = colorFixture.nativeElement.querySelector('svg');
|
|
expect(svg).toBeTruthy();
|
|
expect(svg.children.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should render with custom background', () => {
|
|
@Component({
|
|
standalone: true,
|
|
imports: [BarcodeDirective],
|
|
template: `
|
|
<svg
|
|
sharedBarcode
|
|
[value]="'123456'"
|
|
[background]="'#F0F0F0'"
|
|
></svg>
|
|
`,
|
|
})
|
|
class BackgroundTestComponent {}
|
|
|
|
const bgFixture = TestBed.createComponent(BackgroundTestComponent);
|
|
bgFixture.detectChanges();
|
|
|
|
const svg = bgFixture.nativeElement.querySelector('svg');
|
|
expect(svg).toBeTruthy();
|
|
});
|
|
|
|
it('should render with custom font size', () => {
|
|
@Component({
|
|
standalone: true,
|
|
imports: [BarcodeDirective],
|
|
template: `
|
|
<svg sharedBarcode [value]="'123456'" [fontSize]="30"></svg>
|
|
`,
|
|
})
|
|
class FontSizeTestComponent {}
|
|
|
|
const fontFixture = TestBed.createComponent(FontSizeTestComponent);
|
|
fontFixture.detectChanges();
|
|
|
|
const svg = fontFixture.nativeElement.querySelector('svg');
|
|
expect(svg).toBeTruthy();
|
|
});
|
|
|
|
it('should render with custom margin', () => {
|
|
@Component({
|
|
standalone: true,
|
|
imports: [BarcodeDirective],
|
|
template: `
|
|
<svg sharedBarcode [value]="'123456'" [margin]="20"></svg>
|
|
`,
|
|
})
|
|
class MarginTestComponent {}
|
|
|
|
const marginFixture = TestBed.createComponent(MarginTestComponent);
|
|
marginFixture.detectChanges();
|
|
|
|
const svg = marginFixture.nativeElement.querySelector('svg');
|
|
expect(svg).toBeTruthy();
|
|
});
|
|
});
|
|
});
|