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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 1a15ada was 1a15ada, checked in by Piotr Rozyczko <rozyczko@…>, 13 months ago

Modified PD header tooltip SASVIEW-1067

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