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

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

Fixed unit tests for fitting SASVIEW-1024
Corrected behaviour of the Structure factor combo box on model/category change

  • Property mode set to 100644
File size: 57.6 KB
Line 
1import sys
2import unittest
3import time
4import logging
5
6from PyQt5 import QtGui
7from PyQt5 import QtWidgets
8from PyQt5 import QtTest
9from PyQt5 import QtCore
10from unittest.mock import MagicMock
11from twisted.internet import threads
12
13# set up import paths
14import sas.qtgui.path_prepare
15
16# Local
17from sas.qtgui.Utilities.GuiUtils import *
18from sas.qtgui.Perspectives.Fitting.FittingWidget import *
19from sas.qtgui.Perspectives.Fitting.Constraint import Constraint
20import sas.qtgui.Utilities.LocalConfig
21from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy
22from sas.qtgui.Perspectives.Fitting.ModelThread import Calc1D
23from sas.qtgui.Perspectives.Fitting.ModelThread import Calc2D
24
25from sas.qtgui.Plotting.PlotterData import Data1D
26from sas.qtgui.Plotting.PlotterData import Data2D
27
28if not QtWidgets.QApplication.instance():
29    app = QtWidgets.QApplication(sys.argv)
30
31class dummy_manager(object):
32    HELP_DIRECTORY_LOCATION = "html"
33    communicate = Communicate()
34
35class FittingWidgetTest(unittest.TestCase):
36    """Test the fitting widget GUI"""
37
38    def setUp(self):
39        """Create the GUI"""
40        self.widget = FittingWidget(dummy_manager())
41
42    def tearDown(self):
43        """Destroy the GUI"""
44        self.widget.close()
45        del self.widget
46
47    def testDefaults(self):
48        """Test the GUI in its default state"""
49        self.assertIsInstance(self.widget, QtWidgets.QWidget)
50        self.assertEqual(self.widget.windowTitle(), "Fitting")
51        self.assertEqual(self.widget.sizePolicy().Policy(), QtWidgets.QSizePolicy.Fixed)
52        self.assertIsInstance(self.widget.lstParams.model(), QtGui.QStandardItemModel)
53        self.assertIsInstance(self.widget.lstPoly.model(), QtGui.QStandardItemModel)
54        self.assertIsInstance(self.widget.lstMagnetic.model(), QtGui.QStandardItemModel)
55        self.assertFalse(self.widget.cbModel.isEnabled())
56        self.assertFalse(self.widget.cbStructureFactor.isEnabled())
57        self.assertFalse(self.widget.cmdFit.isEnabled())
58        self.assertTrue(self.widget.acceptsData())
59        self.assertFalse(self.widget.data_is_loaded)
60
61    def testSelectCategoryDefault(self):
62        """
63        Test if model categories have been loaded properly
64        """
65        fittingWindow =  self.widget
66
67        #Test loading from json categories
68        category_list = list(fittingWindow.master_category_dict.keys())
69
70        for category in category_list:
71            self.assertNotEqual(fittingWindow.cbCategory.findText(category),-1)
72
73        #Test what is current text in the combobox
74        self.assertEqual(fittingWindow.cbCategory.currentText(), CATEGORY_DEFAULT)
75
76    def testWidgetWithData(self):
77        """
78        Test the instantiation of the widget with initial data
79        """
80        data = Data1D(x=[1,2], y=[1,2])
81        GuiUtils.dataFromItem = MagicMock(return_value=data)
82        item = QtGui.QStandardItem("test")
83
84        widget_with_data = FittingWidget(dummy_manager(), data=item, tab_id=3)
85
86        self.assertEqual(widget_with_data.data, data)
87        self.assertTrue(widget_with_data.data_is_loaded)
88        # self.assertTrue(widget_with_data.cmdFit.isEnabled())
89        self.assertFalse(widget_with_data.acceptsData())
90
91    def testSelectPolydispersity(self):
92        """
93        Test if models have been loaded properly
94        """
95        fittingWindow =  self.widget
96
97        self.assertIsInstance(fittingWindow.lstPoly.itemDelegate(), QtWidgets.QStyledItemDelegate)
98        #Test loading from json categories
99        fittingWindow.SASModelToQModel("cylinder")
100        pd_index = fittingWindow.lstPoly.model().index(0,0)
101        self.assertEqual(str(pd_index.data()), "Distribution of radius")
102        pd_index = fittingWindow.lstPoly.model().index(1,0)
103        self.assertEqual(str(pd_index.data()), "Distribution of length")
104
105        # test the delegate a bit
106        delegate = fittingWindow.lstPoly.itemDelegate()
107        self.assertEqual(len(delegate.POLYDISPERSE_FUNCTIONS), 5)
108        self.assertEqual(delegate.editableParameters(), [1, 2, 3, 4, 5])
109        self.assertEqual(delegate.poly_function, 6)
110        self.assertIsInstance(delegate.combo_updated, QtCore.pyqtBoundSignal)
111
112    def testSelectMagnetism(self):
113        """
114        Test if models have been loaded properly
115        """
116        fittingWindow =  self.widget
117
118        self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), QtWidgets.QStyledItemDelegate)
119        #Test loading from json categories
120        fittingWindow.SASModelToQModel("cylinder")
121        mag_index = fittingWindow.lstMagnetic.model().index(0,0)
122        self.assertEqual(mag_index.data(), "up:frac_i")
123        mag_index = fittingWindow.lstMagnetic.model().index(1,0)
124        self.assertEqual(mag_index.data(), "up:frac_f")
125        mag_index = fittingWindow.lstMagnetic.model().index(2,0)
126        self.assertEqual(mag_index.data(), "up:angle")
127        mag_index = fittingWindow.lstMagnetic.model().index(3,0)
128        self.assertEqual(mag_index.data(), "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        # 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 testPlotData(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        item = QtGui.QStandardItem()
652        updateModelItem(item, test_data, "test")
653        # Force same data into logic
654        self.widget.data = item
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 testOnEmptyFit(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    def testOnEmptyFit2(self):
701        test_data = Data2D(image=[1.0, 2.0, 3.0],
702                           err_image=[0.01, 0.02, 0.03],
703                           qx_data=[0.1, 0.2, 0.3],
704                           qy_data=[0.1, 0.2, 0.3],
705                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
706                           mask=[True, True, True])
707
708        # Force same data into logic
709        item = QtGui.QStandardItem()
710        updateModelItem(item, test_data, "test")
711
712        # Force same data into logic
713        self.widget.data = item
714        category_index = self.widget.cbCategory.findText("Sphere")
715        self.widget.cbCategory.setCurrentIndex(category_index)
716
717        self.widget.show()
718
719        # Test no fitting params
720        self.widget.main_params_to_fit = []
721
722        logging.error = MagicMock()
723
724        self.widget.onFit()
725        self.assertTrue(logging.error.called_once())
726        self.assertTrue(logging.error.called_with('no fitting parameters'))
727        self.widget.close()
728
729    def notestOnFit1D(self):
730        """
731        Test the threaded fitting call
732        """
733        # Set data
734        test_data = Data1D(x=[1,2], y=[1,2])
735        item = QtGui.QStandardItem()
736        updateModelItem(item, test_data, "test")
737        # Force same data into logic
738        self.widget.data = item
739        category_index = self.widget.cbCategory.findText("Sphere")
740        self.widget.cbCategory.setCurrentIndex(category_index)
741
742        self.widget.show()
743
744        # Assing fitting params
745        self.widget.main_params_to_fit = ['scale']
746
747        # Spying on status update signal
748        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
749
750        with threads.deferToThread as MagicMock:
751            self.widget.onFit()
752            # thread called
753            self.assertTrue(threads.deferToThread.called)
754            # thread method is 'compute'
755            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
756
757            # the fit button changed caption and got disabled
758            # could fail if machine fast enough to finish
759            #self.assertEqual(self.widget.cmdFit.text(), 'Stop fit')
760            #self.assertFalse(self.widget.cmdFit.isEnabled())
761
762            # Signal pushed up
763            self.assertEqual(update_spy.count(), 1)
764
765        self.widget.close()
766
767    def notestOnFit2D(self):
768        """
769        Test the threaded fitting call
770        """
771        # Set data
772        test_data = Data2D(image=[1.0, 2.0, 3.0],
773                           err_image=[0.01, 0.02, 0.03],
774                           qx_data=[0.1, 0.2, 0.3],
775                           qy_data=[0.1, 0.2, 0.3],
776                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
777                           mask=[True, True, True])
778
779        # Force same data into logic
780        item = QtGui.QStandardItem()
781        updateModelItem(item, test_data, "test")
782        # Force same data into logic
783        self.widget.data = item
784        category_index = self.widget.cbCategory.findText("Sphere")
785        self.widget.cbCategory.setCurrentIndex(category_index)
786
787        self.widget.show()
788
789        # Assing fitting params
790        self.widget.main_params_to_fit = ['scale']
791
792        # Spying on status update signal
793        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
794
795        with threads.deferToThread as MagicMock:
796            self.widget.onFit()
797            # thread called
798            self.assertTrue(threads.deferToThread.called)
799            # thread method is 'compute'
800            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
801
802            # the fit button changed caption and got disabled
803            #self.assertEqual(self.widget.cmdFit.text(), 'Stop fit')
804            #self.assertFalse(self.widget.cmdFit.isEnabled())
805
806            # Signal pushed up
807            self.assertEqual(update_spy.count(), 1)
808
809    def testOnHelp(self):
810        """
811        Test various help pages shown in this widget
812        """
813        #Mock the webbrowser.open method
814        self.widget.parent.showHelp = MagicMock()
815        #webbrowser.open = MagicMock()
816
817        # Invoke the action on default tab
818        self.widget.onHelp()
819        # Check if show() got called
820        self.assertTrue(self.widget.parent.showHelp.called)
821        # Assure the filename is correct
822        self.assertIn("fitting_help.html", self.widget.parent.showHelp.call_args[0][0])
823
824        # Change the tab to options
825        self.widget.tabFitting.setCurrentIndex(1)
826        self.widget.onHelp()
827        # Check if show() got called
828        self.assertEqual(self.widget.parent.showHelp.call_count, 2)
829        # Assure the filename is correct
830        self.assertIn("residuals_help.html", self.widget.parent.showHelp.call_args[0][0])
831
832        # Change the tab to smearing
833        self.widget.tabFitting.setCurrentIndex(2)
834        self.widget.onHelp()
835        # Check if show() got called
836        self.assertEqual(self.widget.parent.showHelp.call_count, 3)
837        # Assure the filename is correct
838        self.assertIn("resolution.html", self.widget.parent.showHelp.call_args[0][0])
839
840        # Change the tab to poly
841        self.widget.tabFitting.setCurrentIndex(3)
842        self.widget.onHelp()
843        # Check if show() got called
844        self.assertEqual(self.widget.parent.showHelp.call_count, 4)
845        # Assure the filename is correct
846        self.assertIn("polydispersity.html", self.widget.parent.showHelp.call_args[0][0])
847
848        # Change the tab to magnetism
849        self.widget.tabFitting.setCurrentIndex(4)
850        self.widget.onHelp()
851        # Check if show() got called
852        self.assertEqual(self.widget.parent.showHelp.call_count, 5)
853        # Assure the filename is correct
854        self.assertIn("magnetism.html", self.widget.parent.showHelp.call_args[0][0])
855
856    def testReadFitPage(self):
857        """
858        Read in the fitpage object and restore state
859        """
860        # Set data
861        test_data = Data1D(x=[1,2], y=[1,2])
862        item = QtGui.QStandardItem()
863        updateModelItem(item, test_data, "test")
864        # Force same data into logic
865        self.widget.data = item
866
867        # Force same data into logic
868        category_index = self.widget.cbCategory.findText('Sphere')
869
870        self.widget.cbCategory.setCurrentIndex(category_index)
871        self.widget.main_params_to_fit = ['scale']
872        # Invoke the tested method
873        fp = self.widget.currentState()
874
875        # Prepare modified fit page
876        fp.current_model = 'onion'
877        fp.is_polydisperse = True
878
879        # Read in modified state
880        self.widget.readFitPage(fp)
881
882        # Check if the widget got updated accordingly
883        self.assertEqual(self.widget.cbModel.currentText(), 'onion')
884        self.assertTrue(self.widget.chkPolydispersity.isChecked())
885        #Check if polidispersity tab is available
886        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
887
888        #Check if magnetism box and tab are disabled when 1D data is loaded
889        self.assertFalse(self.widget.chkMagnetism.isEnabled())
890        self.assertFalse(self.widget.tabFitting.isTabEnabled(4))
891
892    def testReadFitPage2D(self):
893        """
894        Read in the fitpage object and restore state
895        """
896        # Set data
897
898        test_data = Data2D(image=[1.0, 2.0, 3.0],
899                           err_image=[0.01, 0.02, 0.03],
900                           qx_data=[0.1, 0.2, 0.3],
901                           qy_data=[0.1, 0.2, 0.3],
902                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
903                           mask=[True, True, True])
904
905        # Force same data into logic
906        self.widget.logic.data = test_data
907        self.widget.data_is_loaded = True
908
909        #item = QtGui.QStandardItem()
910        #updateModelItem(item, [test_data], "test")
911        # Force same data into logic
912        #self.widget.logic.data = item
913        #self.widget.data_is_loaded = True
914
915        category_index = self.widget.cbCategory.findText("Cylinder")
916        self.widget.cbCategory.setCurrentIndex(category_index)
917
918        # Test no fitting params
919        self.widget.main_params_to_fit = ['scale']
920
921        # Invoke the tested method
922        fp = self.widget.currentState()
923
924        # Prepare modified fit page
925        fp.current_model = 'cylinder'
926        fp.is_polydisperse = True
927        fp.is_magnetic = True
928        fp.is2D = True
929
930        # Read in modified state
931        self.widget.readFitPage(fp)
932
933        # Check if the widget got updated accordingly
934        self.assertEqual(self.widget.cbModel.currentText(), 'cylinder')
935        self.assertTrue(self.widget.chkPolydispersity.isChecked())
936        self.assertTrue(self.widget.chkPolydispersity.isEnabled())
937        #Check if polidispersity tab is available
938        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
939
940        #Check if magnetism box and tab are disabled when 1D data is loaded
941        self.assertTrue(self.widget.chkMagnetism.isChecked())
942        self.assertTrue(self.widget.chkMagnetism.isEnabled())
943        self.assertTrue(self.widget.tabFitting.isTabEnabled(4))
944
945    def testCurrentState(self):
946        """
947        Set up the fitpage with current state
948        """
949        # Set data
950        test_data = Data1D(x=[1,2], y=[1,2])
951        item = QtGui.QStandardItem()
952        updateModelItem(item, test_data, "test")
953        # Force same data into logic
954        self.widget.data = item
955        category_index = self.widget.cbCategory.findText("Sphere")
956        self.widget.cbCategory.setCurrentIndex(category_index)
957        self.widget.main_params_to_fit = ['scale']
958
959        # Invoke the tested method
960        fp = self.widget.currentState()
961
962        # Test some entries. (Full testing of fp is done in FitPageTest)
963        self.assertIsInstance(fp.data, Data1D)
964        self.assertListEqual(list(fp.data.x), [1,2])
965        self.assertTrue(fp.data_is_loaded)
966        self.assertEqual(fp.current_category, "Sphere")
967        self.assertEqual(fp.current_model, "adsorbed_layer")
968        self.assertListEqual(fp.main_params_to_fit, ['scale'])
969
970    def testPushFitPage(self):
971        """
972        Push current state of fitpage onto stack
973        """
974        # Set data
975        test_data = Data1D(x=[1,2], y=[1,2])
976        item = QtGui.QStandardItem()
977        updateModelItem(item, test_data, "test")
978        # Force same data into logic
979        self.widget.data = item
980        category_index = self.widget.cbCategory.findText("Sphere")
981
982        # Asses the initial state of stack
983        self.assertEqual(self.widget.page_stack, [])
984
985        # Set the undo flag
986        self.widget.undo_supported = True
987        self.widget.cbCategory.setCurrentIndex(category_index)
988        self.widget.main_params_to_fit = ['scale']
989
990        # Check that the stack is updated
991        self.assertEqual(len(self.widget.page_stack), 1)
992
993        # Change another parameter
994        self.widget._model_model.item(2, 1).setText("3.0")
995        # Check that the stack is updated
996        self.assertEqual(len(self.widget.page_stack), 2)
997
998    def testPopFitPage(self):
999        """
1000        Pop current state of fitpage from stack
1001        """
1002        # TODO: to be added when implementing UNDO/REDO
1003        pass
1004
1005    def testOnMainPageChange(self):
1006        """
1007        Test update  values of modified parameters in models
1008        """
1009        # select model: cylinder / cylinder
1010        category_index = self.widget.cbCategory.findText("Cylinder")
1011        self.widget.cbCategory.setCurrentIndex(category_index)
1012
1013        model_index = self.widget.cbModel.findText("cylinder")
1014        self.widget.cbModel.setCurrentIndex(model_index)
1015
1016        # modify the initial value of length (different from default)
1017        # print self.widget.kernel_module.details['length']
1018
1019        new_value = "333.0"
1020        self.widget._model_model.item(5, 1).setText(new_value)
1021
1022        # name of modified parameter
1023        name_modified_param = str(self.widget._model_model.item(5, 0).text())
1024
1025         # Check the model
1026        self.assertEqual(self.widget._model_model.rowCount(), 6)
1027        self.assertEqual(self.widget._model_model.columnCount(), 5)
1028
1029        # Test the header
1030        #self.assertEqual(self.widget.lstParams.horizontalHeader().count(), 5)
1031        #self.assertFalse(self.widget.lstParams.horizontalHeader().stretchLastSection())
1032
1033        self.assertEqual(len(self.widget._model_model.header_tooltips), 5)
1034        header_tooltips = ['Select parameter for fitting',
1035                             'Enter parameter value',
1036                             'Enter minimum value for parameter',
1037                             'Enter maximum value for parameter',
1038                             'Unit of the parameter']
1039        for column, tooltip in enumerate(header_tooltips):
1040             self.assertEqual(self.widget._model_model.headerData(column,
1041                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
1042                         header_tooltips[column])
1043
1044        # check that the value has been modified in kernel_module
1045        self.assertEqual(new_value,
1046                         str(self.widget.kernel_module.params[name_modified_param]))
1047
1048        # check that range of variation for this parameter has NOT been changed
1049        self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] )
1050
1051    def testModelContextMenu(self):
1052        """
1053        Test the right click context menu in the parameter table
1054        """
1055        # select model: cylinder / cylinder
1056        category_index = self.widget.cbCategory.findText("Cylinder")
1057        self.widget.cbCategory.setCurrentIndex(category_index)
1058
1059        model_index = self.widget.cbModel.findText("cylinder")
1060        self.widget.cbModel.setCurrentIndex(model_index)
1061
1062        # no rows selected
1063        menu = self.widget.modelContextMenu([])
1064        self.assertEqual(len(menu.actions()), 0)
1065
1066        # 1 row selected
1067        menu = self.widget.modelContextMenu([1])
1068        self.assertEqual(len(menu.actions()), 4)
1069
1070        # 2 rows selected
1071        menu = self.widget.modelContextMenu([1,3])
1072        self.assertEqual(len(menu.actions()), 5)
1073
1074        # 3 rows selected
1075        menu = self.widget.modelContextMenu([1,2,3])
1076        self.assertEqual(len(menu.actions()), 4)
1077
1078        # over 9000
1079        with self.assertRaises(AttributeError):
1080            menu = self.widget.modelContextMenu([i for i in range(9001)])
1081        self.assertEqual(len(menu.actions()), 4)
1082
1083    def testShowModelContextMenu(self):
1084        # select model: cylinder / cylinder
1085        category_index = self.widget.cbCategory.findText("Cylinder")
1086        self.widget.cbCategory.setCurrentIndex(category_index)
1087
1088        model_index = self.widget.cbModel.findText("cylinder")
1089        self.widget.cbModel.setCurrentIndex(model_index)
1090
1091        # No selection
1092        logging.error = MagicMock()
1093        self.widget.showModelDescription = MagicMock()
1094        # Show the menu
1095        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1096
1097        # Assure the description menu is shown
1098        self.assertTrue(self.widget.showModelDescription.called)
1099        self.assertFalse(logging.error.called)
1100
1101        # "select" two rows
1102        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1103        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1104        selection_model = self.widget.lstParams.selectionModel()
1105        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1106        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1107
1108        QtWidgets.QMenu.exec_ = MagicMock()
1109        logging.error = MagicMock()
1110        # Show the menu
1111        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1112
1113        # Assure the menu is shown
1114        self.assertFalse(logging.error.called)
1115        self.assertTrue(QtWidgets.QMenu.exec_.called)
1116
1117    def testShowMultiConstraint(self):
1118        """
1119        Test the widget update on new multi constraint
1120        """
1121        # select model: cylinder / cylinder
1122        category_index = self.widget.cbCategory.findText("Cylinder")
1123        self.widget.cbCategory.setCurrentIndex(category_index)
1124
1125        model_index = self.widget.cbModel.findText("cylinder")
1126        self.widget.cbModel.setCurrentIndex(model_index)
1127
1128        # nothing selected
1129        with self.assertRaises(AssertionError):
1130            self.widget.showMultiConstraint()
1131
1132        # one row selected
1133        index = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1134        selection_model = self.widget.lstParams.selectionModel()
1135        selection_model.select(index, selection_model.Select | selection_model.Rows)
1136        with self.assertRaises(AssertionError):
1137            # should also throw
1138            self.widget.showMultiConstraint()
1139
1140        # two rows selected
1141        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1142        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1143        selection_model = self.widget.lstParams.selectionModel()
1144        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1145        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1146
1147        # return non-OK from dialog
1148        QtWidgets.QDialog.exec_ = MagicMock()
1149        self.widget.showMultiConstraint()
1150        # Check the dialog called
1151        self.assertTrue(QtWidgets.QDialog.exec_.called)
1152
1153        # return OK from dialog
1154        QtWidgets.QDialog.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted)
1155        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1156
1157        self.widget.showMultiConstraint()
1158
1159        # Make sure the signal has been emitted
1160        self.assertEqual(spy.count(), 1)
1161
1162        # Check the argument value - should be row '1'
1163        self.assertEqual(spy.called()[0]['args'][0], [1])
1164
1165    def testGetRowFromName(self):
1166        """
1167        Helper function for parameter table
1168        """
1169        # select model: cylinder / cylinder
1170        category_index = self.widget.cbCategory.findText("Cylinder")
1171        self.widget.cbCategory.setCurrentIndex(category_index)
1172
1173        model_index = self.widget.cbModel.findText("cylinder")
1174        self.widget.cbModel.setCurrentIndex(model_index)
1175
1176        # several random parameters
1177        self.assertEqual(self.widget.getRowFromName('scale'), 0)
1178        self.assertEqual(self.widget.getRowFromName('length'), 5)
1179
1180    def testGetParamNames(self):
1181        """
1182        Helper function for parameter table
1183        """
1184        # select model: cylinder / cylinder
1185        category_index = self.widget.cbCategory.findText("Cylinder")
1186        self.widget.cbCategory.setCurrentIndex(category_index)
1187
1188        model_index = self.widget.cbModel.findText("cylinder")
1189        self.widget.cbModel.setCurrentIndex(model_index)
1190
1191        cylinder_params = ['scale','background','sld','sld_solvent','radius','length']
1192        # assure all parameters are returned
1193        self.assertEqual(self.widget.getParamNames(), cylinder_params)
1194
1195        # Switch to another model
1196        model_index = self.widget.cbModel.findText("pringle")
1197        self.widget.cbModel.setCurrentIndex(model_index)
1198
1199        # make sure the parameters are different than before
1200        self.assertFalse(self.widget.getParamNames() == cylinder_params)
1201
1202    def testAddConstraintToRow(self):
1203        """
1204        Test the constraint row add operation
1205        """
1206        # select model: cylinder / cylinder
1207        category_index = self.widget.cbCategory.findText("Cylinder")
1208        self.widget.cbCategory.setCurrentIndex(category_index)
1209
1210        model_index = self.widget.cbModel.findText("cylinder")
1211        self.widget.cbModel.setCurrentIndex(model_index)
1212
1213        # Create a constraint object
1214        const = Constraint(parent=None, value=7.0)
1215        row = 2
1216
1217        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1218
1219        # call the method tested
1220        self.widget.addConstraintToRow(constraint=const, row=row)
1221
1222        # Make sure the signal has been emitted
1223        self.assertEqual(spy.count(), 1)
1224
1225        # Check the argument value - should be row 'row'
1226        self.assertEqual(spy.called()[0]['args'][0], [row])
1227
1228        # Assure the row has the constraint
1229        self.assertEqual(self.widget.getConstraintForRow(row), const)
1230        self.assertTrue(self.widget.rowHasConstraint(row))
1231
1232        # assign complex constraint now
1233        const = Constraint(parent=None, param='radius', func='5*sld')
1234        row = 4
1235        # call the method tested
1236        self.widget.addConstraintToRow(constraint=const, row=row)
1237
1238        # Make sure the signal has been emitted
1239        self.assertEqual(spy.count(), 2)
1240
1241        # Check the argument value - should be row 'row'
1242        self.assertEqual(spy.called()[1]['args'][0], [row])
1243
1244        # Assure the row has the constraint
1245        self.assertEqual(self.widget.getConstraintForRow(row), const)
1246        # and it is a complex constraint
1247        self.assertTrue(self.widget.rowHasConstraint(row))
1248
1249    def testAddSimpleConstraint(self):
1250        """
1251        Test the constraint add operation
1252        """
1253        # select model: cylinder / cylinder
1254        category_index = self.widget.cbCategory.findText("Cylinder")
1255        self.widget.cbCategory.setCurrentIndex(category_index)
1256
1257        model_index = self.widget.cbModel.findText("cylinder")
1258        self.widget.cbModel.setCurrentIndex(model_index)
1259
1260        # select two rows
1261        row1 = 1
1262        row2 = 4
1263        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1264        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1265        selection_model = self.widget.lstParams.selectionModel()
1266        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1267        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1268
1269        # define the signal spy
1270        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1271
1272        # call the method tested
1273        self.widget.addSimpleConstraint()
1274
1275        # Make sure the signal has been emitted
1276        self.assertEqual(spy.count(), 2)
1277
1278        # Check the argument value
1279        self.assertEqual(spy.called()[0]['args'][0], [row1])
1280        self.assertEqual(spy.called()[1]['args'][0], [row2])
1281
1282    def testDeleteConstraintOnParameter(self):
1283        """
1284        Test the constraint deletion in model/view
1285        """
1286        # select model: cylinder / cylinder
1287        category_index = self.widget.cbCategory.findText("Cylinder")
1288        self.widget.cbCategory.setCurrentIndex(category_index)
1289
1290        model_index = self.widget.cbModel.findText("cylinder")
1291        self.widget.cbModel.setCurrentIndex(model_index)
1292
1293        # select two rows
1294        row1 = 1
1295        row2 = 4
1296        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1297        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1298        selection_model = self.widget.lstParams.selectionModel()
1299        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1300        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1301
1302        # add constraints
1303        self.widget.addSimpleConstraint()
1304
1305        # deselect the model
1306        selection_model.clear()
1307
1308        # select a single row
1309        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1310
1311        # delete one of the constraints
1312        self.widget.deleteConstraintOnParameter(param='background')
1313
1314        # see that the other constraint is still present
1315        cons = self.widget.getConstraintForRow(4) # 4 = radius
1316        self.assertEqual(cons.param, "radius")
1317        self.assertEqual(cons.value, "20")
1318
1319        # kill the other constraint
1320        self.widget.deleteConstraint()
1321
1322        # see that the other constraint is still present
1323        self.assertEqual(self.widget.getConstraintsForModel(), [('radius', None)])
1324
1325    def testGetConstraintForRow(self):
1326        """
1327        Helper function for parameter table
1328        """
1329        # tested extensively elsewhere
1330        pass
1331
1332    def testRowHasConstraint(self):
1333        """
1334        Helper function for parameter table
1335        """
1336        # select model: cylinder / cylinder
1337        category_index = self.widget.cbCategory.findText("Cylinder")
1338        self.widget.cbCategory.setCurrentIndex(category_index)
1339
1340        model_index = self.widget.cbModel.findText("cylinder")
1341        self.widget.cbModel.setCurrentIndex(model_index)
1342
1343        # select two rows
1344        row1 = 1
1345        row2 = 4
1346        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1347        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1348        selection_model = self.widget.lstParams.selectionModel()
1349        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1350        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1351
1352        # add constraints
1353        self.widget.addSimpleConstraint()
1354
1355        con_list = [False, True, False, False, True, False]
1356        new_list = []
1357        for row in range(self.widget._model_model.rowCount()):
1358            new_list.append(self.widget.rowHasConstraint(row))
1359
1360        self.assertEqual(new_list, con_list)
1361
1362    def testRowHasActiveConstraint(self):
1363        """
1364        Helper function for parameter table
1365        """
1366        # select model: cylinder / cylinder
1367        category_index = self.widget.cbCategory.findText("Cylinder")
1368        self.widget.cbCategory.setCurrentIndex(category_index)
1369
1370        model_index = self.widget.cbModel.findText("cylinder")
1371        self.widget.cbModel.setCurrentIndex(model_index)
1372
1373        # select two rows
1374        row1 = 1
1375        row2 = 4
1376        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1377        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1378        selection_model = self.widget.lstParams.selectionModel()
1379        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1380        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1381
1382        # add constraints
1383        self.widget.addSimpleConstraint()
1384
1385        # deactivate the first constraint
1386        constraint_objects = self.widget.getConstraintObjectsForModel()
1387        constraint_objects[0].active = False
1388
1389        con_list = [False, False, False, False, True, False]
1390        new_list = []
1391        for row in range(self.widget._model_model.rowCount()):
1392            new_list.append(self.widget.rowHasActiveConstraint(row))
1393
1394        self.assertEqual(new_list, con_list)
1395
1396    def testGetConstraintsForModel(self):
1397        """
1398        Test the constraint getter for constraint texts
1399        """
1400        # select model: cylinder / cylinder
1401        category_index = self.widget.cbCategory.findText("Cylinder")
1402        self.widget.cbCategory.setCurrentIndex(category_index)
1403
1404        model_index = self.widget.cbModel.findText("cylinder")
1405        self.widget.cbModel.setCurrentIndex(model_index)
1406
1407        # no constraints
1408        self.assertEqual(self.widget.getConstraintsForModel(),[])
1409
1410        # select two rows
1411        row1 = 1
1412        row2 = 4
1413        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1414        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1415        selection_model = self.widget.lstParams.selectionModel()
1416        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1417        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1418
1419        # add constraints
1420        self.widget.addSimpleConstraint()
1421
1422        # simple constraints
1423        # self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
1424        cons = self.widget.getConstraintForRow(1) # 1 - background
1425        self.assertEqual(cons.param, "background")
1426        self.assertEqual(cons.value, "0.001")
1427        cons = self.widget.getConstraintForRow(4) # 4 = radius
1428        self.assertEqual(cons.param, "radius")
1429        self.assertEqual(cons.value, "20")
1430
1431        objects = self.widget.getConstraintObjectsForModel()
1432        self.assertEqual(len(objects), 2)
1433        self.assertEqual(objects[1].value, '20')
1434        self.assertEqual(objects[0].param, 'background')
1435
1436        # add complex constraint
1437        const = Constraint(parent=None, param='scale', func='5*sld')
1438        row = 0
1439        self.widget.addConstraintToRow(constraint=const, row=row)
1440        #self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', None)])
1441        cons = self.widget.getConstraintForRow(4) # 4 = radius
1442        self.assertEqual(cons.param, "radius")
1443        self.assertEqual(cons.value, "20")
1444
1445        objects = self.widget.getConstraintObjectsForModel()
1446        self.assertEqual(len(objects), 3)
1447        self.assertEqual(objects[0].func, '5*sld')
1448
1449    def testReplaceConstraintName(self):
1450        """
1451        Test the replacement of constraint moniker
1452        """
1453        # select model: cylinder / cylinder
1454        category_index = self.widget.cbCategory.findText("Cylinder")
1455        self.widget.cbCategory.setCurrentIndex(category_index)
1456
1457        model_index = self.widget.cbModel.findText("cylinder")
1458        self.widget.cbModel.setCurrentIndex(model_index)
1459
1460        old_name = 'M5'
1461        new_name = 'poopy'
1462        # add complex constraint
1463        const = Constraint(parent=None, param='scale', func='%s.5*sld'%old_name)
1464        row = 0
1465        self.widget.addConstraintToRow(constraint=const, row=row)
1466
1467        # Replace 'm5' with 'poopy'
1468        self.widget.replaceConstraintName(old_name, new_name)
1469
1470        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', 'poopy.5*sld')])
1471
1472
1473if __name__ == "__main__":
1474    unittest.main()
Note: See TracBrowser for help on using the repository browser.