You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
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')
|
|
})
|
|
})
|