The status column holds no sortable data, so its header click slot was free: it now does Ctrl+A, pairing with the Unmeasured chip to grab everything still to be done in one motion. The sort indicator is put back afterwards - the click drags it onto '#' even though the model ignores sorting there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
145 lines
4.7 KiB
Python
145 lines
4.7 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]]
|