Pannello visione operatore: zoom, overlay e Salva passano a <tmf-viewer>
Refs #122 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,46 +93,28 @@ check('#116 riga: key = subtask_id', r3[0].key === 30 && r3[1].key === 31);
|
||||
check('#116 riga: name senza marker', r3[0].name === 'Larghezza' && r3[1].name === 'Diametro', r3[0].name + '/' + r3[1].name);
|
||||
check('#116 riga: vision_output', r3[0].vision_output === 'larg.value' && r3[1].vision_output == null);
|
||||
check('#116 label invariato (#114)', r3[0].label === '1. Larghezza', r3[0].label);
|
||||
check('#116 senza VisionOverlay: nessuna etichetta', c3.overlayLabels.length === 0);
|
||||
check('#116 senza VisionOverlay: nessuna etichetta', c3.viewerLabels.length === 0);
|
||||
|
||||
let seen = null;
|
||||
ctx.VisionOverlay = {
|
||||
labels(rows, overlay) { seen = { rows, overlay }; return [{ key: 30, x: 500, y: 250, text: 'Larghezza 1.500 mm', status: 'pass' }]; },
|
||||
};
|
||||
check('#116 senza immagine: nessuna etichetta', c3.overlayLabels.length === 0);
|
||||
c3.z.w = 2000; c3.z.h = 1000;
|
||||
const ls = c3.overlayLabels;
|
||||
const ls = c3.viewerLabels;
|
||||
check('#116 una etichetta', ls.length === 1 && ls[0].key === 30 && ls[0].text === 'Larghezza 1.500 mm', JSON.stringify(ls));
|
||||
check('#116 posizione in percentuale', ls[0].style === 'left:25%;top:25%;transform:translate(8px,-50%)', ls[0].style);
|
||||
// #122: posizione in px logici dell'immagine (la percentuale la calcola <tmf-viewer>), niente style
|
||||
check('#122 posizione in px logici', ls[0].x === 500 && ls[0].y === 250 && ls[0].style === undefined, JSON.stringify(ls[0]));
|
||||
check('#116 classe da esito', ls[0].cls === 'bg-green-600 text-white', ls[0].cls);
|
||||
check('#116 labels riceve rows e overlay', seen.rows.length === 2 && seen.overlay.length === 1);
|
||||
c3.result = null;
|
||||
check('#116 senza risultato: nessuna etichetta', c3.overlayLabels.length === 0);
|
||||
check('#116 senza risultato: nessuna etichetta', c3.viewerLabels.length === 0);
|
||||
check('#116 labelClass pass', c3.labelClass('pass') === 'bg-green-600 text-white');
|
||||
check('#116 labelClass warning', c3.labelClass('warning') === 'bg-amber-400 text-black');
|
||||
check('#116 labelClass fail', c3.labelClass('fail') === 'bg-red-600 text-white');
|
||||
check('#116 labelClass altro', c3.labelClass('boh') === 'bg-gray-700 text-white' && c3.labelClass(undefined) === 'bg-gray-700 text-white');
|
||||
|
||||
// #121: drawOverlay imposta il viewBox naturale e riempie l'svg con VisionOverlay.svg(overlay, {zoom})
|
||||
let svgArgs = null;
|
||||
ctx.VisionOverlay = { svg(overlay, opts) { svgArgs = { overlay, opts }; return '<circle cx="1" cy="2" r="3"/>'; } };
|
||||
const attrs = {};
|
||||
const sv = { innerHTML: 'vecchio', setAttribute(k, v) { attrs[k] = v; } };
|
||||
c3.$refs = { img: { naturalWidth: 2000, naturalHeight: 1000 }, overlay: sv };
|
||||
c3.result = { overlay: [{ type: 'circle' }] };
|
||||
c3.z.zoom = 0.5;
|
||||
c3.drawOverlay();
|
||||
check('#121 viewBox = dimensioni naturali', attrs.viewBox === '0 0 2000 1000', attrs.viewBox);
|
||||
check('#121 innerHTML = VisionOverlay.svg', sv.innerHTML === '<circle cx="1" cy="2" r="3"/>', sv.innerHTML);
|
||||
check('#121 svg riceve overlay e zoom', svgArgs.overlay.length === 1 && svgArgs.opts.zoom === 0.5, JSON.stringify(svgArgs.opts));
|
||||
c3.result = null;
|
||||
c3.drawOverlay();
|
||||
check('#121 senza risultato: svg vuoto', sv.innerHTML === '');
|
||||
ctx.VisionOverlay = {};
|
||||
c3.result = { overlay: [{ type: 'circle' }] };
|
||||
c3.drawOverlay();
|
||||
check('#121 senza VisionOverlay.svg: svg vuoto, nessun errore', sv.innerHTML === '');
|
||||
check('#121 niente codice canvas', !/getContext|clearRect|\.width\s*=/.test(src.slice(src.indexOf('drawOverlay() {'))));
|
||||
// #122: zoom, overlay SVG e Salva sono di <tmf-viewer>: il componente non li ha piu'
|
||||
check('#122 niente drawOverlay/saveImage/overlayLabels/z', c3.drawOverlay === undefined && c3.saveImage === undefined && c3.overlayLabels === undefined && c3.z === undefined);
|
||||
check('#122 niente codice canvas', !/getContext|clearRect/.test(src));
|
||||
|
||||
// #118: quota non misurata (value null) = NaN, mai Number(null) = 0
|
||||
check('#118 fmtValue(null) = NaN', c3.fmtValue(null, 'mm') === 'NaN mm', c3.fmtValue(null, 'mm'));
|
||||
@@ -193,17 +175,11 @@ c5.acquire = async function () { this.result = { vision_result_id: 7, measuremen
|
||||
h();
|
||||
check('#120 task-result-reset: saved = false', c5.saved === false);
|
||||
|
||||
// #119: saveImage -> tmfSaveImage(src dell'immagine mostrata, nome del task)
|
||||
let saveArgs = null;
|
||||
ctx.tmfSaveImage = (src, name) => { saveArgs = { src, name }; return Promise.resolve(true); };
|
||||
// #119 #122: saveName = titolo del task (nome proposto da <tmf-viewer save-name>), 'immagine' se manca
|
||||
win.__visionCfg = { taskName: 'Roto3', subtasks: [] };
|
||||
const c9 = ctx.visionExecute();
|
||||
c9.imageSrc = 'blob:abc';
|
||||
const r9 = await c9.saveImage();
|
||||
check('#119 saveImage: tmfSaveImage(src, nome task)', r9 === true && saveArgs.src === 'blob:abc' && saveArgs.name === 'Roto3', JSON.stringify(saveArgs));
|
||||
c9.imageSrc = '';
|
||||
saveArgs = null;
|
||||
check('#119 saveImage senza immagine: niente', (await c9.saveImage()) === false && saveArgs === null);
|
||||
check('#122 saveName = nome del task', ctx.visionExecute().saveName === 'Roto3');
|
||||
win.__visionCfg = { subtasks: [] };
|
||||
check('#122 saveName di riserva', ctx.visionExecute().saveName === 'immagine');
|
||||
|
||||
process.exit(failures ? 1 : 0);
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* #115: zoom, Adatta e pan del riquadro immagine del pannello visione, con plain node.
|
||||
* #122: lo zoom del pannello visione non e' piu' in vision_execute.js ma in <tmf-viewer> (image_viewer.js).
|
||||
* Qui: il componente passa al viewer solo le etichette in px logici (viewerLabels) e non ha piu' codice di zoom.
|
||||
*
|
||||
* Run from src/frontend/flask_app: node tests/js/test_vision_zoom.js
|
||||
*/
|
||||
@@ -11,7 +12,7 @@ const vm = require('vm');
|
||||
const src = fs.readFileSync(
|
||||
path.join(__dirname, '..', '..', 'static', 'js', 'vision_execute.js'), 'utf8',
|
||||
);
|
||||
const win = {};
|
||||
const win = { __visionCfg: { subtasks: [{ id: 1, description: 'Larg', unit: 'mm' }] } };
|
||||
const ctx = vm.createContext({ window: win, document: { addEventListener() {}, querySelector() { return null; } } });
|
||||
vm.runInContext(src + ';this.visionExecute = visionExecute;', ctx);
|
||||
|
||||
@@ -20,106 +21,24 @@ function check(label, cond, extra) {
|
||||
if (cond) console.log('ok - ' + label);
|
||||
else { failures++; console.log('FAIL - ' + label + (extra ? ' :: ' + extra : '')); }
|
||||
}
|
||||
const near = (a, b) => Math.abs(a - b) < 1e-9 * Math.max(1, Math.abs(b));
|
||||
|
||||
// Componente con viewport e immagine finti ($nextTick sincrono); getComputedStyle assente: si usano client*
|
||||
function make(cw, ch) {
|
||||
const c = ctx.visionExecute();
|
||||
const vp = { clientWidth: cw, clientHeight: ch, scrollLeft: 0, scrollTop: 0, setPointerCapture() {} };
|
||||
const img = { naturalWidth: 2000, naturalHeight: 1000 };
|
||||
c.$refs = { vp, img, overlay: null };
|
||||
c.$nextTick = (fn) => fn();
|
||||
return c;
|
||||
// Niente piu' stato ne' funzioni di zoom/pan/resize/Salva/overlay nel componente
|
||||
const c = ctx.visionExecute();
|
||||
for (const k of ['z', 'zoomBy', 'zFit', 'zWheel', 'zCanPan', 'zCursor', 'zPct', 'zSize', 'zPanDown', 'zPanMove', 'zPanEnd',
|
||||
'onImgLoad', 'drawOverlay', 'saveImage', 'overlayLabels']) {
|
||||
check('#122 componente senza ' + k, c[k] === undefined);
|
||||
}
|
||||
check('#122 sorgente senza $watch di zoom ne ResizeObserver', !/z\.zoom|ResizeObserver|tmfSaveImage/.test(src));
|
||||
|
||||
// Prima del load: il wrapper riempie il riquadro
|
||||
let c = make(800, 400);
|
||||
check('#115 zSize prima del load = 100 %', c.zSize() === 'width:100%;height:100%', c.zSize());
|
||||
check('#115 zoomBy senza immagine: ignorato', (c.zoomBy(2), c.z.zoom === 1));
|
||||
|
||||
// Load: fit = min(800/2000, 400/1000) = 0.4
|
||||
c.onImgLoad();
|
||||
check('#115 load: Adatta = 0.4', near(c.z.zoom, 0.4) && near(c.z.fit, 0.4) && c.z.auto === true, String(c.z.zoom));
|
||||
check('#115 zSize = naturale x zoom', c.zSize() === 'width:800px;height:400px', c.zSize());
|
||||
check('#115 zPct = 40%', c.zPct() === '40%', c.zPct());
|
||||
check('#115 a Adatta non si trascina', c.zCanPan() === false && c.zCursor() === 'default');
|
||||
|
||||
// Zoom in/out e limiti
|
||||
c.zoomBy(1.25);
|
||||
check('#115 zoomBy moltiplica', near(c.z.zoom, 0.5) && c.z.auto === false);
|
||||
check('#115 dopo lo zoom si trascina', c.zCanPan() === true && c.zCursor() === 'grab');
|
||||
c.zoomBy(1e6);
|
||||
check('#115 limite alto 8', c.z.zoom === 8, String(c.z.zoom));
|
||||
c.zoomBy(1e-9);
|
||||
check('#115 limite basso fit*0.5', near(c.z.zoom, 0.2), String(c.z.zoom));
|
||||
c.zFit();
|
||||
check('#115 Adatta torna al fit e a auto', near(c.z.zoom, 0.4) && c.z.auto === true);
|
||||
|
||||
// Il punto sotto il cursore resta fermo: zoom da 0.4 a 0.8 col cursore a (100, 50), scroll 0
|
||||
c.zoomBy(2, 100, 50);
|
||||
check('#115 zoom al cursore: scrollLeft', near(c.$refs.vp.scrollLeft, 100) && near(c.$refs.vp.scrollTop, 50),
|
||||
c.$refs.vp.scrollLeft + ',' + c.$refs.vp.scrollTop);
|
||||
|
||||
// Frame nuovo con le stesse dimensioni: tiene lo zoom; dimensioni diverse: rifa il fit
|
||||
const z0 = c.z.zoom;
|
||||
c.onImgLoad();
|
||||
check('#115 frame con stesse dimensioni: zoom invariato', c.z.zoom === z0 && c.z.auto === false);
|
||||
c.$refs.img.naturalWidth = 1000; c.$refs.img.naturalHeight = 1000;
|
||||
c.onImgLoad();
|
||||
check('#115 dimensioni diverse: nuovo fit', near(c.z.fit, 0.4) && c.z.zoom === c.z.fit && c.z.auto === true);
|
||||
|
||||
// Riquadro nascosto (0 x 0): niente fit, stato invariato
|
||||
c = make(0, 0);
|
||||
c.onImgLoad();
|
||||
check('#115 riquadro nascosto: nessun fit', c.z.zoom === 1 && c.z.w === 2000);
|
||||
c.$refs.vp.clientWidth = 800; c.$refs.vp.clientHeight = 400;
|
||||
c.zFit();
|
||||
check('#115 riquadro visibile: il fit parte', near(c.z.zoom, 0.4));
|
||||
|
||||
// offset meno bordi (getComputedStyle disponibile)
|
||||
c = make(780, 380);
|
||||
c.$refs.vp.offsetWidth = 802; c.$refs.vp.offsetHeight = 402;
|
||||
ctx.getComputedStyle = () => ({ borderLeftWidth: '1px', borderRightWidth: '1px', borderTopWidth: '1px', borderBottomWidth: '1px' });
|
||||
c.onImgLoad();
|
||||
check('#115 area = offset meno bordi', near(c.z.fit, 0.4), String(c.z.fit));
|
||||
delete ctx.getComputedStyle;
|
||||
|
||||
// Rotella: solo con Ctrl
|
||||
c = make(800, 400); c.onImgLoad();
|
||||
let prevented = false;
|
||||
c.zWheel({ ctrlKey: false, deltaY: -1, preventDefault() { prevented = true; } });
|
||||
check('#115 rotella senza Ctrl: ignorata', !prevented && near(c.z.zoom, 0.4));
|
||||
c.zWheel({ ctrlKey: true, deltaY: -1, clientX: 0, clientY: 0, preventDefault() { prevented = true; } });
|
||||
check('#115 Ctrl+rotella su: zoom x1.25', prevented && near(c.z.zoom, 0.5));
|
||||
c.zWheel({ ctrlKey: true, deltaY: 1, preventDefault() {} });
|
||||
check('#115 Ctrl+rotella giu: zoom /1.25', near(c.z.zoom, 0.4));
|
||||
|
||||
// Pan: sposta lo scroll solo se ingrandito
|
||||
c = make(800, 400); c.onImgLoad();
|
||||
c.zPanDown({ clientX: 10, clientY: 10, pointerType: 'mouse', button: 0, pointerId: 1 });
|
||||
check('#115 pan a Adatta: ignorato', c._zPan === null && c.zPanning === false);
|
||||
c.zoomBy(2);
|
||||
c.$refs.vp.scrollLeft = 50; c.$refs.vp.scrollTop = 40;
|
||||
c.zPanDown({ clientX: 100, clientY: 100, pointerType: 'mouse', button: 0, pointerId: 1 });
|
||||
check('#115 pan avviato: cursore grabbing', c.zPanning === true && c.zCursor() === 'grabbing');
|
||||
c.zPanMove({ clientX: 80, clientY: 90 });
|
||||
check('#115 pan: scroll segue il puntatore', c.$refs.vp.scrollLeft === 70 && c.$refs.vp.scrollTop === 50,
|
||||
c.$refs.vp.scrollLeft + ',' + c.$refs.vp.scrollTop);
|
||||
c.zPanEnd();
|
||||
check('#115 pan finito', c._zPan === null && c.zPanning === false);
|
||||
|
||||
// #121: init registra un watcher su z.zoom che ridisegna l'overlay SVG (spessore e croci costanti a ogni zoom)
|
||||
{
|
||||
const w = make(800, 400);
|
||||
const watched = {};
|
||||
w.$watch = (expr, fn) => { watched[expr] = fn; };
|
||||
win.addEventListener = () => {}; // init ascolta task-result-reset su window
|
||||
let redraws = 0;
|
||||
w.drawOverlay = () => { redraws++; };
|
||||
w.init();
|
||||
check('#121 init: watcher su z.zoom', typeof watched['z.zoom'] === 'function');
|
||||
watched['z.zoom']();
|
||||
check('#121 cambio zoom: overlay ridisegnato', redraws === 1, String(redraws));
|
||||
}
|
||||
// viewerLabels: {key, x, y, text, cls} in px logici, classe dall'esito; vuote senza risultato o senza VisionOverlay
|
||||
check('#122 senza VisionOverlay: nessuna etichetta', (c.result = { measurements: [] }, c.viewerLabels.length === 0));
|
||||
win.VisionOverlay = ctx.VisionOverlay = {
|
||||
labels() { return [{ key: 1, x: 500, y: 250, text: 'Larg 1.500 mm', status: 'fail' }]; },
|
||||
};
|
||||
const ls = c.viewerLabels;
|
||||
check('#122 viewerLabels in px logici', ls.length === 1 && ls[0].x === 500 && ls[0].y === 250 && ls[0].key === 1 && ls[0].text === 'Larg 1.500 mm', JSON.stringify(ls));
|
||||
check('#122 viewerLabels: classe da esito', ls[0].cls === 'bg-red-600 text-white', ls[0].cls);
|
||||
c.result = null;
|
||||
check('#122 senza risultato: nessuna etichetta', c.viewerLabels.length === 0);
|
||||
|
||||
process.exit(failures ? 1 : 0);
|
||||
|
||||
@@ -40,20 +40,28 @@ def test_camera_bar_has_grab_and_record_only():
|
||||
assert "onPick(" not in cam
|
||||
|
||||
|
||||
def test_save_button_in_zoom_bar_between_adatta_and_percent():
|
||||
# #119: «Salva» dopo «Adatta», prima della percentuale; script save_image.js incluso
|
||||
def test_save_button_is_tmf_viewer_with_texts():
|
||||
# #119 #122: «Salva» e' del componente <tmf-viewer> (barra: Riduci, Ingrandisci, Adatta, Salva, %); il template gli passa nome e testi
|
||||
panel = _panel()
|
||||
assert panel.index("{{ _('Adatta') }}") < panel.index("{{ _('Salva') }}") < panel.index('x-text="zPct()"')
|
||||
assert '@click="saveImage()"' in panel and "{{ _('Salva immagine') }}" in panel
|
||||
assert "js/save_image.js" in SRC
|
||||
assert "<tmf-viewer" in panel and ':src="shownImage"' in panel and ':save-name="saveName"' in panel
|
||||
for t in ("Adatta", "Salva", "Ingrandisci", "Riduci", "Salva immagine"):
|
||||
assert "{{ _('%s') }}" % t in panel, t
|
||||
assert "saveImage()" not in panel and "zPct()" not in panel
|
||||
assert "taskName:" in SRC
|
||||
|
||||
|
||||
def test_overlay_is_svg_not_canvas():
|
||||
# #121: svg dentro il wrapper dello zoom, nessun canvas nel pannello
|
||||
def test_viewer_scripts_order():
|
||||
# #122: image_viewer.js dopo vision_overlay.js e save_image.js (e prima di vision_execute.js non serve: e' un Custom Element)
|
||||
assert SRC.index("js/vision_overlay.js") < SRC.index("js/image_viewer.js")
|
||||
assert SRC.index("js/save_image.js") < SRC.index("js/image_viewer.js")
|
||||
|
||||
|
||||
def test_overlay_and_labels_passed_to_viewer():
|
||||
# #121 #122: overlay e etichette arrivano al viewer con x-effect (proprieta'), niente canvas
|
||||
panel = _panel()
|
||||
assert '<svg x-ref="overlay"' in panel and "<canvas" not in panel
|
||||
assert panel.index('zSize()') < panel.index('<svg x-ref="overlay"') < panel.index('x-for="l in overlayLabels"')
|
||||
assert "<canvas" not in panel
|
||||
assert "$el.overlay = (result && result.overlay) || []" in panel and "$el.labels = viewerLabels" in panel
|
||||
assert "overlayLabels" not in panel
|
||||
|
||||
|
||||
def test_value_cell_shows_nan_for_null():
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""gitea:Adriano/TieMeasureFlow#96 - l'immagine del task visione sta in fit, senza scorrimento.
|
||||
"""gitea:Adriano/TieMeasureFlow#96 #122 - l'immagine del task visione sta in fit, senza scorrimento.
|
||||
|
||||
Controllo sulle classi del template: pannello overflow-hidden, img/canvas
|
||||
object-contain sovrapposti, nessun h-auto sull'immagine (era la causa dello scorrimento).
|
||||
Controllo sulle classi del template: pannello overflow-hidden, riquadro flex-1 min-h-0 che contiene
|
||||
<tmf-viewer> a tutta altezza (#122: zoom, img e overlay stanno dentro il componente).
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
@@ -37,28 +37,22 @@ def test_panel_does_not_scroll():
|
||||
assert "overflow-auto" not in tag
|
||||
|
||||
|
||||
def test_image_is_object_contain_without_h_auto():
|
||||
def test_image_is_inside_tmf_viewer():
|
||||
# #122: img, svg overlay, barra zoom e pan sono di <tmf-viewer>: il template non ha piu' img/svg/viewport propri
|
||||
panel = _panel()
|
||||
img = _tag_with(panel, 'x-ref="img"')
|
||||
assert "object-contain" in img
|
||||
assert "h-auto" not in img
|
||||
# #121: l'overlay e' un svg (non piu' un canvas) con viewBox naturale e preserveAspectRatio="none":
|
||||
# riempie il wrapper come l'img (che e' esattamente naturale x zoom), quindi resta allineato
|
||||
overlay = _tag_with(panel, 'x-ref="overlay"')
|
||||
assert overlay.startswith("<svg") and "<canvas" not in panel
|
||||
assert 'preserveAspectRatio="none"' in overlay and "pointer-events-none" in overlay
|
||||
assert "absolute" in overlay and "inset-0" in overlay and "w-full" in overlay and "h-full" in overlay
|
||||
viewer = _tag_with(panel, "tmf-viewer class")
|
||||
assert "block" in viewer and "w-full" in viewer and "h-full" in viewer
|
||||
assert ':src="shownImage"' in viewer
|
||||
assert "x-show" not in viewer # mai x-show e :style stringa sullo stesso elemento: x-show sta sul riquadro
|
||||
assert 'x-ref="img"' not in panel and 'x-ref="overlay"' not in panel and "<canvas" not in panel
|
||||
assert "<img" not in panel and "<svg" not in panel
|
||||
|
||||
|
||||
def test_zoom_viewport_scrolls_inside_the_box():
|
||||
# #115: il riquadro resta overflow-hidden (il pannello non scorre); lo scorrimento e' solo del viewport interno
|
||||
def test_no_own_zoom_viewport_in_template():
|
||||
# #122: niente piu' viewport/wrapper di zoom del pannello (#115): li fa il componente
|
||||
panel = _panel()
|
||||
vp = _tag_with(panel, 'x-ref="vp"')
|
||||
assert "absolute" in vp and "inset-0" in vp
|
||||
assert "overflow:auto" in vp # :style stringa del viewport
|
||||
assert 'style="' not in vp.replace(':style="', "") # niente style statico accanto a :style (Alpine lo cancella)
|
||||
assert "x-show" not in vp # mai x-show e :style stringa sullo stesso elemento
|
||||
assert "zSize()" in panel # wrapper naturale x zoom
|
||||
assert 'x-ref="vp"' not in panel
|
||||
assert "zSize()" not in panel and "zoomBy(" not in panel and "zFit()" not in panel
|
||||
|
||||
|
||||
def test_image_box_and_outcome_column_can_shrink():
|
||||
@@ -77,12 +71,11 @@ def test_quote_table_shows_nominal_and_tolerances():
|
||||
assert "text-measure-fail" in panel and "text-measure-warning" in panel
|
||||
|
||||
|
||||
def test_overlay_script_loaded_before_vision_execute_and_labels_layer():
|
||||
# #116: vision_overlay.js prima di vision_execute.js; strato etichette dentro il wrapper dello zoom, dopo l'svg
|
||||
def test_overlay_script_loaded_before_vision_execute_and_viewer():
|
||||
# #116 #122: vision_overlay.js prima di vision_execute.js; le etichette le disegna <tmf-viewer> (viewerLabels via x-effect)
|
||||
src = TEMPLATE.read_text(encoding="utf-8")
|
||||
assert src.index("js/vision_overlay.js") < src.index("js/vision_execute.js")
|
||||
assert src.index("js/vision_overlay.js") < src.index("js/image_viewer.js")
|
||||
panel = _panel()
|
||||
assert panel.index('x-ref="overlay"') < panel.index('x-for="l in overlayLabels"')
|
||||
label = _tag_with(panel, 'x-text="l.text"')
|
||||
assert ':style="l.style"' in label and "pointer-events-none" in label
|
||||
assert 'style="' not in label.replace(':style="', "") # niente style statico accanto a :style
|
||||
assert "$el.labels = viewerLabels" in panel
|
||||
assert 'x-for="l in overlayLabels"' not in panel
|
||||
|
||||
@@ -4,7 +4,7 @@ Same thin pytest wrapper pattern as test_vision_rows_js.py: shells out to
|
||||
``node`` and asserts the script's own exit code. Skips (does not fail) when
|
||||
node is not installed.
|
||||
|
||||
Covers gitea:Adriano/TieMeasureFlow#115.
|
||||
Covers gitea:Adriano/TieMeasureFlow#115 #122 (dal #122 il test JS verifica che lo zoom non sia piu' nel componente).
|
||||
"""
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
Reference in New Issue
Block a user