import { describe, expect, it } from 'vitest' import { ean13CheckDigit, isValidEan13, parseScaleBarcode, type ScaleBarcodeProfile, parseEntry, WedgeDetector, } from '@sims/scanning' describe('EAN-13', () => { it('computes and validates check digits', () => { expect(ean13CheckDigit('400638133393')).toBe(1) expect(isValidEan13('4006381333931')).toBe(true) expect(isValidEan13('4006381333932')).toBe(false) expect(isValidEan13('123')).toBe(false) }) }) describe('label-scale barcodes (2x prefix)', () => { const profile: ScaleBarcodeProfile = { prefixes: ['21'], pluLength: 5, valueKind: 'weight', valueDivisor: 1000, } const body = '210004201250' // prefix 21 · PLU 00042 · 1250 g const code = body + String(ean13CheckDigit(body)) it('extracts PLU and weight as quantity', () => { expect(parseScaleBarcode(code, profile)).toEqual({ kind: 'scale-weight', plu: '00042', qty: 1.25 }) }) it('rejects a corrupted check digit instead of guessing a weight', () => { const bad = body + String((ean13CheckDigit(body) + 1) % 10) expect(parseScaleBarcode(bad, profile)).toEqual({ kind: 'invalid', reason: 'check-digit' }) }) it('passes non-scale codes through untouched', () => { expect(parseScaleBarcode('4006381333931', profile)).toEqual({ kind: 'not-scale' }) }) it('supports price-embedded profiles', () => { const priceProfile: ScaleBarcodeProfile = { ...profile, valueKind: 'price', valueDivisor: 1 } expect(parseScaleBarcode(code, priceProfile)).toEqual({ kind: 'scale-price', plu: '00042', pricePaise: 1250, }) }) }) describe('scan-box entry grammar', () => { it('parses the four grammars without modes', () => { expect(parseEntry('')).toEqual({ kind: 'empty' }) expect(parseEntry('3*')).toEqual({ kind: 'multiplier', qty: 3, rest: '' }) expect(parseEntry('1.5*atta')).toEqual({ kind: 'multiplier', qty: 1.5, rest: 'atta' }) expect(parseEntry('4006381333931')).toEqual({ kind: 'barcode', code: '4006381333931' }) expect(parseEntry('42')).toEqual({ kind: 'code', code: '42' }) expect(parseEntry('chawal')).toEqual({ kind: 'search', text: 'chawal' }) }) }) describe('wedge detector — scanner vs human, by timing', () => { it('classifies a fast burst as a scan', () => { const d = new WedgeDetector() '4006381333931'.split('').forEach((c, i) => d.feed(c, i * 10)) expect(d.terminate()).toEqual({ type: 'scan', code: '4006381333931' }) }) it('classifies human-speed typing as typed input', () => { const d = new WedgeDetector() '400638'.split('').forEach((c, i) => d.feed(c, i * 120)) expect(d.terminate()).toEqual({ type: 'typed', text: '400638' }) }) it('treats short fast bursts as typed (quick-key PLUs)', () => { const d = new WedgeDetector() '42'.split('').forEach((c, i) => d.feed(c, i * 5)) expect(d.terminate()).toEqual({ type: 'typed', text: '42' }) }) it('resets cleanly between bursts', () => { const d = new WedgeDetector() d.feed('9', 0) d.terminate() '4006381333931'.split('').forEach((c, i) => d.feed(c, 1000 + i * 10)) expect(d.terminate().type).toBe('scan') }) })