From 1dbeda396c2b0d43d89b5c28e92c63e4b87908a8 Mon Sep 17 00:00:00 2001 From: appel_c Date: Thu, 2 Jul 2026 16:35:01 +0200 Subject: [PATCH] fix: deprecate ShutterState --- .../services/beamline_states/dialogs.py | 1 - tests/unit_tests/test_beamline_state_pill.py | 139 +++++++----------- 2 files changed, 54 insertions(+), 86 deletions(-) diff --git a/bec_widgets/widgets/services/beamline_states/dialogs.py b/bec_widgets/widgets/services/beamline_states/dialogs.py index 0655bea7..96039f3c 100644 --- a/bec_widgets/widgets/services/beamline_states/dialogs.py +++ b/bec_widgets/widgets/services/beamline_states/dialogs.py @@ -31,7 +31,6 @@ BEAMLINE_STATE_STATUS_LABELS = { SUPPORTED_BEAMLINE_STATES: tuple[type[bl_states.BeamlineState], ...] = ( bl_states.DeviceWithinLimitsState, - bl_states.ShutterState, ) diff --git a/tests/unit_tests/test_beamline_state_pill.py b/tests/unit_tests/test_beamline_state_pill.py index 16c5f1e3..a0c9939b 100644 --- a/tests/unit_tests/test_beamline_state_pill.py +++ b/tests/unit_tests/test_beamline_state_pill.py @@ -45,13 +45,6 @@ def _limits_state(name: str = "limits", **overrides) -> messages.BeamlineStateCo return _wire_state(bl_states.DeviceWithinLimitsState, config) -def _shutter_state( - name: str = "shutter_open", device: str = "samy" -) -> messages.BeamlineStateConfig: - config = bl_states.ShutterState.CONFIG_CLASS(name=name, device=device) - return _wire_state(bl_states.ShutterState, config) - - def _as_status_list(value: str | list[str]) -> list[str]: return [value] if isinstance(value, str) else list(value) @@ -88,19 +81,19 @@ def _install_fake_scan_interlock( def test_beamline_state_pill_updates_from_message(qtbot, mocked_client): - pill = create_widget(qtbot, BeamlineStatePill, state_name="shutter_open", client=mocked_client) - pill.update_state({"name": "shutter_open", "status": "valid", "label": "Shutter is open."}, {}) + pill = create_widget(qtbot, BeamlineStatePill, state_name="limits", client=mocked_client) + pill.update_state({"name": "limits", "status": "valid", "label": "Within limits."}, {}) - assert pill._state_name == "shutter_open" - assert pill._name_label.text() == "shutter_open" + assert pill._state_name == "limits" + assert pill._name_label.text() == "limits" assert pill._status_label.text() == "VALID" - assert pill._detail_label.text() == "Shutter is open." + assert pill._detail_label.text() == "Within limits." assert not pill._icon_label.pixmap().isNull() - assert pill.toolTip() == "Shutter is open." + assert pill.toolTip() == "Within limits." def test_beamline_state_pill_ignores_other_states(qtbot, mocked_client): - pill = create_widget(qtbot, BeamlineStatePill, state_name="shutter_open", client=mocked_client) + pill = create_widget(qtbot, BeamlineStatePill, state_name="limits", client=mocked_client) pill.update_state( {"name": "other_state", "status": "invalid", "label": "Should be ignored."}, {} ) @@ -256,16 +249,16 @@ def test_beamline_state_manager_adds_and_removes_pills(qtbot, mocked_client): beamline_state_manager.update_available_states( { "states": [ - _state("shutter_open", "ShutterState"), _state("limits", "DeviceWithinLimitsState"), + _limits_state(name="samy_limits", device="samy"), ] }, {}, ) - assert sorted(beamline_state_manager._state_pills) == ["limits", "shutter_open"] + assert sorted(beamline_state_manager._state_pills) == ["limits", "samy_limits"] assert beamline_state_manager._model.rowCount() == 2 - assert beamline_state_manager._state_pills["shutter_open"]._name_label.text() == "shutter_open" + assert beamline_state_manager._state_pills["samy_limits"]._name_label.text() == "samy_limits" assert not beamline_state_manager._empty_label.isVisible() beamline_state_manager._state_pills["limits"].update_state( @@ -273,7 +266,7 @@ def test_beamline_state_manager_adds_and_removes_pills(qtbot, mocked_client): ) summary = beamline_state_manager.state_summary() assert summary["limits"] == {"status": "valid", "label": "Within limits."} - assert summary["shutter_open"]["status"] == "unknown" + assert summary["samy_limits"]["status"] == "unknown" beamline_state_manager.update_available_states( {"states": [_state("limits", "DeviceWithinLimitsState")]}, {} @@ -299,19 +292,21 @@ def test_beamline_state_manager_ignores_unchanged_available_states(qtbot, mocked def test_beamline_state_manager_adds_state_without_recreating_existing_pills(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) limits_state = _limits_state() - shutter_state = _state("shutter_open", "ShutterState") + samy_limits_state = _limits_state(name="samy_limits", device="samy") beamline_state_manager.update_available_states({"states": [limits_state]}, {}) pill = beamline_state_manager._state_pills["limits"] pill.set_expanded(True) config_form = pill._config_form - beamline_state_manager.update_available_states({"states": [limits_state, shutter_state]}, {}) + beamline_state_manager.update_available_states( + {"states": [limits_state, samy_limits_state]}, {} + ) assert beamline_state_manager._state_pills["limits"] is pill assert pill._config_form is config_form assert pill.is_expanded() - assert sorted(beamline_state_manager._state_pills) == ["limits", "shutter_open"] + assert sorted(beamline_state_manager._state_pills) == ["limits", "samy_limits"] def test_beamline_state_manager_header_click_expands_pill_once(qtbot, mocked_client): @@ -343,7 +338,7 @@ def test_beamline_state_manager_preserves_expanded_pill_on_refresh(qtbot, mocked def test_beamline_state_manager_filters_status(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) beamline_state_manager.update_available_states( - {"states": [_shutter_state(), _limits_state()]}, {} + {"states": [_limits_state(name="samy_limits", device="samy"), _limits_state()]}, {} ) assert isinstance(beamline_state_manager._toolbar, ModularToolBar) @@ -352,8 +347,8 @@ def test_beamline_state_manager_filters_status(qtbot, mocked_client): beamline_state_manager._state_pills["limits"].update_state( {"name": "limits", "status": "valid", "label": "Within limits."}, {} ) - beamline_state_manager._state_pills["shutter_open"].update_state( - {"name": "shutter_open", "status": "invalid", "label": "Closed."}, {} + beamline_state_manager._state_pills["samy_limits"].update_state( + {"name": "samy_limits", "status": "invalid", "label": "Out of limits."}, {} ) beamline_state_manager._selected_statuses = {"valid"} beamline_state_manager._apply_filters() @@ -364,22 +359,22 @@ def test_beamline_state_manager_filters_status(qtbot, mocked_client): beamline_state_manager._model.index_for_name("limits").row() ) assert beamline_state_manager._view.isRowHidden( - beamline_state_manager._model.index_for_name("shutter_open").row() + beamline_state_manager._model.index_for_name("samy_limits").row() ) beamline_state_manager._hidden_summary.click() assert not beamline_state_manager._view.isRowHidden( - beamline_state_manager._model.index_for_name("shutter_open").row() + beamline_state_manager._model.index_for_name("samy_limits").row() ) - assert shiboken6.isValid(beamline_state_manager._state_pills["shutter_open"]) + assert shiboken6.isValid(beamline_state_manager._state_pills["samy_limits"]) beamline_state_manager._hidden_summary.click() assert beamline_state_manager._view.isRowHidden( - beamline_state_manager._model.index_for_name("shutter_open").row() + beamline_state_manager._model.index_for_name("samy_limits").row() ) - assert shiboken6.isValid(beamline_state_manager._state_pills["shutter_open"]) + assert shiboken6.isValid(beamline_state_manager._state_pills["samy_limits"]) def test_beamline_state_manager_status_filter_reacts_to_state_changes(qtbot, mocked_client): @@ -447,8 +442,7 @@ def test_beamline_state_manager_filters_devices(qtbot, mocked_client, monkeypatc def test_beamline_state_manager_collapse_all(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) beamline_state_manager.update_available_states( - {"states": [_limits_state(), _state("shutter_open", "ShutterState", {"device": "samy"})]}, - {}, + {"states": [_limits_state(), _limits_state(name="samy_limits", device="samy")]}, {} ) for pill in beamline_state_manager._state_pills.values(): @@ -628,7 +622,7 @@ def test_beamline_state_manager_toolbar_scan_interlock_on_right(qtbot, mocked_cl def test_beamline_state_manager_groups_states_under_headers(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) beamline_state_manager.update_available_states( - {"states": [_limits_state(), _shutter_state()]}, {} + {"states": [_limits_state(), _limits_state(name="samy_limits", device="samy")]}, {} ) model = beamline_state_manager._model @@ -636,30 +630,30 @@ def test_beamline_state_manager_groups_states_under_headers(qtbot, mocked_client assert model.data(model.index(0, 0), model.HeaderRole) is None _install_fake_scan_interlock( - beamline_state_manager, _FakeScanInterlock(states_watched={"shutter_open": "valid"}) + beamline_state_manager, _FakeScanInterlock(states_watched={"samy_limits": "valid"}) ) assert model.rowCount() == 4 assert model.data(model.index(0, 0), model.HeaderRole) == model.INTERLOCK_HEADER assert model.data(model.index(0, 0), Qt.ItemDataRole.DisplayRole) == "Scan interlock states" - assert model.data(model.index(1, 0), model.NameRole) == "shutter_open" + assert model.data(model.index(1, 0), model.NameRole) == "samy_limits" assert model.data(model.index(2, 0), model.HeaderRole) == model.OTHERS_HEADER assert ( model.data(model.index(2, 0), Qt.ItemDataRole.DisplayRole) == "Not included in scan interlock" ) assert model.data(model.index(3, 0), model.NameRole) == "limits" - assert sorted(beamline_state_manager._state_pills) == ["limits", "shutter_open"] + assert sorted(beamline_state_manager._state_pills) == ["limits", "samy_limits"] _install_fake_scan_interlock( beamline_state_manager, - _FakeScanInterlock(states_watched={"shutter_open": "valid", "limits": "valid"}), + _FakeScanInterlock(states_watched={"samy_limits": "valid", "limits": "valid"}), ) assert model.rowCount() == 3 assert model.data(model.index(0, 0), model.HeaderRole) == model.INTERLOCK_HEADER assert model.data(model.index(1, 0), model.NameRole) == "limits" - assert model.data(model.index(2, 0), model.NameRole) == "shutter_open" + assert model.data(model.index(2, 0), model.NameRole) == "samy_limits" _install_fake_scan_interlock(beamline_state_manager, _FakeScanInterlock()) @@ -670,24 +664,24 @@ def test_beamline_state_manager_groups_states_under_headers(qtbot, mocked_client def test_beamline_state_manager_header_visibility_follows_filters(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) beamline_state_manager.update_available_states( - {"states": [_limits_state(), _shutter_state()]}, {} + {"states": [_limits_state(), _limits_state(name="samy_limits", device="samy")]}, {} ) _install_fake_scan_interlock( - beamline_state_manager, _FakeScanInterlock(states_watched={"shutter_open": "valid"}) + beamline_state_manager, _FakeScanInterlock(states_watched={"samy_limits": "valid"}) ) beamline_state_manager._state_pills["limits"].update_state( {"name": "limits", "status": "invalid", "label": "Out of limits."}, {} ) - beamline_state_manager._state_pills["shutter_open"].update_state( - {"name": "shutter_open", "status": "valid", "label": "Open."}, {} + beamline_state_manager._state_pills["samy_limits"].update_state( + {"name": "samy_limits", "status": "valid", "label": "Within limits."}, {} ) beamline_state_manager._selected_statuses = {"valid"} beamline_state_manager._apply_filters() model = beamline_state_manager._model assert not beamline_state_manager._view.isRowHidden(0) - assert not beamline_state_manager._view.isRowHidden(model.index_for_name("shutter_open").row()) + assert not beamline_state_manager._view.isRowHidden(model.index_for_name("samy_limits").row()) assert beamline_state_manager._view.isRowHidden(2) assert beamline_state_manager._view.isRowHidden(model.index_for_name("limits").row()) @@ -699,40 +693,40 @@ def test_beamline_state_manager_header_visibility_follows_filters(qtbot, mocked_ def test_beamline_state_manager_interlock_states_bypass_filters(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) beamline_state_manager.update_available_states( - {"states": [_limits_state(), _shutter_state()]}, {} + {"states": [_limits_state(), _limits_state(name="samy_limits", device="samy")]}, {} ) _install_fake_scan_interlock( - beamline_state_manager, _FakeScanInterlock(states_watched={"shutter_open": "valid"}) + beamline_state_manager, _FakeScanInterlock(states_watched={"samy_limits": "valid"}) ) beamline_state_manager._state_pills["limits"].update_state( {"name": "limits", "status": "invalid", "label": "Out of limits."}, {} ) - beamline_state_manager._state_pills["shutter_open"].update_state( - {"name": "shutter_open", "status": "invalid", "label": "Closed."}, {} + beamline_state_manager._state_pills["samy_limits"].update_state( + {"name": "samy_limits", "status": "invalid", "label": "Out of limits."}, {} ) beamline_state_manager._selected_statuses = {"valid"} beamline_state_manager._apply_filters() model = beamline_state_manager._model - assert not beamline_state_manager._view.isRowHidden(model.index_for_name("shutter_open").row()) + assert not beamline_state_manager._view.isRowHidden(model.index_for_name("samy_limits").row()) assert beamline_state_manager._view.isRowHidden(model.index_for_name("limits").row()) assert "1 state is hidden" in beamline_state_manager._hidden_summary.text() beamline_state_manager._device_filter_text = "nonexistent_device" beamline_state_manager._apply_filters() - assert not beamline_state_manager._view.isRowHidden(model.index_for_name("shutter_open").row()) + assert not beamline_state_manager._view.isRowHidden(model.index_for_name("samy_limits").row()) def test_beamline_state_manager_section_headers_are_widgets(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) beamline_state_manager.update_available_states( - {"states": [_limits_state(), _shutter_state()]}, {} + {"states": [_limits_state(), _limits_state(name="samy_limits", device="samy")]}, {} ) _install_fake_scan_interlock( beamline_state_manager, - _FakeScanInterlock(enabled=True, states_watched={"shutter_open": "valid"}), + _FakeScanInterlock(enabled=True, states_watched={"samy_limits": "valid"}), ) model = beamline_state_manager._model @@ -754,19 +748,19 @@ def test_beamline_state_manager_section_headers_are_widgets(qtbot, mocked_client def test_beamline_state_manager_marks_triggered_pills(qtbot, mocked_client): beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client) - beamline_state_manager.update_available_states({"states": [_shutter_state()]}, {}) - fake_interlock = _FakeScanInterlock(enabled=True, states_watched={"shutter_open": "valid"}) + beamline_state_manager.update_available_states({"states": [_limits_state()]}, {}) + fake_interlock = _FakeScanInterlock(enabled=True, states_watched={"limits": "valid"}) _install_fake_scan_interlock(beamline_state_manager, fake_interlock) - pill = beamline_state_manager._state_pills["shutter_open"] + pill = beamline_state_manager._state_pills["limits"] assert pill._interlock_required_statuses == ["valid"] assert pill._interlock_triggered - pill.update_state({"name": "shutter_open", "status": "valid", "label": "Open."}, {}) + pill.update_state({"name": "limits", "status": "valid", "label": "Within limits."}, {}) assert not pill._interlock_triggered - pill.update_state({"name": "shutter_open", "status": "invalid", "label": "Closed."}, {}) + pill.update_state({"name": "limits", "status": "invalid", "label": "Out of limits."}, {}) assert pill._interlock_triggered @@ -834,7 +828,7 @@ def test_beamline_state_manager_orders_by_status_severity_on_initial_render( # severity order must hold from the very first render. ``get_last`` is patched (rather than # writing to the session-shared fake connector) so the seeded statuses can't leak into and # pollute other tests. - seeded = {"limits": "valid", "shutter_open": "invalid"} + seeded = {"limits": "valid", "samy_limits": "invalid"} real_get_last = mocked_client.connector.get_last def fake_get_last(endpoint, *args, **kwargs): @@ -847,13 +841,13 @@ def test_beamline_state_manager_orders_by_status_severity_on_initial_render( monkeypatch.setattr(mocked_client.connector, "get_last", fake_get_last) beamline_state_manager.update_available_states( - {"states": [_limits_state(), _shutter_state()]}, {} + {"states": [_limits_state(), _limits_state(name="samy_limits", device="samy")]}, {} ) model = beamline_state_manager._model names = [model.data(model.index(row, 0), model.NameRole) for row in range(model.rowCount())] - assert names == ["shutter_open", "limits"] - assert beamline_state_manager._state_pills["shutter_open"]._status == "invalid" + assert names == ["samy_limits", "limits"] + assert beamline_state_manager._state_pills["samy_limits"]._status == "invalid" def test_beamline_state_manager_toolbar_toggle_writes_backend(qtbot, mocked_client): @@ -984,6 +978,7 @@ def test_add_beamline_state_dialog_uses_generated_widgets_and_normalizes_name(qt add_state_dialog = create_widget(qtbot, AddBeamlineStateDialog, client=mocked_client) limits_index = add_state_dialog._type_combo.findText(bl_states.DeviceWithinLimitsState.__name__) assert limits_index >= 0 + assert add_state_dialog._type_combo.findText("ShutterState") == -1 add_state_dialog._type_combo.setCurrentIndex(limits_index) assert add_state_dialog._config_form.model is bl_states.DeviceWithinLimitsState.CONFIG_CLASS @@ -1120,32 +1115,6 @@ def test_add_beamline_state_dialog_generates_name_only_after_valid_device_select assert name.text() == "samx_device_within_limits_state" -def test_add_beamline_state_dialog_switches_state_type_without_collapsing(qtbot, mocked_client): - add_state_dialog = create_widget(qtbot, AddBeamlineStateDialog, client=mocked_client) - initial_height = add_state_dialog.height() - limits_index = add_state_dialog._type_combo.findText("DeviceWithinLimitsState") - assert limits_index >= 0 - shutter_index = add_state_dialog._type_combo.findText("ShutterState") - assert shutter_index >= 0 - - add_state_dialog._type_combo.setCurrentIndex(shutter_index) - qtbot.wait(0) - - assert add_state_dialog._config_form.model is bl_states.DeviceStateConfig - assert add_state_dialog._config_form_host.count() == 1 - assert not add_state_dialog._config_form.isHidden() - assert not add_state_dialog._buttons.isHidden() - assert add_state_dialog.sizeHint().height() > add_state_dialog._buttons.sizeHint().height() - assert add_state_dialog.minimumHeight() == add_state_dialog.maximumHeight() - - add_state_dialog._type_combo.setCurrentIndex(limits_index) - qtbot.wait(0) - - assert add_state_dialog._config_form.model is bl_states.DeviceWithinLimitsState.CONFIG_CLASS - assert add_state_dialog.height() >= initial_height - assert add_state_dialog.minimumHeight() == add_state_dialog.maximumHeight() - - def test_add_beamline_state_dialog_cleanup_deletes_device_widgets(qtbot, mocked_client): add_state_dialog = create_widget(qtbot, AddBeamlineStateDialog, client=mocked_client) device = add_state_dialog._config_form.input_widget("device")