Files
bec_widgets/tests/unit_tests/test_scan_index_combobox.py

165 lines
6.2 KiB
Python

import pytest
from bec_lib.scan_history import ScanHistory
from qtpy.QtCore import Qt
from bec_widgets.widgets.utility.scan_index_combobox.scan_index_combobox import ScanIndexComboBox
# pylint: disable=unused-import
from tests.unit_tests.client_mocks import mocked_client
from tests.unit_tests.conftest import create_widget
@pytest.fixture
def scan_index_combo(qtbot, mocked_client, scan_history_factory):
"""ScanIndexComboBox with two history scans injected into the client."""
msgs = [
scan_history_factory(scan_id="hid1", scan_number=1),
scan_history_factory(scan_id="hid2", scan_number=2),
]
mocked_client.history = ScanHistory(mocked_client, False)
for msg in msgs:
mocked_client.history._scan_data[msg.scan_id] = msg
mocked_client.history._scan_ids.append(msg.scan_id)
mocked_client.history._scan_numbers.append(msg.scan_number)
combo = create_widget(qtbot, ScanIndexComboBox, client=mocked_client)
return combo
def test_scan_index_combobox_populates_history(scan_index_combo):
"""The combobox lists 'live' plus all scan numbers (latest first) with scan IDs as item data."""
assert scan_index_combo.count() == 3
assert scan_index_combo.itemText(0) == "live"
assert scan_index_combo.itemData(0) is None
assert scan_index_combo.itemText(1) == "2"
assert scan_index_combo.itemData(1) == "hid2"
assert scan_index_combo.itemText(2) == "1"
assert scan_index_combo.itemData(2) == "hid1"
def test_scan_index_combobox_scan_id_and_number(scan_index_combo):
"""scan_id/scan_number map the selection to live (None) or a history scan."""
assert scan_index_combo.currentText() == "live"
assert scan_index_combo.scan_id is None
assert scan_index_combo.scan_number is None
scan_index_combo.setCurrentIndex(1)
assert scan_index_combo.scan_id == "hid2"
assert scan_index_combo.scan_number == 2
# Typing a scan number does not move the current index; scan_id must follow the text
scan_index_combo.setCurrentText("live")
scan_index_combo.setCurrentText("1")
assert scan_index_combo.scan_id == "hid1"
assert scan_index_combo.scan_number == 1
def test_scan_index_combobox_set_scan_id(scan_index_combo):
"""set_scan_id selects the matching scan or falls back to 'live'."""
scan_index_combo.set_scan_id("hid1")
assert scan_index_combo.currentText() == "1"
scan_index_combo.set_scan_id("unknown")
assert scan_index_combo.currentText() == "live"
scan_index_combo.set_scan_id("hid2")
assert scan_index_combo.currentText() == "2"
scan_index_combo.set_scan_id(None)
assert scan_index_combo.currentText() == "live"
def test_scan_index_combobox_refresh_keeps_selection(scan_index_combo):
"""refresh_scan_indices repopulates the items without losing the current selection."""
scan_index_combo.set_scan_id("hid2")
scan_index_combo.refresh_scan_indices()
assert scan_index_combo.count() == 3
assert scan_index_combo.currentText() == "2"
assert scan_index_combo.scan_id == "hid2"
@pytest.mark.parametrize("history", [None, "empty"])
def test_scan_index_combobox_empty_history(qtbot, mocked_client, history):
"""
With no scan history (client not started) or an empty one, only 'live' is offered
and any typed scan number is flagged as invalid.
"""
mocked_client.history = ScanHistory(mocked_client, False) if history == "empty" else None
combo = create_widget(qtbot, ScanIndexComboBox, client=mocked_client)
assert combo.count() == 1
assert combo.currentText() == "live"
assert combo.scan_id is None
assert combo.scan_number is None
assert combo.is_valid_input
# Typing is not blocked by a validator, but scan numbers are flagged since none exist
assert combo.lineEdit().validator() is None
combo.lineEdit().clear()
qtbot.keyClicks(combo.lineEdit(), "1")
assert combo.currentText() == "1"
assert not combo.is_valid_input
with pytest.raises(ValueError):
_ = combo.scan_id
def test_scan_index_combobox_typing_multi_digit_scan(qtbot, mocked_client, scan_history_factory):
"""
Multi-digit scan numbers can be typed even when their prefixes are not scan numbers
themselves, and the completer autocompletes against the listed entries.
"""
msgs = [
scan_history_factory(scan_id="hid101", scan_number=101),
scan_history_factory(scan_id="hid105", scan_number=105),
]
mocked_client.history = ScanHistory(mocked_client, False)
for msg in msgs:
mocked_client.history._scan_data[msg.scan_id] = msg
mocked_client.history._scan_ids.append(msg.scan_id)
mocked_client.history._scan_numbers.append(msg.scan_number)
combo = create_widget(qtbot, ScanIndexComboBox, client=mocked_client)
combo.lineEdit().clear()
qtbot.keyClicks(combo.lineEdit(), "101")
assert combo.currentText() == "101"
assert combo.is_valid_input
assert combo.styleSheet() == ""
assert combo.scan_id == "hid101"
assert combo.scan_number == 101
# The default editable-combobox completer offers the listed entries while typing
completer = combo.completer()
assert completer is not None
completer.setCompletionPrefix("l")
assert completer.currentCompletion() == "live"
completer.setCompletionPrefix("10")
assert completer.currentCompletion() == "105"
def test_scan_index_combobox_invalid_input_flagged(scan_index_combo, qtbot):
"""
Unknown scan numbers can be typed; they are flagged with a red border, raise on
read-out, and are not inserted as new items when committed.
"""
combo = scan_index_combo
combo.lineEdit().clear()
qtbot.keyClicks(combo.lineEdit(), "99")
assert combo.currentText() == "99"
assert not combo.is_valid_input
assert "red" in combo.styleSheet()
with pytest.raises(ValueError):
_ = combo.scan_id
with pytest.raises(ValueError):
_ = combo.scan_number
qtbot.keyClick(combo.lineEdit(), Qt.Key_Return)
assert combo.count() == 3
# A refresh with invalid text falls back to 'live' instead of raising
combo.refresh_scan_indices()
assert combo.currentText() == "live"
assert combo.is_valid_input
combo.setCurrentText("2")
assert combo.is_valid_input
assert combo.styleSheet() == ""
assert combo.scan_id == "hid2"