fix(numpad): segno perso e raffica HID concatenata a cifre manuali (#11, #27)

toggleSign() usciva subito a display vuoto: il segno del calibro, che arriva
prima delle cifre, veniva scartato. Ora '-'/'+' impostano il segno in modo
esplicito (non toggle) quando arrivano da tastiera/HID; toggleSign() (bottone
manuale +/-) funziona anche a vuoto.

_trackBurst riconosce l'inizio di una raffica solo dal secondo carattere:
prima ripuliva il display in ritardo, concatenando cifre digitate a mano.
Ora la raffica ripulisce il display fino al proprio inizio quando la pausa
prima di essa è > 300ms. Parte intera oltre 6 cifre: rifiutata con evento
numpad-error invece di essere troncata in silenzio.

Test node in tests/js/test_numpad.js, lanciato da pytest
(tests/test_numpad_js.py, skip se node manca).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 22:40:21 +02:00
parent 1576fa190b
commit d40c74870e
3 changed files with 216 additions and 7 deletions
@@ -0,0 +1,124 @@
/**
* numpad.js behaviour under a simulated USB caliper / physical keyboard, run
* with plain node (no browser, no test framework) against the same
* handleKeydown() path a real wedge device drives.
*
* Covers gitea:Adriano/TieMeasureFlow#11 (sign lost when the caliper sends it
* before the digits) and #27 (leftover manual digits concatenate onto a
* caliper burst; an integer part over the digit limit used to be truncated
* in silence instead of refused).
*
* Run from src/frontend/flask_app: node tests/js/test_numpad.js
*/
'use strict';
const fs = require('fs');
const path = require('path');
// A fake performance.now(): T advances by the gap the test hands to send()/
// press(), so "fast" (<80ms, HID) and "slow" (>80ms, human) keystrokes are
// simulated without any real waiting.
global.performance = { now: () => T };
let T = 1000;
const numpadSrc = fs.readFileSync(
path.join(__dirname, '..', '..', 'static', 'js', 'numpad.js'), 'utf8',
);
eval(numpadSrc + ';global.numpad = numpad;');
function mk() {
const n = numpad({});
n._events = [];
n.$dispatch = (name, detail) => n._events.push({ name: name, detail: detail });
return n;
}
function press(n, key, gap) {
T += gap;
n.handleKeydown({
key: key,
target: { tagName: 'BODY' },
preventDefault() {},
});
}
function send(n, str, gap) {
for (const ch of str) press(n, ch === '\r' ? 'Enter' : ch, gap);
}
function lastEvent(n, name) {
const evs = n._events.filter(e => e.name === name);
return evs.length ? evs[evs.length - 1].detail : null;
}
let failures = 0;
function check(label, cond, extra) {
if (cond) {
console.log('ok - ' + label);
} else {
failures++;
console.error('NOT OK - ' + label + (extra ? ' (' + extra + ')' : ''));
}
}
// #11 — the caliper sends the sign before the digits: "-1.25\r"
{
const n = mk();
send(n, '-1.25\r', 10);
const c = lastEvent(n, 'numpad-confirm');
check('#11 raffica negativa "-1.25\\r" -> -1.25', c && c.value === -1.25, JSON.stringify(c));
check('#11 raffica negativa classificata usb_caliper', c && c.inputMethod === 'usb_caliper');
}
// #11 — explicit "+" also arrives as its own character
{
const n = mk();
send(n, '+12.34\r', 10);
const c = lastEvent(n, 'numpad-confirm');
check('#11 raffica positiva "+12.34\\r" -> 12.34', c && c.value === 12.34, JSON.stringify(c));
}
// #11 — a manual "-" press followed by clear must not leak into the next reading
{
const n = mk();
press(n, '-', 10);
n.clearAll();
send(n, '1.2\r', 200);
const c = lastEvent(n, 'numpad-confirm');
check('#11 "-" poi clear poi "1.2\\r" -> 1.2 (segno azzerato)', c && c.value === 1.2, JSON.stringify(c));
}
// #27 — a digit left on screen from manual typing must not concatenate onto
// a caliper burst that follows a real pause.
{
const n = mk();
n.addDigit('1');
T += 5000;
send(n, '12.34\r', 10);
const c = lastEvent(n, 'numpad-confirm');
check('#27 tocco "1", pausa, raffica "12.34\\r" -> 12.34 (non 112.34)', c && c.value === 12.34, JSON.stringify(c));
}
// #27 — an integer part over the digit limit is refused, not silently cut down
{
const n = mk();
send(n, '1234567.5\r', 10);
const c = lastEvent(n, 'numpad-confirm');
const err = lastEvent(n, 'numpad-error');
check('#27 raffica "1234567.5\\r" -> nessun confirm', c === null, JSON.stringify(c));
check('#27 raffica "1234567.5\\r" -> numpad-error emesso', !!err, JSON.stringify(err));
}
// #27 — slow, one-key-at-a-time manual typing is unaffected by any of the above
{
const n = mk();
send(n, '12.5\r', 200);
const c = lastEvent(n, 'numpad-confirm');
check('#27 digitazione manuale lenta "1,2,.,5,Enter" -> 12.5 invariato', c && c.value === 12.5, JSON.stringify(c));
check('#27 digitazione manuale lenta classificata manual', c && c.inputMethod === 'manual');
}
if (failures > 0) {
console.error(failures + ' test falliti su numpad.js');
process.exit(1);
}
console.log('tutti i test numpad.js superati');