Wizard DXF e riepilogo: overlay SVG, Salva immagine, NaN per quota non misurata
Refs #118 #119 #121 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,20 @@ const MEASUREMENT = {
|
||||
);
|
||||
}
|
||||
|
||||
// #118: quota non misurata (value null) -> NaN nel valore e nella deviazione; tolleranze nulle restano vuote
|
||||
{
|
||||
const e = csvExport();
|
||||
const nan = { ...MEASUREMENT, value: null, deviation: null, pass_fail: 'fail',
|
||||
subtask: { ...MEASUREMENT.subtask, utl: null, ltl: null } };
|
||||
const row = e.buildMeasurementCSV([nan]).split('\n')[1].split(';');
|
||||
check('#118 value null -> NaN', row[3] === 'NaN', row.join('|'));
|
||||
check('#118 deviazione null -> NaN', row[8] === 'NaN', row.join('|'));
|
||||
check('#118 tolleranze nulle restano vuote', row[6] === '' && row[7] === '', row.join('|'));
|
||||
check('#118 esito fail', row[9] === 'fail', row.join('|'));
|
||||
const ok = e.buildMeasurementCSV([MEASUREMENT]).split('\n')[1].split(';');
|
||||
check('#118 valore presente invariato', ok[3] !== 'NaN' && ok[8] !== 'NaN', ok.join('|'));
|
||||
}
|
||||
|
||||
if (failures > 0) {
|
||||
console.error(failures + ' test falliti su csv-export.js');
|
||||
process.exit(1);
|
||||
|
||||
@@ -139,6 +139,44 @@ delete global.getComputedStyle;
|
||||
await wd.loadDxfPreview();
|
||||
check('#113 DXF tolto: vista azzerata', wd.views.dxf.w === 0 && wd.views.dxf.zoom === 1);
|
||||
|
||||
// #121: overlay SVG al posto del canvas; ridisegno a ogni cambio di zoom (zoomBy, fit)
|
||||
{
|
||||
const calls = [];
|
||||
global.window = { VisionOverlay: { svg: (shapes, o) => { calls.push({ n: shapes.length, zoom: o.zoom }); return '<path d="M0 0"/>'; } } };
|
||||
const attrs = {};
|
||||
const sv = { innerHTML: '', setAttribute: (k, v) => { attrs[k] = v; } };
|
||||
const mk = () => {
|
||||
const x = make(400);
|
||||
x.$refs = { viewport: { clientWidth: 400, clientHeight: 300, scrollLeft: 0, scrollTop: 0 },
|
||||
img: { naturalWidth: 800, naturalHeight: 600 }, overlay: sv };
|
||||
x.result = { result: { overlay: [{ type: 'contour', role: 'found', rows: [1], cols: [2] }] } };
|
||||
return x;
|
||||
};
|
||||
const wo = mk();
|
||||
wo.drawOverlay();
|
||||
check('#121 viewBox = dimensioni naturali', attrs.viewBox === '0 0 800 600', attrs.viewBox);
|
||||
check('#121 innerHTML = VisionOverlay.svg', sv.innerHTML === '<path d="M0 0"/>' && calls[0].n === 1);
|
||||
wo.zoomBy(2);
|
||||
check('#121 zoomBy ridisegna col nuovo zoom', calls[calls.length - 1].zoom === wo.zoom && wo.zoom === 2, JSON.stringify(calls));
|
||||
wo.fit();
|
||||
check('#121 fit ridisegna col nuovo zoom', calls[calls.length - 1].zoom === wo.zoom, JSON.stringify(calls));
|
||||
// senza modulo o senza svg: vuoto, nessun errore
|
||||
global.window = {};
|
||||
sv.innerHTML = 'x';
|
||||
let err = null;
|
||||
try { mk().drawOverlay(); } catch (e) { err = e; }
|
||||
check('#121 senza VisionOverlay: vuoto senza errori', err === null && sv.innerHTML === '', String(err));
|
||||
global.window = { VisionOverlay: {} };
|
||||
sv.innerHTML = 'x';
|
||||
try { mk().drawOverlay(); } catch (e) { err = e; }
|
||||
check('#121 senza svg(): vuoto senza errori', err === null && sv.innerHTML === '', String(err));
|
||||
// immagine non ancora caricata: non tocca nulla
|
||||
const wn = mk(); wn.$refs.img.naturalWidth = 0; sv.innerHTML = 'y';
|
||||
wn.drawOverlay();
|
||||
check('#121 immagine non caricata: nessun disegno', sv.innerHTML === 'y');
|
||||
delete global.window;
|
||||
}
|
||||
|
||||
if (failures) { console.log(failures + ' test falliti'); process.exit(1); }
|
||||
console.log('tutti i test #113 ok');
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""#118: il riepilogo task mostra NaN per una quota non misurata (value None)."""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _station(monkeypatch):
|
||||
monkeypatch.setattr("blueprints.measure.Config.STATION_CODE", "ST-TEST")
|
||||
|
||||
|
||||
def test_task_complete_renders_nan_for_null_value(logged_in_client, mock_api_client):
|
||||
mock_api_client.get.side_effect = [
|
||||
{"id": 1, "code": "REC-001", "name": "Test Recipe", "version": 1},
|
||||
[{"id": 1, "title": "Task 1", "order_index": 0, "subtasks": [
|
||||
{"id": 1, "marker_number": 1, "description": "quota mancante", "nominal": 1.0,
|
||||
"unit": "mm"},
|
||||
{"id": 2, "marker_number": 2, "description": "quota ok", "nominal": 2.0,
|
||||
"unit": "mm"}]}],
|
||||
None,
|
||||
{"items": [
|
||||
{"id": 1, "subtask_id": 1, "value": None, "deviation": None, "pass_fail": "fail",
|
||||
"measured_at": "2026-10-06T08:00:00+00:00"},
|
||||
{"id": 2, "subtask_id": 2, "value": 2.5, "pass_fail": "pass",
|
||||
"measured_at": "2026-10-06T08:00:00+00:00"},
|
||||
]},
|
||||
]
|
||||
resp = logged_in_client.get("/measure/complete/1?version_id=5")
|
||||
assert resp.status_code == 200
|
||||
html = resp.get_data(as_text=True)
|
||||
assert "NaN" in html
|
||||
assert "2.500" in html # la quota misurata resta formattata
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Controllo del template del wizard DXF: Salva in due barre zoom e overlay SVG (#119, #121).
|
||||
|
||||
Legge task_editor.html come testo (come test_vision_panel_fit.py), senza renderlo.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
TEMPLATE = Path(__file__).resolve().parent.parent / "templates" / "maker" / "task_editor.html"
|
||||
|
||||
|
||||
def _src() -> str:
|
||||
return TEMPLATE.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _bar_of(src: str, marker: str) -> str:
|
||||
"""Testo della barra zoom che contiene il marker (dal div della barra al primo </div>)."""
|
||||
i = src.index(marker)
|
||||
start = src.rfind('absolute top-2 right-2', 0, i)
|
||||
return src[start:src.index("</div>", i)]
|
||||
|
||||
|
||||
def test_prova_overlay_is_svg_not_canvas():
|
||||
# #121: l'overlay della Prova e' un <svg> con preserveAspectRatio none, non piu' un canvas
|
||||
src = _src()
|
||||
i = src.index('x-ref="overlay"')
|
||||
tag = src[src.rfind("<", 0, i):src.index(">", i) + 1]
|
||||
assert tag.startswith("<svg")
|
||||
assert 'preserveAspectRatio="none"' in tag
|
||||
assert "pointer-events-none" in tag
|
||||
assert "<canvas" not in src[i - 200:i + 200]
|
||||
|
||||
|
||||
def test_save_button_in_image_and_prova_bars_only():
|
||||
# #119: Salva nella barra dell'immagine e in quella della Prova, non in quella del DXF
|
||||
src = _src()
|
||||
img_bar = _bar_of(src, "vFit('img')")
|
||||
prova_bar = _bar_of(src, 'zoomBy(1 / 1.25)')
|
||||
dxf_bar = _bar_of(src, "vFit('dxf')")
|
||||
assert "tmfSaveImage(imagePreviewUrl, 'immagine')" in img_bar
|
||||
assert "tmfSaveImage(imageSrc, 'prova')" in prova_bar
|
||||
assert "tmfSaveImage" not in dxf_bar
|
||||
assert src.count("tmfSaveImage(") == 2
|
||||
# il pulsante sta dopo Adatta e prima della percentuale
|
||||
assert img_bar.index("vFit('img')") < img_bar.index("tmfSaveImage") < img_bar.index("vPct('img')")
|
||||
assert prova_bar.index("fit()") < prova_bar.index("tmfSaveImage") < prova_bar.index("zoomPct")
|
||||
|
||||
|
||||
def test_scripts_loaded_before_wizard():
|
||||
# #119 #121: vision_overlay.js e save_image.js prima di dxf_recipe_wizard.js
|
||||
src = _src()
|
||||
wiz = src.index("js/dxf_recipe_wizard.js")
|
||||
assert src.index("js/vision_overlay.js") < wiz
|
||||
assert src.index("js/save_image.js") < wiz
|
||||
Reference in New Issue
Block a user