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

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 b764ae5 was b764ae5, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

processEvents() helps with proper chart generation. - SASVIEW-890
Fixed weighing in fitting - SASVIEW-1017
Fixed error bars after fitting - SASVIEW-1004

  • Property mode set to 100644
File size: 57.4 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 reset to None
265        self.assertEqual(self.widget.cbStructureFactor.currentText(), STRUCTURE_DEFAULT)
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        # Change the number of points
448        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
449        self.widget._poly_model.item(0,4).setText("22")
450        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
451        # try something stupid
452        self.widget._poly_model.item(0,4).setText("butt")
453        # see that this didn't annoy the control at all
454        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
455
456        # Change the number of sigmas
457        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 3)
458        self.widget._poly_model.item(0,5).setText("222")
459        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
460        # try something stupid again
461        self.widget._poly_model.item(0,4).setText("beer")
462        # no efect
463        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
464
465    def testOnPolyComboIndexChange(self):
466        """
467        Test the slot method for polydisp. combo box index change
468        """
469        self.widget.show()
470        # Change the category index so we have a model with polydisp
471        category_index = self.widget.cbCategory.findText("Cylinder")
472        self.widget.cbCategory.setCurrentIndex(category_index)
473        model_index = self.widget.cbModel.findText("barbell")
474        self.widget.cbModel.setCurrentIndex(model_index)
475
476        # call method with default settings
477        self.widget.onPolyComboIndexChange('gaussian', 0)
478        # check values
479        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
480        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 3)
481        # Change the index
482        self.widget.onPolyComboIndexChange('rectangle', 0)
483        # check values
484        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
485        self.assertAlmostEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 1.73205, 5)
486        # Change the index
487        self.widget.onPolyComboIndexChange('lognormal', 0)
488        # check values
489        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
490        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
491        # Change the index
492        self.widget.onPolyComboIndexChange('schulz', 0)
493        # check values
494        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
495        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
496
497        # mock up file load
498        self.widget.loadPolydispArray = MagicMock()
499        # Change to 'array'
500        self.widget.onPolyComboIndexChange('array', 0)
501        # See the mock fire
502        self.assertTrue(self.widget.loadPolydispArray.called)
503
504    def testLoadPolydispArray(self):
505        """
506        Test opening of the load file dialog for 'array' polydisp. function
507        """
508        filename = os.path.join("UnitTesting", "testdata_noexist.txt")
509        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
510        self.widget.show()
511        # Change the category index so we have a model with polydisp
512        category_index = self.widget.cbCategory.findText("Cylinder")
513        self.widget.cbCategory.setCurrentIndex(category_index)
514        model_index = self.widget.cbModel.findText("barbell")
515        self.widget.cbModel.setCurrentIndex(model_index)
516
517        self.widget.onPolyComboIndexChange('array', 0)
518        # check values - unchanged since the file doesn't exist
519        self.assertTrue(self.widget._poly_model.item(0, 1).isEnabled())
520        with self.assertRaises(AttributeError):
521            self.widget.disp_model()
522
523        # good file
524        filename = os.path.join("UnitTesting", "testdata.txt")
525        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
526
527        self.widget.onPolyComboIndexChange('array', 0)
528        # check values - disabled control, present weights
529        self.assertFalse(self.widget._poly_model.item(0, 1).isEnabled())
530        self.assertEqual(self.widget.disp_model.weights[0], 2.83954)
531        self.assertEqual(len(self.widget.disp_model.weights), 19)
532        self.assertEqual(len(self.widget.disp_model.values), 19)
533        self.assertEqual(self.widget.disp_model.values[0], 0.0)
534        self.assertEqual(self.widget.disp_model.values[18], 3.67347)
535
536    def testSetMagneticModel(self):
537        """
538        Test the magnetic model setup
539        """
540        self.widget.show()
541        # Change the category index so we have a model available
542        category_index = self.widget.cbCategory.findText("Sphere")
543        self.widget.cbCategory.setCurrentIndex(category_index)
544
545        # Check the magnetic model
546        self.assertEqual(self.widget._magnet_model.rowCount(), 9)
547        self.assertEqual(self.widget._magnet_model.columnCount(), 5)
548
549        # Test the header
550        self.assertEqual(self.widget.lstMagnetic.horizontalHeader().count(), 5)
551        self.assertFalse(self.widget.lstMagnetic.horizontalHeader().stretchLastSection())
552
553        #Test tooltips
554        self.assertEqual(len(self.widget._magnet_model.header_tooltips), 5)
555
556        header_tooltips = ['Select parameter for fitting',
557                           'Enter parameter value',
558                           'Enter minimum value for parameter',
559                           'Enter maximum value for parameter',
560                           'Unit of the parameter']
561        for column, tooltip in enumerate(header_tooltips):
562             self.assertEqual(self.widget._magnet_model.headerData(column,
563                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
564                         header_tooltips[column])
565
566        # Test rows
567        for row in range(self.widget._magnet_model.rowCount()):
568            func_index = self.widget._magnet_model.index(row, 0)
569            self.assertIn(':', self.widget._magnet_model.item(row, 0).text())
570
571
572    def testAddExtraShells(self):
573        """
574        Test how the extra shells are presented
575        """
576        pass
577
578    def testModifyShellsInList(self):
579        """
580        Test the additional rows added by modifying the shells combobox
581        """
582        self.widget.show()
583        # Change the model to multi shell
584        category_index = self.widget.cbCategory.findText("Sphere")
585        self.widget.cbCategory.setCurrentIndex(category_index)
586        model_index = self.widget.cbModel.findText("core_multi_shell")
587        self.widget.cbModel.setCurrentIndex(model_index)
588
589        # Assure we have the combobox available
590        last_row = self.widget._last_model_row
591        func_index = self.widget._model_model.index(last_row-1, 1)
592        self.assertIsInstance(self.widget.lstParams.indexWidget(func_index), QtWidgets.QComboBox)
593
594        # Change the combo box index
595        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(3)
596
597        # Check that the number of rows increased
598        more_rows = self.widget._model_model.rowCount() - last_row
599        self.assertEqual(more_rows, 6) # 6 new rows: 2 params per index
600
601        # Back to 0
602        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(0)
603        self.assertEqual(self.widget._model_model.rowCount(), last_row)
604
605    def testPlotTheory(self):
606        """
607        See that theory item can produce a chart
608        """
609        # By default, the compute/plot button is disabled
610        self.assertFalse(self.widget.cmdPlot.isEnabled())
611        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
612
613        # Assign a model
614        self.widget.show()
615        # Change the category index so we have a model available
616        category_index = self.widget.cbCategory.findText("Sphere")
617        self.widget.cbCategory.setCurrentIndex(category_index)
618
619        # Check the enablement/text
620        self.assertTrue(self.widget.cmdPlot.isEnabled())
621        self.assertEqual(self.widget.cmdPlot.text(), 'Calculate')
622
623        # Spying on plot update signal
624        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
625
626        # Press Calculate
627        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
628
629        # Observe cmdPlot caption change
630        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
631
632        # Make sure the signal has NOT been emitted
633        self.assertEqual(spy.count(), 0)
634
635        # Click again
636        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
637
638        # This time, we got the update signal
639        self.assertEqual(spy.count(), 0)
640
641    def notestPlotData(self):
642        """
643        See that data item can produce a chart
644        """
645        # By default, the compute/plot button is disabled
646        self.assertFalse(self.widget.cmdPlot.isEnabled())
647        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
648
649        # Set data
650        test_data = Data1D(x=[1,2], y=[1,2])
651
652        # Force same data into logic
653        self.widget.logic.data = test_data
654        self.widget.data_is_loaded = True
655
656        # Change the category index so we have a model available
657        category_index = self.widget.cbCategory.findText("Sphere")
658        self.widget.cbCategory.setCurrentIndex(category_index)
659
660        # Check the enablement/text
661        self.assertTrue(self.widget.cmdPlot.isEnabled())
662        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
663
664        # Spying on plot update signal
665        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
666
667        # Press Calculate
668        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
669
670        # Observe cmdPlot caption did not change
671        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
672
673        # Make sure the signal has been emitted == new plot
674        self.assertEqual(spy.count(), 1)
675
676    def notestOnEmptyFit(self):
677        """
678        Test a 1D/2D fit with no parameters
679        """
680        # Set data
681        test_data = Data1D(x=[1,2], y=[1,2])
682        item = QtGui.QStandardItem()
683        updateModelItem(item, test_data, "test")
684        # Force same data into logic
685        self.widget.data = item
686        category_index = self.widget.cbCategory.findText("Sphere")
687        self.widget.cbCategory.setCurrentIndex(category_index)
688
689        #self.widget.show()
690
691        # Test no fitting params
692        self.widget.main_params_to_fit = []
693
694        logging.error = MagicMock()
695
696        self.widget.onFit()
697        self.assertTrue(logging.error.called_with('no fitting parameters'))
698        self.widget.close()
699
700        test_data = Data2D(image=[1.0, 2.0, 3.0],
701                           err_image=[0.01, 0.02, 0.03],
702                           qx_data=[0.1, 0.2, 0.3],
703                           qy_data=[0.1, 0.2, 0.3],
704                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
705                           mask=[True, True, True])
706
707        # Force same data into logic
708        item = QtGui.QStandardItem()
709        updateModelItem(item, test_data, "test")
710        # Force same data into logic
711        self.widget.data = item
712        category_index = self.widget.cbCategory.findText("Sphere")
713        self.widget.cbCategory.setCurrentIndex(category_index)
714
715        self.widget.show()
716
717        # Test no fitting params
718        self.widget.main_params_to_fit = []
719
720        logging.error = MagicMock()
721
722        self.widget.onFit()
723        self.assertTrue(logging.error.called_once())
724        self.assertTrue(logging.error.called_with('no fitting parameters'))
725        #self.widget.close()
726
727
728    def notestOnFit1D(self):
729        """
730        Test the threaded fitting call
731        """
732        # Set data
733        test_data = Data1D(x=[1,2], y=[1,2])
734        item = QtGui.QStandardItem()
735        updateModelItem(item, test_data, "test")
736        # Force same data into logic
737        self.widget.data = item
738        category_index = self.widget.cbCategory.findText("Sphere")
739        self.widget.cbCategory.setCurrentIndex(category_index)
740
741        self.widget.show()
742
743        # Assing fitting params
744        self.widget.main_params_to_fit = ['scale']
745
746        # Spying on status update signal
747        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
748
749        with threads.deferToThread as MagicMock:
750            self.widget.onFit()
751            # thread called
752            self.assertTrue(threads.deferToThread.called)
753            # thread method is 'compute'
754            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
755
756            # the fit button changed caption and got disabled
757            # could fail if machine fast enough to finish
758            #self.assertEqual(self.widget.cmdFit.text(), 'Stop fit')
759            #self.assertFalse(self.widget.cmdFit.isEnabled())
760
761            # Signal pushed up
762            self.assertEqual(update_spy.count(), 1)
763
764        self.widget.close()
765
766    def notestOnFit2D(self):
767        """
768        Test the threaded fitting call
769        """
770        # Set data
771        test_data = Data2D(image=[1.0, 2.0, 3.0],
772                           err_image=[0.01, 0.02, 0.03],
773                           qx_data=[0.1, 0.2, 0.3],
774                           qy_data=[0.1, 0.2, 0.3],
775                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
776                           mask=[True, True, True])
777
778        # Force same data into logic
779        item = QtGui.QStandardItem()
780        updateModelItem(item, test_data, "test")
781        # Force same data into logic
782        self.widget.data = item
783        category_index = self.widget.cbCategory.findText("Sphere")
784        self.widget.cbCategory.setCurrentIndex(category_index)
785
786        self.widget.show()
787
788        # Assing fitting params
789        self.widget.main_params_to_fit = ['scale']
790
791        # Spying on status update signal
792        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
793
794        with threads.deferToThread as MagicMock:
795            self.widget.onFit()
796            # thread called
797            self.assertTrue(threads.deferToThread.called)
798            # thread method is 'compute'
799            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
800
801            # the fit button changed caption and got disabled
802            #self.assertEqual(self.widget.cmdFit.text(), 'Stop fit')
803            #self.assertFalse(self.widget.cmdFit.isEnabled())
804
805            # Signal pushed up
806            self.assertEqual(update_spy.count(), 1)
807
808    def testOnHelp(self):
809        """
810        Test various help pages shown in this widget
811        """
812        #Mock the webbrowser.open method
813        self.widget.parent.showHelp = MagicMock()
814        #webbrowser.open = MagicMock()
815
816        # Invoke the action on default tab
817        self.widget.onHelp()
818        # Check if show() got called
819        self.assertTrue(self.widget.parent.showHelp.called)
820        # Assure the filename is correct
821        self.assertIn("fitting_help.html", self.widget.parent.showHelp.call_args[0][0])
822
823        # Change the tab to options
824        self.widget.tabFitting.setCurrentIndex(1)
825        self.widget.onHelp()
826        # Check if show() got called
827        self.assertEqual(self.widget.parent.showHelp.call_count, 2)
828        # Assure the filename is correct
829        self.assertIn("residuals_help.html", self.widget.parent.showHelp.call_args[0][0])
830
831        # Change the tab to smearing
832        self.widget.tabFitting.setCurrentIndex(2)
833        self.widget.onHelp()
834        # Check if show() got called
835        self.assertEqual(self.widget.parent.showHelp.call_count, 3)
836        # Assure the filename is correct
837        self.assertIn("resolution.html", self.widget.parent.showHelp.call_args[0][0])
838
839        # Change the tab to poly
840        self.widget.tabFitting.setCurrentIndex(3)
841        self.widget.onHelp()
842        # Check if show() got called
843        self.assertEqual(self.widget.parent.showHelp.call_count, 4)
844        # Assure the filename is correct
845        self.assertIn("polydispersity.html", self.widget.parent.showHelp.call_args[0][0])
846
847        # Change the tab to magnetism
848        self.widget.tabFitting.setCurrentIndex(4)
849        self.widget.onHelp()
850        # Check if show() got called
851        self.assertEqual(self.widget.parent.showHelp.call_count, 5)
852        # Assure the filename is correct
853        self.assertIn("magnetism.html", self.widget.parent.showHelp.call_args[0][0])
854
855    def notestReadFitPage(self):
856        """
857        Read in the fitpage object and restore state
858        """
859        # Set data
860        test_data = Data1D(x=[1,2], y=[1,2])
861
862        # Force same data into logic
863        self.widget.logic.data = test_data
864        self.widget.data_is_loaded = True
865        category_index = self.widget.cbCategory.findText('Sphere')
866        self.widget.cbCategory.setCurrentIndex(category_index)
867        self.widget.main_params_to_fit = ['scale']
868        # Invoke the tested method
869        fp = self.widget.currentState()
870
871        # Prepare modified fit page
872        fp.current_model = 'onion'
873        fp.is_polydisperse = True
874
875        # Read in modified state
876        self.widget.readFitPage(fp)
877
878        # Check if the widget got updated accordingly
879        self.assertEqual(self.widget.cbModel.currentText(), 'onion')
880        self.assertTrue(self.widget.chkPolydispersity.isChecked())
881        #Check if polidispersity tab is available
882        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
883
884        #Check if magnetism box and tab are disabled when 1D data is loaded
885        self.assertFalse(self.widget.chkMagnetism.isEnabled())
886        self.assertFalse(self.widget.tabFitting.isTabEnabled(4))
887
888    def testReadFitPage2D(self):
889        """
890        Read in the fitpage object and restore state
891        """
892        # Set data
893
894        test_data = Data2D(image=[1.0, 2.0, 3.0],
895                           err_image=[0.01, 0.02, 0.03],
896                           qx_data=[0.1, 0.2, 0.3],
897                           qy_data=[0.1, 0.2, 0.3],
898                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
899                           mask=[True, True, True])
900
901        # Force same data into logic
902        self.widget.logic.data = test_data
903        self.widget.data_is_loaded = True
904
905        #item = QtGui.QStandardItem()
906        #updateModelItem(item, [test_data], "test")
907        # Force same data into logic
908        #self.widget.logic.data = item
909        #self.widget.data_is_loaded = True
910
911        category_index = self.widget.cbCategory.findText("Cylinder")
912        self.widget.cbCategory.setCurrentIndex(category_index)
913
914        # Test no fitting params
915        self.widget.main_params_to_fit = ['scale']
916
917        # Invoke the tested method
918        fp = self.widget.currentState()
919
920        # Prepare modified fit page
921        fp.current_model = 'cylinder'
922        fp.is_polydisperse = True
923        fp.is_magnetic = True
924        fp.is2D = True
925
926        # Read in modified state
927        self.widget.readFitPage(fp)
928
929        # Check if the widget got updated accordingly
930        self.assertEqual(self.widget.cbModel.currentText(), 'cylinder')
931        self.assertTrue(self.widget.chkPolydispersity.isChecked())
932        self.assertTrue(self.widget.chkPolydispersity.isEnabled())
933        #Check if polidispersity tab is available
934        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
935
936        #Check if magnetism box and tab are disabled when 1D data is loaded
937        self.assertTrue(self.widget.chkMagnetism.isChecked())
938        self.assertTrue(self.widget.chkMagnetism.isEnabled())
939        self.assertTrue(self.widget.tabFitting.isTabEnabled(4))
940
941    def notestCurrentState(self):
942        """
943        Set up the fitpage with current state
944        """
945        # Set data
946        test_data = Data1D(x=[1,2], y=[1,2])
947
948        # Force same data into logic
949        self.widget.logic.data = test_data
950        self.widget.data_is_loaded = True
951        category_index = self.widget.cbCategory.findText("Sphere")
952        self.widget.cbCategory.setCurrentIndex(category_index)
953        self.widget.main_params_to_fit = ['scale']
954
955        # Invoke the tested method
956        fp = self.widget.currentState()
957
958        # Test some entries. (Full testing of fp is done in FitPageTest)
959        self.assertIsInstance(fp.data, Data1D)
960        self.assertListEqual(list(fp.data.x), [1,2])
961        self.assertTrue(fp.data_is_loaded)
962        self.assertEqual(fp.current_category, "Sphere")
963        self.assertEqual(fp.current_model, "adsorbed_layer")
964        self.assertListEqual(fp.main_params_to_fit, ['scale'])
965
966    def notestPushFitPage(self):
967        """
968        Push current state of fitpage onto stack
969        """
970        # Set data
971        test_data = Data1D(x=[1,2], y=[1,2])
972
973        # Force same data into logic
974        self.widget.logic.data = test_data
975        self.widget.data_is_loaded = True
976        category_index = self.widget.cbCategory.findText("Sphere")
977
978        # Asses the initial state of stack
979        self.assertEqual(self.widget.page_stack, [])
980
981        # Set the undo flag
982        self.widget.undo_supported = True
983        self.widget.cbCategory.setCurrentIndex(category_index)
984        self.widget.main_params_to_fit = ['scale']
985
986        # Check that the stack is updated
987        self.assertEqual(len(self.widget.page_stack), 1)
988
989        # Change another parameter
990        self.widget._model_model.item(2, 1).setText("3.0")
991        # Check that the stack is updated
992        self.assertEqual(len(self.widget.page_stack), 2)
993
994    def testPopFitPage(self):
995        """
996        Pop current state of fitpage from stack
997        """
998        # TODO: to be added when implementing UNDO/REDO
999        pass
1000
1001    def testOnMainPageChange(self):
1002        """
1003        Test update  values of modified parameters in models
1004        """
1005        # select model: cylinder / cylinder
1006        category_index = self.widget.cbCategory.findText("Cylinder")
1007        self.widget.cbCategory.setCurrentIndex(category_index)
1008
1009        model_index = self.widget.cbModel.findText("cylinder")
1010        self.widget.cbModel.setCurrentIndex(model_index)
1011
1012        # modify the initial value of length (different from default)
1013        # print self.widget.kernel_module.details['length']
1014
1015        new_value = "333.0"
1016        self.widget._model_model.item(5, 1).setText(new_value)
1017
1018        # name of modified parameter
1019        name_modified_param = str(self.widget._model_model.item(5, 0).text())
1020
1021         # Check the model
1022        self.assertEqual(self.widget._model_model.rowCount(), 6)
1023        self.assertEqual(self.widget._model_model.columnCount(), 5)
1024
1025        # Test the header
1026        #self.assertEqual(self.widget.lstParams.horizontalHeader().count(), 5)
1027        #self.assertFalse(self.widget.lstParams.horizontalHeader().stretchLastSection())
1028
1029        self.assertEqual(len(self.widget._model_model.header_tooltips), 5)
1030        header_tooltips = ['Select parameter for fitting',
1031                             'Enter parameter value',
1032                             'Enter minimum value for parameter',
1033                             'Enter maximum value for parameter',
1034                             'Unit of the parameter']
1035        for column, tooltip in enumerate(header_tooltips):
1036             self.assertEqual(self.widget._model_model.headerData(column,
1037                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
1038                         header_tooltips[column])
1039
1040        # check that the value has been modified in kernel_module
1041        self.assertEqual(new_value,
1042                         str(self.widget.kernel_module.params[name_modified_param]))
1043
1044        # check that range of variation for this parameter has NOT been changed
1045        self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] )
1046
1047    def testModelContextMenu(self):
1048        """
1049        Test the right click context menu in the parameter table
1050        """
1051        # select model: cylinder / cylinder
1052        category_index = self.widget.cbCategory.findText("Cylinder")
1053        self.widget.cbCategory.setCurrentIndex(category_index)
1054
1055        model_index = self.widget.cbModel.findText("cylinder")
1056        self.widget.cbModel.setCurrentIndex(model_index)
1057
1058        # no rows selected
1059        menu = self.widget.modelContextMenu([])
1060        self.assertEqual(len(menu.actions()), 0)
1061
1062        # 1 row selected
1063        menu = self.widget.modelContextMenu([1])
1064        self.assertEqual(len(menu.actions()), 4)
1065
1066        # 2 rows selected
1067        menu = self.widget.modelContextMenu([1,3])
1068        self.assertEqual(len(menu.actions()), 5)
1069
1070        # 3 rows selected
1071        menu = self.widget.modelContextMenu([1,2,3])
1072        self.assertEqual(len(menu.actions()), 4)
1073
1074        # over 9000
1075        with self.assertRaises(AttributeError):
1076            menu = self.widget.modelContextMenu([i for i in range(9001)])
1077        self.assertEqual(len(menu.actions()), 4)
1078
1079    def testShowModelContextMenu(self):
1080        # select model: cylinder / cylinder
1081        category_index = self.widget.cbCategory.findText("Cylinder")
1082        self.widget.cbCategory.setCurrentIndex(category_index)
1083
1084        model_index = self.widget.cbModel.findText("cylinder")
1085        self.widget.cbModel.setCurrentIndex(model_index)
1086
1087        # No selection
1088        logging.error = MagicMock()
1089        self.widget.showModelDescription = MagicMock()
1090        # Show the menu
1091        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1092
1093        # Assure the description menu is shown
1094        self.assertTrue(self.widget.showModelDescription.called)
1095        self.assertFalse(logging.error.called)
1096
1097        # "select" two rows
1098        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1099        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1100        selection_model = self.widget.lstParams.selectionModel()
1101        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1102        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1103
1104        QtWidgets.QMenu.exec_ = MagicMock()
1105        logging.error = MagicMock()
1106        # Show the menu
1107        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1108
1109        # Assure the menu is shown
1110        self.assertFalse(logging.error.called)
1111        self.assertTrue(QtWidgets.QMenu.exec_.called)
1112
1113    def testShowMultiConstraint(self):
1114        """
1115        Test the widget update on new multi constraint
1116        """
1117        # select model: cylinder / cylinder
1118        category_index = self.widget.cbCategory.findText("Cylinder")
1119        self.widget.cbCategory.setCurrentIndex(category_index)
1120
1121        model_index = self.widget.cbModel.findText("cylinder")
1122        self.widget.cbModel.setCurrentIndex(model_index)
1123
1124        # nothing selected
1125        with self.assertRaises(AssertionError):
1126            self.widget.showMultiConstraint()
1127
1128        # one row selected
1129        index = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1130        selection_model = self.widget.lstParams.selectionModel()
1131        selection_model.select(index, selection_model.Select | selection_model.Rows)
1132        with self.assertRaises(AssertionError):
1133            # should also throw
1134            self.widget.showMultiConstraint()
1135
1136        # two rows selected
1137        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1138        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1139        selection_model = self.widget.lstParams.selectionModel()
1140        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1141        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1142
1143        # return non-OK from dialog
1144        QtWidgets.QDialog.exec_ = MagicMock()
1145        self.widget.showMultiConstraint()
1146        # Check the dialog called
1147        self.assertTrue(QtWidgets.QDialog.exec_.called)
1148
1149        # return OK from dialog
1150        QtWidgets.QDialog.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted)
1151        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1152
1153        self.widget.showMultiConstraint()
1154
1155        # Make sure the signal has been emitted
1156        self.assertEqual(spy.count(), 1)
1157
1158        # Check the argument value - should be row '1'
1159        self.assertEqual(spy.called()[0]['args'][0], [1])
1160
1161    def testGetRowFromName(self):
1162        """
1163        Helper function for parameter table
1164        """
1165        # select model: cylinder / cylinder
1166        category_index = self.widget.cbCategory.findText("Cylinder")
1167        self.widget.cbCategory.setCurrentIndex(category_index)
1168
1169        model_index = self.widget.cbModel.findText("cylinder")
1170        self.widget.cbModel.setCurrentIndex(model_index)
1171
1172        # several random parameters
1173        self.assertEqual(self.widget.getRowFromName('scale'), 0)
1174        self.assertEqual(self.widget.getRowFromName('length'), 5)
1175
1176    def testGetParamNames(self):
1177        """
1178        Helper function for parameter table
1179        """
1180        # select model: cylinder / cylinder
1181        category_index = self.widget.cbCategory.findText("Cylinder")
1182        self.widget.cbCategory.setCurrentIndex(category_index)
1183
1184        model_index = self.widget.cbModel.findText("cylinder")
1185        self.widget.cbModel.setCurrentIndex(model_index)
1186
1187        cylinder_params = ['scale','background','sld','sld_solvent','radius','length']
1188        # assure all parameters are returned
1189        self.assertEqual(self.widget.getParamNames(), cylinder_params)
1190
1191        # Switch to another model
1192        model_index = self.widget.cbModel.findText("pringle")
1193        self.widget.cbModel.setCurrentIndex(model_index)
1194
1195        # make sure the parameters are different than before
1196        self.assertFalse(self.widget.getParamNames() == cylinder_params)
1197
1198    def testAddConstraintToRow(self):
1199        """
1200        Test the constraint row add operation
1201        """
1202        # select model: cylinder / cylinder
1203        category_index = self.widget.cbCategory.findText("Cylinder")
1204        self.widget.cbCategory.setCurrentIndex(category_index)
1205
1206        model_index = self.widget.cbModel.findText("cylinder")
1207        self.widget.cbModel.setCurrentIndex(model_index)
1208
1209        # Create a constraint object
1210        const = Constraint(parent=None, value=7.0)
1211        row = 2
1212
1213        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1214
1215        # call the method tested
1216        self.widget.addConstraintToRow(constraint=const, row=row)
1217
1218        # Make sure the signal has been emitted
1219        self.assertEqual(spy.count(), 1)
1220
1221        # Check the argument value - should be row 'row'
1222        self.assertEqual(spy.called()[0]['args'][0], [row])
1223
1224        # Assure the row has the constraint
1225        self.assertEqual(self.widget.getConstraintForRow(row), const)
1226        self.assertTrue(self.widget.rowHasConstraint(row))
1227
1228        # assign complex constraint now
1229        const = Constraint(parent=None, param='radius', func='5*sld')
1230        row = 4
1231        # call the method tested
1232        self.widget.addConstraintToRow(constraint=const, row=row)
1233
1234        # Make sure the signal has been emitted
1235        self.assertEqual(spy.count(), 2)
1236
1237        # Check the argument value - should be row 'row'
1238        self.assertEqual(spy.called()[1]['args'][0], [row])
1239
1240        # Assure the row has the constraint
1241        self.assertEqual(self.widget.getConstraintForRow(row), const)
1242        # and it is a complex constraint
1243        self.assertTrue(self.widget.rowHasConstraint(row))
1244
1245    def testAddSimpleConstraint(self):
1246        """
1247        Test the constraint add operation
1248        """
1249        # select model: cylinder / cylinder
1250        category_index = self.widget.cbCategory.findText("Cylinder")
1251        self.widget.cbCategory.setCurrentIndex(category_index)
1252
1253        model_index = self.widget.cbModel.findText("cylinder")
1254        self.widget.cbModel.setCurrentIndex(model_index)
1255
1256        # select two rows
1257        row1 = 1
1258        row2 = 4
1259        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1260        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1261        selection_model = self.widget.lstParams.selectionModel()
1262        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1263        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1264
1265        # define the signal spy
1266        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1267
1268        # call the method tested
1269        self.widget.addSimpleConstraint()
1270
1271        # Make sure the signal has been emitted
1272        self.assertEqual(spy.count(), 2)
1273
1274        # Check the argument value
1275        self.assertEqual(spy.called()[0]['args'][0], [row1])
1276        self.assertEqual(spy.called()[1]['args'][0], [row2])
1277
1278    def testDeleteConstraintOnParameter(self):
1279        """
1280        Test the constraint deletion in model/view
1281        """
1282        # select model: cylinder / cylinder
1283        category_index = self.widget.cbCategory.findText("Cylinder")
1284        self.widget.cbCategory.setCurrentIndex(category_index)
1285
1286        model_index = self.widget.cbModel.findText("cylinder")
1287        self.widget.cbModel.setCurrentIndex(model_index)
1288
1289        # select two rows
1290        row1 = 1
1291        row2 = 4
1292        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1293        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1294        selection_model = self.widget.lstParams.selectionModel()
1295        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1296        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1297
1298        # add constraints
1299        self.widget.addSimpleConstraint()
1300
1301        # deselect the model
1302        selection_model.clear()
1303
1304        # select a single row
1305        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1306
1307        # delete one of the constraints
1308        self.widget.deleteConstraintOnParameter(param='background')
1309
1310        # see that the other constraint is still present
1311        cons = self.widget.getConstraintForRow(4) # 4 = radius
1312        self.assertEqual(cons.param, "radius")
1313        self.assertEqual(cons.value, "20")
1314
1315        # kill the other constraint
1316        self.widget.deleteConstraint()
1317
1318        # see that the other constraint is still present
1319        self.assertEqual(self.widget.getConstraintsForModel(), [('radius', None)])
1320
1321    def testGetConstraintForRow(self):
1322        """
1323        Helper function for parameter table
1324        """
1325        # tested extensively elsewhere
1326        pass
1327
1328    def testRowHasConstraint(self):
1329        """
1330        Helper function for parameter table
1331        """
1332        # select model: cylinder / cylinder
1333        category_index = self.widget.cbCategory.findText("Cylinder")
1334        self.widget.cbCategory.setCurrentIndex(category_index)
1335
1336        model_index = self.widget.cbModel.findText("cylinder")
1337        self.widget.cbModel.setCurrentIndex(model_index)
1338
1339        # select two rows
1340        row1 = 1
1341        row2 = 4
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        con_list = [False, True, False, False, True, False]
1352        new_list = []
1353        for row in range(self.widget._model_model.rowCount()):
1354            new_list.append(self.widget.rowHasConstraint(row))
1355
1356        self.assertEqual(new_list, con_list)
1357
1358    def testRowHasActiveConstraint(self):
1359        """
1360        Helper function for parameter table
1361        """
1362        # select model: cylinder / cylinder
1363        category_index = self.widget.cbCategory.findText("Cylinder")
1364        self.widget.cbCategory.setCurrentIndex(category_index)
1365
1366        model_index = self.widget.cbModel.findText("cylinder")
1367        self.widget.cbModel.setCurrentIndex(model_index)
1368
1369        # select two rows
1370        row1 = 1
1371        row2 = 4
1372        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1373        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1374        selection_model = self.widget.lstParams.selectionModel()
1375        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1376        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1377
1378        # add constraints
1379        self.widget.addSimpleConstraint()
1380
1381        # deactivate the first constraint
1382        constraint_objects = self.widget.getConstraintObjectsForModel()
1383        constraint_objects[0].active = False
1384
1385        con_list = [False, False, False, False, True, False]
1386        new_list = []
1387        for row in range(self.widget._model_model.rowCount()):
1388            new_list.append(self.widget.rowHasActiveConstraint(row))
1389
1390        self.assertEqual(new_list, con_list)
1391
1392    def testGetConstraintsForModel(self):
1393        """
1394        Test the constraint getter for constraint texts
1395        """
1396        # select model: cylinder / cylinder
1397        category_index = self.widget.cbCategory.findText("Cylinder")
1398        self.widget.cbCategory.setCurrentIndex(category_index)
1399
1400        model_index = self.widget.cbModel.findText("cylinder")
1401        self.widget.cbModel.setCurrentIndex(model_index)
1402
1403        # no constraints
1404        self.assertEqual(self.widget.getConstraintsForModel(),[])
1405
1406        # select two rows
1407        row1 = 1
1408        row2 = 4
1409        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1410        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1411        selection_model = self.widget.lstParams.selectionModel()
1412        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1413        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1414
1415        # add constraints
1416        self.widget.addSimpleConstraint()
1417
1418        # simple constraints
1419        # self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
1420        cons = self.widget.getConstraintForRow(1) # 1 - background
1421        self.assertEqual(cons.param, "background")
1422        self.assertEqual(cons.value, "0.001")
1423        cons = self.widget.getConstraintForRow(4) # 4 = radius
1424        self.assertEqual(cons.param, "radius")
1425        self.assertEqual(cons.value, "20")
1426
1427        objects = self.widget.getConstraintObjectsForModel()
1428        self.assertEqual(len(objects), 2)
1429        self.assertEqual(objects[1].value, '20')
1430        self.assertEqual(objects[0].param, 'background')
1431
1432        # add complex constraint
1433        const = Constraint(parent=None, param='scale', func='5*sld')
1434        row = 0
1435        self.widget.addConstraintToRow(constraint=const, row=row)
1436        #self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', None)])
1437        cons = self.widget.getConstraintForRow(4) # 4 = radius
1438        self.assertEqual(cons.param, "radius")
1439        self.assertEqual(cons.value, "20")
1440
1441        objects = self.widget.getConstraintObjectsForModel()
1442        self.assertEqual(len(objects), 3)
1443        self.assertEqual(objects[0].func, '5*sld')
1444
1445    def testReplaceConstraintName(self):
1446        """
1447        Test the replacement of constraint moniker
1448        """
1449        # select model: cylinder / cylinder
1450        category_index = self.widget.cbCategory.findText("Cylinder")
1451        self.widget.cbCategory.setCurrentIndex(category_index)
1452
1453        model_index = self.widget.cbModel.findText("cylinder")
1454        self.widget.cbModel.setCurrentIndex(model_index)
1455
1456        old_name = 'M5'
1457        new_name = 'poopy'
1458        # add complex constraint
1459        const = Constraint(parent=None, param='scale', func='%s.5*sld'%old_name)
1460        row = 0
1461        self.widget.addConstraintToRow(constraint=const, row=row)
1462
1463        # Replace 'm5' with 'poopy'
1464        self.widget.replaceConstraintName(old_name, new_name)
1465
1466        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', 'poopy.5*sld')])
1467
1468
1469if __name__ == "__main__":
1470    unittest.main()
Note: See TracBrowser for help on using the repository browser.