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

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 00b7ddf0 was 00b7ddf0, checked in by Torin Cooper-Bennun <torin.cooper-bennun@…>, 18 months ago

[CHERRY-PICK FROM 4f80a834f] put sub-heading creation into FittingUtilities?; fix a couple of caused issues; fix affected tests

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