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

ESS_GUIESS_GUI_InvariantESS_GUI_batch_fittingESS_GUI_ordering
Last change on this file since d2007a8 was d2007a8, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 2 months ago

Fitpage state copy/paste reworked. SASVIEW-1053 and SASVIEW-1196

  • Property mode set to 100644
File size: 58.6 KB
Line 
1import sys
2import unittest
3import time
4import logging
5
6from PyQt5 import QtGui
7from PyQt5 import QtWidgets
8from PyQt5 import QtTest
9from PyQt5 import QtCore
10from unittest.mock import MagicMock
11from twisted.internet import threads
12
13# set up import paths
14import sas.qtgui.path_prepare
15
16# Local
17from sas.qtgui.Utilities.GuiUtils import *
18from sas.qtgui.Perspectives.Fitting.FittingWidget import *
19from sas.qtgui.Perspectives.Fitting.Constraint import Constraint
20import sas.qtgui.Utilities.LocalConfig
21from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy
22from sas.qtgui.Perspectives.Fitting.ModelThread import Calc1D
23from sas.qtgui.Perspectives.Fitting.ModelThread import Calc2D
24
25from sas.qtgui.Plotting.PlotterData import Data1D
26from sas.qtgui.Plotting.PlotterData import Data2D
27
28if not QtWidgets.QApplication.instance():
29    app = QtWidgets.QApplication(sys.argv)
30
31class dummy_manager(object):
32    HELP_DIRECTORY_LOCATION = "html"
33    communicate = Communicate()
34
35class FittingWidgetTest(unittest.TestCase):
36    """Test the fitting widget GUI"""
37
38    def setUp(self):
39        """Create the GUI"""
40        self.widget = FittingWidget(dummy_manager())
41
42    def tearDown(self):
43        """Destroy the GUI"""
44        self.widget.close()
45        del self.widget
46
47    def testDefaults(self):
48        """Test the GUI in its default state"""
49        self.assertIsInstance(self.widget, QtWidgets.QWidget)
50        self.assertEqual(self.widget.windowTitle(), "Fitting")
51        self.assertEqual(self.widget.sizePolicy().Policy(), QtWidgets.QSizePolicy.Fixed)
52        self.assertIsInstance(self.widget.lstParams.model(), QtGui.QStandardItemModel)
53        self.assertIsInstance(self.widget.lstPoly.model(), QtGui.QStandardItemModel)
54        self.assertIsInstance(self.widget.lstMagnetic.model(), QtGui.QStandardItemModel)
55        self.assertFalse(self.widget.cbModel.isEnabled())
56        self.assertFalse(self.widget.cbStructureFactor.isEnabled())
57        self.assertFalse(self.widget.cmdFit.isEnabled())
58        self.assertTrue(self.widget.acceptsData())
59        self.assertFalse(self.widget.data_is_loaded)
60
61    def testSelectCategoryDefault(self):
62        """
63        Test if model categories have been loaded properly
64        """
65        fittingWindow =  self.widget
66
67        #Test loading from json categories
68        category_list = list(fittingWindow.master_category_dict.keys())
69
70        for category in category_list:
71            self.assertNotEqual(fittingWindow.cbCategory.findText(category),-1)
72
73        #Test what is current text in the combobox
74        self.assertEqual(fittingWindow.cbCategory.currentText(), CATEGORY_DEFAULT)
75
76    def testWidgetWithData(self):
77        """
78        Test the instantiation of the widget with initial data
79        """
80        data = Data1D(x=[1,2], y=[1,2])
81        GuiUtils.dataFromItem = MagicMock(return_value=data)
82        item = QtGui.QStandardItem("test")
83
84        widget_with_data = FittingWidget(dummy_manager(), data=item, tab_id=3)
85
86        self.assertEqual(widget_with_data.data, data)
87        self.assertTrue(widget_with_data.data_is_loaded)
88        # self.assertTrue(widget_with_data.cmdFit.isEnabled())
89        self.assertFalse(widget_with_data.acceptsData())
90
91    def testSelectPolydispersity(self):
92        """
93        Test if models have been loaded properly
94        """
95        fittingWindow =  self.widget
96
97        self.assertIsInstance(fittingWindow.lstPoly.itemDelegate(), QtWidgets.QStyledItemDelegate)
98        #Test loading from json categories
99        fittingWindow.SASModelToQModel("cylinder")
100        pd_index = fittingWindow.lstPoly.model().index(0,0)
101        self.assertEqual(str(pd_index.data()), "Distribution of radius")
102        pd_index = fittingWindow.lstPoly.model().index(1,0)
103        self.assertEqual(str(pd_index.data()), "Distribution of length")
104
105        # test the delegate a bit
106        delegate = fittingWindow.lstPoly.itemDelegate()
107        self.assertEqual(len(delegate.POLYDISPERSE_FUNCTIONS), 5)
108        self.assertEqual(delegate.editableParameters(), [1, 2, 3, 4, 5])
109        self.assertEqual(delegate.poly_function, 6)
110        self.assertIsInstance(delegate.combo_updated, QtCore.pyqtBoundSignal)
111
112    def testSelectMagnetism(self):
113        """
114        Test if models have been loaded properly
115        """
116        fittingWindow =  self.widget
117
118        self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), QtWidgets.QStyledItemDelegate)
119        #Test loading from json categories
120        fittingWindow.SASModelToQModel("cylinder")
121        mag_index = fittingWindow.lstMagnetic.model().index(0,0)
122        self.assertEqual(mag_index.data(), "up_frac_i")
123        mag_index = fittingWindow.lstMagnetic.model().index(1,0)
124        self.assertEqual(mag_index.data(), "up_frac_f")
125        mag_index = fittingWindow.lstMagnetic.model().index(2,0)
126        self.assertEqual(mag_index.data(), "up_angle")
127        mag_index = fittingWindow.lstMagnetic.model().index(3,0)
128        self.assertEqual(mag_index.data(), "sld_M0")
129        mag_index = fittingWindow.lstMagnetic.model().index(4,0)
130        self.assertEqual(mag_index.data(), "sld_mtheta")
131        mag_index = fittingWindow.lstMagnetic.model().index(5,0)
132        self.assertEqual(mag_index.data(), "sld_mphi")
133        mag_index = fittingWindow.lstMagnetic.model().index(6,0)
134        self.assertEqual(mag_index.data(), "sld_solvent_M0")
135        mag_index = fittingWindow.lstMagnetic.model().index(7,0)
136        self.assertEqual(mag_index.data(), "sld_solvent_mtheta")
137        mag_index = fittingWindow.lstMagnetic.model().index(8,0)
138        self.assertEqual(mag_index.data(), "sld_solvent_mphi")
139
140        # test the delegate a bit
141        delegate = fittingWindow.lstMagnetic.itemDelegate()
142        self.assertEqual(delegate.editableParameters(), [1, 2, 3])
143
144    def testSelectStructureFactor(self):
145        """
146        Test if structure factors have been loaded properly
147        """
148        fittingWindow =  self.widget
149
150        #Test for existence in combobox
151        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("stickyhardsphere"),-1)
152        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("hayter_msa"),-1)
153        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("squarewell"),-1)
154        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("hardsphere"),-1)
155
156        #Test what is current text in the combobox
157        self.assertTrue(fittingWindow.cbCategory.currentText(), "None")
158
159    def testSignals(self):
160        """
161        Test the widget emitted signals
162        """
163        pass
164
165    def testSelectCategory(self):
166        """
167        Assure proper behaviour on changing category
168        """
169        self.widget.show()
170        self.assertEqual(self.widget._previous_category_index, 0)
171        # confirm the model combo contains no models
172        self.assertEqual(self.widget.cbModel.count(), 0)
173
174        # invoke the method by changing the index
175        category_index = self.widget.cbCategory.findText("Shape Independent")
176        self.widget.cbCategory.setCurrentIndex(category_index)
177
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 deviation/mean).\n'+
399                            'For angles this can be either std deviation or half width (for uniform distributions) in degrees',
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        # (note that n == 1 by default in core_multi_shell so this increases index by 2)
616        more_rows = self.widget._model_model.rowCount() - last_row
617        self.assertEqual(more_rows, 4) # 4 new rows: 2 params per index
618
619        # Set to 0
620        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(0)
621        self.assertEqual(self.widget._model_model.rowCount(), last_row - 2)
622
623    def testPlotTheory(self):
624        """
625        See that theory item can produce a chart
626        """
627        # By default, the compute/plot button is disabled
628        self.assertFalse(self.widget.cmdPlot.isEnabled())
629        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
630
631        # Assign a model
632        self.widget.show()
633        # Change the category index so we have a model available
634        category_index = self.widget.cbCategory.findText("Sphere")
635        self.widget.cbCategory.setCurrentIndex(category_index)
636
637        # Check the enablement/text
638        self.assertTrue(self.widget.cmdPlot.isEnabled())
639        self.assertEqual(self.widget.cmdPlot.text(), 'Calculate')
640
641        # Spying on plot update signal
642        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
643
644        # Press Calculate
645        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
646
647        # Observe cmdPlot caption change
648        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
649
650        # Make sure the signal has NOT been emitted
651        self.assertEqual(spy.count(), 0)
652
653        # Click again
654        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
655
656        # This time, we got the update signal
657        self.assertEqual(spy.count(), 0)
658
659    def notestPlotData(self):
660        """
661        See that data item can produce a chart
662        """
663        # By default, the compute/plot button is disabled
664        self.assertFalse(self.widget.cmdPlot.isEnabled())
665        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
666
667        # Set data
668        test_data = Data1D(x=[1,2], y=[1,2])
669        item = QtGui.QStandardItem()
670        updateModelItem(item, test_data, "test")
671        # Force same data into logic
672        self.widget.data = item
673
674        # Change the category index so we have a model available
675        category_index = self.widget.cbCategory.findText("Sphere")
676        self.widget.cbCategory.setCurrentIndex(category_index)
677
678        # Check the enablement/text
679        self.assertTrue(self.widget.cmdPlot.isEnabled())
680        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
681
682        # Spying on plot update signal
683        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
684
685        # Press Calculate
686        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
687
688        # Observe cmdPlot caption did not change
689        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
690
691        # Make sure the signal has been emitted == new plot
692        self.assertEqual(spy.count(), 1)
693
694    def testOnEmptyFit(self):
695        """
696        Test a 1D/2D fit with no parameters
697        """
698        # Set data
699        test_data = Data1D(x=[1,2], y=[1,2])
700        item = QtGui.QStandardItem()
701        updateModelItem(item, test_data, "test")
702        # Force same data into logic
703        self.widget.data = item
704
705        category_index = self.widget.cbCategory.findText("Sphere")
706        self.widget.cbCategory.setCurrentIndex(category_index)
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    # to be fixed after functionality is ready
910    def notestReadFitPage2D(self):
911        """
912        Read in the fitpage object and restore state
913        """
914        # Set data
915
916        test_data = Data2D(image=[1.0, 2.0, 3.0],
917                           err_image=[0.01, 0.02, 0.03],
918                           qx_data=[0.1, 0.2, 0.3],
919                           qy_data=[0.1, 0.2, 0.3],
920                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
921                           mask=[True, True, True])
922
923        # Force same data into logic
924        self.widget.logic.data = test_data
925        self.widget.data_is_loaded = True
926
927        #item = QtGui.QStandardItem()
928        #updateModelItem(item, [test_data], "test")
929        # Force same data into logic
930        #self.widget.logic.data = item
931        #self.widget.data_is_loaded = True
932
933        category_index = self.widget.cbCategory.findText("Cylinder")
934        self.widget.cbCategory.setCurrentIndex(category_index)
935
936        # Test no fitting params
937        self.widget.main_params_to_fit = ['scale']
938
939        # Invoke the tested method
940        fp = self.widget.currentState()
941
942        # Prepare modified fit page
943        fp.current_model = 'cylinder'
944        fp.is_polydisperse = True
945        fp.is_magnetic = True
946        fp.is2D = True
947
948        # Read in modified state
949        self.widget.readFitPage(fp)
950
951        # Check if the widget got updated accordingly
952        self.assertEqual(self.widget.cbModel.currentText(), 'cylinder')
953        self.assertTrue(self.widget.chkPolydispersity.isChecked())
954        self.assertTrue(self.widget.chkPolydispersity.isEnabled())
955        #Check if polidispersity tab is available
956        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
957
958        #Check if magnetism box and tab are disabled when 1D data is loaded
959        self.assertTrue(self.widget.chkMagnetism.isChecked())
960        self.assertTrue(self.widget.chkMagnetism.isEnabled())
961        self.assertTrue(self.widget.tabFitting.isTabEnabled(4))
962
963    def testCurrentState(self):
964        """
965        Set up the fitpage with current state
966        """
967        # Set data
968        test_data = Data1D(x=[1,2], y=[1,2])
969        item = QtGui.QStandardItem()
970        updateModelItem(item, test_data, "test")
971        # Force same data into logic
972        self.widget.data = item
973        category_index = self.widget.cbCategory.findText("Sphere")
974        self.widget.cbCategory.setCurrentIndex(category_index)
975        self.widget.main_params_to_fit = ['scale']
976
977        # Invoke the tested method
978        fp = self.widget.currentState()
979
980        # Test some entries. (Full testing of fp is done in FitPageTest)
981        self.assertIsInstance(fp.data, Data1D)
982        self.assertListEqual(list(fp.data.x), [1,2])
983        self.assertTrue(fp.data_is_loaded)
984        self.assertEqual(fp.current_category, "Sphere")
985        self.assertEqual(fp.current_model, "adsorbed_layer")
986        self.assertListEqual(fp.main_params_to_fit, ['scale'])
987
988    def testPushFitPage(self):
989        """
990        Push current state of fitpage onto stack
991        """
992        # Set data
993        test_data = Data1D(x=[1,2], y=[1,2])
994        item = QtGui.QStandardItem()
995        updateModelItem(item, test_data, "test")
996        # Force same data into logic
997        self.widget.data = item
998        category_index = self.widget.cbCategory.findText("Sphere")
999
1000        # Asses the initial state of stack
1001        self.assertEqual(self.widget.page_stack, [])
1002
1003        # Set the undo flag
1004        self.widget.undo_supported = True
1005        self.widget.cbCategory.setCurrentIndex(category_index)
1006        self.widget.main_params_to_fit = ['scale']
1007
1008        # Check that the stack is updated
1009        self.assertEqual(len(self.widget.page_stack), 1)
1010
1011        # Change another parameter
1012        self.widget._model_model.item(3, 1).setText("3.0")
1013
1014        # Check that the stack is updated
1015        self.assertEqual(len(self.widget.page_stack), 2)
1016
1017    def testPopFitPage(self):
1018        """
1019        Pop current state of fitpage from stack
1020        """
1021        # TODO: to be added when implementing UNDO/REDO
1022        pass
1023
1024    def testOnMainPageChange(self):
1025        """
1026        Test update  values of modified parameters in models
1027        """
1028        # select model: cylinder / cylinder
1029        category_index = self.widget.cbCategory.findText("Cylinder")
1030        self.widget.cbCategory.setCurrentIndex(category_index)
1031
1032        model_index = self.widget.cbModel.findText("cylinder")
1033        self.widget.cbModel.setCurrentIndex(model_index)
1034
1035        # modify the initial value of length (different from default)
1036        # print self.widget.kernel_module.details['length']
1037
1038        new_value = "333.0"
1039        self.widget._model_model.item(5, 1).setText(new_value)
1040
1041        # name of modified parameter
1042        name_modified_param = str(self.widget._model_model.item(5, 0).text())
1043
1044         # Check the model
1045        self.assertEqual(self.widget._model_model.rowCount(), 7)
1046        self.assertEqual(self.widget._model_model.columnCount(), 5)
1047
1048        # Test the header
1049        #self.assertEqual(self.widget.lstParams.horizontalHeader().count(), 5)
1050        #self.assertFalse(self.widget.lstParams.horizontalHeader().stretchLastSection())
1051
1052        self.assertEqual(len(self.widget._model_model.header_tooltips), 5)
1053        header_tooltips = ['Select parameter for fitting',
1054                             'Enter parameter value',
1055                             'Enter minimum value for parameter',
1056                             'Enter maximum value for parameter',
1057                             'Unit of the parameter']
1058        for column, tooltip in enumerate(header_tooltips):
1059             self.assertEqual(self.widget._model_model.headerData(column,
1060                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
1061                         header_tooltips[column])
1062
1063        # check that the value has been modified in kernel_module
1064        self.assertEqual(new_value,
1065                         str(self.widget.kernel_module.params[name_modified_param]))
1066
1067        # check that range of variation for this parameter has NOT been changed
1068        self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] )
1069
1070    def testModelContextMenu(self):
1071        """
1072        Test the right click context menu in the parameter table
1073        """
1074        # select model: cylinder / cylinder
1075        category_index = self.widget.cbCategory.findText("Cylinder")
1076        self.widget.cbCategory.setCurrentIndex(category_index)
1077
1078        model_index = self.widget.cbModel.findText("cylinder")
1079        self.widget.cbModel.setCurrentIndex(model_index)
1080
1081        # no rows selected
1082        menu = self.widget.modelContextMenu([])
1083        self.assertEqual(len(menu.actions()), 0)
1084
1085        # 1 row selected
1086        menu = self.widget.modelContextMenu([1])
1087        self.assertEqual(len(menu.actions()), 4)
1088
1089        # 2 rows selected
1090        menu = self.widget.modelContextMenu([1,3])
1091        self.assertEqual(len(menu.actions()), 5)
1092
1093        # 3 rows selected
1094        menu = self.widget.modelContextMenu([1,2,3])
1095        self.assertEqual(len(menu.actions()), 4)
1096
1097        # over 9000
1098        with self.assertRaises(AttributeError):
1099            menu = self.widget.modelContextMenu([i for i in range(9001)])
1100        self.assertEqual(len(menu.actions()), 4)
1101
1102    def testShowModelContextMenu(self):
1103        # select model: cylinder / cylinder
1104        category_index = self.widget.cbCategory.findText("Cylinder")
1105        self.widget.cbCategory.setCurrentIndex(category_index)
1106
1107        model_index = self.widget.cbModel.findText("cylinder")
1108        self.widget.cbModel.setCurrentIndex(model_index)
1109
1110        # No selection
1111        logging.error = MagicMock()
1112        self.widget.showModelDescription = MagicMock()
1113        # Show the menu
1114        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1115
1116        # Assure the description menu is shown
1117        self.assertTrue(self.widget.showModelDescription.called)
1118        self.assertFalse(logging.error.called)
1119
1120        # "select" two rows
1121        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1122        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1123        selection_model = self.widget.lstParams.selectionModel()
1124        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1125        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1126
1127        QtWidgets.QMenu.exec_ = MagicMock()
1128        logging.error = MagicMock()
1129        # Show the menu
1130        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1131
1132        # Assure the menu is shown
1133        self.assertFalse(logging.error.called)
1134        self.assertTrue(QtWidgets.QMenu.exec_.called)
1135
1136    def testShowMultiConstraint(self):
1137        """
1138        Test the widget update on new multi constraint
1139        """
1140        # select model: cylinder / cylinder
1141        category_index = self.widget.cbCategory.findText("Cylinder")
1142        self.widget.cbCategory.setCurrentIndex(category_index)
1143
1144        model_index = self.widget.cbModel.findText("cylinder")
1145        self.widget.cbModel.setCurrentIndex(model_index)
1146
1147        # nothing selected
1148        with self.assertRaises(AssertionError):
1149            self.widget.showMultiConstraint()
1150
1151        # one row selected
1152        index = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1153        selection_model = self.widget.lstParams.selectionModel()
1154        selection_model.select(index, selection_model.Select | selection_model.Rows)
1155        with self.assertRaises(AssertionError):
1156            # should also throw
1157            self.widget.showMultiConstraint()
1158
1159        # two rows selected
1160        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1161        index2 = self.widget.lstParams.model().index(3, 0, QtCore.QModelIndex())
1162        selection_model = self.widget.lstParams.selectionModel()
1163        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1164        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1165
1166        # return non-OK from dialog
1167        QtWidgets.QDialog.exec_ = MagicMock()
1168        self.widget.showMultiConstraint()
1169        # Check the dialog called
1170        self.assertTrue(QtWidgets.QDialog.exec_.called)
1171
1172        # return OK from dialog
1173        QtWidgets.QDialog.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted)
1174        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1175
1176        self.widget.showMultiConstraint()
1177
1178        # Make sure the signal has been emitted
1179        self.assertEqual(spy.count(), 1)
1180
1181        # Check the argument value - should be row '1'
1182        self.assertEqual(spy.called()[0]['args'][0], [1])
1183
1184    def testGetRowFromName(self):
1185        """
1186        Helper function for parameter table
1187        """
1188        # select model: cylinder / cylinder
1189        category_index = self.widget.cbCategory.findText("Cylinder")
1190        self.widget.cbCategory.setCurrentIndex(category_index)
1191
1192        model_index = self.widget.cbModel.findText("cylinder")
1193        self.widget.cbModel.setCurrentIndex(model_index)
1194
1195        # several random parameters
1196        self.assertEqual(self.widget.getRowFromName('scale'), 0)
1197        self.assertEqual(self.widget.getRowFromName('length'), 6)
1198
1199    def testGetParamNames(self):
1200        """
1201        Helper function for parameter table
1202        """
1203        # select model: cylinder / cylinder
1204        category_index = self.widget.cbCategory.findText("Cylinder")
1205        self.widget.cbCategory.setCurrentIndex(category_index)
1206
1207        model_index = self.widget.cbModel.findText("cylinder")
1208        self.widget.cbModel.setCurrentIndex(model_index)
1209
1210        cylinder_params = ['scale','background','sld','sld_solvent','radius','length']
1211        # assure all parameters are returned
1212        self.assertEqual(self.widget.getParamNames(), cylinder_params)
1213
1214        # Switch to another model
1215        model_index = self.widget.cbModel.findText("pringle")
1216        self.widget.cbModel.setCurrentIndex(model_index)
1217
1218        # make sure the parameters are different than before
1219        self.assertFalse(self.widget.getParamNames() == cylinder_params)
1220
1221    def testAddConstraintToRow(self):
1222        """
1223        Test the constraint row add operation
1224        """
1225        # select model: cylinder / cylinder
1226        category_index = self.widget.cbCategory.findText("Cylinder")
1227        self.widget.cbCategory.setCurrentIndex(category_index)
1228
1229        model_index = self.widget.cbModel.findText("cylinder")
1230        self.widget.cbModel.setCurrentIndex(model_index)
1231
1232        # Create a constraint object
1233        const = Constraint(parent=None, value=7.0)
1234        row = 3
1235
1236        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1237
1238        # call the method tested
1239        self.widget.addConstraintToRow(constraint=const, row=row)
1240
1241        # Make sure the signal has been emitted
1242        self.assertEqual(spy.count(), 1)
1243
1244        # Check the argument value - should be row 'row'
1245        self.assertEqual(spy.called()[0]['args'][0], [row])
1246
1247        # Assure the row has the constraint
1248        self.assertEqual(self.widget.getConstraintForRow(row), const)
1249        self.assertTrue(self.widget.rowHasConstraint(row))
1250
1251        # assign complex constraint now
1252        const = Constraint(parent=None, param='radius', func='5*sld')
1253        row = 5
1254        # call the method tested
1255        self.widget.addConstraintToRow(constraint=const, row=row)
1256
1257        # Make sure the signal has been emitted
1258        self.assertEqual(spy.count(), 2)
1259
1260        # Check the argument value - should be row 'row'
1261        self.assertEqual(spy.called()[1]['args'][0], [row])
1262
1263        # Assure the row has the constraint
1264        self.assertEqual(self.widget.getConstraintForRow(row), const)
1265        # and it is a complex constraint
1266        self.assertTrue(self.widget.rowHasConstraint(row))
1267
1268    def testAddSimpleConstraint(self):
1269        """
1270        Test the constraint add operation
1271        """
1272        # select model: cylinder / cylinder
1273        category_index = self.widget.cbCategory.findText("Cylinder")
1274        self.widget.cbCategory.setCurrentIndex(category_index)
1275
1276        model_index = self.widget.cbModel.findText("cylinder")
1277        self.widget.cbModel.setCurrentIndex(model_index)
1278
1279        # select two rows
1280        row1 = 1
1281        row2 = 4
1282        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1283        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1284        selection_model = self.widget.lstParams.selectionModel()
1285        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1286        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1287
1288        # define the signal spy
1289        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1290
1291        # call the method tested
1292        self.widget.addSimpleConstraint()
1293
1294        # Make sure the signal has been emitted
1295        self.assertEqual(spy.count(), 2)
1296
1297        # Check the argument value
1298        self.assertEqual(spy.called()[0]['args'][0], [row1])
1299        self.assertEqual(spy.called()[1]['args'][0], [row2])
1300
1301    def testDeleteConstraintOnParameter(self):
1302        """
1303        Test the constraint deletion in model/view
1304        """
1305        # select model: cylinder / cylinder
1306        category_index = self.widget.cbCategory.findText("Cylinder")
1307        self.widget.cbCategory.setCurrentIndex(category_index)
1308
1309        model_index = self.widget.cbModel.findText("cylinder")
1310        self.widget.cbModel.setCurrentIndex(model_index)
1311
1312        row1 = 1
1313        row2 = 5
1314
1315        param1 = "background"
1316        param2 = "radius"
1317
1318        #default_value1 = "0.001"
1319        default_value2 = "20"
1320
1321        # select two rows
1322        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1323        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1324        selection_model = self.widget.lstParams.selectionModel()
1325        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1326        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1327
1328        # add constraints
1329        self.widget.addSimpleConstraint()
1330
1331        # deselect the model
1332        selection_model.clear()
1333
1334        # select a single row
1335        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1336
1337        # delete one of the constraints
1338        self.widget.deleteConstraintOnParameter(param=param1)
1339
1340        # see that the other constraint is still present
1341        cons = self.widget.getConstraintForRow(row2)
1342        self.assertEqual(cons.param, param2)
1343        self.assertEqual(cons.value, default_value2)
1344
1345        # kill the other constraint
1346        self.widget.deleteConstraint()
1347
1348        # see that the other constraint is still present
1349        self.assertEqual(self.widget.getConstraintsForModel(), [(param2, None)])
1350
1351    def testGetConstraintForRow(self):
1352        """
1353        Helper function for parameter table
1354        """
1355        # tested extensively elsewhere
1356        pass
1357
1358    def testRowHasConstraint(self):
1359        """
1360        Helper function for parameter table
1361        """
1362        # select model: cylinder / cylinder
1363        category_index = self.widget.cbCategory.findText("Cylinder")
1364        self.widget.cbCategory.setCurrentIndex(category_index)
1365
1366        model_index = self.widget.cbModel.findText("cylinder")
1367        self.widget.cbModel.setCurrentIndex(model_index)
1368
1369        row1 = 1
1370        row2 = 5
1371
1372        # select two rows
1373        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1374        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1375        selection_model = self.widget.lstParams.selectionModel()
1376        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1377        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1378
1379        # add constraints
1380        self.widget.addSimpleConstraint()
1381
1382        con_list = [False, True, False, False, False, True, False]
1383        new_list = []
1384        for row in range(self.widget._model_model.rowCount()):
1385            new_list.append(self.widget.rowHasConstraint(row))
1386
1387        self.assertEqual(new_list, con_list)
1388
1389    def testRowHasActiveConstraint(self):
1390        """
1391        Helper function for parameter table
1392        """
1393        # select model: cylinder / cylinder
1394        category_index = self.widget.cbCategory.findText("Cylinder")
1395        self.widget.cbCategory.setCurrentIndex(category_index)
1396
1397        model_index = self.widget.cbModel.findText("cylinder")
1398        self.widget.cbModel.setCurrentIndex(model_index)
1399
1400        row1 = 1
1401        row2 = 5
1402
1403        # select two rows
1404        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1405        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1406        selection_model = self.widget.lstParams.selectionModel()
1407        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1408        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1409
1410        # add constraints
1411        self.widget.addSimpleConstraint()
1412
1413        # deactivate the first constraint
1414        constraint_objects = self.widget.getConstraintObjectsForModel()
1415        constraint_objects[0].active = False
1416
1417        con_list = [False, False, False, False, False, True, False]
1418        new_list = []
1419        for row in range(self.widget._model_model.rowCount()):
1420            new_list.append(self.widget.rowHasActiveConstraint(row))
1421
1422        self.assertEqual(new_list, con_list)
1423
1424    def testGetConstraintsForModel(self):
1425        """
1426        Test the constraint getter for constraint texts
1427        """
1428        # select model: cylinder / cylinder
1429        category_index = self.widget.cbCategory.findText("Cylinder")
1430        self.widget.cbCategory.setCurrentIndex(category_index)
1431
1432        model_index = self.widget.cbModel.findText("cylinder")
1433        self.widget.cbModel.setCurrentIndex(model_index)
1434
1435        # no constraints
1436        self.assertEqual(self.widget.getConstraintsForModel(),[])
1437
1438        row1 = 1
1439        row2 = 5
1440
1441        param1 = "background"
1442        param2 = "radius"
1443
1444        default_value1 = "0.001"
1445        default_value2 = "20"
1446
1447        # select two rows
1448        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1449        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1450        selection_model = self.widget.lstParams.selectionModel()
1451        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1452        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1453
1454        # add constraints
1455        self.widget.addSimpleConstraint()
1456
1457        # simple constraints
1458        # self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
1459        cons = self.widget.getConstraintForRow(row1)
1460        self.assertEqual(cons.param, param1)
1461        self.assertEqual(cons.value, default_value1)
1462        cons = self.widget.getConstraintForRow(row2)
1463        self.assertEqual(cons.param, param2)
1464        self.assertEqual(cons.value, default_value2)
1465
1466        objects = self.widget.getConstraintObjectsForModel()
1467        self.assertEqual(len(objects), 2)
1468        self.assertEqual(objects[1].value, default_value2)
1469        self.assertEqual(objects[0].param, param1)
1470
1471        row = 0
1472        param = "scale"
1473        func = "5*sld"
1474
1475        # add complex constraint
1476        const = Constraint(parent=None, param=param, func=func)
1477        self.widget.addConstraintToRow(constraint=const, row=row)
1478        #self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', None)])
1479        cons = self.widget.getConstraintForRow(row2)
1480        self.assertEqual(cons.param, param2)
1481        self.assertEqual(cons.value, default_value2)
1482
1483        objects = self.widget.getConstraintObjectsForModel()
1484        self.assertEqual(len(objects), 3)
1485        self.assertEqual(objects[0].func, func)
1486
1487    def testReplaceConstraintName(self):
1488        """
1489        Test the replacement of constraint moniker
1490        """
1491        # select model: cylinder / cylinder
1492        category_index = self.widget.cbCategory.findText("Cylinder")
1493        self.widget.cbCategory.setCurrentIndex(category_index)
1494
1495        model_index = self.widget.cbModel.findText("cylinder")
1496        self.widget.cbModel.setCurrentIndex(model_index)
1497
1498        old_name = 'M5'
1499        new_name = 'poopy'
1500        # add complex constraint
1501        const = Constraint(parent=None, param='scale', func='%s.5*sld'%old_name)
1502        row = 0
1503        self.widget.addConstraintToRow(constraint=const, row=row)
1504
1505        # Replace 'm5' with 'poopy'
1506        self.widget.replaceConstraintName(old_name, new_name)
1507
1508        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', 'poopy.5*sld')])
1509
1510
1511if __name__ == "__main__":
1512    unittest.main()
Note: See TracBrowser for help on using the repository browser.