Files
AareDAQ/tests/unit/gui/test_tell_sample_panel.py
T
duan_jandClaude Fable 5.1 27649375b1 fix(gui): zoom-proof the Dewar table and control heights, theme the mounted HUD ink
Dewar "#" column sat 1px above the other rows at some zoom steps: the
frozen-column overlay sizes its own header from "#" alone while the main
header also sees the symbol columns, whose fallback-font glyphs have a
taller line box. The overlay header is now pinned to the main header
height on resize and (deferred, receiver-bound) on font/style change.

Zoom also left columns at their startup widths (headers truncated) and
clipped descenders in every button/combo/entry box: the 16px height pin
in both sheets was raw px while the body font grew to 18/21px. Columns
re-autosize on FontChange and the pin scales with the font ladder.

Camera "Currently mounted" HUD: black ink on a white halo in the light
themes, white on black in Sunset, instead of white-on-black everywhere.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 14:27:19 +02:00

187 lines
6.4 KiB
Python

"""The combined sample dock: chip row filters the table by status, chips
double as drop targets for queue/flag relabeling, and a pop-out panel shares
the docked panel's model so both stay in sync without wiring."""
import pytest
from aarecommon.models.models import DewarAddress, SampleShortInfo, SampleShortInfoList
from aare.gui.panels.tell_sample_panel import TellSamplePanel
@pytest.fixture
def samples():
return SampleShortInfoList(
s=[
SampleShortInfo(
db_id=i,
puck_name=f"P{i}",
dewar_name="D1",
sample_name=f"S{i}",
run_number=i,
user="U1",
pin=i,
location=DewarAddress(segment="A", pos=i),
)
for i in (1, 2, 3)
]
)
@pytest.fixture
def panel(qtbot, samples):
panel = TellSamplePanel(samples=samples)
qtbot.addWidget(panel)
panel.table_model.set_show_all_pgroups(True)
return panel
def _chip(panel, key):
return next(c for c in panel.status_chips.buttons() if c.property("status_key") == key)
def test_chip_click_drives_the_status_filter(panel):
panel.table_model.set_queued_ids({2})
_chip(panel, "queued").click()
assert panel.table_model.status_filter == "queued"
assert panel.table_model.rowCount() == 1
_chip(panel, None).click()
assert panel.table_model.status_filter is None
assert panel.table_model.rowCount() == 3
def test_set_status_chip_syncs_without_filtering(panel):
panel.set_status_chip("flagged")
assert _chip(panel, "flagged").isChecked()
# Sync only checks the chip; it must not fire the filter.
assert panel.table_model.status_filter is None
def test_queued_chip_drop_relays_to_the_queue(panel, qtbot, samples):
chip = _chip(panel, "queued")
with qtbot.waitSignal(panel.add_to_queue) as blocker:
chip.samples_dropped.emit(samples)
assert [s.db_id for s in blocker.args[0].s] == [1, 2, 3]
def test_flagged_chip_drop_flags_in_the_model(panel, samples):
panel._flag_dropped_samples(SampleShortInfoList(s=samples.s[:2]))
assert panel.table_model.flagged_ids == {1, 2}
def test_popout_panel_shares_the_model(panel, qtbot):
popout = TellSamplePanel(model=panel.table_model)
qtbot.addWidget(popout)
assert popout.table_model is panel.table_model
panel.table_model.set_flagged(3, True)
assert 3 in popout.table_model.flagged_ids
def test_new_sample_list_updates_rows(panel, samples):
extra = samples.s + [
SampleShortInfo(
db_id=9,
puck_name="P9",
dewar_name="D2",
sample_name="S9",
run_number=9,
user="U2",
pin=9,
location=DewarAddress(segment="B", pos=1),
)
]
panel.new_sample_list(SampleShortInfoList(s=extra))
assert panel.table_model.rowCount() == 4
def test_queue_drop_chip_accepts_sample_payloads(panel, qtbot, samples):
from PySide6.QtCore import QMimeData, QPointF, Qt
from PySide6.QtGui import QDropEvent
from aare.gui.panels.tell_sample_panel import QueueDropChip
chip = next(c for c in panel.status_chips.buttons() if isinstance(c, QueueDropChip))
# The event only borrows the QMimeData (C++ pointer), so the mime must
# outlive the dropEvent call — hence created in the test's scope.
def drop(mime):
return QDropEvent(
QPointF(1, 1),
Qt.DropAction.CopyAction,
mime,
Qt.MouseButton.NoButton,
Qt.KeyboardModifier.NoModifier,
)
good = QMimeData()
good.setText(samples.model_dump_json())
with qtbot.waitSignal(chip.samples_dropped):
chip.dropEvent(drop(good))
# A non-sample payload is ignored, not crashed on.
bad = QMimeData()
bad.setText("not json")
with qtbot.assertNotEmitted(chip.samples_dropped):
chip.dropEvent(drop(bad))
def test_status_header_click_selects_all(panel):
header = panel.table_view.frozen.horizontalHeader()
header.sectionClicked.emit(0)
assert len(panel.table_view.selectionModel().selectedRows()) == 3
# Other sections keep their normal sort-click behavior.
panel.table_view.clearSelection()
header.sectionClicked.emit(1)
assert len(panel.table_view.selectionModel().selectedRows()) == 0
def test_selected_samples_follow_the_click(panel):
view = panel.table_view
view.selectRow(0)
row_ids = [panel.table_model.get_id(r).db_id for r in range(3)]
# Click inside the selection: the selection is acted on.
assert [s.db_id for s in panel._selected_samples(0)] == [row_ids[0]]
# Click outside the selection: only the clicked row is acted on.
assert [s.db_id for s in panel._selected_samples(2)] == [row_ids[2]]
def test_font_zoom_keeps_frozen_column_aligned_and_reautosizes(panel, qtbot):
"""Ctrl+plus zoom: the frozen "#" overlay header must stay exactly as
tall as the main header (else its rows sit 1px higher), and the columns
sized once at startup must re-autosize for the wider glyphs."""
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QApplication
from aare.gui import styles
app = QApplication.instance()
assert isinstance(app, QApplication) # font() lives on QApplication, not QCoreApplication
base = QFont(app.font())
# Startup look first: under a styled ancestor (MainWindow) fonts reach
# the view through the QSS path; without any sheet Qt would not
# propagate the app font to a child that carries WA_StyleSheet.
panel.setStyleSheet(styles.build_app_stylesheet(styles.THEME_SUNRISE))
panel.resize(600, 300)
panel.show()
qtbot.waitExposed(panel)
view = panel.table_view
width_before = view.columnWidth(1)
def row0_top(v):
return v.viewport().mapTo(panel, v.viewport().rect().topLeft()).y() + v.rowViewportPosition(
0
)
try:
# Same three steps as MainWindow._apply_theme on a zoom change.
styles.set_font_scale(1.25)
big = QFont(base)
big.setPointSizeF(base.pointSizeF() * 1.25)
app.setFont(big)
panel.setStyleSheet(styles.build_app_stylesheet(styles.THEME_SUNRISE))
qtbot.waitUntil(lambda: view.columnWidth(1) > width_before)
assert view.frozen.horizontalHeader().height() == view.horizontalHeader().height()
assert row0_top(view.frozen) == row0_top(view)
finally:
styles.set_font_scale(1.0)
app.setFont(base)