Skip to content

Commit

Permalink
Merge pull request #81 from zdomke/dev_tests
Browse files Browse the repository at this point in the history
TST/FIX: Add Tests for Models & Widgets
  • Loading branch information
zdomke authored Nov 1, 2024
2 parents 8cbfcd2 + 5eeb90e commit 8b9091f
Show file tree
Hide file tree
Showing 10 changed files with 622 additions and 101 deletions.
16 changes: 7 additions & 9 deletions trace/table_models/axis_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Dict, List

from qtpy.QtCore import Qt, Signal, QVariant, QModelIndex, QPersistentModelIndex
from qtpy.QtCore import Qt, Signal, QModelIndex, QPersistentModelIndex

from pydm.widgets.baseplot import BasePlot, BasePlotAxisItem
from pydm.widgets.axis_table_model import BasePlotAxesModel
Expand Down Expand Up @@ -38,6 +38,9 @@ def __init__(self, plot: BasePlot, parent=None) -> None:

def flags(self, index: QModelIndex) -> Qt.ItemFlags:
"""Return flags that determine how users can interact with the items in the table"""
if not index.isValid():
return Qt.NoItemFlags

flags = super().flags(index)
if index.column() in self.checkable_col:
flags = Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable
Expand All @@ -56,7 +59,7 @@ def data(self, index: QModelIndex, role=Qt.DisplayRole) -> Any:
needs, by default Qt.DisplayRole
"""
if not index.isValid():
return QVariant()
return None
elif role == Qt.CheckStateRole and self._column_names[index.column()] == "Hidden":
return Qt.Unchecked if self.plot._axes[index.row()].isVisible() else Qt.Checked
elif role == Qt.CheckStateRole and index.column() in self.checkable_col:
Expand All @@ -81,7 +84,7 @@ def setData(self, index: QModelIndex, value: Any, role=Qt.EditRole) -> bool:
"""
logger.debug(f"Setting {self._column_names[index.column()]} on axis {index.siblingAtColumn(0).data()}")
if not index.isValid():
return QVariant()
return None
# Specifically the Hidden column must be affected in axis_model as opposed to elsewhere
elif role == Qt.CheckStateRole and self._column_names[index.column()] == "Hidden":
self.plot._axes[index.row()].setHidden(bool(value))
Expand Down Expand Up @@ -137,6 +140,7 @@ def set_model_axes(self, axes: List[Dict] = []) -> None:
}
cleaned_axes = []
for a in axes:
# The bare requirements for a new axis
clean_a = {
"name": f"Axis {len(cleaned_axes) + 1}",
"orientation": "left",
Expand All @@ -151,12 +155,6 @@ def set_model_axes(self, axes: List[Dict] = []) -> None:
else:
clean_a[k] = a[k]
cleaned_axes.append(clean_a)
clean_a = {
"name": f"Axis {len(cleaned_axes) + 1}",
"orientation": "left",
"label": f"Axis {len(cleaned_axes) + 1}",
}
cleaned_axes.append(clean_a)
logger.debug("Clearing axes model")
self.beginResetModel()
self._plot.clearAxes()
Expand Down
19 changes: 11 additions & 8 deletions trace/table_models/curve_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,15 @@ def set_data(self, column_name: str, curve: BasePlotCurveItem, value: Any) -> bo
try:
ret_code = self.replaceToFormula(index=index, formula=value)
except (SyntaxError, ValueError) as e:
logger.error(e)
logger.error(str(e))
return False
elif value_is_formula and curve_is_formula:
try:
pv_dict = self.formulaToPVDict(self._row_names[index.row()], value)
curve.formula = value
curve.pvs = pv_dict
except (SyntaxError, ValueError) as e:
logger.error(e)
logger.error(str(e))
return False
elif not value_is_formula:
if not curve_is_formula:
Expand Down Expand Up @@ -240,8 +240,10 @@ def flags(self, index: QModelIndex) -> Qt.ItemFlags:
Returns the model's default flags, and if the column is Live Data or
Archive Data, then may also disable the index depending on channel status.
"""
flags = super().flags(index)
if not index.isValid():
return Qt.NoItemFlags

flags = super().flags(index)
col_name = self._column_names[index.column()]
if col_name in ("Live Data", "Archive Data"):
curve = self.curve_at_index(index)
Expand Down Expand Up @@ -280,17 +282,16 @@ def append(
self._plot.addYChannel(y_channel=address, name=name, color=color, useArchiveData=True, yAxisName=y_axis.name)
self.endInsertRows()

new_curve = self._plot._curves[-1]
new_curve = self.curve_at_index(-1)
new_curve.hide()
if self.rowCount() != 1:
logger.debug("Hide blank Y-axis")
self._axis_model.plot.plotItem.axes[y_axis.name]["item"].hide()
new_curve.unitSignal.connect(self.setAxis)
logger.debug("Finished adding new empty curve to plot")

curve = self.curve_at_index(-1)
curve.live_channel_connection.connect(self.live_connection_slot)
curve.archive_channel_connection.connect(self.archive_connection_slot)
new_curve.live_channel_connection.connect(self.live_connection_slot)
new_curve.archive_channel_connection.connect(self.archive_connection_slot)
logger.debug("Finished adding new empty curve to plot")

def set_model_curves(self, curves: List[Dict] = []) -> None:
"""Reset the model to only contain the list of given curves.
Expand Down Expand Up @@ -602,6 +603,8 @@ def live_connection_slot(self, connection: bool) -> None:
The curve's live connection status.
"""
curve = self.sender()
if not isinstance(curve, (ArchivePlotCurveItem, FormulaCurveItem)):
return

if connection:
self._invalid_live_channels.discard(curve)
Expand Down
9 changes: 7 additions & 2 deletions trace/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ def qapp(qapp_args):
if "pytest-qt-qapp" == qapp_args[0]:
qapp_args.remove("pytest-qt-qapp")

yield PyDMApplication(use_main_window=False, *qapp_args)
app = PyDMApplication(use_main_window=False, *qapp_args)
yield app
app.quit()


@pytest.fixture
Expand All @@ -75,9 +77,12 @@ def qtrace(qapp):

# updateXAxis would be called on application render; necessary for testing X-Axis
trace.ui.main_plot.updateXAxis(True)

yield trace

trace.close()
qapp.processEvents()
del trace


@pytest.fixture
def mock_logger():
Expand Down
8 changes: 8 additions & 0 deletions trace/tests/test_data/test_file.trc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
"maxRange": 1.04,
"autoRange": true,
"logMode": false
},
{
"name": "FOO Axis",
"orientation": "right",
"minRange": -15,
"maxRange": 15,
"autoRange": false,
"logMode": true
}
],
"curves": [
Expand Down
105 changes: 83 additions & 22 deletions trace/tests/test_mixins/test_traces_table.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from json import loads
from unittest import mock

import pytest
from qtpy.QtCore import Qt

from widgets.item_delegates import (
ComboBoxDelegate,
DeleteRowDelegate,
Expand All @@ -20,32 +26,87 @@ def test_traces_table_delegates(qtrace):
curves_model = qtrace.curves_model
table_view = qtrace.ui.traces_tbl

axis_col = curves_model.getColumnIndex("Y-Axis Name")
color_col = curves_model.getColumnIndex("Color")
style_col = curves_model.getColumnIndex("Style")
line_style_col = curves_model.getColumnIndex("Line Style")
line_width_col = curves_model.getColumnIndex("Line Width")
symbol_col = curves_model.getColumnIndex("Symbol")
symbol_size_col = curves_model.getColumnIndex("Symbol Size")
delete_col = curves_model.getColumnIndex("")
delegates = []
for column in range(curves_model.columnCount()):
delegate = table_view.itemDelegateForColumn(column)
delegates.append(type(delegate) if delegate else None)
expected = [
None,
None,
None,
None,
ColorButtonDelegate,
ComboBoxDelegate,
ComboBoxDelegate,
ComboBoxDelegate,
ComboBoxDelegate,
ComboBoxDelegate,
ComboBoxDelegate,
None,
DeleteRowDelegate,
]
assert delegates == expected

assert type(table_view.itemDelegateForColumn(axis_col)) is ComboBoxDelegate
assert type(table_view.itemDelegateForColumn(color_col)) is ColorButtonDelegate
assert type(table_view.itemDelegateForColumn(style_col)) is ComboBoxDelegate
assert type(table_view.itemDelegateForColumn(line_style_col)) is ComboBoxDelegate
assert type(table_view.itemDelegateForColumn(line_width_col)) is ComboBoxDelegate
assert type(table_view.itemDelegateForColumn(symbol_col)) is ComboBoxDelegate
assert type(table_view.itemDelegateForColumn(symbol_size_col)) is ComboBoxDelegate
assert type(table_view.itemDelegateForColumn(delete_col)) is DeleteRowDelegate

@pytest.mark.parametrize(
("test_data", "expected_calls"),
(("FOO:BAR:CHANNEL", 1), ("FOO:CHANNEL, BAR:CHANNEL", 2), ("FOO:CHANNEL BAR:CHANNEL", 2)),
)
def test_insert_pvs(qtrace, test_data, expected_calls):
"""Test that insertPVs() calls curves_model.set_data the correct amount of
times. Splits on whitespace or commas.
Parameters
----------
qtrace : fixture
Instance of TraceDisplay for application testing
test_data : str
Data that resembles what insertPVs() might recieve
expected_calls : int
The number of times curves_model.set_data() is expected to be called
Expectations
------------
curves_model.set_data gets called the expected number of times.
"""
qtrace.curves_model.set_data = mock.Mock()

qtrace.insertPVs(test_data)

assert qtrace.curves_model.set_data.call_count == expected_calls


def test_formula_dialog(qtbot, qtrace, get_test_file):
"""Test the formula dialog box will allow users to enter an existing PV,
type in a formula, and hit enter to accept the formula.
Parameters
----------
qtbot : fixture
pytest-qt window for widget testing
qtrace : fixture
Instance of TraceDisplay for application testing
get_test_file : fixture
A fixture used to get test files from the test_data directory
Expectations
------------
The "user entered" formula should be accepted and matches the expected outcome
"""
test_filename = get_test_file("test_file.trc")
test_data = loads(test_filename.read_text())

def test_drag_drop(qtrace):
pass
qtrace.curves_model.set_model_curves(test_data["curves"])
qtrace.curves_model.setData = mock.Mock()

index = qtrace.curves_model.index(1, 0)
qtrace.menu.selected_index = index
dialog = qtrace.menu._formula_dialog

def test_context_menu(qtrace):
pass
delegate = dialog.pv_list.itemDelegateForColumn(qtrace.curves_model.columnCount() - 1)
delegate.button_clicked.emit("{A}")
dialog.field.insert(" + 7")

qtbot.keyClick(dialog, Qt.Key_Enter)

def test_formula_dialog(qtrace):
pass
assert qtrace.curves_model.setData.call_args.args[1] == "f://{A}+7"
Loading

0 comments on commit 8b9091f

Please sign in to comment.