source: sasview/src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py @ 66d4370

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 66d4370 was 66d4370, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Separate kernel parameters so they can be selectively added during fitting and calculation. SASVIEW-1008, SASVIEW-1013

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