task editor: liste Tipo/Unita e tolleranze generali dalle Impostazioni (#47, #48)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 12:37:14 +02:00
parent c8dd5eecfd
commit dffb11f9f2
3 changed files with 294 additions and 21 deletions
@@ -0,0 +1,164 @@
"""#47/#48: liste Tipo/Unita e tolleranze generali dalle Impostazioni nel task editor."""
import json
import os
import re
import shutil
import subprocess
import tempfile
import pytest
from tests.conftest import api_get_router
_RECIPE_ROUTES = {
"/api/recipes/3": {"id": 3, "code": "R", "name": "R"},
"/api/recipes/3/tasks": [{
"id": 11, "title": "T", "task_type": "measure", "subtasks": [],
"file_path": None, "order_index": 1,
}],
}
def _render(logged_in_client, mock_api_client, settings):
mock_api_client.get.side_effect = api_get_router({**_RECIPE_ROUTES, "/api/settings": settings})
resp = logged_in_client.get("/maker/recipes/3/tasks")
assert resp.status_code == 200
return resp.get_data(as_text=True)
class TestTaskEditorSettings:
def test_configured_lists_reach_template(self, logged_in_client, mock_api_client):
html = _render(logged_in_client, mock_api_client, {
"list_measurement_types": json.dumps(["Planarita", "Concentricita"]),
"list_units": json.dumps(["mm", "thou"]),
})
assert 'configuredTypes: ["Planarita", "Concentricita"]' in html
assert 'configuredUnits: ["mm", "thou"]' in html
# le select leggono le liste, non hanno piu' le option scritte a mano
assert 'x-for="o in measurementTypeOptions()"' in html
assert 'x-for="o in unitOptions()"' in html
assert '<option value="diameter">' not in html
def test_missing_keys_keep_hand_written_values(self, logged_in_client, mock_api_client):
for settings in ({}, {"list_measurement_types": "", "list_units": "non json"},
{"list_units": json.dumps({"a": 1})}, {"list_units": "[]"}):
html = _render(logged_in_client, mock_api_client, settings)
assert "configuredTypes: null," in html
assert "configuredUnits: null," in html
# le voci di oggi restano come default
assert "value: 'diameter'" in html
assert "defaultUnits: ['mm', 'cm'" in html
def test_settings_error_keeps_defaults(self, logged_in_client, mock_api_client):
html = _render(logged_in_client, mock_api_client, {"error": True, "detail": "x"})
assert "configuredTypes: null," in html
assert "generalTolPlus: null," in html
def test_general_tolerances_reach_template(self, logged_in_client, mock_api_client):
html = _render(logged_in_client, mock_api_client, {
"general_tolerance_plus": "0.1", "general_tolerance_minus": "0.25",
})
assert "generalTolPlus: 0.1," in html
assert "generalTolMinus: 0.25," in html
def test_invalid_tolerances_are_none(self, logged_in_client, mock_api_client):
for plus, minus in (("", "abc"), ("-1", "nan"), ("inf", None)):
settings = {"general_tolerance_plus": plus}
if minus is not None:
settings["general_tolerance_minus"] = minus
html = _render(logged_in_client, mock_api_client, settings)
assert "generalTolPlus: null," in html
assert "generalTolMinus: null," in html
# ---- comportamento JS (node): opzioni fuori lista e automatismo delle tolleranze ----
_NODE = shutil.which("node")
def _run_js(html, body):
"""Esegue taskEditor() estratto dalla pagina e poi `body` (che usa `ed`); stampa JSON."""
scripts = re.findall(r"<script(?![^>]*\bsrc=)[^>]*>(.*?)</script>", html, re.S | re.I)
src = next(s for s in scripts if "function taskEditor()" in s)
program = src + "\nconst ed = taskEditor();\nconst out = (function(){" + body + "})();\nconsole.log(JSON.stringify(out));"
# su file: la riga di comando di Windows non regge lo script intero
with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False, encoding="utf-8") as fh:
fh.write(program)
try:
res = subprocess.run([_NODE, fh.name], capture_output=True, text=True,
encoding="utf-8", timeout=30)
finally:
os.unlink(fh.name)
assert res.returncode == 0, res.stderr
return json.loads(res.stdout)
@pytest.mark.skipif(_NODE is None, reason="node non disponibile")
class TestTaskEditorJs:
def test_out_of_list_value_stays_selectable(self, logged_in_client, mock_api_client):
html = _render(logged_in_client, mock_api_client, {
"list_measurement_types": json.dumps(["Planarita"]),
"list_units": json.dumps(["thou"]),
})
out = _run_js(html, """
ed.newSubtask.measurement_type = 'Vecchio'; ed.newSubtask.unit = 'mm';
return {t: ed.measurementTypeOptions().map(o => o.value),
u: ed.unitOptions().map(o => o.value), d: ed.defaultUnit()};
""")
assert out["t"] == ["Planarita", "Vecchio"]
assert out["u"] == ["thou", "mm"]
assert out["d"] == "thou"
def test_default_lists_when_not_configured(self, logged_in_client, mock_api_client):
html = _render(logged_in_client, mock_api_client, {})
out = _run_js(html, """
return {t: ed.measurementTypeOptions().map(o => o.value),
u: ed.unitOptions().map(o => o.value), d: ed.defaultUnit()};
""")
assert out["t"][0] == "linear" and out["t"][-1] == "other"
assert out["u"][0] == "mm" and "Nm" in out["u"]
assert out["d"] == "mm"
def test_tolerance_autofill(self, logged_in_client, mock_api_client):
html = _render(logged_in_client, mock_api_client, {
"general_tolerance_plus": "0.1", "general_tolerance_minus": "0.2",
})
out = _run_js(html, """
ed.tasks = [{id: 1, subtasks: []}];
ed.resetNewSubtask(1);
const r = {};
ed.newSubtask.nominal = 10; ed.onNominalInput({target: {value: '10'}});
r.first = [ed.newSubtask.utl, ed.newSubtask.ltl];
// cambia ancora il nominale prima di toccare i limiti: ricalcola
ed.onNominalInput({target: {value: '12.5'}});
r.second = [ed.newSubtask.utl, ed.newSubtask.ltl];
// modifica a mano utl: smette di riempire
ed.newSubtask.utl = 99; ed.tolAuto = false;
ed.onNominalInput({target: {value: '20'}});
r.third = [ed.newSubtask.utl, ed.newSubtask.ltl];
r.text = ed.generalTolText();
return r;
""")
assert out["first"] == [10.1, 9.8]
assert out["second"] == [12.6, 12.3]
assert out["third"] == [99, 12.3]
assert "+0.1 / −0.2" in out["text"]
def test_no_autofill_without_tolerances_or_when_editing(self, logged_in_client, mock_api_client):
html = _render(logged_in_client, mock_api_client, {})
out = _run_js(html, """
ed.tasks = [{id: 1, subtasks: []}];
ed.resetNewSubtask(1);
ed.onNominalInput({target: {value: '10'}});
return {utl: ed.newSubtask.utl, ltl: ed.newSubtask.ltl, auto: ed.tolAuto,
text: ed.generalTolText()};
""")
assert out == {"utl": None, "ltl": None, "auto": False, "text": ""}
html = _render(logged_in_client, mock_api_client, {"general_tolerance_plus": "0.1"})
out = _run_js(html, """
ed.startEditSubtask({id: 5, marker_number: 1, nominal: 10, utl: null, ltl: null}, 1);
ed.onNominalInput({target: {value: '10'}});
return {utl: ed.newSubtask.utl, auto: ed.tolAuto};
""")
assert out == {"utl": None, "auto": False}