sprint-3: editores de mimicos + tags + alarmas

3 editores nuevos integrados como tabs en MainWindow.

vmssailor/studio/editors/symbols.py
- 8 simbolos navales: motor, pump, valve, tank, sensor, indicator, line, label
- _BaseSymbol QGraphicsItemGroup arrastrable + seleccionable
- TankSymbol con fill animable por porcentaje
- SymbolSpec dataclass serializable a JSON

vmssailor/studio/editors/mimic_editor.py
- Paleta lateral con tipos de simbolos
- Doble-click crea simbolo en canvas
- Selector de sistema (de los habilitados en el proyecto)
- Boton 'Cargar demo' inserta P&ID demo (tanque-valvula-bomba-motor-sensor)
- serialize_to_project() para guardar en .vmsproj

vmssailor/studio/editors/tag_editor.py
- Tabla CRUD con 13 columnas
- Edicion inline de description, unit_si, range, control_mode
- Validacion via Pydantic en cada edit
- Filtro por texto en vivo
- Contador de tags

vmssailor/studio/editors/alarm_editor.py
- Tabla aplanada de todas las alarmas configuradas
- Counts por prioridad coloreados
- Dialog modal para agregar AlarmConfig nueva
- Edicion inline de threshold, operator, priority, hysteresis, delay, message
- Eliminacion individual

vmssailor/studio/main_window.py
- Tabs 'Equipos', 'Mimicos', 'Tags', 'Alarmas' en panel derecho
- Todos los editores reciben set_project y emiten projectMutated

Tests (tests/studio/test_editors.py, 6 nuevos, total 126/126):
- Symbol factory para los 8 tipos
- MimicEditor con demo
- TagEditor render
- AlarmEditor priority counts + empty state
- MainWindow tabs presentes

126/126 verde, ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 18:34:30 -04:00
parent 6ad76a89fa
commit fbce1ecb42
7 changed files with 1233 additions and 4 deletions
+67
View File
@@ -0,0 +1,67 @@
"""Tests Sprint 3: editores de mímicos, tags, alarmas + símbolos."""
from __future__ import annotations
def test_mimic_symbols_factory(qtbot):
from vmssailor.studio.editors.symbols import SYMBOL_KINDS, SymbolSpec, make_symbol
for kind in SYMBOL_KINDS:
spec = SymbolSpec(kind=kind, x=0, y=0, width=60, height=60, label="X")
item = make_symbol(spec)
assert item is not None
def test_mimic_editor_demo_loads(qtbot, sample_project):
from vmssailor.studio.editors.mimic_editor import MimicEditor
me = MimicEditor()
qtbot.addWidget(me)
me.set_project(sample_project)
assert me._system_combo.count() >= 1
# Activar primer sistema y cargar demo
me._system_combo.setCurrentIndex(0)
me._on_add_demo()
serialized = me.serialize_to_project()
assert len(serialized) >= 1
first_sys = next(iter(serialized))
assert len(serialized[first_sys]) >= 5 # demo trae 9 símbolos
def test_tag_editor_renders(qtbot, sample_project):
from vmssailor.studio.editors.tag_editor import TagEditor
te = TagEditor()
qtbot.addWidget(te)
te.set_project(sample_project)
assert te._table.rowCount() == len(sample_project.tags)
def test_alarm_editor_counts_priorities(qtbot, sample_project):
from vmssailor.studio.editors.alarm_editor import AlarmEditor
ae = AlarmEditor()
qtbot.addWidget(ae)
ae.set_project(sample_project)
# sample_project tiene 1 alarma EMERGENCY en sample_tag
total = sum(len(t.alarms) for t in sample_project.tags)
assert ae._table.rowCount() == total
def test_alarm_editor_handles_empty(qtbot):
from vmssailor.studio.editors.alarm_editor import AlarmEditor
ae = AlarmEditor()
qtbot.addWidget(ae)
ae.set_project(None)
assert ae._table.rowCount() == 0
def test_main_window_includes_tabs(qtbot):
from vmssailor.studio.main_window import MainWindow
w = MainWindow()
qtbot.addWidget(w)
assert w._tabs.count() == 4
titles = [w._tabs.tabText(i) for i in range(w._tabs.count())]
assert titles == ["Equipos", "Mímicos", "Tags", "Alarmas"]