[01ef3f7] | 1 | import sys |
---|
| 2 | import os |
---|
| 3 | import unittest |
---|
| 4 | import webbrowser |
---|
| 5 | import tempfile |
---|
| 6 | from unittest.mock import MagicMock, patch |
---|
| 7 | |
---|
| 8 | from PyQt5 import QtGui |
---|
| 9 | from PyQt5 import QtWidgets |
---|
| 10 | |
---|
| 11 | # set up import paths |
---|
| 12 | import path_prepare |
---|
| 13 | |
---|
| 14 | from sas.qtgui.Utilities.GuiUtils import Communicate |
---|
| 15 | |
---|
| 16 | # Local |
---|
| 17 | from sas.qtgui.Utilities.AddMultEditor import AddMultEditor |
---|
| 18 | |
---|
| 19 | if not QtWidgets.QApplication.instance(): |
---|
| 20 | app = QtWidgets.QApplication(sys.argv) |
---|
| 21 | |
---|
| 22 | class dummy_manager(object): |
---|
| 23 | HELP_DIRECTORY_LOCATION = "html" |
---|
| 24 | communicate = Communicate() |
---|
[27689dc] | 25 | _parent = QtWidgets.QDialog() |
---|
[01ef3f7] | 26 | |
---|
| 27 | class AddMultEditorTest(unittest.TestCase): |
---|
| 28 | """ Test the simple AddMultEditor dialog """ |
---|
| 29 | @patch.object(AddMultEditor, 'readModels') |
---|
| 30 | def setUp(self, mock_list_models): |
---|
| 31 | """ Create AddMultEditor dialog """ |
---|
| 32 | |
---|
| 33 | # mock models from plugin folder |
---|
| 34 | mock_list_models.return_value = ['cylinder', 'rpa', |
---|
| 35 | 'core_shell_cylinder', 'sphere'] |
---|
| 36 | |
---|
| 37 | self.widget = AddMultEditor(dummy_manager()) |
---|
| 38 | |
---|
| 39 | def tearDown(self): |
---|
| 40 | """ Destroy the GUI """ |
---|
| 41 | |
---|
| 42 | self.widget.close() |
---|
| 43 | self.widget = None |
---|
| 44 | |
---|
| 45 | def testDefaults(self): |
---|
| 46 | """ Test the GUI in its default state """ |
---|
| 47 | |
---|
| 48 | self.assertIsInstance(self.widget, QtWidgets.QDialog) |
---|
| 49 | |
---|
| 50 | self.assertEqual(self.widget.sizePolicy().verticalPolicy(), 0) |
---|
| 51 | self.assertEqual(self.widget.sizePolicy().horizontalPolicy(), 5) |
---|
| 52 | |
---|
| 53 | # Default title |
---|
| 54 | self.assertEqual(self.widget.windowTitle(), "Easy Add/Multiply Editor") |
---|
| 55 | |
---|
| 56 | # Default types |
---|
| 57 | self.assertIsInstance(self.widget.cbOperator, QtWidgets.QComboBox) |
---|
| 58 | self.assertIsInstance(self.widget.cbModel1, QtWidgets.QComboBox) |
---|
| 59 | self.assertIsInstance(self.widget.cbModel2, QtWidgets.QComboBox) |
---|
| 60 | |
---|
| 61 | # Modal window |
---|
| 62 | self.assertFalse(self.widget.isModal()) |
---|
| 63 | |
---|
| 64 | # Checkbox not tristate, not checked |
---|
| 65 | self.assertFalse(self.widget.chkOverwrite.isTristate()) |
---|
| 66 | self.assertFalse(self.widget.chkOverwrite.isChecked()) |
---|
| 67 | |
---|
| 68 | # Push buttons disabled |
---|
| 69 | self.assertFalse(self.widget.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).isEnabled()) |
---|
| 70 | |
---|
| 71 | # Text of labels... |
---|
| 72 | self.assertEqual(self.widget.chkOverwrite.text(), |
---|
| 73 | "Overwrite existing model") |
---|
| 74 | |
---|
| 75 | # Allowed operators |
---|
| 76 | self.assertListEqual([str(self.widget.cbOperator.itemText(i)) for i in |
---|
| 77 | range(self.widget.cbOperator.count())], |
---|
| 78 | ['+', '*']) |
---|
| 79 | |
---|
| 80 | # Default operator |
---|
| 81 | self.assertEqual(self.widget.cbOperator.currentText(), '+') |
---|
| 82 | |
---|
| 83 | # checkbox unchecked |
---|
| 84 | self.assertFalse(self.widget.chkOverwrite.isChecked()) |
---|
| 85 | |
---|
| 86 | # Default 'good_name' flag, name for plugin file and plugin dir |
---|
| 87 | self.assertFalse(self.widget.good_name) |
---|
| 88 | self.assertIsNone(self.widget.plugin_filename) |
---|
| 89 | |
---|
| 90 | # default content of displayed equation (to create the new model) |
---|
| 91 | self.assertEqual(self.widget.lblEquation.text(), |
---|
| 92 | "<html><head/><body><p>Plugin_model = " |
---|
| 93 | "scale_factor * (model_1 + model_2) + background" |
---|
| 94 | "</p></body></html>") |
---|
| 95 | |
---|
| 96 | # Tooltips |
---|
| 97 | self.assertEqual(self.widget.cbOperator.toolTip(), |
---|
| 98 | "Add: +\nMultiply: *") |
---|
| 99 | self.assertEqual(self.widget.txtName.toolTip(), |
---|
| 100 | "Sum / Multiply model function name.") |
---|
| 101 | self.widget.chkOverwrite.setChecked(True) |
---|
| 102 | |
---|
| 103 | self.assertNotEqual(len(self.widget.chkOverwrite.toolTip()), 0) |
---|
| 104 | |
---|
| 105 | def testOnModelComboboxes(self): |
---|
| 106 | """ Test on Model1 and Model2 comboboxes """ |
---|
| 107 | |
---|
| 108 | # content of model_1 and model_2 comboboxes |
---|
| 109 | # same content for the two comboboxes |
---|
| 110 | self.assertEqual(self.widget.cbModel1.count(), |
---|
| 111 | self.widget.cbModel2.count()) |
---|
| 112 | |
---|
| 113 | self.assertEqual(self.widget.cbModel1.count(), 4) |
---|
| 114 | |
---|
| 115 | # default of cbModel1 and cbModel2 |
---|
| 116 | self.assertEqual(self.widget.cbModel1.currentText(), 'sphere') |
---|
| 117 | self.assertEqual(self.widget.cbModel2.currentText(), 'cylinder') |
---|
| 118 | |
---|
| 119 | def testValidateName(self): |
---|
| 120 | """ Test validity of output name (syntax only) """ |
---|
| 121 | |
---|
| 122 | # Invalid plugin name |
---|
| 123 | self.widget.txtName.setText('+++') |
---|
| 124 | |
---|
| 125 | state = self.widget.txtName.validator().validate(self.widget.txtName.text(), 0)[0] |
---|
| 126 | self.assertEqual(state, QtGui.QValidator.Invalid) |
---|
| 127 | |
---|
| 128 | # Valid new plugin name |
---|
| 129 | self.widget.txtName.setText('cylinder_test_case') |
---|
| 130 | state = \ |
---|
| 131 | self.widget.txtName.validator().validate(self.widget.txtName.text(), |
---|
| 132 | 0)[0] |
---|
| 133 | self.assertEqual(state, QtGui.QValidator.Acceptable) |
---|
| 134 | |
---|
| 135 | def testOnApply(self): |
---|
| 136 | """ Test onApply """ |
---|
| 137 | |
---|
| 138 | self.widget.txtName.setText("new_model") |
---|
| 139 | self.widget.updateModels = MagicMock() |
---|
| 140 | |
---|
| 141 | # make sure the flag is set correctly |
---|
| 142 | self.widget.is_modified = True |
---|
| 143 | |
---|
| 144 | # Mock self.write_new_model_to_file |
---|
| 145 | self.widget.plugin_dir = tempfile.gettempdir() |
---|
| 146 | self.widget.plugin_filename = \ |
---|
| 147 | os.path.join(self.widget.plugin_dir, 'new_model.py') |
---|
| 148 | |
---|
| 149 | self.widget.write_new_mode_to_file = MagicMock() |
---|
| 150 | |
---|
| 151 | # invoke the tested method |
---|
| 152 | self.widget.onApply() |
---|
| 153 | |
---|
| 154 | self.assertTrue(self.widget.write_new_mode_to_file.called_once_with( |
---|
| 155 | os.path.join(self.widget.plugin_dir, 'new_model.py'), |
---|
| 156 | self.widget.cbModel1.currentText(), |
---|
| 157 | self.widget.cbModel2.currentText(), |
---|
| 158 | self.widget.cbOperator.currentText())) |
---|
| 159 | |
---|
| 160 | self.assertTrue(self.widget.updateModels.called_once()) |
---|
| 161 | |
---|
| 162 | def testWriteNewModelToFile(self): |
---|
| 163 | """ Test content of generated plugin""" |
---|
| 164 | |
---|
| 165 | dummy_file_path = os.path.join(tempfile.gettempdir(), |
---|
| 166 | "test_datafile.py") |
---|
| 167 | |
---|
| 168 | # prepare data to create file and check its content: names of 2 models, |
---|
| 169 | # operator and description (default or not) |
---|
| 170 | model1_name = self.widget.cbModel1.currentText() |
---|
| 171 | model2_name = self.widget.cbModel2.currentText() |
---|
| 172 | symbol_operator = self.widget.cbOperator.currentText() |
---|
| 173 | |
---|
| 174 | # default description |
---|
| 175 | desc_line = "{} {} {}".format(model1_name, |
---|
| 176 | symbol_operator, |
---|
| 177 | model2_name) |
---|
| 178 | |
---|
| 179 | self.widget.write_new_model_to_file(dummy_file_path, model1_name, |
---|
| 180 | model2_name, symbol_operator) |
---|
| 181 | # check content of dummy file path |
---|
| 182 | with open(dummy_file_path, 'r') as f_in: |
---|
| 183 | lines_from_generated_file = [line.strip() for line in f_in] |
---|
| 184 | |
---|
| 185 | # SUM_TEMPLATE with updated entries |
---|
| 186 | completed_template = ["from sasmodels.core import load_model_info", |
---|
| 187 | "from sasmodels.sasview_model import make_model_from_info", |
---|
| 188 | "model_info = load_model_info('{model1}{operator}{model2}')". |
---|
| 189 | format(model1=model1_name, |
---|
| 190 | operator=symbol_operator, |
---|
| 191 | model2=model2_name), |
---|
| 192 | "model_info.name = '{}'".format("test_datafile"), |
---|
| 193 | "model_info.description = '{}'".format(desc_line), |
---|
| 194 | "Model = make_model_from_info(model_info)"] |
---|
| 195 | |
---|
| 196 | for item in completed_template: |
---|
| 197 | self.assertIn(item, lines_from_generated_file) |
---|
| 198 | |
---|
| 199 | # check content with description entered by user |
---|
| 200 | self.widget.txtDescription.setText('New description for test ') |
---|
| 201 | |
---|
| 202 | new_desc_line = "model_info.description = '{}'".format('New description for test') |
---|
| 203 | |
---|
| 204 | # re-run function to test |
---|
| 205 | self.widget.write_new_model_to_file(dummy_file_path, model1_name, |
---|
| 206 | model2_name, symbol_operator) |
---|
| 207 | |
---|
| 208 | # check content of dummy file path |
---|
| 209 | with open(dummy_file_path, 'r') as f_in: |
---|
| 210 | lines_from_generated_file = [line.strip() for line in f_in] |
---|
| 211 | |
---|
| 212 | # update completed template |
---|
| 213 | completed_template[4] = new_desc_line |
---|
| 214 | |
---|
| 215 | # check content of generated file |
---|
| 216 | for item in completed_template: |
---|
| 217 | self.assertIn(item, lines_from_generated_file) |
---|
| 218 | |
---|
| 219 | def testOnOperatorChange(self): |
---|
| 220 | """ |
---|
| 221 | Test modification of displayed equation |
---|
| 222 | when selected operator is changed |
---|
| 223 | """ |
---|
| 224 | |
---|
| 225 | # check default |
---|
| 226 | self.assertIn(self.widget.cbOperator.currentText(), |
---|
| 227 | self.widget.lblEquation.text()) |
---|
| 228 | |
---|
| 229 | # change operator |
---|
| 230 | if self.widget.cbOperator.currentIndex() == 0: |
---|
| 231 | self.widget.cbOperator.setCurrentIndex(1) |
---|
| 232 | else: |
---|
| 233 | self.widget.cbOperator.setCurrentIndex(0) |
---|
| 234 | |
---|
| 235 | self.assertIn(self.widget.cbOperator.currentText(), |
---|
| 236 | self.widget.lblEquation.text()) |
---|
| 237 | |
---|
| 238 | def testOnHelp(self): |
---|
| 239 | """ Test the default help renderer """ |
---|
| 240 | |
---|
| 241 | webbrowser.open = MagicMock() |
---|
| 242 | |
---|
| 243 | # invoke the tested method |
---|
| 244 | self.widget.onHelp() |
---|
| 245 | |
---|
| 246 | # see that webbrowser open was attempted |
---|
| 247 | webbrowser.open.assert_called() |
---|
| 248 | |
---|
| 249 | def testOnNameCheck(self): |
---|
| 250 | """ Test onNameCheck """ |
---|
| 251 | |
---|
| 252 | # Enter plugin name already present in list of existing models |
---|
| 253 | self.widget.txtName.setText("cylinder") |
---|
| 254 | |
---|
| 255 | # Scenario 1 |
---|
| 256 | # overwrite not checked -> message box should appear |
---|
| 257 | # and good_name set to False, 'Apply' button disabled |
---|
| 258 | |
---|
| 259 | # mock QMessageBox |
---|
| 260 | QtWidgets.QMessageBox.critical = MagicMock() |
---|
| 261 | |
---|
| 262 | self.widget.chkOverwrite.setChecked(False) |
---|
| 263 | self.widget.txtName.editingFinished.emit() |
---|
| 264 | self.assertFalse(self.widget.good_name) |
---|
| 265 | self.assertFalse(self.widget.buttonBox.button( |
---|
| 266 | QtWidgets.QDialogButtonBox.Apply).isEnabled()) |
---|
| 267 | |
---|
| 268 | self.assertTrue(QtWidgets.QMessageBox.critical.called_once()) |
---|
| 269 | |
---|
| 270 | msg = "Plugin with specified name already exists.\n" |
---|
| 271 | msg += "Please specify different filename or allow file overwrite." |
---|
| 272 | |
---|
| 273 | self.assertIn('Plugin Error', QtWidgets.QMessageBox.critical.call_args[0][1]) |
---|
| 274 | self.assertIn(msg, QtWidgets.QMessageBox.critical.call_args[0][2]) |
---|
| 275 | |
---|
| 276 | # Scenario 2 |
---|
| 277 | # overwrite checked -> no message box displayed |
---|
| 278 | # and good_name set to True, Apply button enabled, output name created |
---|
| 279 | |
---|
| 280 | # mock QMessageBox |
---|
| 281 | QtWidgets.QMessageBox.critical = MagicMock() |
---|
| 282 | # create dummy plugin_dir for output file |
---|
| 283 | self.widget.plugin_dir = tempfile.gettempdir() |
---|
| 284 | |
---|
| 285 | self.widget.chkOverwrite.setChecked(True) |
---|
| 286 | self.widget.txtName.editingFinished.emit() |
---|
| 287 | self.assertTrue(self.widget.good_name) |
---|
| 288 | self.assertTrue(self.widget.buttonBox.button( |
---|
| 289 | QtWidgets.QDialogButtonBox.Apply).isEnabled()) |
---|
| 290 | |
---|
| 291 | self.assertFalse(QtWidgets.QMessageBox.critical.called) |
---|
| 292 | |
---|
| 293 | self.assertIn('cylinder.py', self.widget.plugin_filename) |
---|
| 294 | |
---|
| 295 | # Scenario 3 Enter valid new plugin name -> filename created and Apply |
---|
| 296 | # button enabled |
---|
| 297 | # forbidding overwriting should not change anything |
---|
| 298 | # since it's a new name |
---|
| 299 | self.widget.txtName.setText(" cylinder0 ") |
---|
| 300 | self.widget.chkOverwrite.setChecked(False) |
---|
| 301 | self.widget.txtName.editingFinished.emit() |
---|
| 302 | self.assertIn("cylinder0.py", self.widget.plugin_filename) |
---|
| 303 | self.assertTrue(self.widget.buttonBox.button( |
---|
| 304 | QtWidgets.QDialogButtonBox.Apply).isEnabled()) |
---|
| 305 | |
---|
| 306 | @patch.object(AddMultEditor, 'readModels') |
---|
| 307 | def testOnUpdateModels(self, mock_new_list_models): |
---|
| 308 | """ Test onUpdateModels """ |
---|
| 309 | |
---|
| 310 | ini_count_models = self.widget.cbModel1.count() |
---|
| 311 | |
---|
| 312 | mock_new_list_models.return_value = ['cylinder', 'rpa', |
---|
| 313 | 'core_shell_cylinder', 'sphere', |
---|
| 314 | 'cylinder0'] |
---|
| 315 | |
---|
| 316 | self.widget.updateModels() |
---|
| 317 | # check that the number of models in the comboboxes |
---|
| 318 | # has been incremented by 1 |
---|
| 319 | self.assertEqual(self.widget.cbModel1.count(), ini_count_models + 1) |
---|
| 320 | self.assertEqual(self.widget.cbModel2.count(), ini_count_models + 1) |
---|
| 321 | |
---|
| 322 | # check that the new model is in the list |
---|
| 323 | combobox = self.widget.cbModel1 |
---|
| 324 | self.assertIn('cylinder0', [combobox.itemText(indx) |
---|
| 325 | for indx in range(combobox.count())]) |
---|
| 326 | |
---|
| 327 | def testOnReadModels(self): |
---|
| 328 | """ The output of ReadModels is the return value of MagicMock defined |
---|
| 329 | in the SetUp of these tests. """ |
---|
| 330 | |
---|
| 331 | self.assertEqual(self.widget.list_models, ['cylinder', 'rpa', |
---|
| 332 | 'core_shell_cylinder', |
---|
| 333 | 'sphere']) |
---|
| 334 | |
---|
| 335 | def testCheckModel(self): |
---|
| 336 | """ Test CheckModel""" |
---|
| 337 | |
---|
| 338 | # TODO first: solve problem with empty 'test' |
---|
| 339 | pass |
---|