Files
cimtechniques-service-suite/tests/core/kit/test_delegate_editing.py

97 lines
3.8 KiB
Python

"""InstrumentDelegate editing UX: styled editor, validation, Tab-move, hover chip."""
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QLineEdit, QStyle, QTableWidget, QTableWidgetItem
from cim_suite.core.ui.kit import CellKind, InstrumentDelegate
from cim_suite.core.ui.theme import current
def _table(qtbot, rows, editable):
"""editable: set of (row, col) that keep Qt.ItemIsEditable."""
table = QTableWidget(len(rows), len(rows[0]))
qtbot.addWidget(table)
for r, row in enumerate(rows):
for c, text in enumerate(row):
item = QTableWidgetItem(text)
if (r, c) not in editable:
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
table.setItem(r, c, item)
delegate = InstrumentDelegate(table)
table.setItemDelegate(delegate)
table.show()
return table, delegate
def _editor(table):
editor = table.findChild(QLineEdit, "CellEditor")
assert editor is not None
return editor
def test_editor_is_the_styled_cell_editor(qtbot):
table, _ = _table(qtbot, [["a"]], {(0, 0)})
table.editItem(table.item(0, 0))
editor = _editor(table)
assert editor.objectName() == "CellEditor"
def test_invalid_input_blocks_enter_until_fixed(qtbot):
table, delegate = _table(qtbot, [["5"]], {(0, 0)})
delegate.set_column_validator(
0, lambda text: None if text.strip().isdigit() else "whole numbers only"
)
table.editItem(table.item(0, 0))
editor = _editor(table)
editor.setText("abc")
qtbot.keyClick(editor, Qt.Key.Key_Return)
assert editor.property("state") == "invalid"
assert editor.toolTip() == "whole numbers only"
assert table.item(0, 0).text() == "5" # commit was blocked
editor.setText("12") # textChanged clears the invalid state
assert editor.property("state") == "valid"
qtbot.keyClick(editor, Qt.Key.Key_Return)
assert table.item(0, 0).text() == "12"
def test_tab_saves_and_moves_to_next_editable_cell(qtbot):
# (0,0) and (1,0) editable; (0,1) and (1,1) read-only — Tab must skip them.
table, _ = _table(qtbot, [["a", "ro"], ["b", "ro"]], {(0, 0), (1, 0)})
table.editItem(table.item(0, 0))
editor = _editor(table)
editor.setText("edited")
qtbot.keyClick(editor, Qt.Key.Key_Tab)
assert table.item(0, 0).text() == "edited" # Tab saved
assert (table.currentRow(), table.currentColumn()) == (1, 0) # skipped the RO cell
assert table.state() == table.State.EditingState # and opened the editor
def test_shift_tab_moves_backwards(qtbot):
table, _ = _table(qtbot, [["a", "ro"], ["b", "ro"]], {(0, 0), (1, 0)})
table.editItem(table.item(1, 0))
editor = _editor(table)
qtbot.keyClick(editor, Qt.Key.Key_Backtab)
assert (table.currentRow(), table.currentColumn()) == (0, 0)
def test_escape_cancels(qtbot):
table, _ = _table(qtbot, [["keep"]], {(0, 0)})
table.editItem(table.item(0, 0))
editor = _editor(table)
editor.setText("discard me")
qtbot.keyClick(editor, Qt.Key.Key_Escape)
assert table.item(0, 0).text() == "keep"
def test_hover_chip_only_on_editable_cells(qtbot, render_cell, near):
table, delegate = _table(qtbot, [["v", "ro"]], {(0, 0)})
delegate.set_column_kind(0, CellKind.NUMERIC)
delegate.set_column_kind(1, CellKind.NUMERIC)
hover = QStyle.StateFlag.State_MouseOver | QStyle.StateFlag.State_Enabled
editable_img = render_cell(delegate, table.model().index(0, 0), state=hover)
readonly_img = render_cell(delegate, table.model().index(0, 1), state=hover)
# §5.6: the chip appears on the editable cell; its absence IS the RO affordance.
assert near(editable_img.pixelColor(60, 6), current().accent_soft, tol=12)
assert near(readonly_img.pixelColor(60, 6), current().surface, tol=8)
assert editable_img.pixelColor(60, 6) != readonly_img.pixelColor(60, 6)