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

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 80468f6 was 80468f6, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Fixed a few unit test files

  • Property mode set to 100644
File size: 56.4 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+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(), 'Stop fit')
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(), 'Stop fit')
784            self.assertFalse(self.widget.cmdFit.isEnabled())
785
786            # Signal pushed up
787            self.assertEqual(update_spy.count(), 1)
788
789    def testOnHelp(self):
790        """
791        Test various help pages shown in this widget
792        """
793        #Mock the webbrowser.open method
794        self.widget.parent.showHelp = MagicMock()
795        #webbrowser.open = MagicMock()
796
797        # Invoke the action on default tab
798        self.widget.onHelp()
799        # Check if show() got called
800        self.assertTrue(self.widget.parent.showHelp.called)
801        # Assure the filename is correct
802        self.assertIn("fitting_help.html", self.widget.parent.showHelp.call_args[0][0])
803
804        # Change the tab to options
805        self.widget.tabFitting.setCurrentIndex(1)
806        self.widget.onHelp()
807        # Check if show() got called
808        self.assertEqual(self.widget.parent.showHelp.call_count, 2)
809        # Assure the filename is correct
810        self.assertIn("residuals_help.html", self.widget.parent.showHelp.call_args[0][0])
811
812        # Change the tab to smearing
813        self.widget.tabFitting.setCurrentIndex(2)
814        self.widget.onHelp()
815        # Check if show() got called
816        self.assertEqual(self.widget.parent.showHelp.call_count, 3)
817        # Assure the filename is correct
818        self.assertIn("resolution.html", self.widget.parent.showHelp.call_args[0][0])
819
820        # Change the tab to poly
821        self.widget.tabFitting.setCurrentIndex(3)
822        self.widget.onHelp()
823        # Check if show() got called
824        self.assertEqual(self.widget.parent.showHelp.call_count, 4)
825        # Assure the filename is correct
826        self.assertIn("polydispersity.html", self.widget.parent.showHelp.call_args[0][0])
827
828        # Change the tab to magnetism
829        self.widget.tabFitting.setCurrentIndex(4)
830        self.widget.onHelp()
831        # Check if show() got called
832        self.assertEqual(self.widget.parent.showHelp.call_count, 5)
833        # Assure the filename is correct
834        self.assertIn("magnetism.html", self.widget.parent.showHelp.call_args[0][0])
835
836    def testReadFitPage(self):
837        """
838        Read in the fitpage object and restore state
839        """
840        # Set data
841        test_data = Data1D(x=[1,2], y=[1,2])
842
843        # Force same data into logic
844        self.widget.logic.data = test_data
845        self.widget.data_is_loaded = True
846        category_index = self.widget.cbCategory.findText('Sphere')
847        self.widget.cbCategory.setCurrentIndex(category_index)
848        self.widget.parameters_to_fit = ['scale']
849        # Invoke the tested method
850        fp = self.widget.currentState()
851
852        # Prepare modified fit page
853        fp.current_model = 'onion'
854        fp.is_polydisperse = True
855
856        # Read in modified state
857        self.widget.readFitPage(fp)
858
859        # Check if the widget got updated accordingly
860        self.assertEqual(self.widget.cbModel.currentText(), 'onion')
861        self.assertTrue(self.widget.chkPolydispersity.isChecked())
862        #Check if polidispersity tab is available
863        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
864
865        #Check if magnetism box and tab are disabled when 1D data is loaded
866        self.assertFalse(self.widget.chkMagnetism.isEnabled())
867        self.assertFalse(self.widget.tabFitting.isTabEnabled(4))
868
869    def testReadFitPage2D(self):
870        """
871        Read in the fitpage object and restore state
872        """
873        # Set data
874
875        test_data = Data2D(image=[1.0, 2.0, 3.0],
876                           err_image=[0.01, 0.02, 0.03],
877                           qx_data=[0.1, 0.2, 0.3],
878                           qy_data=[0.1, 0.2, 0.3],
879                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
880                           mask=[True, True, True])
881
882        # Force same data into logic
883        self.widget.logic.data = test_data
884        self.widget.data_is_loaded = True
885
886        #item = QtGui.QStandardItem()
887        #updateModelItem(item, [test_data], "test")
888        # Force same data into logic
889        #self.widget.logic.data = item
890        #self.widget.data_is_loaded = True
891
892        category_index = self.widget.cbCategory.findText("Cylinder")
893        self.widget.cbCategory.setCurrentIndex(category_index)
894
895        # Test no fitting params
896        self.widget.parameters_to_fit = ['scale']
897
898        # Invoke the tested method
899        fp = self.widget.currentState()
900
901        # Prepare modified fit page
902        fp.current_model = 'cylinder'
903        fp.is_polydisperse = True
904        fp.is_magnetic = True
905        fp.is2D = True
906
907        # Read in modified state
908        self.widget.readFitPage(fp)
909
910        # Check if the widget got updated accordingly
911        self.assertEqual(self.widget.cbModel.currentText(), 'cylinder')
912        self.assertTrue(self.widget.chkPolydispersity.isChecked())
913        self.assertTrue(self.widget.chkPolydispersity.isEnabled())
914        #Check if polidispersity tab is available
915        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
916
917        #Check if magnetism box and tab are disabled when 1D data is loaded
918        self.assertTrue(self.widget.chkMagnetism.isChecked())
919        self.assertTrue(self.widget.chkMagnetism.isEnabled())
920        self.assertTrue(self.widget.tabFitting.isTabEnabled(4))
921
922    def testCurrentState(self):
923        """
924        Set up the fitpage with current state
925        """
926        # Set data
927        test_data = Data1D(x=[1,2], y=[1,2])
928
929        # Force same data into logic
930        self.widget.logic.data = test_data
931        self.widget.data_is_loaded = True
932        category_index = self.widget.cbCategory.findText("Sphere")
933        self.widget.cbCategory.setCurrentIndex(category_index)
934        self.widget.parameters_to_fit = ['scale']
935
936        # Invoke the tested method
937        fp = self.widget.currentState()
938
939        # Test some entries. (Full testing of fp is done in FitPageTest)
940        self.assertIsInstance(fp.data, Data1D)
941        self.assertListEqual(list(fp.data.x), [1,2])
942        self.assertTrue(fp.data_is_loaded)
943        self.assertEqual(fp.current_category, "Sphere")
944        self.assertEqual(fp.current_model, "adsorbed_layer")
945        self.assertListEqual(fp.parameters_to_fit, ['scale'])
946
947    def testPushFitPage(self):
948        """
949        Push current state of fitpage onto stack
950        """
951        # Set data
952        test_data = Data1D(x=[1,2], y=[1,2])
953
954        # Force same data into logic
955        self.widget.logic.data = test_data
956        self.widget.data_is_loaded = True
957        category_index = self.widget.cbCategory.findText("Sphere")
958
959        # Asses the initial state of stack
960        self.assertEqual(self.widget.page_stack, [])
961
962        # Set the undo flag
963        self.widget.undo_supported = True
964        self.widget.cbCategory.setCurrentIndex(category_index)
965        self.widget.parameters_to_fit = ['scale']
966
967        # Check that the stack is updated
968        self.assertEqual(len(self.widget.page_stack), 1)
969
970        # Change another parameter
971        self.widget._model_model.item(2, 1).setText("3.0")
972        # Check that the stack is updated
973        self.assertEqual(len(self.widget.page_stack), 2)
974
975    def testPopFitPage(self):
976        """
977        Pop current state of fitpage from stack
978        """
979        # TODO: to be added when implementing UNDO/REDO
980        pass
981
982    def testOnMainPageChange(self):
983        """
984        Test update  values of modified parameters in models
985        """
986        # select model: cylinder / cylinder
987        category_index = self.widget.cbCategory.findText("Cylinder")
988        self.widget.cbCategory.setCurrentIndex(category_index)
989
990        model_index = self.widget.cbModel.findText("cylinder")
991        self.widget.cbModel.setCurrentIndex(model_index)
992
993        # modify the initial value of length (different from default)
994        # print self.widget.kernel_module.details['length']
995
996        new_value = "333.0"
997        self.widget._model_model.item(5, 1).setText(new_value)
998
999        # name of modified parameter
1000        name_modified_param = str(self.widget._model_model.item(5, 0).text())
1001
1002         # Check the model
1003        self.assertEqual(self.widget._model_model.rowCount(), 6)
1004        self.assertEqual(self.widget._model_model.columnCount(), 5)
1005
1006        # Test the header
1007        #self.assertEqual(self.widget.lstParams.horizontalHeader().count(), 5)
1008        #self.assertFalse(self.widget.lstParams.horizontalHeader().stretchLastSection())
1009
1010        self.assertEqual(len(self.widget._model_model.header_tooltips), 5)
1011        header_tooltips = ['Select parameter for fitting',
1012                             'Enter parameter value',
1013                             'Enter minimum value for parameter',
1014                             'Enter maximum value for parameter',
1015                             'Unit of the parameter']
1016        for column, tooltip in enumerate(header_tooltips):
1017             self.assertEqual(self.widget._model_model.headerData(column,
1018                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
1019                         header_tooltips[column])
1020
1021        # check that the value has been modified in kernel_module
1022        self.assertEqual(new_value,
1023                         str(self.widget.kernel_module.params[name_modified_param]))
1024
1025        # check that range of variation for this parameter has NOT been changed
1026        self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] )
1027
1028    def testModelContextMenu(self):
1029        """
1030        Test the right click context menu in the parameter table
1031        """
1032        # select model: cylinder / cylinder
1033        category_index = self.widget.cbCategory.findText("Cylinder")
1034        self.widget.cbCategory.setCurrentIndex(category_index)
1035
1036        model_index = self.widget.cbModel.findText("cylinder")
1037        self.widget.cbModel.setCurrentIndex(model_index)
1038
1039        # no rows selected
1040        menu = self.widget.modelContextMenu([])
1041        self.assertEqual(len(menu.actions()), 0)
1042
1043        # 1 row selected
1044        menu = self.widget.modelContextMenu([1])
1045        self.assertEqual(len(menu.actions()), 4)
1046
1047        # 2 rows selected
1048        menu = self.widget.modelContextMenu([1,3])
1049        self.assertEqual(len(menu.actions()), 5)
1050
1051        # 3 rows selected
1052        menu = self.widget.modelContextMenu([1,2,3])
1053        self.assertEqual(len(menu.actions()), 4)
1054
1055        # over 9000
1056        with self.assertRaises(AttributeError):
1057            menu = self.widget.modelContextMenu([i for i in range(9001)])
1058        self.assertEqual(len(menu.actions()), 4)
1059
1060    def testShowModelContextMenu(self):
1061        # select model: cylinder / cylinder
1062        category_index = self.widget.cbCategory.findText("Cylinder")
1063        self.widget.cbCategory.setCurrentIndex(category_index)
1064
1065        model_index = self.widget.cbModel.findText("cylinder")
1066        self.widget.cbModel.setCurrentIndex(model_index)
1067
1068        # No selection
1069        logging.error = MagicMock()
1070        self.widget.showModelDescription = MagicMock()
1071        # Show the menu
1072        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1073
1074        # Assure the description menu is shown
1075        self.assertTrue(self.widget.showModelDescription.called)
1076        self.assertFalse(logging.error.called)
1077
1078        # "select" two rows
1079        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1080        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1081        selection_model = self.widget.lstParams.selectionModel()
1082        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1083        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1084
1085        QtWidgets.QMenu.exec_ = MagicMock()
1086        logging.error = MagicMock()
1087        # Show the menu
1088        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
1089
1090        # Assure the menu is shown
1091        self.assertFalse(logging.error.called)
1092        self.assertTrue(QtWidgets.QMenu.exec_.called)
1093
1094    def testShowMultiConstraint(self):
1095        """
1096        Test the widget update on new multi constraint
1097        """
1098        # select model: cylinder / cylinder
1099        category_index = self.widget.cbCategory.findText("Cylinder")
1100        self.widget.cbCategory.setCurrentIndex(category_index)
1101
1102        model_index = self.widget.cbModel.findText("cylinder")
1103        self.widget.cbModel.setCurrentIndex(model_index)
1104
1105        # nothing selected
1106        with self.assertRaises(AssertionError):
1107            self.widget.showMultiConstraint()
1108
1109        # one row selected
1110        index = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1111        selection_model = self.widget.lstParams.selectionModel()
1112        selection_model.select(index, selection_model.Select | selection_model.Rows)
1113        with self.assertRaises(AssertionError):
1114            # should also throw
1115            self.widget.showMultiConstraint()
1116
1117        # two rows selected
1118        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
1119        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
1120        selection_model = self.widget.lstParams.selectionModel()
1121        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1122        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1123
1124        # return non-OK from dialog
1125        QtWidgets.QDialog.exec_ = MagicMock()
1126        self.widget.showMultiConstraint()
1127        # Check the dialog called
1128        self.assertTrue(QtWidgets.QDialog.exec_.called)
1129
1130        # return OK from dialog
1131        QtWidgets.QDialog.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted)
1132        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1133
1134        self.widget.showMultiConstraint()
1135
1136        # Make sure the signal has been emitted
1137        self.assertEqual(spy.count(), 1)
1138
1139        # Check the argument value - should be row '1'
1140        self.assertEqual(spy.called()[0]['args'][0], [1])
1141
1142    def testGetRowFromName(self):
1143        """
1144        Helper function for parameter table
1145        """
1146        # select model: cylinder / cylinder
1147        category_index = self.widget.cbCategory.findText("Cylinder")
1148        self.widget.cbCategory.setCurrentIndex(category_index)
1149
1150        model_index = self.widget.cbModel.findText("cylinder")
1151        self.widget.cbModel.setCurrentIndex(model_index)
1152
1153        # several random parameters
1154        self.assertEqual(self.widget.getRowFromName('scale'), 0)
1155        self.assertEqual(self.widget.getRowFromName('length'), 5)
1156
1157    def testGetParamNames(self):
1158        """
1159        Helper function for parameter table
1160        """
1161        # select model: cylinder / cylinder
1162        category_index = self.widget.cbCategory.findText("Cylinder")
1163        self.widget.cbCategory.setCurrentIndex(category_index)
1164
1165        model_index = self.widget.cbModel.findText("cylinder")
1166        self.widget.cbModel.setCurrentIndex(model_index)
1167
1168        cylinder_params = ['scale','background','sld','sld_solvent','radius','length']
1169        # assure all parameters are returned
1170        self.assertEqual(self.widget.getParamNames(), cylinder_params)
1171
1172        # Switch to another model
1173        model_index = self.widget.cbModel.findText("pringle")
1174        self.widget.cbModel.setCurrentIndex(model_index)
1175
1176        # make sure the parameters are different than before
1177        self.assertFalse(self.widget.getParamNames() == cylinder_params)
1178
1179    def testAddConstraintToRow(self):
1180        """
1181        Test the constraint row add operation
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        # Create a constraint object
1191        const = Constraint(parent=None, value=7.0)
1192        row = 2
1193
1194        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1195
1196        # call the method tested
1197        self.widget.addConstraintToRow(constraint=const, row=row)
1198
1199        # Make sure the signal has been emitted
1200        self.assertEqual(spy.count(), 1)
1201
1202        # Check the argument value - should be row 'row'
1203        self.assertEqual(spy.called()[0]['args'][0], [row])
1204
1205        # Assure the row has the constraint
1206        self.assertEqual(self.widget.getConstraintForRow(row), const)
1207        self.assertTrue(self.widget.rowHasConstraint(row))
1208
1209        # assign complex constraint now
1210        const = Constraint(parent=None, param='radius', func='5*sld')
1211        row = 4
1212        # call the method tested
1213        self.widget.addConstraintToRow(constraint=const, row=row)
1214
1215        # Make sure the signal has been emitted
1216        self.assertEqual(spy.count(), 2)
1217
1218        # Check the argument value - should be row 'row'
1219        self.assertEqual(spy.called()[1]['args'][0], [row])
1220
1221        # Assure the row has the constraint
1222        self.assertEqual(self.widget.getConstraintForRow(row), const)
1223        # and it is a complex constraint
1224        self.assertTrue(self.widget.rowHasConstraint(row))
1225
1226    def testAddSimpleConstraint(self):
1227        """
1228        Test the constraint add operation
1229        """
1230        # select model: cylinder / cylinder
1231        category_index = self.widget.cbCategory.findText("Cylinder")
1232        self.widget.cbCategory.setCurrentIndex(category_index)
1233
1234        model_index = self.widget.cbModel.findText("cylinder")
1235        self.widget.cbModel.setCurrentIndex(model_index)
1236
1237        # select two rows
1238        row1 = 1
1239        row2 = 4
1240        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1241        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1242        selection_model = self.widget.lstParams.selectionModel()
1243        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1244        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1245
1246        # define the signal spy
1247        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
1248
1249        # call the method tested
1250        self.widget.addSimpleConstraint()
1251
1252        # Make sure the signal has been emitted
1253        self.assertEqual(spy.count(), 2)
1254
1255        # Check the argument value
1256        self.assertEqual(spy.called()[0]['args'][0], [row1])
1257        self.assertEqual(spy.called()[1]['args'][0], [row2])
1258
1259    def testDeleteConstraintOnParameter(self):
1260        """
1261        Test the constraint deletion in model/view
1262        """
1263        # select model: cylinder / cylinder
1264        category_index = self.widget.cbCategory.findText("Cylinder")
1265        self.widget.cbCategory.setCurrentIndex(category_index)
1266
1267        model_index = self.widget.cbModel.findText("cylinder")
1268        self.widget.cbModel.setCurrentIndex(model_index)
1269
1270        # select two rows
1271        row1 = 1
1272        row2 = 4
1273        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1274        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1275        selection_model = self.widget.lstParams.selectionModel()
1276        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1277        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1278
1279        # add constraints
1280        self.widget.addSimpleConstraint()
1281
1282        # deselect the model
1283        selection_model.clear()
1284
1285        # select a single row
1286        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1287
1288        # delete one of the constraints
1289        self.widget.deleteConstraintOnParameter(param='background')
1290
1291        # see that the other constraint is still present
1292        cons = self.widget.getConstraintForRow(4) # 4 = radius
1293        self.assertEqual(cons.param, "radius")
1294        self.assertEqual(cons.value, "20")
1295
1296        # kill the other constraint
1297        self.widget.deleteConstraint()
1298
1299        # see that the other constraint is still present
1300        self.assertEqual(self.widget.getConstraintsForModel(), [('radius', None)])
1301
1302    def testGetConstraintForRow(self):
1303        """
1304        Helper function for parameter table
1305        """
1306        # tested extensively elsewhere
1307        pass
1308
1309    def testRowHasConstraint(self):
1310        """
1311        Helper function for parameter table
1312        """
1313        # select model: cylinder / cylinder
1314        category_index = self.widget.cbCategory.findText("Cylinder")
1315        self.widget.cbCategory.setCurrentIndex(category_index)
1316
1317        model_index = self.widget.cbModel.findText("cylinder")
1318        self.widget.cbModel.setCurrentIndex(model_index)
1319
1320        # select two rows
1321        row1 = 1
1322        row2 = 4
1323        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1324        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1325        selection_model = self.widget.lstParams.selectionModel()
1326        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1327        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1328
1329        # add constraints
1330        self.widget.addSimpleConstraint()
1331
1332        con_list = [False, True, False, False, True, False]
1333        new_list = []
1334        for row in range(self.widget._model_model.rowCount()):
1335            new_list.append(self.widget.rowHasConstraint(row))
1336
1337        self.assertEqual(new_list, con_list)
1338
1339    def testRowHasActiveConstraint(self):
1340        """
1341        Helper function for parameter table
1342        """
1343        # select model: cylinder / cylinder
1344        category_index = self.widget.cbCategory.findText("Cylinder")
1345        self.widget.cbCategory.setCurrentIndex(category_index)
1346
1347        model_index = self.widget.cbModel.findText("cylinder")
1348        self.widget.cbModel.setCurrentIndex(model_index)
1349
1350        # select two rows
1351        row1 = 1
1352        row2 = 4
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        # deactivate the first constraint
1363        constraint_objects = self.widget.getConstraintObjectsForModel()
1364        constraint_objects[0].active = False
1365
1366        con_list = [False, False, False, False, True, False]
1367        new_list = []
1368        for row in range(self.widget._model_model.rowCount()):
1369            new_list.append(self.widget.rowHasActiveConstraint(row))
1370
1371        self.assertEqual(new_list, con_list)
1372
1373    def testGetConstraintsForModel(self):
1374        """
1375        Test the constraint getter for constraint texts
1376        """
1377        # select model: cylinder / cylinder
1378        category_index = self.widget.cbCategory.findText("Cylinder")
1379        self.widget.cbCategory.setCurrentIndex(category_index)
1380
1381        model_index = self.widget.cbModel.findText("cylinder")
1382        self.widget.cbModel.setCurrentIndex(model_index)
1383
1384        # no constraints
1385        self.assertEqual(self.widget.getConstraintsForModel(),[])
1386
1387        # select two rows
1388        row1 = 1
1389        row2 = 4
1390        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
1391        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
1392        selection_model = self.widget.lstParams.selectionModel()
1393        selection_model.select(index1, selection_model.Select | selection_model.Rows)
1394        selection_model.select(index2, selection_model.Select | selection_model.Rows)
1395
1396        # add constraints
1397        self.widget.addSimpleConstraint()
1398
1399        # simple constraints
1400        # self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
1401        cons = self.widget.getConstraintForRow(1) # 1 - background
1402        self.assertEqual(cons.param, "background")
1403        self.assertEqual(cons.value, "0.001")
1404        cons = self.widget.getConstraintForRow(4) # 4 = radius
1405        self.assertEqual(cons.param, "radius")
1406        self.assertEqual(cons.value, "20")
1407
1408        objects = self.widget.getConstraintObjectsForModel()
1409        self.assertEqual(len(objects), 2)
1410        self.assertEqual(objects[1].value, '20')
1411        self.assertEqual(objects[0].param, 'background')
1412
1413        # add complex constraint
1414        const = Constraint(parent=None, param='scale', func='5*sld')
1415        row = 0
1416        self.widget.addConstraintToRow(constraint=const, row=row)
1417        #self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', None)])
1418        cons = self.widget.getConstraintForRow(4) # 4 = radius
1419        self.assertEqual(cons.param, "radius")
1420        self.assertEqual(cons.value, "20")
1421
1422        objects = self.widget.getConstraintObjectsForModel()
1423        self.assertEqual(len(objects), 3)
1424        self.assertEqual(objects[0].func, '5*sld')
1425
1426    def testReplaceConstraintName(self):
1427        """
1428        Test the replacement of constraint moniker
1429        """
1430        # select model: cylinder / cylinder
1431        category_index = self.widget.cbCategory.findText("Cylinder")
1432        self.widget.cbCategory.setCurrentIndex(category_index)
1433
1434        model_index = self.widget.cbModel.findText("cylinder")
1435        self.widget.cbModel.setCurrentIndex(model_index)
1436
1437        old_name = 'M5'
1438        new_name = 'poopy'
1439        # add complex constraint
1440        const = Constraint(parent=None, param='scale', func='%s.5*sld'%old_name)
1441        row = 0
1442        self.widget.addConstraintToRow(constraint=const, row=row)
1443
1444        # Replace 'm5' with 'poopy'
1445        self.widget.replaceConstraintName(old_name, new_name)
1446
1447        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', 'poopy.5*sld')])
1448
1449
1450if __name__ == "__main__":
1451    unittest.main()
Note: See TracBrowser for help on using the repository browser.