mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-06-05 12:58:40 +02:00
459 lines
15 KiB
Python
459 lines
15 KiB
Python
import json
|
|
|
|
import shiboken6
|
|
from bec_lib import messages
|
|
from qtpy.QtCore import QCoreApplication, QEvent, Qt
|
|
from qtpy.QtWidgets import QMessageBox, QSizePolicy
|
|
|
|
from bec_widgets.utils.toolbars.toolbar import ModularToolBar
|
|
from bec_widgets.widgets.services.beamline_states.beamline_state_pill import (
|
|
AddBeamlineStateDialog,
|
|
BeamlineStateManager,
|
|
BeamlineStatePill,
|
|
)
|
|
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox
|
|
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combobox import SignalComboBox
|
|
from bec_widgets.widgets.utility.spinbox.decimal_spinbox import BECSpinBox
|
|
|
|
from .client_mocks import mocked_client
|
|
|
|
|
|
def test_beamline_state_pill_updates_from_message(qtbot, mocked_client):
|
|
widget = BeamlineStatePill(state_name="shutter_open", title="Shutter", client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_state(
|
|
{"name": "shutter_open", "status": "valid", "label": "Shutter is open."}, {}
|
|
)
|
|
|
|
assert widget.state_name == "shutter_open"
|
|
assert widget._name_label.text() == "Shutter"
|
|
assert widget._status_label.text() == "VALID"
|
|
assert widget._detail_label.text() == "Shutter is open."
|
|
assert not widget._icon_label.pixmap().isNull()
|
|
assert widget._flash_active
|
|
assert widget.toolTip() == "Shutter is open."
|
|
|
|
|
|
def test_beamline_state_pill_ignores_other_states(qtbot, mocked_client):
|
|
widget = BeamlineStatePill(state_name="shutter_open", client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_state(
|
|
{"name": "other_state", "status": "invalid", "label": "Should be ignored."}, {}
|
|
)
|
|
|
|
assert widget._status_label.text() == "UNKNOWN"
|
|
assert widget.toolTip() == "No state information available."
|
|
|
|
|
|
def test_beamline_state_pill_expands_and_emits_updated_limits(qtbot, mocked_client):
|
|
widget = BeamlineStatePill(state_name="limits", title="Limits", client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
widget.set_state_config(
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"device": "samx",
|
|
"signal": "samx",
|
|
"low_limit": 0.0,
|
|
"high_limit": 10.0,
|
|
"tolerance": 0.1,
|
|
},
|
|
}
|
|
)
|
|
|
|
assert widget._settings.isHidden()
|
|
|
|
qtbot.mouseClick(widget._header, Qt.MouseButton.LeftButton)
|
|
widget._high_limit.setValue(20.0)
|
|
|
|
assert not widget._settings.isHidden()
|
|
assert isinstance(widget._device_edit, DeviceComboBox)
|
|
assert isinstance(widget._signal_edit, SignalComboBox)
|
|
assert widget._device_edit.currentText() == "samx"
|
|
for field in widget._settings_input_fields():
|
|
assert field.minimumWidth() == widget._SETTINGS_FIELD_WIDTH
|
|
assert field.sizePolicy().horizontalPolicy() == QSizePolicy.Policy.Expanding
|
|
assert widget.edited_parameters()["high_limit"] == 20.0
|
|
|
|
with qtbot.waitSignal(widget.update_requested) as signal:
|
|
widget._update_button.click()
|
|
|
|
assert signal.args[0] == "limits"
|
|
assert signal.args[1]["device"] == "samx"
|
|
assert signal.args[1]["signal"] == "samx"
|
|
assert signal.args[1]["low_limit"] == 0.0
|
|
assert signal.args[1]["high_limit"] == 20.0
|
|
assert signal.args[1]["tolerance"] == 0.1
|
|
|
|
|
|
def test_beamline_state_pill_uses_card_style_when_expanded(qtbot, mocked_client):
|
|
widget = BeamlineStatePill(state_name="limits", title="Limits", client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
assert "qlineargradient" not in widget.styleSheet()
|
|
assert "#BeamlineStatePill {background: transparent" in widget.styleSheet()
|
|
|
|
widget._toggle_expanded()
|
|
|
|
assert "qlineargradient" in widget.styleSheet()
|
|
assert widget._shadow.isEnabled()
|
|
|
|
|
|
def test_beamline_state_pill_can_keep_idle_background(qtbot, mocked_client):
|
|
widget = BeamlineStatePill(state_name="limits", title="Limits", client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
assert "#BeamlineStatePill {background: transparent" in widget.styleSheet()
|
|
|
|
widget.card_background_mode = "always"
|
|
|
|
assert "#BeamlineStatePill {background: transparent" not in widget.styleSheet()
|
|
assert "qlineargradient" not in widget.styleSheet()
|
|
|
|
|
|
def test_beamline_state_pill_uses_card_style_when_hovered(qtbot, mocked_client):
|
|
widget = BeamlineStatePill(state_name="limits", title="Limits", client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
assert "qlineargradient" not in widget.styleSheet()
|
|
|
|
widget.eventFilter(widget._header, QEvent(QEvent.Type.Enter))
|
|
|
|
assert "qlineargradient" in widget.styleSheet()
|
|
assert widget._shadow.isEnabled()
|
|
|
|
|
|
def test_beamline_state_pill_drag_payload_modes(qtbot, mocked_client):
|
|
widget = BeamlineStatePill(state_name="limits", title="Limits", client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
widget.set_state_config(
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {"device": "samx", "signal": "samx"},
|
|
}
|
|
)
|
|
|
|
config_mime = widget._create_drag_mime_data()
|
|
config_payload = bytes(config_mime.data(BeamlineStatePill.MIME_PAYLOAD)).decode("utf-8")
|
|
assert json.loads(config_payload)["parameters"]["device"] == "samx"
|
|
assert config_mime.text() == config_payload
|
|
|
|
widget.drag_payload_mode = "device"
|
|
device_mime = widget._create_drag_mime_data()
|
|
|
|
assert bytes(device_mime.data(BeamlineStatePill.MIME_PAYLOAD)).decode("utf-8") == "samx"
|
|
assert bytes(device_mime.data(BeamlineStatePill.MIME_DEVICE)).decode("utf-8") == "samx"
|
|
assert device_mime.text() == "samx"
|
|
|
|
|
|
def test_beamline_state_manager_adds_and_removes_pills(qtbot, mocked_client):
|
|
widget = BeamlineStateManager(client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_available_states(
|
|
{
|
|
"states": [
|
|
messages.BeamlineStateConfig(
|
|
name="shutter_open", title="Shutter", state_type="ShutterState", parameters={}
|
|
),
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {},
|
|
},
|
|
]
|
|
},
|
|
{},
|
|
)
|
|
|
|
assert sorted(widget._state_pills) == ["limits", "shutter_open"]
|
|
assert widget._state_pills["shutter_open"]._name_label.text() == "Shutter"
|
|
assert not widget._empty_label.isVisible()
|
|
|
|
widget.update_available_states(
|
|
{
|
|
"states": [
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {},
|
|
}
|
|
]
|
|
},
|
|
{},
|
|
)
|
|
|
|
assert sorted(widget._state_pills) == ["limits"]
|
|
|
|
|
|
def test_beamline_state_manager_propagates_drag_payload_mode(qtbot, mocked_client):
|
|
widget = BeamlineStateManager(client=mocked_client, drag_payload_mode="device")
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_available_states(
|
|
{
|
|
"states": [
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {"device": "samx"},
|
|
}
|
|
]
|
|
},
|
|
{},
|
|
)
|
|
|
|
assert widget._state_pills["limits"].drag_payload_mode == "device"
|
|
|
|
widget.drag_payload_mode = "config"
|
|
|
|
assert widget._state_pills["limits"].drag_payload_mode == "config"
|
|
|
|
|
|
def test_beamline_state_manager_propagates_card_background_mode(qtbot, mocked_client):
|
|
widget = BeamlineStateManager(client=mocked_client, card_background_mode="always")
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_available_states(
|
|
{
|
|
"states": [
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {"device": "samx"},
|
|
}
|
|
]
|
|
},
|
|
{},
|
|
)
|
|
|
|
assert widget._state_pills["limits"].card_background_mode == "always"
|
|
|
|
widget.card_background_mode = "hover"
|
|
|
|
assert widget._state_pills["limits"].card_background_mode == "hover"
|
|
|
|
|
|
def test_beamline_state_manager_filters_status(qtbot, mocked_client):
|
|
widget = BeamlineStateManager(client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_available_states(
|
|
{
|
|
"states": [
|
|
{
|
|
"name": "shutter_open",
|
|
"title": "Shutter",
|
|
"state_type": "ShutterState",
|
|
"parameters": {"device": "samy"},
|
|
},
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {"device": "samx"},
|
|
},
|
|
]
|
|
},
|
|
{},
|
|
)
|
|
|
|
assert isinstance(widget._toolbar, ModularToolBar)
|
|
|
|
widget._state_pills["limits"].update_state(
|
|
{"name": "limits", "status": "valid", "label": "Within limits."}, {}
|
|
)
|
|
widget._state_pills["shutter_open"].update_state(
|
|
{"name": "shutter_open", "status": "invalid", "label": "Closed."}, {}
|
|
)
|
|
widget._selected_statuses = {"valid"}
|
|
widget._apply_filters()
|
|
|
|
assert not widget._hidden_summary.isHidden()
|
|
assert "1 state is hidden" in widget._hidden_summary.text()
|
|
assert widget._state_pills["limits"].parent() is widget._content
|
|
assert widget._state_pills["shutter_open"].parent() is widget._hidden_content
|
|
|
|
widget._toggle_hidden_states(True)
|
|
|
|
assert not widget._hidden_content.isHidden()
|
|
|
|
|
|
def test_beamline_state_manager_status_filter_reacts_to_state_changes(qtbot, mocked_client):
|
|
widget = BeamlineStateManager(client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_available_states(
|
|
{
|
|
"states": [
|
|
{
|
|
"name": "limits",
|
|
"title": "Limits",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {"device": "samx"},
|
|
}
|
|
]
|
|
},
|
|
{},
|
|
)
|
|
|
|
widget._selected_statuses = {"valid"}
|
|
widget._state_pills["limits"].update_state(
|
|
{"name": "limits", "status": "valid", "label": "Within limits."}, {}
|
|
)
|
|
|
|
assert widget._hidden_summary.isHidden()
|
|
|
|
widget._state_pills["limits"].update_state(
|
|
{"name": "limits", "status": "invalid", "label": "Out of limits."}, {}
|
|
)
|
|
|
|
assert not widget._hidden_summary.isHidden()
|
|
assert widget._state_pills["limits"].parent() is widget._hidden_content
|
|
|
|
|
|
def test_beamline_state_manager_filters_devices(qtbot, mocked_client):
|
|
widget = BeamlineStateManager(client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update_available_states(
|
|
{
|
|
"states": [
|
|
{
|
|
"name": "samx_limits",
|
|
"title": "samx",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {"device": "samx"},
|
|
},
|
|
{
|
|
"name": "samy_limits",
|
|
"title": "samy",
|
|
"state_type": "DeviceWithinLimitsState",
|
|
"parameters": {"device": "samy"},
|
|
},
|
|
]
|
|
},
|
|
{},
|
|
)
|
|
|
|
widget._device_filter_text = "samx"
|
|
widget._apply_filters()
|
|
|
|
assert not widget._hidden_summary.isHidden()
|
|
assert "1 state is hidden" in widget._hidden_summary.text()
|
|
assert widget._available_devices() == ["samx", "samy"]
|
|
|
|
|
|
def test_beamline_state_manager_updates_state_parameters(qtbot, mocked_client):
|
|
widget = BeamlineStateManager(client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
class StateClient:
|
|
def __init__(self):
|
|
self.parameters = None
|
|
|
|
def update_parameters(self, **kwargs):
|
|
self.parameters = kwargs
|
|
|
|
class StateManager:
|
|
def __init__(self):
|
|
self.limits = StateClient()
|
|
|
|
mocked_client.beamline_states = StateManager()
|
|
widget._update_state_parameters("limits", {"low_limit": -1.0, "high_limit": 20.0})
|
|
|
|
assert mocked_client.beamline_states.limits.parameters == {
|
|
"low_limit": -1.0,
|
|
"high_limit": 20.0,
|
|
}
|
|
|
|
|
|
def test_beamline_state_manager_removes_state(qtbot, mocked_client, monkeypatch):
|
|
widget = BeamlineStateManager(client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
|
|
class StateManager:
|
|
def __init__(self):
|
|
self.deleted = None
|
|
|
|
def delete(self, state_name):
|
|
self.deleted = state_name
|
|
|
|
mocked_client.beamline_states = StateManager()
|
|
monkeypatch.setattr(
|
|
QMessageBox, "question", lambda *args, **kwargs: QMessageBox.StandardButton.Yes
|
|
)
|
|
|
|
widget._remove_state_requested("limits")
|
|
|
|
assert mocked_client.beamline_states.deleted == "limits"
|
|
|
|
|
|
def test_add_beamline_state_dialog_uses_device_signal_widgets_and_normalizes_name(
|
|
qtbot, mocked_client
|
|
):
|
|
dialog = AddBeamlineStateDialog(client=mocked_client)
|
|
qtbot.addWidget(dialog)
|
|
|
|
dialog._name.setText("samx-limits")
|
|
dialog._title.setText("samx-limits-15")
|
|
dialog._device.set_device("samx")
|
|
dialog._signal.set_signal("samx")
|
|
dialog._high_limit.setValue(15.0)
|
|
|
|
config = dialog.config()
|
|
|
|
assert config.name == "samx_limits"
|
|
assert config.title == "samx-limits-15"
|
|
assert config.device == "samx"
|
|
assert config.signal == "samx"
|
|
assert config.low_limit == 0.0
|
|
assert config.high_limit == 15.0
|
|
assert isinstance(dialog._low_limit, BECSpinBox)
|
|
assert isinstance(dialog._high_limit, BECSpinBox)
|
|
assert dialog._low_limit.width() == dialog._device.width()
|
|
|
|
|
|
def test_add_beamline_state_dialog_generates_name_only_after_valid_device_selection(
|
|
qtbot, mocked_client
|
|
):
|
|
dialog = AddBeamlineStateDialog(client=mocked_client)
|
|
qtbot.addWidget(dialog)
|
|
|
|
dialog._device.setCurrentText("s")
|
|
|
|
assert dialog._name.text() == ""
|
|
|
|
dialog._device.set_device("samx")
|
|
|
|
assert dialog._name.text() == "samx_limits"
|
|
|
|
|
|
def test_add_beamline_state_dialog_cleanup_deletes_device_widgets(qtbot, mocked_client):
|
|
dialog = AddBeamlineStateDialog(client=mocked_client)
|
|
qtbot.addWidget(dialog)
|
|
device = dialog._device
|
|
signal = dialog._signal
|
|
|
|
dialog.reject()
|
|
assert shiboken6.isValid(device)
|
|
assert shiboken6.isValid(signal)
|
|
|
|
dialog.cleanup()
|
|
QCoreApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete)
|
|
|
|
assert not shiboken6.isValid(device)
|
|
assert not shiboken6.isValid(signal)
|