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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 2eeda93 was 2eeda93, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Working version of Save/Load? Analysis. SASVIEW-983.
Changed the default behaviour of Category/Model? combos:
Selecting a category does not pre-select the first model now.

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