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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 63319b0 was 63319b0, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

unit tests for constraints: FittingWidget?, FittingPerspective?

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