Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion rascal2/widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
from rascal2.widgets.controls import ControlsWidget
from rascal2.widgets.inputs import AdaptiveDoubleSpinBox, MultiSelectComboBox, MultiSelectList, get_validated_input
from rascal2.widgets.inputs import (
AdaptiveDoubleSpinBox,
MultiSelectComboBox,
MultiSelectList,
PathWidget,
ProgressButton,
get_validated_input,
)
from rascal2.widgets.plot import PlotWidget
from rascal2.widgets.project.slider_view import SliderViewWidget
from rascal2.widgets.terminal import TerminalWidget
Expand All @@ -10,7 +17,9 @@
"get_validated_input",
"MultiSelectComboBox",
"MultiSelectList",
"PathWidget",
"PlotWidget",
"ProgressButton",
"TerminalWidget",
"SliderViewWidget",
]
47 changes: 46 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
import tempfile
from pathlib import Path
from unittest.mock import patch
from unittest.mock import MagicMock, patch

os.environ["DELAY_MATLAB_START"] = "1"
import pytest
from PyQt6 import QtCore, QtWidgets

Expand All @@ -19,6 +21,11 @@ def global_setting():
return GLOBAL_SETTING


@pytest.fixture
def mock_window_view():
return MockWindowView()


@pytest.fixture(scope="session", autouse=True)
def mock_setting(request):
global GLOBAL_SETTING
Expand All @@ -38,3 +45,41 @@ def teardown_mock_setting():
target.stop()

request.addfinalizer(teardown_mock_setting)


class MockUndoStack:
"""A mock Undo stack."""

def __init__(self):
self.stack = []
self.clean = True

def push(self, command):
self.clean = False
command.redo()

def setClean(self):
self.clean = True

def isClean(self):
return self.clean


class MockWindowView(QtWidgets.QMainWindow):
"""A mock MainWindowView class."""

def __init__(self):
super().__init__()
self.undo_stack = MockUndoStack()
self.presenter = MagicMock()
self.controls_widget = MagicMock()
self.project_widget = MagicMock()
self.terminal_widget = MagicMock()
self.plot_widget = MagicMock()
self.handle_results = MagicMock()
self.settings = MagicMock()
self.get_project_folder = lambda: "new path/"
self.windowTitle = lambda: "RasCAL2"
self.show_message = MagicMock()
self.toggle_sliders = MagicMock()
self.set_editing_enabled = MagicMock()
90 changes: 90 additions & 0 deletions tests/core/test_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Tests for the undo Command classes."""

from unittest.mock import MagicMock, patch

import pytest
from ratapi import Controls, Project
from ratapi.rat_core import ProblemDefinition

from rascal2.core.commands import CommandID, EditControls, EditProject, SaveCalculationOutputs
from rascal2.ui.presenter import MainWindowPresenter


@pytest.fixture
def presenter(mock_window_view):
with (
patch("rascal2.ui.presenter.LOGGER", autospec=True),
patch("rascal2.ui.model.os.chdir", autospec=True),
):
pr = MainWindowPresenter(mock_window_view)
results = MagicMock()
results.calculationResults.sumChi = 45
pr.quick_run = MagicMock(return_value=results)
pr.model.controls = Controls()
pr.model.project = Project()
pr.model.results = None
pr.model.result_log = ""

yield pr


def test_edit_controls(presenter):
command = EditControls({"procedure": "de", "targetValue": 3}, presenter)
assert command.id() == CommandID.EditControls
assert presenter.model.controls.procedure == "calculate"
assert presenter.model.controls.targetValue == 1
command.redo()
assert presenter.model.controls.procedure == "de"
assert presenter.model.controls.targetValue == 3
command.undo()
assert presenter.model.controls.procedure == "calculate"
assert presenter.model.controls.targetValue == 1


def test_edit_project(presenter):
command = EditProject({"model": "custom layers"}, presenter)
assert command.id() == CommandID.EditProject
assert presenter.model.project.model == "standard layers"
command.redo()
assert presenter.model.project.model == "custom layers"
command.undo()
assert presenter.model.project.model == "standard layers"


def test_edit_project_preview(presenter):
command = EditProject({"model": "custom layers"}, presenter, preview=True)
command.redo()
presenter.quick_run.assert_called_once()
assert presenter.model.results.calculationResults.sumChi == 45
command.undo()
assert presenter.model.results is None
command.redo()
# confirm quick_run is always done once
presenter.quick_run.assert_called_once()
assert presenter.model.results.calculationResults.sumChi == 45

presenter.quick_run.side_effect = ValueError("calculate error")
command = EditProject({"model": "custom layers"}, presenter, preview=True)
command.redo()
# run failed so result is None
assert command.new_result is None


def test_save_calculation_outputs(presenter):
project = ProblemDefinition()
project.params = [4.5]
results = MagicMock()
results.calculationResults.sumChi = 45
log = "Stuff happened during calculation"
command = SaveCalculationOutputs(project, results, log, presenter)
assert presenter.model.project.parameters[0].value == 3
assert presenter.model.results is None
assert presenter.model.result_log == ""
command.redo()
assert presenter.model.project.parameters[0].value == 4.5
assert presenter.model.results.calculationResults.sumChi == 45
assert presenter.model.result_log == log
command.undo()
assert presenter.model.project.parameters[0].value == 3
assert presenter.model.results is None
assert presenter.model.result_log == ""
10 changes: 8 additions & 2 deletions tests/dialogs/test_about_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
@patch("rascal2.dialogs.about_dialog.MatlabHelper", autospec=True)
def test_update_info_works(mock_matlab):
"""Check if `update_rascal_info` adds all necessary information to the dialog."""
mock_matlab.return_value = MagicMock()
helper = MagicMock()
mock_matlab.return_value = helper
helper.get_matlab_path.return_value = "Test_Path"
parent = QtWidgets.QMainWindow()
about = AboutDialog(parent)
assert about._rascal_label.text() == "information about RASCAL-2"
Expand All @@ -17,5 +19,9 @@ def test_update_info_works(mock_matlab):
rascal_info = about._rascal_label.text()
assert "Version" in rascal_info
assert "RasCAL 2" in rascal_info
assert "Matlab Path:" in rascal_info
assert "Matlab Path:</td><td>Test_Path" in rascal_info
assert "Log File:" in rascal_info

helper.get_matlab_path.return_value = ""
about.update_rascal_info()
assert "Matlab Path:</td><td>None" in about._rascal_label.text()
105 changes: 87 additions & 18 deletions tests/dialogs/test_custom_file_editor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests for the custom file editor."""

import logging
import os
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
Expand All @@ -9,15 +10,22 @@
from PyQt6 import Qsci, QtWidgets
from ratapi.utils.enums import Languages

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

parent = QtWidgets.QMainWindow()
from rascal2.dialogs.custom_file_editor import (
CustomFileEditorDialog,
create_new_file,
edit_file,
edit_file_local,
edit_file_matlab,
)
from tests.utils import assert_error_logged


@pytest.fixture
def custom_file_dialog():
"""Fixture for a custom file dialog."""
parent = QtWidgets.QMainWindow()
dlg = CustomFileEditorDialog(parent)
dlg.show = MagicMock()
yield dlg
dlg.reject()

Expand All @@ -31,28 +39,24 @@ def temp_file():
f.close()


@patch("rascal2.dialogs.custom_file_editor.CustomFileEditorDialog.show")
def test_edit_file_local(exec_mock):
def test_edit_file_local(custom_file_dialog, mock_window_view):
"""Test that the dialog is executed when edit_file_local() is called on a valid file."""
with tempfile.TemporaryDirectory() as tmp:
file = Path(tmp, "testfile.py")
file.touch()
edit_file_local(file, Languages.Python, parent)
edit_file_local(file, Languages.Python, mock_window_view)

exec_mock.assert_called_once()
custom_file_dialog.show.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):
def test_edit_incorrect_file(filepath, caplog, mock_window_view):
"""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_local(file, Languages.Python, parent)
edit_file_local(file, Languages.Python, mock_window_view)

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
assert_error_logged(caplog, "Attempted to edit a custom file which does not exist!")


@patch("rascal2.dialogs.custom_file_editor.MatlabHelper", autospec=True)
Expand Down Expand Up @@ -101,9 +105,8 @@ def test_dialog_init(custom_file_dialog, temp_file, language, expected_lexer):
assert custom_file_dialog.editor.text() == "Test text for a test dialog!"


@patch("rascal2.dialogs.custom_file_editor.LOGGER")
@patch("rascal2.dialogs.custom_file_editor.QtWidgets.QMessageBox")
def test_dialog_save(mock_msg_box, mock_logger, custom_file_dialog):
def test_dialog_save(mock_msg_box, caplog, custom_file_dialog):
"""Text changes to the editor are saved to the file when save_file is called."""
temp_file = MagicMock()
temp_file.read_text = MagicMock(return_value="This is a test")
Expand Down Expand Up @@ -132,11 +135,11 @@ def test_dialog_save(mock_msg_box, mock_logger, custom_file_dialog):
custom_file_dialog.save_file()
temp_file.write_text.assert_called_once()
assert not custom_file_dialog.is_modified
custom_file_dialog.unchanged_text = temp_file.write_text.call_args[0]

custom_file_dialog.editor.setText("User changed text")
temp_file.write_text = MagicMock(side_effect=OSError)
custom_file_dialog.save_file()
mock_logger.error.assert_called_once()
assert_error_logged(caplog, f"Failed to save custom file to {custom_file_dialog.file}")
mock_msg_box.critical.assert_called_once()


Expand All @@ -146,7 +149,6 @@ def test_save_changes_when_opening_file(mock_msg_box, custom_file_dialog, temp_f
custom_file_dialog.open_file(temp_file, Languages.Python)

custom_file_dialog.editor.setText("New test text...")

# Opening the same file should not trigger a save warning
custom_file_dialog.open_file(temp_file, Languages.Python)

Expand All @@ -167,3 +169,70 @@ def test_save_changes_when_opening_file(mock_msg_box, custom_file_dialog, temp_f
# Changes should be discarded as user selected discard in msg box
custom_file_dialog.open_file(temp_file, Languages.Python)
assert new_file.read_text() == "This is a new file"


@pytest.mark.parametrize(
"language, file_type, domain",
(
["python", "Background", False],
["python", "Model", True],
["python", "Model", False],
["matlab", "Background", False],
["matlab", "Model", True],
["matlab", "Model", False],
),
)
@patch("rascal2.dialogs.custom_file_editor.edit_file", autospec=True)
def test_create_file(mock_edit_file, mock_window_view, caplog, language, file_type, domain):
with tempfile.TemporaryDirectory() as tmp:
cur_dir = os.getcwd()
try:
os.chdir(tmp)
create_new_file("hello world", language, domain, file_type, mock_window_view)
mock_edit_file.assert_called()
file = Path("hello_world.py" if language == "python" else "hello_world.m")
assert file.is_file()
assert file.read_text().find("hello_world(") != -1

create_new_file("hello world", language, domain, file_type, mock_window_view)
# non-unique name so file already exist
assert_error_logged(
caplog, f"The file ({file.name}) already exists, change custom file name to create a different file."
)
finally:
os.chdir(cur_dir)


@pytest.mark.parametrize(
"language, file_type, domain",
(
["c++", "Background", False],
["c++", "Model", True],
),
)
@patch("rascal2.dialogs.custom_file_editor.edit_file", autospec=True)
def test_create_bad_file_type(mock_edit_file, mock_window_view, caplog, language, file_type, domain):
create_new_file("hello world", language, domain, file_type, mock_window_view)
mock_edit_file.assert_not_called()

assert_error_logged(caplog, f"Creating a new file for {language} is not supported.")


@patch("rascal2.dialogs.custom_file_editor.SETTINGS", autospec=True)
@patch("rascal2.dialogs.custom_file_editor.edit_file_matlab", autospec=True)
@patch("rascal2.dialogs.custom_file_editor.edit_file_local", autospec=True)
def test_edit_file(mock_edit_file_local, mock_edit_file_matlab, mock_setting, mock_window_view):
mock_setting.matlab_as_default_editor = False
edit_file("hello", "python", mock_window_view)
mock_edit_file_local.assert_called_once()

mock_edit_file_matlab.return_value = True
edit_file("hello", "matlab", mock_window_view)
mock_edit_file_matlab.assert_called_once()
assert mock_edit_file_local.call_count == 1

mock_edit_file_matlab.return_value = False # matlab editor failed so fallback on local
mock_setting.matlab_as_default_editor = True
edit_file("hello", "python", mock_window_view)
assert mock_edit_file_matlab.call_count == 2
assert mock_edit_file_local.call_count == 2
Loading
Loading