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

ESS_GUI
Last change on this file was 6edd344, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

More unit test fixes.

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