source: sasview/src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py @ 186d678

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 186d678 was 186d678, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Merge branch 'ESS_GUI' into ESS_GUI_project_save

  • Property mode set to 100644
File size: 59.6 KB
Line 
1import sys
2import unittest
3import time
4import logging
5
6from PyQt5 import QtGui
7from PyQt5 import QtWidgets
8from PyQt5 import QtTest
9from PyQt5 import QtCore
10from unittest.mock import MagicMock
11from twisted.internet import threads
12
13# set up import paths
14import sas.qtgui.path_prepare
15
16# Local
17from sas.qtgui.Utilities.GuiUtils import *
18from sas.qtgui.Perspectives.Fitting.FittingWidget import *
19from sas.qtgui.Perspectives.Fitting.Constraint import Constraint
20import sas.qtgui.Utilities.LocalConfig
21from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy
22from sas.qtgui.Perspectives.Fitting.ModelThread import Calc1D
23from sas.qtgui.Perspectives.Fitting.ModelThread import Calc2D
24
25from sas.qtgui.Plotting.PlotterData import Data1D
26from sas.qtgui.Plotting.PlotterData import Data2D
27
28if not QtWidgets.QApplication.instance():
29    app = QtWidgets.QApplication(sys.argv)
30
31class dummy_manager(object):
32    HELP_DIRECTORY_LOCATION = "html"
33    communicate = Communicate()
34
35class FittingWidgetTest(unittest.TestCase):
36    """Test the fitting widget GUI"""
37
38    def setUp(self):
39        """Create the GUI"""
40        self.widget = FittingWidget(dummy_manager())
41
42    def tearDown(self):
43        """Destroy the GUI"""
44        self.widget.close()
45        del self.widget
46
47    def testDefaults(self):
48        """Test the GUI in its default state"""
49        self.assertIsInstance(self.widget, QtWidgets.QWidget)
50        self.assertEqual(self.widget.windowTitle(), "Fitting")
51        self.assertEqual(self.widget.sizePolicy().Policy(), QtWidgets.QSizePolicy.Fixed)
52        self.assertIsInstance(self.widget.lstParams.model(), QtGui.QStandardItemModel)
53        self.assertIsInstance(self.widget.lstPoly.model(), QtGui.QStandardItemModel)
54        self.assertIsInstance(self.widget.lstMagnetic.model(), QtGui.QStandardItemModel)
55        self.assertFalse(self.widget.cbModel.isEnabled())
56        self.assertFalse(self.widget.cbStructureFactor.isEnabled())
57        self.assertFalse(self.widget.cmdFit.isEnabled())
58        self.assertTrue(self.widget.acceptsData())
59        self.assertFalse(self.widget.data_is_loaded)
60
61    def testSelectCategoryDefault(self):
62        """
63        Test if model categories have been loaded properly
64        """
65        fittingWindow =  self.widget
66
67        #Test loading from json categories
68        category_list = list(fittingWindow.master_category_dict.keys())
69
70        for category in category_list:
71            self.assertNotEqual(fittingWindow.cbCategory.findText(category),-1)
72
73        #Test what is current text in the combobox
74        self.assertEqual(fittingWindow.cbCategory.currentText(), CATEGORY_DEFAULT)
75
76    def testWidgetWithData(self):
77        """
78        Test the instantiation of the widget with initial data
79        """
80        data = Data1D(x=[1,2], y=[1,2])
81        GuiUtils.dataFromItem = MagicMock(return_value=data)
82        item = QtGui.QStandardItem("test")
83
84        widget_with_data = FittingWidget(dummy_manager(), data=item, tab_id=3)
85
86        self.assertEqual(widget_with_data.data, data)
87        self.assertTrue(widget_with_data.data_is_loaded)
88        # self.assertTrue(widget_with_data.cmdFit.isEnabled())
89        self.assertFalse(widget_with_data.acceptsData())
90
91    def testSelectPolydispersity(self):
92        """
93        Test if models have been loaded properly
94        """
95        fittingWindow =  self.widget
96
97        self.assertIsInstance(fittingWindow.lstPoly.itemDelegate(), QtWidgets.QStyledItemDelegate)
98        #Test loading from json categories
99        fittingWindow.SASModelToQModel("cylinder")
100        pd_index = fittingWindow.lstPoly.model().index(0,0)
101        self.assertEqual(str(pd_index.data()), "Distribution of radius")
102        pd_index = fittingWindow.lstPoly.model().index(1,0)
103        self.assertEqual(str(pd_index.data()), "Distribution of length")
104
105        # test the delegate a bit
106        delegate = fittingWindow.lstPoly.itemDelegate()
107        self.assertEqual(len(delegate.POLYDISPERSE_FUNCTIONS), 5)
108        self.assertEqual(delegate.editableParameters(), [1, 2, 3, 4, 5])
109        self.assertEqual(delegate.poly_function, 6)
110        self.assertIsInstance(delegate.combo_updated, QtCore.pyqtBoundSignal)
111
112    def testSelectMagnetism(self):
113        """
114        Test if models have been loaded properly
115        """
116        fittingWindow =  self.widget
117
118        self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), QtWidgets.QStyledItemDelegate)
119        #Test loading from json categories
120        fittingWindow.SASModelToQModel("cylinder")
121        mag_index = fittingWindow.lstMagnetic.model().index(0,0)
122        self.assertEqual(mag_index.data(), "up_frac_i")
123        mag_index = fittingWindow.lstMagnetic.model().index(1,0)
124        self.assertEqual(mag_index.data(), "up_frac_f")
125        mag_index = fittingWindow.lstMagnetic.model().index(2,0)
126        self.assertEqual(mag_index.data(), "up_angle")
127        mag_index = fittingWindow.lstMagnetic.model().index(3,0)
128        self.assertEqual(mag_index.data(), "sld_M0")
129        mag_index = fittingWindow.lstMagnetic.model().index(4,0)
130        self.assertEqual(mag_index.data(), "sld_mtheta")
131        mag_index = fittingWindow.lstMagnetic.model().index(5,0)
132        self.assertEqual(mag_index.data(), "sld_mphi")
133        mag_index = fittingWindow.lstMagnetic.model().index(6,0)
134        self.assertEqual(mag_index.data(), "sld_solvent_M0")
135        mag_index = fittingWindow.lstMagnetic.model().index(7,0)
136        self.assertEqual(mag_index.data(), "sld_solvent_mtheta")
137        mag_index = fittingWindow.lstMagnetic.model().index(8,0)
138        self.assertEqual(mag_index.data(), "sld_solvent_mphi")
139
140        # test the delegate a bit
141        delegate = fittingWindow.lstMagnetic.itemDelegate()
142        self.assertEqual(delegate.editableParameters(), [1, 2, 3])
143
144    def testSelectStructureFactor(self):
145        """
146        Test if structure factors have been loaded properly
147        """
148        fittingWindow =  self.widget
149
150        #Test for existence in combobox
151        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("stickyhardsphere"),-1)
152        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("hayter_msa"),-1)
153        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("squarewell"),-1)
154        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("hardsphere"),-1)
155
156        #Test what is current text in the combobox
157        self.assertTrue(fittingWindow.cbCategory.currentText(), "None")
158
159    def testSignals(self):
160        """
161        Test the widget emitted signals
162        """
163        pass
164
165    def testSelectCategory(self):
166        """
167        Assure proper behaviour on changing category
168        """
169        self.widget.show()
170        self.assertEqual(self.widget._previous_category_index, 0)
171        # confirm the model combo contains no models
172        self.assertEqual(self.widget.cbModel.count(), 0)
173
174        # invoke the method by changing the index
175        category_index = self.widget.cbCategory.findText("Shape Independent")
176        self.widget.cbCategory.setCurrentIndex(category_index)
177        model_index = self.widget.cbModel.findText("be_polyelectrolyte")
178        self.widget.cbModel.setCurrentIndex(model_index)
179
180        # test the model combo content
181        self.assertEqual(self.widget.cbModel.count(), 30)
182
183        # Try to change back to default
184        self.widget.cbCategory.setCurrentIndex(0)
185
186        # Observe no such luck
187        self.assertEqual(self.widget.cbCategory.currentIndex(), 6)
188        self.assertEqual(self.widget.cbModel.count(), 29)
189
190        # Set the structure factor
191        structure_index=self.widget.cbCategory.findText(CATEGORY_STRUCTURE)
192        self.widget.cbCategory.setCurrentIndex(structure_index)
193        # check the enablement of controls
194        self.assertFalse(self.widget.cbModel.isEnabled())
195        self.assertTrue(self.widget.cbStructureFactor.isEnabled())
196
197    def testSelectModel(self):
198        """
199        Assure proper behaviour on changing model
200        """
201        self.widget.show()
202        # Change the category index so we have some models
203        category_index = self.widget.cbCategory.findText("Shape Independent")
204        self.widget.cbCategory.setCurrentIndex(category_index)
205        model_index = self.widget.cbModel.findText("be_polyelectrolyte")
206        self.widget.cbModel.setCurrentIndex(model_index)
207
208        # check the enablement of controls
209        self.assertTrue(self.widget.cbModel.isEnabled())
210        self.assertFalse(self.widget.cbStructureFactor.isEnabled())
211
212        # set up the model update spy
213        # spy = QtSignalSpy(self.widget._model_model, self.widget._model_model.itemChanged)
214
215        # mock the tested methods
216        self.widget.SASModelToQModel = MagicMock()
217        self.widget.createDefaultDataset = MagicMock()
218        self.widget.calculateQGridForModel = MagicMock()
219        #
220        # Now change the model
221<<<<<<< HEAD
222        self.widget.cbModel.setCurrentIndex(4)
223=======
224        self.widget.cbModel.setCurrentIndex(2)
225>>>>>>> ESS_GUI
226        self.assertEqual(self.widget.cbModel.currentText(),'dab')
227
228        # No data sent -> no index set, only createDefaultDataset called
229        self.assertTrue(self.widget.createDefaultDataset.called)
230        self.assertTrue(self.widget.SASModelToQModel.called)
231        self.assertFalse(self.widget.calculateQGridForModel.called)
232
233        # Let's tell the widget that data has been loaded
234        self.widget.data_is_loaded = True
235        # Reset the sasmodel index
236        self.widget.cbModel.setCurrentIndex(2)
237        self.assertEqual(self.widget.cbModel.currentText(),'broad_peak')
238
239        # Observe calculateQGridForModel called
240        self.assertTrue(self.widget.calculateQGridForModel.called)
241
242    def testSelectFactor(self):
243        """
244        Assure proper behaviour on changing structure factor
245        """
246        self.widget.show()
247        # Change the category index so we have some models
248        category_index = self.widget.cbCategory.findText("Shape Independent")
249        self.widget.cbCategory.setCurrentIndex(category_index)
250        # Change the model to one that supports structure factors
251        model_index = self.widget.cbModel.findText('fractal_core_shell')
252        self.widget.cbModel.setCurrentIndex(model_index)
253
254        # Check that the factor combo is active and the default is chosen
255        self.assertTrue(self.widget.cbStructureFactor.isEnabled())
256        self.assertEqual(self.widget.cbStructureFactor.currentText(), STRUCTURE_DEFAULT)
257
258        # We have this many rows in the model
259        rowcount = self.widget._model_model.rowCount()
260        #self.assertEqual(self.widget._model_model.rowCount(), 8)
261
262        # Change structure factor to something more exciting
263        structure_index = self.widget.cbStructureFactor.findText('squarewell')
264        self.widget.cbStructureFactor.setCurrentIndex(structure_index)
265
266        # We have 3 more param rows now (radius_effective is removed), and a new heading
267        self.assertEqual(self.widget._model_model.rowCount(), rowcount+4)
268
269        # Switch models
270        self.widget.cbModel.setCurrentIndex(0)
271
272        # Observe factor doesn't reset to None
273        self.assertEqual(self.widget.cbStructureFactor.currentText(), 'squarewell')
274
275        # Switch category to structure factor
276        structure_index=self.widget.cbCategory.findText(CATEGORY_STRUCTURE)
277        self.widget.cbCategory.setCurrentIndex(structure_index)
278        # Observe the correct enablement
279        self.assertTrue(self.widget.cbStructureFactor.isEnabled())
280        self.assertFalse(self.widget.cbModel.isEnabled())
281        self.assertEqual(self.widget._model_model.rowCount(), 0)
282
283        # Choose the last factor
284        last_index = self.widget.cbStructureFactor.count()
285        self.widget.cbStructureFactor.setCurrentIndex(last_index-1)
286        # Do we have all the rows (incl. radius_effective & heading row)?
287        self.assertEqual(self.widget._model_model.rowCount(), 5)
288
289        # Are the command buttons properly enabled?
290        self.assertTrue(self.widget.cmdPlot.isEnabled())
291        self.assertFalse(self.widget.cmdFit.isEnabled())
292
293    def testReadCategoryInfo(self):
294        """
295        Check the category file reader
296        """
297        # Tested in default checks
298        pass
299
300    def testUpdateParamsFromModel(self):
301        """
302        Checks the sasmodel parameter update from QModel items
303        """
304        # Tested in default checks
305        pass
306
307    def testCreateTheoryIndex(self):
308        """
309        Test the data->QIndex conversion
310        """
311        # set up the model update spy
312        spy = QtSignalSpy(self.widget._model_model, self.widget.communicate.updateTheoryFromPerspectiveSignal)
313
314        self.widget.show()
315        # Change the category index so we have some models
316        self.widget.cbCategory.setCurrentIndex(1)
317
318        # Create the index
319        self.widget.createTheoryIndex(Data1D(x=[1,2], y=[1,2]))
320
321        # Make sure the signal has been emitted
322        self.assertEqual(spy.count(), 1)
323
324        # Check the argument type
325        self.assertIsInstance(spy.called()[0]['args'][0], QtGui.QStandardItem)
326
327    def testCalculateQGridForModel(self):
328        """
329        Check that the fitting 1D data object is ready
330        """
331
332        if LocalConfig.USING_TWISTED:
333            # Mock the thread creation
334            threads.deferToThread = MagicMock()
335            # Model for theory
336            self.widget.SASModelToQModel("cylinder")
337            # Call the tested method
338            self.widget.calculateQGridForModel()
339            time.sleep(1)
340            # Test the mock
341            self.assertTrue(threads.deferToThread.called)
342            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, "compute")
343        else:
344            Calc2D.queue = MagicMock()
345            # Model for theory
346            self.widget.SASModelToQModel("cylinder")
347            # Call the tested method
348            self.widget.calculateQGridForModel()
349            time.sleep(1)
350            # Test the mock
351            self.assertTrue(Calc2D.queue.called)
352
353    def testCalculateResiduals(self):
354        """
355        Check that the residuals are calculated and plots updated
356        """
357        test_data = Data1D(x=[1,2], y=[1,2])
358
359        # Model for theory
360        self.widget.SASModelToQModel("cylinder")
361        # Invoke the tested method
362        self.widget.calculateResiduals(test_data)
363        # Check the Chi2 value - should be undetermined
364        self.assertEqual(self.widget.lblChi2Value.text(), '---')
365
366        # Force same data into logic
367        self.widget.logic.data = test_data
368        self.widget.calculateResiduals(test_data)
369        # Now, the difference is 0, as data is the same
370        self.assertEqual(self.widget.lblChi2Value.text(), '0')
371
372        # Change data
373        test_data_2 = Data1D(x=[1,2], y=[2.1,3.49])
374        self.widget.logic.data = test_data_2
375        self.widget.calculateResiduals(test_data)
376        # Now, the difference is non-zero
377        self.assertEqual(float(self.widget.lblChi2Value.text()), 1.7151)
378
379    def testSetPolyModel(self):
380        """
381        Test the polydispersity model setup
382        """
383        self.widget.show()
384        # Change the category index so we have a model with no poly
385        category_index = self.widget.cbCategory.findText("Shape Independent")
386        self.widget.cbCategory.setCurrentIndex(category_index)
387        model_index = self.widget.cbModel.findText("be_polyelectrolyte")
388        self.widget.cbModel.setCurrentIndex(model_index)
389
390        # Check the poly model
391        self.assertEqual(self.widget._poly_model.rowCount(), 0)
392        self.assertEqual(self.widget._poly_model.columnCount(), 0)
393
394        # Change the category index so we have a model available
395        self.widget.cbCategory.setCurrentIndex(2)
396        self.widget.cbModel.setCurrentIndex(1)
397
398        # Check the poly model
399        self.assertEqual(self.widget._poly_model.rowCount(), 4)
400        self.assertEqual(self.widget._poly_model.columnCount(), 8)
401
402        # Test the header
403        self.assertEqual(self.widget.lstPoly.horizontalHeader().count(), 8)
404        self.assertFalse(self.widget.lstPoly.horizontalHeader().stretchLastSection())
405
406        # Test tooltips
407        self.assertEqual(len(self.widget._poly_model.header_tooltips), 8)
408
409        header_tooltips = ['Select parameter for fitting',
410                            'Enter polydispersity ratio (Std deviation/mean).\n'+
411                            'For angles this can be either std deviation or half width (for uniform distributions) in degrees',
412                            'Enter minimum value for parameter',
413                            'Enter maximum value for parameter',
414                            'Enter number of points for parameter',
415                            'Enter number of sigmas parameter',
416                            'Select distribution function',
417                            'Select filename with user-definable distribution']
418        for column, tooltip in enumerate(header_tooltips):
419             self.assertEqual(self.widget._poly_model.headerData( column,
420                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
421                         header_tooltips[column])
422
423        # Test presence of comboboxes in last column
424        for row in range(self.widget._poly_model.rowCount()):
425            func_index = self.widget._poly_model.index(row, 6)
426            self.assertTrue(isinstance(self.widget.lstPoly.indexWidget(func_index), QtWidgets.QComboBox))
427            self.assertIn('Distribution of', self.widget._poly_model.item(row, 0).text())
428        #self.widget.close()
429
430    def testPolyModelChange(self):
431        """
432        Polydispersity model changed - test all possible scenarios
433        """
434        self.widget.show()
435        # Change the category index so we have a model with polydisp
436        category_index = self.widget.cbCategory.findText("Cylinder")
437        self.widget.cbCategory.setCurrentIndex(category_index)
438        model_index = self.widget.cbModel.findText("barbell")
439        self.widget.cbModel.setCurrentIndex(model_index)
440
441        # click on a poly parameter checkbox
442        index = self.widget._poly_model.index(0,0)
443
444        # Set the checbox
445        self.widget._poly_model.item(0,0).setCheckState(2)
446        # Assure the parameter is added
447        self.assertEqual(self.widget.poly_params_to_fit, ['radius_bell.width'])
448
449        # Add another parameter
450        self.widget._poly_model.item(2,0).setCheckState(2)
451        # Assure the parameters are added
452        self.assertEqual(self.widget.poly_params_to_fit, ['radius_bell.width', 'length.width'])
453
454        # Change the min/max values
455        self.assertEqual(self.widget.kernel_module.details['radius_bell'][1], 0.0)
456        self.widget._poly_model.item(0,2).setText("1.0")
457        self.assertEqual(self.widget.kernel_module.details['radius_bell'][1], 1.0)
458
459        #self.widget.show()
460        #QtWidgets.QApplication.exec_()
461
462        # Change the number of points
463        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 35)
464        self.widget._poly_model.item(0,4).setText("22")
465        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 22)
466        # try something stupid
467        self.widget._poly_model.item(0,4).setText("butt")
468        # see that this didn't annoy the control at all
469        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 22)
470
471        # Change the number of sigmas
472        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 3)
473        self.widget._poly_model.item(0,5).setText("222")
474        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 222)
475        # try something stupid again
476        self.widget._poly_model.item(0,4).setText("beer")
477        # no efect
478        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 222)
479
480    def testOnPolyComboIndexChange(self):
481        """
482        Test the slot method for polydisp. combo box index change
483        """
484        self.widget.show()
485        # Change the category index so we have a model with polydisp
486        category_index = self.widget.cbCategory.findText("Cylinder")
487        self.widget.cbCategory.setCurrentIndex(category_index)
488        model_index = self.widget.cbModel.findText("barbell")
489        self.widget.cbModel.setCurrentIndex(model_index)
490
491        # call method with default settings
492        self.widget.onPolyComboIndexChange('gaussian', 0)
493        # check values
494        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
495        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 3)
496        # Change the index
497        self.widget.onPolyComboIndexChange('rectangle', 0)
498        # check values
499        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 35)
500        self.assertAlmostEqual(self.widget.poly_params['radius_bell.nsigmas'], 1.73205, 5)
501        # Change the index
502        self.widget.onPolyComboIndexChange('lognormal', 0)
503        # check values
504        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 80)
505        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 8)
506        # Change the index
507        self.widget.onPolyComboIndexChange('schulz', 0)
508        # check values
509        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 80)
510        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 8)
511
512        # mock up file load
513        self.widget.loadPolydispArray = MagicMock()
514        # Change to 'array'
515        self.widget.onPolyComboIndexChange('array', 0)
516        # See the mock fire
517        self.assertTrue(self.widget.loadPolydispArray.called)
518
519    def testLoadPolydispArray(self):
520        """
521        Test opening of the load file dialog for 'array' polydisp. function
522        """
523
524        # open a non-existent file
525        filename = os.path.join("UnitTesting", "testdata_noexist.txt")
526        with self.assertRaises(OSError, msg="testdata_noexist.txt should be a non-existent file"):
527            os.stat(filename)
528        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
529        self.widget.show()
530        # Change the category index so we have a model with polydisp
531        category_index = self.widget.cbCategory.findText("Cylinder")
532        self.widget.cbCategory.setCurrentIndex(category_index)
533        model_index = self.widget.cbModel.findText("barbell")
534        self.widget.cbModel.setCurrentIndex(model_index)
535
536        self.widget.onPolyComboIndexChange('array', 0)
537        # check values - unchanged since the file doesn't exist
538        self.assertTrue(self.widget._poly_model.item(0, 1).isEnabled())
539        with self.assertRaises(AttributeError):
540            self.widget.disp_model()
541
542        # good file
543        # TODO: this depends on the working directory being src/sas/qtgui,
544        # TODO: which isn't convenient if you want to run this test suite
545        # TODO: individually
546        filename = os.path.join("UnitTesting", "testdata.txt")
547        try:
548            os.stat(filename)
549        except OSError:
550            self.assertTrue(False, "testdata.txt does not exist")
551        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
552
553        self.widget.onPolyComboIndexChange('array', 0)
554        # check values - disabled control, present weights
555        self.assertFalse(self.widget._poly_model.item(0, 1).isEnabled())
556        self.assertEqual(self.widget.disp_model.weights[0], 2.83954)
557        self.assertEqual(len(self.widget.disp_model.weights), 19)
558        self.assertEqual(len(self.widget.disp_model.values), 19)
559        self.assertEqual(self.widget.disp_model.values[0], 0.0)
560        self.assertEqual(self.widget.disp_model.values[18], 3.67347)
561
562    def testSetMagneticModel(self):
563        """
564        Test the magnetic model setup
565        """
566        self.widget.show()
567        # Change the category index so we have a model available
568        category_index = self.widget.cbCategory.findText("Sphere")
569        self.widget.cbCategory.setCurrentIndex(category_index)
570        model_index = self.widget.cbModel.findText("adsorbed_layer")
571        self.widget.cbModel.setCurrentIndex(model_index)
572
573        # Check the magnetic model
574        self.assertEqual(self.widget._magnet_model.rowCount(), 9)
575        self.assertEqual(self.widget._magnet_model.columnCount(), 5)
576
577        # Test the header
578        self.assertEqual(self.widget.lstMagnetic.horizontalHeader().count(), 5)
579        self.assertFalse(self.widget.lstMagnetic.horizontalHeader().stretchLastSection())
580
581        #Test tooltips
582        self.assertEqual(len(self.widget._magnet_model.header_tooltips), 5)
583
584        header_tooltips = ['Select parameter for fitting',
585                           'Enter parameter value',
586                           'Enter minimum value for parameter',
587                           'Enter maximum value for parameter',
588                           'Unit of the parameter']
589        for column, tooltip in enumerate(header_tooltips):
590             self.assertEqual(self.widget._magnet_model.headerData(column,
591                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
592                         header_tooltips[column])
593
594        # Test rows
595        for row in range(self.widget._magnet_model.rowCount()):
596            func_index = self.widget._magnet_model.index(row, 0)
597            self.assertIn('_', self.widget._magnet_model.item(row, 0).text())
598
599
600    def testAddExtraShells(self):
601        """
602        Test how the extra shells are presented
603        """
604        pass
605
606    def testModifyShellsInList(self):
607        """
608        Test the additional rows added by modifying the shells combobox
609        """
610        self.widget.show()
611        # Change the model to multi shell
612        category_index = self.widget.cbCategory.findText("Sphere")
613        self.widget.cbCategory.setCurrentIndex(category_index)
614        model_index = self.widget.cbModel.findText("core_multi_shell")
615        self.widget.cbModel.setCurrentIndex(model_index)
616
617        # Assure we have the combobox available
618        cbox_row = self.widget._n_shells_row
619        func_index = self.widget._model_model.index(cbox_row, 1)
620        self.assertIsInstance(self.widget.lstParams.indexWidget(func_index), QtWidgets.QComboBox)
621
622        # get number of rows before changing shell count
623        last_row = self.widget._model_model.rowCount()
624
625        # Change the combo box index
626        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(3)
627
628        # Check that the number of rows increased
629        # (note that n == 1 by default in core_multi_shell so this increases index by 2)
630        more_rows = self.widget._model_model.rowCount() - last_row
631        self.assertEqual(more_rows, 4) # 4 new rows: 2 params per index
632
633        # Set to 0
634        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(0)
635        self.assertEqual(self.widget._model_model.rowCount(), last_row - 2)
636
637    def testPlotTheory(self):
638        """
639        See that theory item can produce a chart
640        """
641        # By default, the compute/plot button is disabled
642        self.assertFalse(self.widget.cmdPlot.isEnabled())
643        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
644
645        # Assign a model
646        self.widget.show()
647        # Change the category index so we have a model available
648        category_index = self.widget.cbCategory.findText("Sphere")
649        self.widget.cbCategory.setCurrentIndex(category_index)
650        model_index = self.widget.cbModel.findText("adsorbed_layer")
651        self.widget.cbModel.setCurrentIndex(model_index)
652
653        # Check the enablement/text
654        self.assertTrue(self.widget.cmdPlot.isEnabled())
655        self.assertEqual(self.widget.cmdPlot.text(), 'Calculate')
656
657        # Spying on plot update signal
658        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
659
660        # Press Calculate
661        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
662
663        # Observe cmdPlot caption change
664        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
665
666        # Make sure the signal has NOT been emitted
667        self.assertEqual(spy.count(), 0)
668
669        # Click again
670        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
671
672        # This time, we got the update signal
673        self.assertEqual(spy.count(), 0)
674
675    def notestPlotData(self):
676        """
677        See that data item can produce a chart
678        """
679        # By default, the compute/plot button is disabled
680        self.assertFalse(self.widget.cmdPlot.isEnabled())
681        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
682
683        # Set data
684        test_data = Data1D(x=[1,2], y=[1,2])
685        item = QtGui.QStandardItem()
686        updateModelItem(item, test_data, "test")
687        # Force same data into logic
688        self.widget.data = item
689
690        # Change the category index so we have a model available
691        category_index = self.widget.cbCategory.findText("Sphere")
692        self.widget.cbCategory.setCurrentIndex(category_index)
693
694        # Check the enablement/text
695        self.assertTrue(self.widget.cmdPlot.isEnabled())
696        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
697
698        # Spying on plot update signal
699        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
700
701        # Press Calculate
702        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
703
704        # Observe cmdPlot caption did not change
705        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
706
707        # Make sure the signal has been emitted == new plot
708        self.assertEqual(spy.count(), 1)
709
710    def testOnEmptyFit(self):
711        """
712        Test a 1D/2D fit with no parameters
713        """
714        # Set data
715        test_data = Data1D(x=[1,2], y=[1,2])
716        item = QtGui.QStandardItem()
717        updateModelItem(item, test_data, "test")
718        # Force same data into logic
719        self.widget.data = item
720
721        category_index = self.widget.cbCategory.findText("Sphere")
722        self.widget.cbCategory.setCurrentIndex(category_index)
723
724        # Test no fitting params
725        self.widget.main_params_to_fit = []
726
727        logging.error = MagicMock()
728
729        self.widget.onFit()
730        self.assertTrue(logging.error.called_with('no fitting parameters'))
731        self.widget.close()
732
733    def testOnEmptyFit2(self):
734        test_data = Data2D(image=[1.0, 2.0, 3.0],
735                           err_image=[0.01, 0.02, 0.03],
736                           qx_data=[0.1, 0.2, 0.3],
737                           qy_data=[0.1, 0.2, 0.3],
738                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
739                           mask=[True, True, True])
740
741        # Force same data into logic
742        item = QtGui.QStandardItem()
743        updateModelItem(item, test_data, "test")
744
745        # Force same data into logic
746        self.widget.data = item
747        category_index = self.widget.cbCategory.findText("Sphere")
748        self.widget.cbCategory.setCurrentIndex(category_index)
749
750        self.widget.show()
751
752        # Test no fitting params
753        self.widget.main_params_to_fit = []
754
755        logging.error = MagicMock()
756
757        self.widget.onFit()
758        self.assertTrue(logging.error.called_once())
759        self.assertTrue(logging.error.called_with('no fitting parameters'))
760        self.widget.close()
761
762    def notestOnFit1D(self):
763        """
764        Test the threaded fitting call
765        """
766        # Set data
767        test_data = Data1D(x=[1,2], y=[1,2])
768        item = QtGui.QStandardItem()
769        updateModelItem(item, test_data, "test")
770        # Force same data into logic
771        self.widget.data = item
772        category_index = self.widget.cbCategory.findText("Sphere")
773        self.widget.cbCategory.setCurrentIndex(category_index)
774
775        self.widget.show()
776
777        # Assing fitting params
778        self.widget.main_params_to_fit = ['scale']
779
780        # Spying on status update signal
781        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
782
783        with threads.deferToThread as MagicMock:
784            self.widget.onFit()
785            # thread called
786            self.assertTrue(threads.deferToThread.called)
787            # thread method is 'compute'
788            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
789
790            # the fit button changed caption and got disabled
791            # could fail if machine fast enough to finish
792            #self.assertEqual(self.widget.cmdFit.text(), 'Stop fit')
793            #self.assertFalse(self.widget.cmdFit.isEnabled())
794
795            # Signal pushed up
796            self.assertEqual(update_spy.count(), 1)
797
798        self.widget.close()
799
800    def notestOnFit2D(self):
801        """
802        Test the threaded fitting call
803        """
804        # Set data
805        test_data = Data2D(image=[1.0, 2.0, 3.0],
806                           err_image=[0.01, 0.02, 0.03],
807                           qx_data=[0.1, 0.2, 0.3],
808                           qy_data=[0.1, 0.2, 0.3],
809                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
810                           mask=[True, True, True])
811
812        # Force same data into logic
813        item = QtGui.QStandardItem()
814        updateModelItem(item, test_data, "test")
815        # Force same data into logic
816        self.widget.data = item
817        category_index = self.widget.cbCategory.findText("Sphere")
818        self.widget.cbCategory.setCurrentIndex(category_index)
819
820        self.widget.show()
821
822        # Assing fitting params
823        self.widget.main_params_to_fit = ['scale']
824
825        # Spying on status update signal
826        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
827
828        with threads.deferToThread as MagicMock:
829            self.widget.onFit()
830            # thread called
831            self.assertTrue(threads.deferToThread.called)
832            # thread method is 'compute'
833            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
834
835            # the fit button changed caption and got disabled
836            #self.assertEqual(self.widget.cmdFit.text(), 'Stop fit')
837            #self.assertFalse(self.widget.cmdFit.isEnabled())
838
839            # Signal pushed up
840            self.assertEqual(update_spy.count(), 1)
841
842    def testOnHelp(self):
843        """
844        Test various help pages shown in this widget
845        """
846        #Mock the webbrowser.open method
847        self.widget.parent.showHelp = MagicMock()
848        #webbrowser.open = MagicMock()
849
850        # Invoke the action on default tab
851        self.widget.onHelp()
852        # Check if show() got called
853        self.assertTrue(self.widget.parent.showHelp.called)
854        # Assure the filename is correct
855        self.assertIn("fitting_help.html", self.widget.parent.showHelp.call_args[0][0])
856
857        # Change the tab to options
858        self.widget.tabFitting.setCurrentIndex(1)
859        self.widget.onHelp()
860        # Check if show() got called
861        self.assertEqual(self.widget.parent.showHelp.call_count, 2)
862        # Assure the filename is correct
863        self.assertIn("residuals_help.html", self.widget.parent.showHelp.call_args[0][0])
864
865        # Change the tab to smearing
866        self.widget.tabFitting.setCurrentIndex(2)
867        self.widget.onHelp()
868        # Check if show() got called
869        self.assertEqual(self.widget.parent.showHelp.call_count, 3)
870        # Assure the filename is correct
871        self.assertIn("resolution.html", self.widget.parent.showHelp.call_args[0][0])
872
873        # Change the tab to poly
874        self.widget.tabFitting.setCurrentIndex(3)
875        self.widget.onHelp()
876        # Check if show() got called
877        self.assertEqual(self.widget.parent.showHelp.call_count, 4)
878        # Assure the filename is correct
879        self.assertIn("polydispersity.html", self.widget.parent.showHelp.call_args[0][0])
880
881        # Change the tab to magnetism
882        self.widget.tabFitting.setCurrentIndex(4)
883        self.widget.onHelp()
884        # Check if show() got called
885        self.assertEqual(self.widget.parent.showHelp.call_count, 5)
886        # Assure the filename is correct
887        self.assertIn("magnetism.html", self.widget.parent.showHelp.call_args[0][0])
888
889    def testReadFitPage(self):
890        """
891        Read in the fitpage object and restore state
892        """
893        # Set data
894        test_data = Data1D(x=[1,2], y=[1,2])
895        item = QtGui.QStandardItem()
896        updateModelItem(item, test_data, "test")
897        # Force same data into logic
898        self.widget.data = item
899
900        # Force same data into logic
901        category_index = self.widget.cbCategory.findText('Sphere')
902
903        self.widget.cbCategory.setCurrentIndex(category_index)
904        self.widget.main_params_to_fit = ['scale']
905        # Invoke the tested method
906        fp = self.widget.currentState()
907
908        # Prepare modified fit page
909        fp.current_model = 'onion'
910        fp.is_polydisperse = True
911
912        # Read in modified state
913        self.widget.readFitPage(fp)
914
915        # Check if the widget got updated accordingly
916        self.assertEqual(self.widget.cbModel.currentText(), 'onion')
917        self.assertTrue(self.widget.chkPolydispersity.isChecked())
918        #Check if polidispersity tab is available
919        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
920
921        #Check if magnetism box and tab are disabled when 1D data is loaded
922        self.assertFalse(self.widget.chkMagnetism.isEnabled())
923        self.assertFalse(self.widget.tabFitting.isTabEnabled(4))
924
925    # to be fixed after functionality is ready
926    def notestReadFitPage2D(self):
927        """
928        Read in the fitpage object and restore state
929        """
930        # Set data
931
932        test_data = Data2D(image=[1.0, 2.0, 3.0],
933                           err_image=[0.01, 0.02, 0.03],
934                           qx_data=[0.1, 0.2, 0.3],
935                           qy_data=[0.1, 0.2, 0.3],
936                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
937                           mask=[True, True, True])
938
939        # Force same data into logic
940        self.widget.logic.data = test_data
941        self.widget.data_is_loaded = True
942
943        #item = QtGui.QStandardItem()
944        #updateModelItem(item, [test_data], "test")
945        # Force same data into logic
946        #self.widget.logic.data = item
947        #self.widget.data_is_loaded = True
948
949        category_index = self.widget.cbCategory.findText("Cylinder")
950        self.widget.cbCategory.setCurrentIndex(category_index)
951
952        # Test no fitting params
953        self.widget.main_params_to_fit = ['scale']
954
955        # Invoke the tested method
956        fp = self.widget.currentState()
957
958        # Prepare modified fit page
959        fp.current_model = 'cylinder'
960        fp.is_polydisperse = True
961        fp.is_magnetic = True
962        fp.is2D = True
963
964        # Read in modified state
965        self.widget.readFitPage(fp)
966
967        # Check if the widget got updated accordingly
968        self.assertEqual(self.widget.cbModel.currentText(), 'cylinder')
969        self.assertTrue(self.widget.chkPolydispersity.isChecked())
970        self.assertTrue(self.widget.chkPolydispersity.isEnabled())
971        #Check if polidispersity tab is available
972        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
973
974        #Check if magnetism box and tab are disabled when 1D data is loaded
975        self.assertTrue(self.widget.chkMagnetism.isChecked())
976        self.assertTrue(self.widget.chkMagnetism.isEnabled())
977        self.assertTrue(self.widget.tabFitting.isTabEnabled(4))
978
979    def testCurrentState(self):
980        """
981        Set up the fitpage with current state
982        """
983        # Set data
984        test_data = Data1D(x=[1,2], y=[1,2])
985        item = QtGui.QStandardItem()
986        updateModelItem(item, test_data, "test")
987        # Force same data into logic
988        self.widget.data = item
989        category_index = self.widget.cbCategory.findText("Sphere")
990        self.widget.cbCategory.setCurrentIndex(category_index)
991        model_index = self.widget.cbModel.findText("adsorbed_layer")
992        self.widget.cbModel.setCurrentIndex(model_index)
993        self.widget.main_params_to_fit = ['scale']
994
995        # Invoke the tested method
996        fp = self.widget.currentState()
997
998        # Test some entries. (Full testing of fp is done in FitPageTest)
999        self.assertIsInstance(fp.data, Data1D)
1000        self.assertListEqual(list(fp.data.x), [1,2])
1001        self.assertTrue(fp.data_is_loaded)
1002        self.assertEqual(fp.current_category, "Sphere")
1003        self.assertEqual(fp.current_model, "adsorbed_layer")
1004        self.assertListEqual(fp.main_params_to_fit, ['scale'])
1005
1006    def notestPushFitPage(self):
1007        """
1008        Push current state of fitpage onto stack
1009        """
1010        # Set data
1011        test_data = Data1D(x=[1,2], y=[1,2])
1012        item = QtGui.QStandardItem()
1013        updateModelItem(item, test_data, "test")
1014        # Force same data into logic
1015        self.widget.data = item
1016        category_index = self.widget.cbCategory.findText("Sphere")
1017        model_index = self.widget.cbModel.findText("adsorbed_layer")
1018        self.widget.cbModel.setCurrentIndex(model_index)
1019
1020        # Asses the initial state of stack
1021        self.assertEqual(self.widget.page_stack, [])
1022
1023        # Set the undo flag
1024        self.widget.undo_supported = True
1025        self.widget.cbCategory.setCurrentIndex(category_index)
1026        self.widget.main_params_to_fit = ['scale']
1027
1028        # Check that the stack is updated
1029        self.assertEqual(len(self.widget.page_stack), 1)
1030
1031        # Change another parameter
1032        self.widget._model_model.item(3, 1).setText("3.0")
1033
1034        # Check that the stack is updated
1035        self.assertEqual(len(self.widget.page_stack), 2)
1036
1037    def testPopFitPage(self):
1038        """
1039        Pop current state of fitpage from stack
1040        """
1041        # TODO: to be added when implementing UNDO/REDO
1042        pass
1043
1044    def testOnMainPageChange(self):
1045        """
1046        Test update  values of modified parameters in models
1047        """
1048        # select model: cylinder / cylinder
1049        category_index = self.widget.cbCategory.findText("Cylinder")
1050        self.widget.cbCategory.setCurrentIndex(category_index)
1051
1052        model_index = self.widget.cbModel.findText("cylinder")
1053        self.widget.cbModel.setCurrentIndex(model_index)
1054
1055        # modify the initial value of length (different from default)
1056        # print self.widget.kernel_module.details['length']
1057
1058        new_value = "333.0"
1059        self.widget._model_model.item(5, 1).setText(new_value)
1060
1061        # name of modified parameter
1062        name_modified_param = str(self.widget._model_model.item(5, 0).text())
1063
1064         # Check the model
1065        self.assertEqual(self.widget._model_model.rowCount(), 7)
1066        self.assertEqual(self.widget._model_model.columnCount(), 5)
1067
1068        # Test the header
1069        #self.assertEqual(self.widget.lstParams.horizontalHeader().count(), 5)
1070        #self.assertFalse(self.widget.lstParams.horizontalHeader().stretchLastSection())
1071
1072        self.assertEqual(len(self.widget._model_model.header_tooltips), 5)
1073        header_tooltips = ['Select parameter for fitting',
1074                             'Enter parameter value',
1075                             'Enter minimum value for parameter',
1076                             'Enter maximum value for parameter',
1077                             'Unit of the parameter']
1078        for column, tooltip in enumerate(header_tooltips):
1079             self.assertEqual(self.widget._model_model.headerData(column,
1080                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
1081                         header_tooltips[column])
1082
1083        # check that the value has been modified in kernel_module
1084        self.assertEqual(new_value,
1085                         str(self.widget.kernel_module.params[name_modified_param]))
1086
1087        # check that range of variation for this parameter has NOT been changed
1088        self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] )
1089
1090    def testModelContextMenu(self):
1091        """
1092        Test the right click context menu in the parameter table
1093        """
1094        # select model: cylinder / cylinder
1095        category_index = self.widget.cbCategory.findText("Cylinder")
1096        self.widget.cbCategory.setCurrentIndex(category_index)
1097
1098        model_index = self.widget.cbModel.findText("cylinder")
1099        self.widget.cbModel.setCurrentIndex(model_index)
1100
1101        # no rows selected
1102        menu = self.widget.modelContextMenu([])
1103        self.assertEqual(len(menu.actions()), 0)
1104
1105        # 1 row selected
1106        menu = self.widget.modelContextMenu([1])
1107        self.assertEqual(len(menu.actions()), 4)
1108
1109        # 2 rows selected
1110        menu = self.widget.modelContextMenu([1,3])
1111        self.assertEqual(len(menu.actions()), 5)
1112
1113        # 3 rows selected
1114        menu = self.widget.modelContextMenu([1,2,3])
1115        self.assertEqual(len(menu.actions()), 4)
1116
1117        # over 9000
1118        with self.assertRaises(AttributeError):
1119            menu = self.widget.modelContextMenu([i for i in range(9001)])
1120        self.assertEqual(len(menu.actions()), 4)
1121
1122    def testShowModelContextMenu(self):
1123        # select model: cylinder / cylinder
1124        category_index = self.widget.cbCategory.findText("Cylinder")
1125        self.widget.cbCategory.setCurrentIndex(category_index)
1126
1127        model_index = self.widget.cbModel.findText("cylinder")
1128        self.widget.cbModel.setCurrentIndex(model_index)
1129
1130        # No selection
1131        logging.error = MagicMock()
1132        self.widget.showModelDescription = MagicMock()
1133        # Show the menu
1134        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1135
1136        # Assure the description menu is shown
1137        self.assertTrue(self.widget.showModelDescription.called)
1138        self.assertFalse(logging.error.called)
1139
1140        # "select" two rows
1141        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1142        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1143        selection_model = self.widget.lstParams.selectionModel()
1144        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1145        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1146
1147        QtWidgets.QMenu.exec_ = MagicMock()
1148        logging.error = MagicMock()
1149        # Show the menu
1150        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1151
1152        # Assure the menu is shown
1153        self.assertFalse(logging.error.called)
1154        self.assertTrue(QtWidgets.QMenu.exec_.called)
1155
1156    def testShowMultiConstraint(self):
1157        """
1158        Test the widget update on new multi constraint
1159        """
1160        # select model: cylinder / cylinder
1161        category_index = self.widget.cbCategory.findText("Cylinder")
1162        self.widget.cbCategory.setCurrentIndex(category_index)
1163
1164        model_index = self.widget.cbModel.findText("cylinder")
1165        self.widget.cbModel.setCurrentIndex(model_index)
1166
1167        # nothing selected
1168        with self.assertRaises(AssertionError):
1169            self.widget.showMultiConstraint()
1170
1171        # one row selected
1172        index = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1173        selection_model = self.widget.lstParams.selectionModel()
1174        selection_model.select(index, selection_model.Select | selection_model.Rows)
1175        with self.assertRaises(AssertionError):
1176            # should also throw
1177            self.widget.showMultiConstraint()
1178
1179        # two rows selected
1180        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1181        index2 = self.widget.lstParams.model().index(3, 0, QtCore.QModelIndex())
1182        selection_model = self.widget.lstParams.selectionModel()
1183        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1184        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1185
1186        # return non-OK from dialog
1187        QtWidgets.QDialog.exec_ = MagicMock()
1188        self.widget.showMultiConstraint()
1189        # Check the dialog called
1190        self.assertTrue(QtWidgets.QDialog.exec_.called)
1191
1192        # return OK from dialog
1193        QtWidgets.QDialog.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted)
1194        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1195
1196        self.widget.showMultiConstraint()
1197
1198        # Make sure the signal has been emitted
1199        self.assertEqual(spy.count(), 1)
1200
1201        # Check the argument value - should be row '1'
1202        self.assertEqual(spy.called()[0]['args'][0], [1])
1203
1204    def testGetRowFromName(self):
1205        """
1206        Helper function for parameter table
1207        """
1208        # select model: cylinder / cylinder
1209        category_index = self.widget.cbCategory.findText("Cylinder")
1210        self.widget.cbCategory.setCurrentIndex(category_index)
1211
1212        model_index = self.widget.cbModel.findText("cylinder")
1213        self.widget.cbModel.setCurrentIndex(model_index)
1214
1215        # several random parameters
1216        self.assertEqual(self.widget.getRowFromName('scale'), 0)
1217        self.assertEqual(self.widget.getRowFromName('length'), 6)
1218
1219    def testGetParamNames(self):
1220        """
1221        Helper function for parameter table
1222        """
1223        # select model: cylinder / cylinder
1224        category_index = self.widget.cbCategory.findText("Cylinder")
1225        self.widget.cbCategory.setCurrentIndex(category_index)
1226
1227        model_index = self.widget.cbModel.findText("cylinder")
1228        self.widget.cbModel.setCurrentIndex(model_index)
1229
1230        cylinder_params = ['scale','background','sld','sld_solvent','radius','length']
1231        # assure all parameters are returned
1232        self.assertEqual(self.widget.getParamNames(), cylinder_params)
1233
1234        # Switch to another model
1235        model_index = self.widget.cbModel.findText("pringle")
1236        self.widget.cbModel.setCurrentIndex(model_index)
1237
1238        # make sure the parameters are different than before
1239        self.assertFalse(self.widget.getParamNames() == cylinder_params)
1240
1241    def testAddConstraintToRow(self):
1242        """
1243        Test the constraint row add operation
1244        """
1245        # select model: cylinder / cylinder
1246        category_index = self.widget.cbCategory.findText("Cylinder")
1247        self.widget.cbCategory.setCurrentIndex(category_index)
1248
1249        model_index = self.widget.cbModel.findText("cylinder")
1250        self.widget.cbModel.setCurrentIndex(model_index)
1251
1252        # Create a constraint object
1253        const = Constraint(parent=None, value=7.0)
1254        row = 3
1255
1256        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1257
1258        # call the method tested
1259        self.widget.addConstraintToRow(constraint=const, row=row)
1260
1261        # Make sure the signal has been emitted
1262        self.assertEqual(spy.count(), 1)
1263
1264        # Check the argument value - should be row 'row'
1265        self.assertEqual(spy.called()[0]['args'][0], [row])
1266
1267        # Assure the row has the constraint
1268        self.assertEqual(self.widget.getConstraintForRow(row), const)
1269        self.assertTrue(self.widget.rowHasConstraint(row))
1270
1271        # assign complex constraint now
1272        const = Constraint(parent=None, param='radius', func='5*sld')
1273        row = 5
1274        # call the method tested
1275        self.widget.addConstraintToRow(constraint=const, row=row)
1276
1277        # Make sure the signal has been emitted
1278        self.assertEqual(spy.count(), 2)
1279
1280        # Check the argument value - should be row 'row'
1281        self.assertEqual(spy.called()[1]['args'][0], [row])
1282
1283        # Assure the row has the constraint
1284        self.assertEqual(self.widget.getConstraintForRow(row), const)
1285        # and it is a complex constraint
1286        self.assertTrue(self.widget.rowHasConstraint(row))
1287
1288    def testAddSimpleConstraint(self):
1289        """
1290        Test the constraint add operation
1291        """
1292        # select model: cylinder / cylinder
1293        category_index = self.widget.cbCategory.findText("Cylinder")
1294        self.widget.cbCategory.setCurrentIndex(category_index)
1295
1296        model_index = self.widget.cbModel.findText("cylinder")
1297        self.widget.cbModel.setCurrentIndex(model_index)
1298
1299        # select two rows
1300        row1 = 1
1301        row2 = 4
1302        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1303        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1304        selection_model = self.widget.lstParams.selectionModel()
1305        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1306        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1307
1308        # define the signal spy
1309        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1310
1311        # call the method tested
1312        self.widget.addSimpleConstraint()
1313
1314        # Make sure the signal has been emitted
1315        self.assertEqual(spy.count(), 2)
1316
1317        # Check the argument value
1318        self.assertEqual(spy.called()[0]['args'][0], [row1])
1319        self.assertEqual(spy.called()[1]['args'][0], [row2])
1320
1321    def testDeleteConstraintOnParameter(self):
1322        """
1323        Test the constraint deletion in model/view
1324        """
1325        # select model: cylinder / cylinder
1326        category_index = self.widget.cbCategory.findText("Cylinder")
1327        self.widget.cbCategory.setCurrentIndex(category_index)
1328
1329        model_index = self.widget.cbModel.findText("cylinder")
1330        self.widget.cbModel.setCurrentIndex(model_index)
1331
1332        row1 = 1
1333        row2 = 5
1334
1335        param1 = "background"
1336        param2 = "radius"
1337
1338        #default_value1 = "0.001"
1339        default_value2 = "20"
1340
1341        # select two rows
1342        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1343        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1344        selection_model = self.widget.lstParams.selectionModel()
1345        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1346        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1347
1348        # add constraints
1349        self.widget.addSimpleConstraint()
1350
1351        # deselect the model
1352        selection_model.clear()
1353
1354        # select a single row
1355        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1356
1357        # delete one of the constraints
1358        self.widget.deleteConstraintOnParameter(param=param1)
1359
1360        # see that the other constraint is still present
1361        cons = self.widget.getConstraintForRow(row2)
1362        self.assertEqual(cons.param, param2)
1363        self.assertEqual(cons.value, default_value2)
1364
1365        # kill the other constraint
1366        self.widget.deleteConstraint()
1367
1368        # see that the other constraint is still present
1369        self.assertEqual(self.widget.getConstraintsForModel(), [(param2, None)])
1370
1371    def testGetConstraintForRow(self):
1372        """
1373        Helper function for parameter table
1374        """
1375        # tested extensively elsewhere
1376        pass
1377
1378    def testRowHasConstraint(self):
1379        """
1380        Helper function for parameter table
1381        """
1382        # select model: cylinder / cylinder
1383        category_index = self.widget.cbCategory.findText("Cylinder")
1384        self.widget.cbCategory.setCurrentIndex(category_index)
1385
1386        model_index = self.widget.cbModel.findText("cylinder")
1387        self.widget.cbModel.setCurrentIndex(model_index)
1388
1389        row1 = 1
1390        row2 = 5
1391
1392        # select two rows
1393        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1394        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1395        selection_model = self.widget.lstParams.selectionModel()
1396        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1397        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1398
1399        # add constraints
1400        self.widget.addSimpleConstraint()
1401
1402        con_list = [False, True, False, False, False, True, False]
1403        new_list = []
1404        for row in range(self.widget._model_model.rowCount()):
1405            new_list.append(self.widget.rowHasConstraint(row))
1406
1407        self.assertEqual(new_list, con_list)
1408
1409    def testRowHasActiveConstraint(self):
1410        """
1411        Helper function for parameter table
1412        """
1413        # select model: cylinder / cylinder
1414        category_index = self.widget.cbCategory.findText("Cylinder")
1415        self.widget.cbCategory.setCurrentIndex(category_index)
1416
1417        model_index = self.widget.cbModel.findText("cylinder")
1418        self.widget.cbModel.setCurrentIndex(model_index)
1419
1420        row1 = 1
1421        row2 = 5
1422
1423        # select two rows
1424        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1425        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1426        selection_model = self.widget.lstParams.selectionModel()
1427        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1428        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1429
1430        # add constraints
1431        self.widget.addSimpleConstraint()
1432
1433        # deactivate the first constraint
1434        constraint_objects = self.widget.getConstraintObjectsForModel()
1435        constraint_objects[0].active = False
1436
1437        con_list = [False, False, False, False, False, True, False]
1438        new_list = []
1439        for row in range(self.widget._model_model.rowCount()):
1440            new_list.append(self.widget.rowHasActiveConstraint(row))
1441
1442        self.assertEqual(new_list, con_list)
1443
1444    def testGetConstraintsForModel(self):
1445        """
1446        Test the constraint getter for constraint texts
1447        """
1448        # select model: cylinder / cylinder
1449        category_index = self.widget.cbCategory.findText("Cylinder")
1450        self.widget.cbCategory.setCurrentIndex(category_index)
1451
1452        model_index = self.widget.cbModel.findText("cylinder")
1453        self.widget.cbModel.setCurrentIndex(model_index)
1454
1455        # no constraints
1456        self.assertEqual(self.widget.getConstraintsForModel(),[])
1457
1458        row1 = 1
1459        row2 = 5
1460
1461        param1 = "background"
1462        param2 = "radius"
1463
1464        default_value1 = "0.001"
1465        default_value2 = "20"
1466
1467        # select two rows
1468        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1469        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1470        selection_model = self.widget.lstParams.selectionModel()
1471        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1472        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1473
1474        # add constraints
1475        self.widget.addSimpleConstraint()
1476
1477        # simple constraints
1478        # self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
1479        cons = self.widget.getConstraintForRow(row1)
1480        self.assertEqual(cons.param, param1)
1481        self.assertEqual(cons.value, default_value1)
1482        cons = self.widget.getConstraintForRow(row2)
1483        self.assertEqual(cons.param, param2)
1484        self.assertEqual(cons.value, default_value2)
1485
1486        objects = self.widget.getConstraintObjectsForModel()
1487        self.assertEqual(len(objects), 2)
1488        self.assertEqual(objects[1].value, default_value2)
1489        self.assertEqual(objects[0].param, param1)
1490
1491        row = 0
1492        param = "scale"
1493        func = "5*sld"
1494
1495        # add complex constraint
1496        const = Constraint(parent=None, param=param, func=func)
1497        self.widget.addConstraintToRow(constraint=const, row=row)
1498        #self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', None)])
1499        cons = self.widget.getConstraintForRow(row2)
1500        self.assertEqual(cons.param, param2)
1501        self.assertEqual(cons.value, default_value2)
1502
1503        objects = self.widget.getConstraintObjectsForModel()
1504        self.assertEqual(len(objects), 3)
1505        self.assertEqual(objects[0].func, func)
1506
1507    def testReplaceConstraintName(self):
1508        """
1509        Test the replacement of constraint moniker
1510        """
1511        # select model: cylinder / cylinder
1512        category_index = self.widget.cbCategory.findText("Cylinder")
1513        self.widget.cbCategory.setCurrentIndex(category_index)
1514
1515        model_index = self.widget.cbModel.findText("cylinder")
1516        self.widget.cbModel.setCurrentIndex(model_index)
1517
1518        old_name = 'M5'
1519        new_name = 'poopy'
1520        # add complex constraint
1521        const = Constraint(parent=None, param='scale', func='%s.5*sld'%old_name)
1522        row = 0
1523        self.widget.addConstraintToRow(constraint=const, row=row)
1524
1525        # Replace 'm5' with 'poopy'
1526        self.widget.replaceConstraintName(old_name, new_name)
1527
1528        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', 'poopy.5*sld')])
1529
1530
1531if __name__ == "__main__":
1532    unittest.main()
Note: See TracBrowser for help on using the repository browser.