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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 712db9e was 712db9e, checked in by rozyczko <piotr.rozyczko@…>, 6 years ago

Set pointer to the correct set of data in batch fitting.

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