Files
duan_jandClaude Fable 5.1 d5a7074ec1
CI / lint (pull_request) Successful in 42s
CI / test (3.12) (pull_request) Successful in 1m4s
CI / test (3.14) (pull_request) Successful in 1m3s
CI / test (3.13) (pull_request) Successful in 1m8s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Successful in 1m17s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Successful in 1m25s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Successful in 1m12s
CI / test-with-coverage (pull_request) Successful in 1m26s
CI / lint (push) Successful in 30s
Docs build and publish / docker (push) Successful in 11s
CI / test (3.12) (push) Canceled after 35s
CI / test-with-beamline-plugins (pxii_bec) (push) Canceled after 25s
CI / test (3.13) (push) Canceled after 32s
CI / test (3.14) (push) Canceled after 30s
CI / test-with-beamline-plugins (pxi_bec) (push) Canceled after 27s
CI / test-with-beamline-plugins (pxiii_bec) (push) Canceled after 22s
CI / test-with-coverage (push) Canceled after 20s
CI / coverage-analysis (push) Canceled after 0s
Build and Publish / release (push) Successful in 22s
CI / coverage-analysis (pull_request) Successful in 14s
feat: compact sample list headers, move Comment column
Count columns use glyphs instead of long headers to save width, Comment
moves ahead of the data-collection params, transmission shown as the
stored value instead of percent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 17:04:43 +02:00

307 lines
11 KiB
Python

import pytest
from aarecommon.models.models import DewarAddress, SampleShortInfo
from PySide6.QtCore import Qt
from aare.gui.models.sample_queue_model import SampleQueueSpreadsheet
from aare.gui.models.user_sample_model import UserSampleSpreadsheet
@pytest.fixture
def sample_list():
return [
SampleShortInfo(
db_id=1,
puck_name="P1",
dewar_name="D1",
sample_name="S1",
run_number=1,
user="U1",
pin=1,
location=DewarAddress(segment="A", pos=1),
),
SampleShortInfo(
db_id=2,
puck_name="P2",
dewar_name="D2",
sample_name="S2",
run_number=2,
user="U2",
pin=2,
location=DewarAddress(segment="A", pos=2),
),
SampleShortInfo(
db_id=3,
puck_name="P1",
dewar_name="D1",
sample_name="S3",
run_number=3,
user="U1",
pin=3,
location=DewarAddress(segment="A", pos=1),
),
]
def test_user_sample_model_init(sample_list):
model = UserSampleSpreadsheet(samples=sample_list)
model.set_show_all_pgroups(True) # Ensure all pgroups are shown for testing
assert model.rowCount() == 3
# Check if filtering works
# "User" is column 5
model.set_filter("User", "U2")
assert model.rowCount() == 1
model.clear_filter()
assert model.rowCount() == 3
def test_user_sample_model_column_filter(sample_list):
model = UserSampleSpreadsheet(samples=sample_list)
model.set_show_all_pgroups(True)
# Column 6 is User (column 0 is the frozen #+status cell)
model.set_column_filter(6, "U1")
assert model.rowCount() == 2
model.clear_all_column_filters()
assert model.rowCount() == 3
def test_user_sample_model_unique_values(sample_list):
model = UserSampleSpreadsheet(samples=sample_list)
model.set_show_all_pgroups(True)
# Column 6 is User (column 0 is the frozen #+status cell)
users = model.unique_values_for_column(6)
assert "U1" in users
assert "U2" in users
assert len(users) == 2
def test_sample_queue_model_init(sample_list):
model = SampleQueueSpreadsheet(samples=sample_list[:2])
assert model.rowCount() == 2
assert model.columnCount() == 3
assert model.data(model.index(0, 0), Qt.ItemDataRole.DisplayRole) == "D1"
assert model.data(model.index(1, 2), Qt.ItemDataRole.DisplayRole) == "S2"
def test_sample_queue_model_update(sample_list):
model = SampleQueueSpreadsheet()
assert model.rowCount() == 0
model.updateData(sample_list[:2])
assert model.rowCount() == 2
def test_sample_queue_model_remove(sample_list):
model = SampleQueueSpreadsheet(samples=sample_list[:2])
model.remove_sample(1)
assert model.rowCount() == 1
assert model.samples[0].db_id == 2
def test_sample_queue_model_clear(sample_list):
model = SampleQueueSpreadsheet(samples=sample_list[:2])
model.clearSamples()
assert model.rowCount() == 0
def test_sample_queue_model_set_running(sample_list):
model = SampleQueueSpreadsheet(samples=sample_list[:2])
# Background color role for first row
color_not_running = model.data(model.index(0, 0), Qt.ItemDataRole.BackgroundRole)
model.set_running(True)
color_running = model.data(model.index(0, 0), Qt.ItemDataRole.BackgroundRole)
assert color_not_running != color_running
def test_sample_queue_model_header(sample_list):
model = SampleQueueSpreadsheet(samples=sample_list[:2])
assert model.headerData(0, Qt.Orientation.Horizontal, Qt.ItemDataRole.DisplayRole) == "Dewar"
assert model.headerData(1, Qt.Orientation.Vertical, Qt.ItemDataRole.DisplayRole) == "2"
def test_sample_queue_model_flags(sample_list):
model = SampleQueueSpreadsheet(samples=sample_list[:2])
flags = model.flags(model.index(0, 0))
assert flags & Qt.ItemFlag.ItemIsDropEnabled
# --- Status logic of the combined dewar/queue view ---------------------------
# The dewar table doubles as the queue view: the frozen "#" column carries a
# status fill (mounted > queued > flagged > measured) and the chip row filters
# by status. This is the logic a local contact trusts at a glance, so it gets
# its own tests.
def _status(model, row):
brush = model.data(model.index(row, 0), Qt.ItemDataRole.BackgroundRole)
return None if brush is None else brush.color().name().lower()
def _row_of(model, db_id):
return next(r for r in range(model.rowCount()) if model.get_id(r).db_id == db_id)
@pytest.fixture
def status_model(sample_list):
from aarecommon.models.models import DewarAddress, SampleShortInfo
# A measured sample: any rotation data counts (exactly 1 MUST count;
# unmeasured is rotation_count 0, like the fixture's samples 1-3).
sample_list.append(
SampleShortInfo(
db_id=4,
puck_name="P3",
dewar_name="D3",
sample_name="S4",
run_number=4,
user="U1",
pin=4,
rotation_count=1,
location=DewarAddress(segment="B", pos=1),
)
)
model = UserSampleSpreadsheet(samples=sample_list)
model.set_show_all_pgroups(True)
return model
def test_status_color_priority(status_model):
from aare.gui.styles import (
SAMPLE_ROW_QUEUED_BG,
SAMPLE_STATUS_FLAGGED_BG,
SAMPLE_STATUS_MEASURED_BG,
SAMPLE_STATUS_QUEUED_BG,
)
model = status_model
assert _status(model, _row_of(model, 1)) is None
model.set_queued_ids({1})
model.set_flagged(1, True)
# Queued beats flagged in the All view.
assert _status(model, _row_of(model, 1)) == SAMPLE_STATUS_QUEUED_BG.lower()
model.set_queued_ids(set())
assert _status(model, _row_of(model, 1)) == SAMPLE_STATUS_FLAGGED_BG.lower()
# Measured is automatic: rotation_count 1 counts, the 0s of 1-3 don't.
assert _status(model, _row_of(model, 4)) == SAMPLE_STATUS_MEASURED_BG.lower()
assert _status(model, _row_of(model, 2)) is None
# Mounted always wins.
model.updateCurrentSample(current_puck="P1", current_sample=1)
assert _status(model, _row_of(model, 1)) == SAMPLE_ROW_QUEUED_BG.lower()
def test_status_filter_selects_rows(status_model):
model = status_model
model.set_queued_ids({1, 2})
model.set_flagged(3, True)
model.set_status_filter("queued")
assert {model.get_id(r).db_id for r in range(model.rowCount())} == {1, 2}
model.set_status_filter("flagged")
assert {model.get_id(r).db_id for r in range(model.rowCount())} == {3}
model.set_status_filter("measured")
assert {model.get_id(r).db_id for r in range(model.rowCount())} == {4}
# Unmeasured is the complement: everything still to be done.
model.set_status_filter("unmeasured")
assert {model.get_id(r).db_id for r in range(model.rowCount())} == {1, 2, 3}
model.set_status_filter(None)
assert model.rowCount() == 4
def test_queued_view_shows_queue_order(status_model):
model = status_model
# Queue order deliberately different from location/db order.
model.set_queued_ids([3, 1, 2])
model.set_status_filter("queued")
assert [model.get_id(r).db_id for r in range(model.rowCount())] == [3, 1, 2]
# Header sorts must not reorder the queue view — row 1 runs next.
model.sort(1, Qt.SortOrder.AscendingOrder)
assert [model.get_id(r).db_id for r in range(model.rowCount())] == [3, 1, 2]
# Other views keep the normal header sort.
model.set_status_filter(None)
assert [model.get_id(r).db_id for r in range(model.rowCount())] != [3, 1, 2]
def test_status_tints_are_context_dependent(status_model):
from aare.gui.styles import SAMPLE_STATUS_FLAGGED_BG, SAMPLE_STATUS_QUEUED_BG
model = status_model
model.set_queued_ids({1, 2})
model.set_flagged(1, True)
# Queued view: own tint suppressed, only the also-flagged mark shows.
model.set_status_filter("queued")
assert _status(model, _row_of(model, 1)) == SAMPLE_STATUS_FLAGGED_BG.lower()
assert _status(model, _row_of(model, 2)) is None
# Flagged view: a re-queued sample wears the queued mark.
model.set_status_filter("flagged")
assert _status(model, _row_of(model, 1)) == SAMPLE_STATUS_QUEUED_BG.lower()
def test_status_sets_refilter_while_chip_active(status_model):
model = status_model
model.set_status_filter("queued")
assert model.rowCount() == 0
model.set_queued_ids({2})
assert {model.get_id(r).db_id for r in range(model.rowCount())} == {2}
def test_status_column_is_display_only(status_model):
model = status_model
assert model.data(model.index(0, 0), Qt.ItemDataRole.DisplayRole) == 1
before = [model.get_id(r).db_id for r in range(model.rowCount())]
model.sort(0, Qt.SortOrder.DescendingOrder) # no-op on the "#" column
assert [model.get_id(r).db_id for r in range(model.rowCount())] == before
def test_mime_data_round_trips_for_chip_drops(status_model):
from aarecommon.models.models import SampleShortInfoList
model = status_model
payload = model.mimeData([model.index(0, 1), model.index(1, 1)])
samples = SampleShortInfoList.model_validate_json(payload.text())
assert len(samples.s) == 2
assert samples.s[0].db_id == model.get_id(0).db_id
def test_mounted_row_is_blue_across_all_columns(status_model):
# Whole-row blue (not just "#") so the mounted sample is findable at a
# glance; the blue keeps the theme's own ink (no dark override).
model = status_model
model.updateCurrentSample(current_puck="P1", current_sample=1)
row = _row_of(model, 1)
for col in range(model.columnCount()):
brush = model.data(model.index(row, col), Qt.ItemDataRole.BackgroundRole)
assert brush.color().name().lower() == "#729fcf"
assert model.data(model.index(row, col), Qt.ItemDataRole.ForegroundRole) is None
# Other rows: still "#"-only tint, other columns untouched.
other = _row_of(model, 2)
assert model.data(model.index(other, 1), Qt.ItemDataRole.BackgroundRole) is None
def test_spreadsheet_param_columns(status_model):
from aarecommon.models.models import DataCollectionParameters
model = status_model
hdr = model.header
osc, exp, tot, trans = (
hdr.index("Oscillation (°)"),
hdr.index("Exposure (s)"),
hdr.index("Total range (°)"),
hdr.index("Transmission"),
)
model.get_id(_row_of(model, 1)).aaredb_params = DataCollectionParameters(
oscillation=0.1, exposure=0.01, totalangle=360, transmission=0.125
)
row = _row_of(model, 1)
d = lambda c: model.data(model.index(row, c), Qt.ItemDataRole.DisplayRole)
# Transmission shown as stored, no percent conversion.
assert (d(osc), d(exp), d(tot), d(trans)) == (0.1, 0.01, 360, 0.125)
# No params -> blank, and sorting a mostly-None column must not raise;
# the real value sorts first.
assert model.data(model.index(_row_of(model, 2), osc), Qt.ItemDataRole.DisplayRole) is None
model.sort(osc, Qt.SortOrder.AscendingOrder)
assert model.get_id(0).db_id == 1
assert model.headerData(hdr.index("Comment"), Qt.Orientation.Horizontal, 0) == "Comment"