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.
sims-hq/packages/scanning/test/scanning.test.ts

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')
})
})