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

Last change on this file since 0101c9f was 4ea8020, checked in by GitHub <noreply@…>, 6 years ago

Merge branch 'ESS_GUI' into ESS_GUI_iss966

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