fix: satisfy lint and diff-coverage gates for admin gating

basedpyright requires _admin_tip_anchor to exist after __init__, so the
anchor is created eagerly instead of lazily. Cover the red-tip branch and
the status-bar menu gate; the menu test swaps in a QMenu subclass with a
no-op exec because PySide method lookup ignores class-attr monkeypatches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 10:05:23 +02:00
committed by duan_j
co-authored by Claude Fable 5
parent 089a7dbe00
commit 40e6936580
3 changed files with 39 additions and 6 deletions
+9 -6
View File
@@ -353,6 +353,13 @@ class MainWindow(QMainWindow):
# after the docks), not in the left column. Always visible.
self.beamline_state_panel = BeamlineStatePanel(staff=self._decoded_token.staff, parent=self)
# Anchor for _show_admin_tip: QToolTip inherits QSS from the widget
# it is shown for, so this hidden label carries the red warning wash
# without reddening any other tooltip. Eager, not lazy — basedpyright
# requires instance attributes to exist after __init__.
self._admin_tip_anchor = QLabel(self)
self._admin_tip_anchor.hide()
# Beamline / Experiment as tabs (like the Dewar samples dock) instead
# of two stacked banner groups; the pages keep their banner children.
# documentMode: no pane frame, so the fixed-width panels aren't inset.
@@ -1477,12 +1484,8 @@ class MainWindow(QMainWindow):
def _show_admin_tip(self, message: str) -> None:
# "Not admin" denials are passive red tips, not QMessageBoxes:
# nothing to click away. QToolTip inherits QSS from the widget it is
# shown for, so a hidden anchor keeps the red wash off every other
# tooltip; styled at show time so it always matches the theme.
if not hasattr(self, "_admin_tip_anchor"):
self._admin_tip_anchor = QLabel(self)
self._admin_tip_anchor.hide()
# nothing to click away. Styled at show time so it always matches
# the theme.
self._admin_tip_anchor.setStyleSheet(admin_tip_qss(self._theme_mode))
QToolTip.showText(QCursor.pos(), message, self._admin_tip_anchor)
@@ -42,6 +42,7 @@ def test_non_staff_never_reach_admin_only_states(qtbot):
assert "QToolTip" not in panel._buttons[BeamlineStateEnum.XtalSnapshot].styleSheet()
with qtbot.assertNotEmitted(panel.beam_location):
panel._emit_for_state(BeamlineStateEnum.BeamLocation)
panel._on_left_click(BeamlineStateEnum.BeamLocation) # shows the admin tip
def test_no_targets_while_moving_or_unknown(qtbot):
+29
View File
@@ -33,6 +33,35 @@ def test_operables_share_the_hover_affordance(qtbot):
assert not bar.state_label.font().underline()
def test_state_menu_gates_beam_location_for_non_staff(qtbot, daq_status_factory, monkeypatch):
from aarecommon.models.models import BeamlineStateEnum
from PySide6.QtWidgets import QMenu
import aare.gui.widgets.status_bar as status_bar_module
# exec() would block on a real popup, and PySide's method lookup ignores
# a class-attribute monkeypatch — swap in a subclass instead. The menu
# stays inspectable as a child of the bar afterwards.
class _NoExecMenu(QMenu):
def exec(self): # pyright: ignore[reportIncompatibleMethodOverride]
return None
monkeypatch.setattr(status_bar_module, "QMenu", _NoExecMenu)
def _state_menu_entries(bar):
bar._status = daq_status_factory(state=BeamlineStateEnum.SampleAlignment)
bar.show_state_menu()
# Plain data, not QAction refs: the menu (and its actions) only
# lives until the next GC pass once show_state_menu returns.
return {a.text(): a.isEnabled() for a in bar.findChildren(QMenu)[-1].actions()}
assert _state_menu_entries(_bar(qtbot))["Beam location"]
non_staff = StatusBar(token=TokenData(sub="u", staff=False, pgroups=["p1"], session=1))
qtbot.addWidget(non_staff)
assert not _state_menu_entries(non_staff)["Beam location (admin mode only)"]
def test_passives_left_operables_right(qtbot):
bar = _bar(qtbot)
# QStatusBar hides only the non-permanent (left) section behind a