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:
@@ -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');
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Runs the plain-node numpad.js behaviour test (tests/js/test_numpad.js).
|
||||
|
||||
There is no browser/JS test harness in this project yet, so this is a thin
|
||||
pytest wrapper: it shells out to ``node`` and asserts the script's own exit
|
||||
code. Skips (does not fail) when node is not installed, since numpad.js's
|
||||
logic still gets exercised by whatever picks this up where node is present.
|
||||
|
||||
Covers gitea:Adriano/TieMeasureFlow#11 and #27.
|
||||
"""
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
FLASK_APP_DIR = Path(__file__).resolve().parent.parent
|
||||
JS_TEST = Path(__file__).resolve().parent / "js" / "test_numpad.js"
|
||||
|
||||
|
||||
def test_numpad_js_hid_sign_and_burst_behaviour():
|
||||
node = shutil.which("node")
|
||||
if not node:
|
||||
pytest.skip("node non installato: test JS di numpad.js saltato")
|
||||
|
||||
result = subprocess.run(
|
||||
[node, str(JS_TEST)],
|
||||
cwd=str(FLASK_APP_DIR),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, (
|
||||
f"tests/js/test_numpad.js fallito (exit {result.returncode})\n"
|
||||
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
|
||||
)
|
||||
Reference in New Issue
Block a user