Skip to content

Commit

Permalink
added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexhroom committed Nov 27, 2024
1 parent 7a7458e commit 4d3872a
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 28 deletions.
54 changes: 27 additions & 27 deletions rascal2/widgets/project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,31 @@ def set_item_delegates(self):
)
self.table.setItemDelegateForColumn(2, delegates.MultiSelectLayerDelegate(self.project_widget, self.table))


class CustomFileModel(ClassListModel):
"""Classlist model for custom files."""

def __init__(self, classlist: RATapi.ClassList, parent: QtWidgets.QWidget):
super().__init__(classlist, parent)
self.func_names = {}
self.headers.remove("path")

def columnCount(self, parent=None) -> int:
return super().columnCount() + 1

def headerData(self, section, orientation, role=QtCore.Qt.ItemDataRole.DisplayRole):
if section == self.columnCount() - 1:
return None
return super().headerData(section, orientation, role)

def flags(self, index):
flags = super().flags(index)
if index.column() in [0, self.columnCount() - 1]:
return QtCore.Qt.ItemFlag.NoItemFlags
if self.edit_mode:
flags |= QtCore.Qt.ItemFlag.ItemIsEditable
return flags

def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
data = super().data(index, role)
if role == QtCore.Qt.ItemDataRole.DisplayRole and self.index_header(index) == "filename" and self.edit_mode:
Expand Down Expand Up @@ -499,7 +524,7 @@ def setData(self, index, value, role=QtCore.Qt.ItemDataRole.DisplayRole):
language = None
func_names = None
self.func_names[value] = func_names
if func_names is not None:
if func_names:
self.classlist[row].function_name = func_names[0]
if language is not None:
self.classlist[row].language = language
Expand All @@ -520,31 +545,6 @@ def index_header(self, index):
return super().index_header(index)


class CustomFileModel(ClassListModel):
"""Classlist model for custom files."""

def __init__(self, classlist: RATapi.ClassList, parent: QtWidgets.QWidget):
super().__init__(classlist, parent)
self.func_names = {}
self.headers.remove("path")

def columnCount(self, parent=None) -> int:
return super().columnCount() + 1

def headerData(self, section, orientation, role=QtCore.Qt.ItemDataRole.DisplayRole):
if section == self.columnCount() - 1:
return None
return super().headerData(section, orientation, role)

def flags(self, index):
flags = super().flags(index)
if index.column() in [0, self.columnCount() - 1]:
return QtCore.Qt.ItemFlag.NoItemFlags
if self.edit_mode:
flags |= QtCore.Qt.ItemFlag.ItemIsEditable
return flags


class CustomFileWidget(ProjectFieldWidget):
classlist_model = CustomFileModel

Expand Down Expand Up @@ -598,7 +598,7 @@ def setup_button():
)
)

editable = (language != Languages.Cpp) and (
editable = (language in [Languages.Matlab, Languages.Python]) and (
self.model.data(self.model.index(index, self.model.headers.index("filename") + 1)) != "Browse..."
)
button.setEnabled(editable)
Expand Down
110 changes: 110 additions & 0 deletions tests/dialogs/test_custom_file_editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Tests for the custom file editor."""

import logging
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from PyQt6 import Qsci, QtWidgets
from RATapi.utils.enums import Languages

from rascal2.dialogs.custom_file_editor import CustomFileEditorDialog, edit_file, edit_file_matlab

parent = QtWidgets.QMainWindow()


@pytest.fixture
def custom_file_dialog():
"""Fixture for a custom file dialog."""

def _dialog(language, tmpdir):
file = Path(tmpdir, "test_file")
file.write_text("Test text for a test dialog!")
dlg = CustomFileEditorDialog(file, language, parent)

return dlg

return _dialog


@patch("rascal2.dialogs.custom_file_editor.CustomFileEditorDialog.exec")
def test_edit_file(exec_mock):
"""Test that the dialog is executed when edit_file() is called on a valid file"""

with tempfile.TemporaryDirectory() as tmp:
file = Path(tmp, "testfile.py")
file.touch()
edit_file(file, Languages.Python, parent)

exec_mock.assert_called_once()


@pytest.mark.parametrize("filepath", ["dir/", "not_there.m"])
@patch("rascal2.dialogs.custom_file_editor.CustomFileEditorDialog")
def test_edit_incorrect_file(dialog_mock, filepath, caplog):
"""A logger error should be emitted if a directory or nonexistent file is given to the editor."""

with tempfile.TemporaryDirectory() as tmp:
file = Path(tmp, filepath)
edit_file(file, Languages.Python, parent)

errors = [record for record in caplog.get_records("call") if record.levelno == logging.ERROR]
assert len(errors) == 1
assert "Attempted to edit a custom file which does not exist!" in caplog.text


def test_edit_file_matlab():
"""Assert that a file is passed to the engine when the MATLAB editor is called."""
mock_engine = MagicMock()
mock_engine.edit = MagicMock()
mock_loader = MagicMock()
mock_loader.result = MagicMock(return_value=mock_engine)
with patch("rascal2.dialogs.custom_file_editor.start_matlab", return_value=mock_loader) as mock_start:
with tempfile.TemporaryDirectory() as tmp:
file = Path(tmp, "testfile.m")
file.touch()
edit_file_matlab(file)

mock_start.assert_called_once()
mock_loader.result.assert_called_once()
mock_engine.edit.assert_called_once_with(str(file))


def test_edit_no_matlab_engine(caplog):
"""A logging error should be produced if a user tries to edit a file in MATLAB with no engine available."""
with patch("rascal2.dialogs.custom_file_editor.start_matlab", return_value=None) as mock_loader:
with tempfile.TemporaryDirectory() as tmp:
file = Path(tmp, "testfile.m")
file.touch()
edit_file_matlab(file)
mock_loader.assert_called_once()

errors = [record for record in caplog.get_records("call") if record.levelno == logging.ERROR]
assert len(errors) == 1
assert "Attempted to edit a file in MATLAB engine, but `matlabengine` is not available." in caplog.text


@pytest.mark.parametrize(
"language, expected_lexer",
[(Languages.Python, Qsci.QsciLexerPython), (Languages.Matlab, Qsci.QsciLexerMatlab), (None, type(None))],
)
def test_dialog_init(custom_file_dialog, language, expected_lexer):
"""Ensure the custom file editor is set up correctly."""

with tempfile.TemporaryDirectory() as tmp:
dialog = custom_file_dialog(language, tmp)

assert isinstance(dialog.editor.lexer(), expected_lexer)
assert dialog.editor.text() == "Test text for a test dialog!"


def test_dialog_save(custom_file_dialog):
"""Text changes to the editor are saved to the file when save_file is called."""
with tempfile.TemporaryDirectory() as tmp:
dialog = custom_file_dialog(Languages.Python, tmp)

dialog.editor.setText("New test text...")
dialog.save_file()

assert Path(tmp, "test_file").read_text() == "New test text..."
100 changes: 99 additions & 1 deletion tests/widgets/project/test_models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import tempfile
from pathlib import Path
from unittest.mock import MagicMock

import pydantic
import pytest
import RATapi
from PyQt6 import QtCore, QtWidgets
from RATapi.utils.enums import Languages

import rascal2.widgets.delegates as delegates
import rascal2.widgets.inputs as inputs
from rascal2.widgets.project.models import (
ClassListModel,
DomainContrastWidget,
DomainsModel,
CustomFileModel,
CustomFileWidget,
LayerFieldWidget,
LayersModel,
ParameterFieldWidget,
Expand Down Expand Up @@ -373,7 +378,6 @@ def test_layer_widget_delegates(init_class):
for i, header in enumerate(widget.model.headers):
assert isinstance(widget.table.itemDelegateForColumn(i + 1), expected_delegates[header])


@pytest.mark.parametrize("edit_mode", [True, False])
def test_domains_model_flags(edit_mode, domains_classlist):
"""Test that the DomainsModel flags are set correctly."""
Expand All @@ -390,3 +394,97 @@ def test_domains_widget_item_delegates(domains_classlist):
widget.update_model(domains_classlist)
assert isinstance(widget.table.itemDelegateForColumn(1), delegates.ValidatedInputDelegate)
assert isinstance(widget.table.itemDelegateForColumn(2), delegates.MultiSelectLayerDelegate)

def test_file_model_filename_data():
"""Tests the display data for the CustomFileModel `filename` field is as expected."""
init_list = RATapi.ClassList(
[
RATapi.models.CustomFile(filename="myfile.m", path="/home/user/"),
RATapi.models.CustomFile(filename="", path="/"),
]
)

model = CustomFileModel(init_list, parent)

filename_col = model.headers.index("filename") + 1

assert model.data(model.index(0, filename_col)) == "myfile.m"
assert model.data(model.index(1, filename_col)) == ""

model.edit_mode = True

assert Path(model.data(model.index(0, filename_col))) == Path("/home/user/myfile.m")
assert model.data(model.index(1, filename_col)) == "Browse..."


@pytest.mark.parametrize(
"filename, expected_lang, expected_filenames",
(
["myfile.m", Languages.Matlab, None],
["myfile.py", Languages.Python, ["func1", "func2", "func3"]],
["myfile.dll", Languages.Cpp, None],
["myfile.so", Languages.Cpp, None],
["myfile.dylib", Languages.Cpp, None],
),
)
def test_file_model_set_filename(filename, expected_lang, expected_filenames):
"""Test the custom file row autocompletes when a filename is set."""
init_list = RATapi.ClassList([RATapi.models.CustomFile(filename="", path="/")])

python_file = "def func1(): pass \ndef func2(): pass \ndef func3(): pass"

model = CustomFileModel(init_list, parent)

filename_col = model.headers.index("filename") + 1

with tempfile.TemporaryDirectory() as tmp:
Path(tmp, "myfile.py").write_text(python_file)
filepath = Path(tmp, filename)
model.setData(model.index(0, filename_col), filepath)

assert model.classlist[0].path == Path(tmp)
assert model.classlist[0].filename == filename
assert model.classlist[0].language == expected_lang

if expected_lang == Languages.Python:
assert model.func_names[filepath] == expected_filenames
assert model.classlist[0].function_name == "func1"


@pytest.mark.parametrize("filename", ["file.m", "file.py", "file.dll", ""])
def test_file_widget_edit(filename):
"""Test that the correct index widget is created in edit mode."""

with tempfile.TemporaryDirectory() as tmp:
Path(tmp, "file.py").touch()
init_list = RATapi.ClassList([RATapi.models.CustomFile(filename="")])

widget = CustomFileWidget("files", parent)
widget.update_model(init_list)

edit_col = widget.model.columnCount() - 1
assert widget.table.isColumnHidden(edit_col)

widget.edit()

assert not widget.table.isColumnHidden(edit_col)

if filename != "":
widget.model.setData(
widget.model.index(0, widget.model.headers.index("filename") + 1),
Path(tmp, filename),
)

button = widget.table.indexWidget(widget.model.index(0, edit_col))
assert isinstance(button, QtWidgets.QPushButton)

if filename in ["file.m", "file.py"]:
assert button.isEnabled()
else:
assert not button.isEnabled()

if filename == "file.m":
assert button.menu() is not None
assert len(button.menu().actions()) == 2
else:
assert button.menu() is None

0 comments on commit 4d3872a

Please sign in to comment.