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

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

Initial version of the C&S widget

  • Property mode set to 100644
File size: 40.2 KB
Line 
1import sys
2import unittest
3import time
4import logging
5
6from PyQt5 import QtGui
7from PyQt5 import QtWidgets
8from PyQt5 import QtTest
9from PyQt5 import QtCore
10from unittest.mock import MagicMock
11from twisted.internet import threads
12
13# set up import paths
14import sas.qtgui.path_prepare
15
16# Local
17from sas.qtgui.Utilities.GuiUtils import *
18from sas.qtgui.Perspectives.Fitting.FittingWidget import *
19from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy
20
21from sas.qtgui.Plotting.PlotterData import Data1D
22from sas.qtgui.Plotting.PlotterData import Data2D
23
24if not QtWidgets.QApplication.instance():
25    app = QtWidgets.QApplication(sys.argv)
26
27class dummy_manager(object):
28    HELP_DIRECTORY_LOCATION = "html"
29    communicate = Communicate()
30
31class FittingWidgetTest(unittest.TestCase):
32    """Test the fitting widget GUI"""
33
34    def setUp(self):
35        """Create the GUI"""
36        self.widget = FittingWidget(dummy_manager())
37
38    def tearDown(self):
39        """Destroy the GUI"""
40        self.widget.close()
41        del self.widget
42
43    def testDefaults(self):
44        """Test the GUI in its default state"""
45        self.assertIsInstance(self.widget, QtWidgets.QWidget)
46        self.assertEqual(self.widget.windowTitle(), "Fitting")
47        self.assertEqual(self.widget.sizePolicy().Policy(), QtWidgets.QSizePolicy.Fixed)
48        self.assertIsInstance(self.widget.lstParams.model(), QtGui.QStandardItemModel)
49        self.assertIsInstance(self.widget.lstPoly.model(), QtGui.QStandardItemModel)
50        self.assertIsInstance(self.widget.lstMagnetic.model(), QtGui.QStandardItemModel)
51        self.assertFalse(self.widget.cbModel.isEnabled())
52        self.assertFalse(self.widget.cbStructureFactor.isEnabled())
53        self.assertFalse(self.widget.cmdFit.isEnabled())
54        self.assertTrue(self.widget.acceptsData())
55        self.assertFalse(self.widget.data_is_loaded)
56
57    def testSelectCategoryDefault(self):
58        """
59        Test if model categories have been loaded properly
60        """
61        fittingWindow =  self.widget
62
63        #Test loading from json categories
64        category_list = list(fittingWindow.master_category_dict.keys())
65
66        for category in category_list:
67            self.assertNotEqual(fittingWindow.cbCategory.findText(category),-1)
68
69        #Test what is current text in the combobox
70        self.assertEqual(fittingWindow.cbCategory.currentText(), CATEGORY_DEFAULT)
71
72    def testWidgetWithData(self):
73        """
74        Test the instantiation of the widget with initial data
75        """
76        data = Data1D(x=[1,2], y=[1,2])
77        GuiUtils.dataFromItem = MagicMock(return_value=data)
78        item = QtGui.QStandardItem("test")
79
80        widget_with_data = FittingWidget(dummy_manager(), data=item, tab_id=3)
81
82        self.assertEqual(widget_with_data.data, data)
83        self.assertTrue(widget_with_data.data_is_loaded)
84        # self.assertTrue(widget_with_data.cmdFit.isEnabled())
85        self.assertFalse(widget_with_data.acceptsData())
86
87    def testSelectPolydispersity(self):
88        """
89        Test if models have been loaded properly
90        """
91        fittingWindow =  self.widget
92
93        self.assertIsInstance(fittingWindow.lstPoly.itemDelegate(), QtWidgets.QStyledItemDelegate)
94        #Test loading from json categories
95        fittingWindow.SASModelToQModel("cylinder")
96        pd_index = fittingWindow.lstPoly.model().index(0,0)
97        self.assertEqual(str(pd_index.data()), "Distribution of radius")
98        pd_index = fittingWindow.lstPoly.model().index(1,0)
99        self.assertEqual(str(pd_index.data()), "Distribution of length")
100
101        # test the delegate a bit
102        delegate = fittingWindow.lstPoly.itemDelegate()
103        self.assertEqual(len(delegate.POLYDISPERSE_FUNCTIONS), 5)
104        self.assertEqual(delegate.editableParameters(), [1, 2, 3, 4, 5])
105        self.assertEqual(delegate.poly_function, 6)
106        self.assertIsInstance(delegate.combo_updated, QtCore.pyqtBoundSignal)
107
108    def testSelectMagnetism(self):
109        """
110        Test if models have been loaded properly
111        """
112        fittingWindow =  self.widget
113
114        self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), QtWidgets.QStyledItemDelegate)
115        #Test loading from json categories
116        fittingWindow.SASModelToQModel("cylinder")
117        mag_index = fittingWindow.lstMagnetic.model().index(0,0)
118        self.assertEqual(mag_index.data(), "up:frac_i")
119        mag_index = fittingWindow.lstMagnetic.model().index(1,0)
120        self.assertEqual(mag_index.data(), "up:frac_f")
121        mag_index = fittingWindow.lstMagnetic.model().index(2,0)
122        self.assertEqual(mag_index.data(), "up:angle")
123        mag_index = fittingWindow.lstMagnetic.model().index(3,0)
124        self.assertEqual(mag_index.data(), "M0:sld")
125        mag_index = fittingWindow.lstMagnetic.model().index(4,0)
126        self.assertEqual(mag_index.data(), "mtheta:sld")
127        mag_index = fittingWindow.lstMagnetic.model().index(5,0)
128        self.assertEqual(mag_index.data(), "mphi:sld")
129        mag_index = fittingWindow.lstMagnetic.model().index(6,0)
130        self.assertEqual(mag_index.data(), "M0:sld_solvent")
131        mag_index = fittingWindow.lstMagnetic.model().index(7,0)
132        self.assertEqual(mag_index.data(), "mtheta:sld_solvent")
133        mag_index = fittingWindow.lstMagnetic.model().index(8,0)
134        self.assertEqual(mag_index.data(), "mphi:sld_solvent")
135
136        # test the delegate a bit
137        delegate = fittingWindow.lstMagnetic.itemDelegate()
138        self.assertEqual(delegate.editableParameters(), [1, 2, 3])
139
140    def testSelectStructureFactor(self):
141        """
142        Test if structure factors have been loaded properly
143        """
144        fittingWindow =  self.widget
145
146        #Test for existence in combobox
147        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("stickyhardsphere"),-1)
148        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("hayter_msa"),-1)
149        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("squarewell"),-1)
150        self.assertNotEqual(fittingWindow.cbStructureFactor.findText("hardsphere"),-1)
151
152        #Test what is current text in the combobox
153        self.assertTrue(fittingWindow.cbCategory.currentText(), "None")
154
155    def testSignals(self):
156        """
157        Test the widget emitted signals
158        """
159        pass
160
161    def testSelectCategory(self):
162        """
163        Assure proper behaviour on changing category
164        """
165        self.widget.show()
166        self.assertEqual(self.widget._previous_category_index, 0)
167        # confirm the model combo contains no models
168        self.assertEqual(self.widget.cbModel.count(), 0)
169
170        # invoke the method by changing the index
171        category_index = self.widget.cbCategory.findText("Shape Independent")
172        self.widget.cbCategory.setCurrentIndex(category_index)
173
174        # test the model combo content
175        self.assertEqual(self.widget.cbModel.count(), 29)
176
177        # Try to change back to default
178        self.widget.cbCategory.setCurrentIndex(0)
179
180        # Observe no such luck
181        self.assertEqual(self.widget.cbCategory.currentIndex(), 6)
182        self.assertEqual(self.widget.cbModel.count(), 29)
183
184        # Set the structure factor
185        structure_index=self.widget.cbCategory.findText(CATEGORY_STRUCTURE)
186        self.widget.cbCategory.setCurrentIndex(structure_index)
187        # check the enablement of controls
188        self.assertFalse(self.widget.cbModel.isEnabled())
189        self.assertTrue(self.widget.cbStructureFactor.isEnabled())
190
191    def testSelectModel(self):
192        """
193        Assure proper behaviour on changing model
194        """
195        self.widget.show()
196        # Change the category index so we have some models
197        category_index = self.widget.cbCategory.findText("Shape Independent")
198        self.widget.cbCategory.setCurrentIndex(category_index)
199
200        # check the enablement of controls
201        self.assertTrue(self.widget.cbModel.isEnabled())
202        self.assertFalse(self.widget.cbStructureFactor.isEnabled())
203
204        # set up the model update spy
205        # spy = QtSignalSpy(self.widget._model_model, self.widget._model_model.itemChanged)
206
207        # mock the tested methods
208        self.widget.SASModelToQModel = MagicMock()
209        self.widget.createDefaultDataset = MagicMock()
210        self.widget.calculateQGridForModel = MagicMock()
211        #
212        # Now change the model
213        self.widget.cbModel.setCurrentIndex(3)
214        self.assertEqual(self.widget.cbModel.currentText(),'dab')
215
216        # No data sent -> no index set, only createDefaultDataset called
217        self.assertTrue(self.widget.createDefaultDataset.called)
218        self.assertTrue(self.widget.SASModelToQModel.called)
219        self.assertFalse(self.widget.calculateQGridForModel.called)
220
221        # Let's tell the widget that data has been loaded
222        self.widget.data_is_loaded = True
223        # Reset the sasmodel index
224        self.widget.cbModel.setCurrentIndex(1)
225        self.assertEqual(self.widget.cbModel.currentText(),'broad_peak')
226
227        # Observe calculateQGridForModel called
228        self.assertTrue(self.widget.calculateQGridForModel.called)
229
230    def testSelectFactor(self):
231        """
232        Assure proper behaviour on changing structure factor
233        """
234        self.widget.show()
235        # Change the category index so we have some models
236        category_index = self.widget.cbCategory.findText("Shape Independent")
237        self.widget.cbCategory.setCurrentIndex(category_index)
238        # Change the model to one that supports structure factors
239        model_index = self.widget.cbModel.findText('fractal_core_shell')
240        self.widget.cbModel.setCurrentIndex(model_index)
241
242        # Check that the factor combo is active and the default is chosen
243        self.assertTrue(self.widget.cbStructureFactor.isEnabled())
244        self.assertEqual(self.widget.cbStructureFactor.currentText(), STRUCTURE_DEFAULT)
245
246        # We have this many rows in the model
247        rowcount = self.widget._model_model.rowCount()
248        #self.assertEqual(self.widget._model_model.rowCount(), 8)
249
250        # Change structure factor to something more exciting
251        structure_index = self.widget.cbStructureFactor.findText('squarewell')
252        self.widget.cbStructureFactor.setCurrentIndex(structure_index)
253
254        # We have 4 more rows now
255        self.assertEqual(self.widget._model_model.rowCount(), rowcount+4)
256
257        # Switch models
258        self.widget.cbModel.setCurrentIndex(0)
259
260        # Observe factor reset to None
261        self.assertEqual(self.widget.cbStructureFactor.currentText(), STRUCTURE_DEFAULT)
262
263        # Switch category to structure factor
264        structure_index=self.widget.cbCategory.findText(CATEGORY_STRUCTURE)
265        self.widget.cbCategory.setCurrentIndex(structure_index)
266        # Observe the correct enablement
267        self.assertTrue(self.widget.cbStructureFactor.isEnabled())
268        self.assertFalse(self.widget.cbModel.isEnabled())
269        self.assertEqual(self.widget._model_model.rowCount(), 0)
270
271        # Choose the last factor
272        last_index = self.widget.cbStructureFactor.count()
273        self.widget.cbStructureFactor.setCurrentIndex(last_index-1)
274        # Do we have all the rows?
275        self.assertEqual(self.widget._model_model.rowCount(), 4)
276
277        # Are the command buttons properly enabled?
278        self.assertTrue(self.widget.cmdPlot.isEnabled())
279        self.assertFalse(self.widget.cmdFit.isEnabled())
280
281    def testReadCategoryInfo(self):
282        """
283        Check the category file reader
284        """
285        # Tested in default checks
286        pass
287
288    def testUpdateParamsFromModel(self):
289        """
290        Checks the sasmodel parameter update from QModel items
291        """
292        # Tested in default checks
293        pass
294
295    def testCreateTheoryIndex(self):
296        """
297        Test the data->QIndex conversion
298        """
299        # set up the model update spy
300        spy = QtSignalSpy(self.widget._model_model, self.widget.communicate.updateTheoryFromPerspectiveSignal)
301
302        self.widget.show()
303        # Change the category index so we have some models
304        self.widget.cbCategory.setCurrentIndex(1)
305
306        # Create the index
307        self.widget.createTheoryIndex(Data1D(x=[1,2], y=[1,2]))
308
309        # Make sure the signal has been emitted
310        self.assertEqual(spy.count(), 1)
311
312        # Check the argument type
313        self.assertIsInstance(spy.called()[0]['args'][0], QtGui.QStandardItem)
314
315    def testCalculateQGridForModel(self):
316        """
317        Check that the fitting 1D data object is ready
318        """
319        # Mock the thread creation
320        threads.deferToThread = MagicMock()
321        # Model for theory
322        self.widget.SASModelToQModel("cylinder")
323        # Call the tested method
324        self.widget.calculateQGridForModel()
325        time.sleep(1)
326        # Test the mock
327        self.assertTrue(threads.deferToThread.called)
328        self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, "compute")
329
330    def testCalculateResiduals(self):
331        """
332        Check that the residuals are calculated and plots updated
333        """
334        test_data = Data1D(x=[1,2], y=[1,2])
335
336        # Model for theory
337        self.widget.SASModelToQModel("cylinder")
338        # Invoke the tested method
339        self.widget.calculateResiduals(test_data)
340        # Check the Chi2 value - should be undetermined
341        self.assertEqual(self.widget.lblChi2Value.text(), '---')
342
343        # Force same data into logic
344        self.widget.logic.data = test_data
345        self.widget.calculateResiduals(test_data)
346        # Now, the difference is 0, as data is the same
347        self.assertEqual(self.widget.lblChi2Value.text(), '0')
348
349        # Change data
350        test_data_2 = Data1D(x=[1,2], y=[2.1,3.49])
351        self.widget.logic.data = test_data_2
352        self.widget.calculateResiduals(test_data)
353        # Now, the difference is non-zero
354        self.assertEqual(float(self.widget.lblChi2Value.text()), 1.7151)
355
356    def testSetPolyModel(self):
357        """
358        Test the polydispersity model setup
359        """
360        self.widget.show()
361        # Change the category index so we have a model with no poly
362        category_index = self.widget.cbCategory.findText("Shape Independent")
363        self.widget.cbCategory.setCurrentIndex(category_index)
364        # Check the poly model
365        self.assertEqual(self.widget._poly_model.rowCount(), 0)
366        self.assertEqual(self.widget._poly_model.columnCount(), 0)
367
368        # Change the category index so we have a model available
369        self.widget.cbCategory.setCurrentIndex(2)
370
371        # Check the poly model
372        self.assertEqual(self.widget._poly_model.rowCount(), 4)
373        self.assertEqual(self.widget._poly_model.columnCount(), 8)
374
375        # Test the header
376        self.assertEqual(self.widget.lstPoly.horizontalHeader().count(), 8)
377        self.assertFalse(self.widget.lstPoly.horizontalHeader().stretchLastSection())
378
379        # Test tooltips
380        self.assertEqual(len(self.widget._poly_model.header_tooltips), 8)
381
382        header_tooltips = ['Select parameter for fitting',
383                             'Enter polydispersity ratio (STD/mean). '
384                             'STD: standard deviation from the mean value',
385                             'Enter minimum value for parameter',
386                             'Enter maximum value for parameter',
387                             'Enter number of points for parameter',
388                             'Enter number of sigmas parameter',
389                             'Select distribution function',
390                             'Select filename with user-definable distribution']
391        for column, tooltip in enumerate(header_tooltips):
392             self.assertEqual(self.widget._poly_model.headerData( column,
393                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
394                         header_tooltips[column])
395
396        # Test presence of comboboxes in last column
397        for row in range(self.widget._poly_model.rowCount()):
398            func_index = self.widget._poly_model.index(row, 6)
399            self.assertTrue(isinstance(self.widget.lstPoly.indexWidget(func_index), QtWidgets.QComboBox))
400            self.assertIn('Distribution of', self.widget._poly_model.item(row, 0).text())
401        #self.widget.close()
402
403    def testPolyModelChange(self):
404        """
405        Polydispersity model changed - test all possible scenarios
406        """
407        self.widget.show()
408        # Change the category index so we have a model with polydisp
409        category_index = self.widget.cbCategory.findText("Cylinder")
410        self.widget.cbCategory.setCurrentIndex(category_index)
411
412        # click on a poly parameter checkbox
413        index = self.widget._poly_model.index(0,0)
414        # Set the checbox
415        self.widget._poly_model.item(0,0).setCheckState(2)
416        # Assure the parameter is added
417        self.assertEqual(self.widget.parameters_to_fit, ['radius_bell.width'])
418
419        # Add another parameter
420        self.widget._poly_model.item(2,0).setCheckState(2)
421        # Assure the parameters are added
422        self.assertEqual(self.widget.parameters_to_fit, ['radius_bell.width', 'length.width'])
423
424        # Change the min/max values
425        self.assertEqual(self.widget.kernel_module.details['radius_bell'][1], 0.0)
426        self.widget._poly_model.item(0,2).setText("1.0")
427        self.assertEqual(self.widget.kernel_module.details['radius_bell'][1], 1.0)
428
429        # Change the number of points
430        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
431        self.widget._poly_model.item(0,4).setText("22")
432        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
433        # try something stupid
434        self.widget._poly_model.item(0,4).setText("butt")
435        # see that this didn't annoy the control at all
436        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
437
438        # Change the number of sigmas
439        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 3)
440        self.widget._poly_model.item(0,5).setText("222")
441        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
442        # try something stupid again
443        self.widget._poly_model.item(0,4).setText("beer")
444        # no efect
445        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
446
447    def testOnPolyComboIndexChange(self):
448        """
449        Test the slot method for polydisp. combo box index change
450        """
451        self.widget.show()
452        # Change the category index so we have a model with polydisp
453        category_index = self.widget.cbCategory.findText("Cylinder")
454        self.widget.cbCategory.setCurrentIndex(category_index)
455
456        # call method with default settings
457        self.widget.onPolyComboIndexChange('gaussian', 0)
458        # check values
459        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
460        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 3)
461        # Change the index
462        self.widget.onPolyComboIndexChange('rectangle', 0)
463        # check values
464        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
465        self.assertAlmostEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 1.70325, 5)
466        # Change the index
467        self.widget.onPolyComboIndexChange('lognormal', 0)
468        # check values
469        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
470        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
471        # Change the index
472        self.widget.onPolyComboIndexChange('schulz', 0)
473        # check values
474        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
475        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
476
477        # mock up file load
478        self.widget.loadPolydispArray = MagicMock()
479        # Change to 'array'
480        self.widget.onPolyComboIndexChange('array', 0)
481        # See the mock fire
482        self.assertTrue(self.widget.loadPolydispArray.called)
483
484    def testLoadPolydispArray(self):
485        """
486        Test opening of the load file dialog for 'array' polydisp. function
487        """
488        filename = os.path.join("UnitTesting", "testdata_noexist.txt")
489        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
490        self.widget.show()
491        # Change the category index so we have a model with polydisp
492        category_index = self.widget.cbCategory.findText("Cylinder")
493        self.widget.cbCategory.setCurrentIndex(category_index)
494
495        self.widget.onPolyComboIndexChange('array', 0)
496        # check values - unchanged since the file doesn't exist
497        self.assertTrue(self.widget._poly_model.item(0, 1).isEnabled())
498        with self.assertRaises(AttributeError):
499            self.widget.disp_model()
500
501        # good file
502        filename = os.path.join("UnitTesting", "testdata.txt")
503        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
504
505        self.widget.onPolyComboIndexChange('array', 0)
506        # check values - disabled control, present weights
507        self.assertFalse(self.widget._poly_model.item(0, 1).isEnabled())
508        self.assertEqual(self.widget.disp_model.weights[0], 2.83954)
509        self.assertEqual(len(self.widget.disp_model.weights), 19)
510        self.assertEqual(len(self.widget.disp_model.values), 19)
511        self.assertEqual(self.widget.disp_model.values[0], 0.0)
512        self.assertEqual(self.widget.disp_model.values[18], 3.67347)
513
514    def testSetMagneticModel(self):
515        """
516        Test the magnetic model setup
517        """
518        self.widget.show()
519        # Change the category index so we have a model available
520        category_index = self.widget.cbCategory.findText("Sphere")
521        self.widget.cbCategory.setCurrentIndex(category_index)
522
523        # Check the magnetic model
524        self.assertEqual(self.widget._magnet_model.rowCount(), 9)
525        self.assertEqual(self.widget._magnet_model.columnCount(), 5)
526
527        # Test the header
528        self.assertEqual(self.widget.lstMagnetic.horizontalHeader().count(), 5)
529        self.assertFalse(self.widget.lstMagnetic.horizontalHeader().stretchLastSection())
530
531        #Test tooltips
532        self.assertEqual(len(self.widget._magnet_model.header_tooltips), 5)
533
534        header_tooltips = ['Select parameter for fitting',
535                             'Enter parameter value',
536                             'Enter minimum value for parameter',
537                             'Enter maximum value for parameter',
538                             'Unit of the parameter']
539        for column, tooltip in enumerate(header_tooltips):
540             self.assertEqual(self.widget._magnet_model.headerData(column,
541                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
542                         header_tooltips[column])
543
544        # Test rows
545        for row in range(self.widget._magnet_model.rowCount()):
546            func_index = self.widget._magnet_model.index(row, 0)
547            self.assertIn(':', self.widget._magnet_model.item(row, 0).text())
548
549
550    def testAddExtraShells(self):
551        """
552        Test how the extra shells are presented
553        """
554        pass
555
556    def testModifyShellsInList(self):
557        """
558        Test the additional rows added by modifying the shells combobox
559        """
560        self.widget.show()
561        # Change the model to multi shell
562        category_index = self.widget.cbCategory.findText("Sphere")
563        self.widget.cbCategory.setCurrentIndex(category_index)
564        model_index = self.widget.cbModel.findText("core_multi_shell")
565        self.widget.cbModel.setCurrentIndex(model_index)
566
567        # Assure we have the combobox available
568        last_row = self.widget._last_model_row
569        func_index = self.widget._model_model.index(last_row-1, 1)
570        self.assertIsInstance(self.widget.lstParams.indexWidget(func_index), QtWidgets.QComboBox)
571
572        # Change the combo box index
573        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(3)
574
575        # Check that the number of rows increased
576        more_rows = self.widget._model_model.rowCount() - last_row
577        self.assertEqual(more_rows, 6) # 6 new rows: 2 params per index
578
579        # Back to 0
580        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(0)
581        self.assertEqual(self.widget._model_model.rowCount(), last_row)
582
583    def testPlotTheory(self):
584        """
585        See that theory item can produce a chart
586        """
587        # By default, the compute/plot button is disabled
588        self.assertFalse(self.widget.cmdPlot.isEnabled())
589        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
590
591        # Assign a model
592        self.widget.show()
593        # Change the category index so we have a model available
594        category_index = self.widget.cbCategory.findText("Sphere")
595        self.widget.cbCategory.setCurrentIndex(category_index)
596
597        # Check the enablement/text
598        self.assertTrue(self.widget.cmdPlot.isEnabled())
599        self.assertEqual(self.widget.cmdPlot.text(), 'Calculate')
600
601        # Spying on plot update signal
602        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
603
604        # Press Calculate
605        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
606
607        # Observe cmdPlot caption change
608        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
609
610        # Make sure the signal has NOT been emitted
611        self.assertEqual(spy.count(), 0)
612
613        # Click again
614        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
615
616        # This time, we got the update signal
617        self.assertEqual(spy.count(), 0)
618
619    def testPlotData(self):
620        """
621        See that data item can produce a chart
622        """
623        # By default, the compute/plot button is disabled
624        self.assertFalse(self.widget.cmdPlot.isEnabled())
625        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
626
627        self.widget.show()
628
629        # Set data
630        test_data = Data1D(x=[1,2], y=[1,2])
631
632        # Force same data into logic
633        self.widget.logic.data = test_data
634        self.widget.data_is_loaded = True
635
636        # Change the category index so we have a model available
637        category_index = self.widget.cbCategory.findText("Sphere")
638        self.widget.cbCategory.setCurrentIndex(category_index)
639
640        # Check the enablement/text
641        self.assertTrue(self.widget.cmdPlot.isEnabled())
642        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
643
644        # Spying on plot update signal
645        spy = QtSignalSpy(self.widget, self.widget.communicate.plotRequestedSignal)
646
647        # Press Calculate
648        QtTest.QTest.mouseClick(self.widget.cmdPlot, QtCore.Qt.LeftButton)
649
650        # Observe cmdPlot caption did not change
651        self.assertEqual(self.widget.cmdPlot.text(), 'Show Plot')
652
653        # Make sure the signal has been emitted == new plot
654        self.assertEqual(spy.count(), 1)
655
656    def testOnEmptyFit(self):
657        """
658        Test a 1D/2D fit with no parameters
659        """
660        # Set data
661        test_data = Data1D(x=[1,2], y=[1,2])
662        item = QtGui.QStandardItem()
663        updateModelItem(item, [test_data], "test")
664        # Force same data into logic
665        self.widget.data = item
666        category_index = self.widget.cbCategory.findText("Sphere")
667        self.widget.cbCategory.setCurrentIndex(category_index)
668
669        self.widget.show()
670
671        # Test no fitting params
672        self.widget.parameters_to_fit = []
673
674        logging.error = MagicMock()
675
676        self.widget.onFit()
677        self.assertTrue(logging.error.called_with('no fitting parameters'))
678        self.widget.close()
679
680        test_data = Data2D(image=[1.0, 2.0, 3.0],
681                           err_image=[0.01, 0.02, 0.03],
682                           qx_data=[0.1, 0.2, 0.3],
683                           qy_data=[0.1, 0.2, 0.3],
684                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
685                           mask=[True, True, True])
686
687        # Force same data into logic
688        item = QtGui.QStandardItem()
689        updateModelItem(item, [test_data], "test")
690        # Force same data into logic
691        self.widget.data = item
692        category_index = self.widget.cbCategory.findText("Sphere")
693        self.widget.cbCategory.setCurrentIndex(category_index)
694
695        self.widget.show()
696
697        # Test no fitting params
698        self.widget.parameters_to_fit = []
699
700        logging.error = MagicMock()
701
702        self.widget.onFit()
703        self.assertTrue(logging.error.called_once())
704        self.assertTrue(logging.error.called_with('no fitting parameters'))
705        self.widget.close()
706
707
708    def testOnFit1D(self):
709        """
710        Test the threaded fitting call
711        """
712        # Set data
713        test_data = Data1D(x=[1,2], y=[1,2])
714        item = QtGui.QStandardItem()
715        updateModelItem(item, [test_data], "test")
716        # Force same data into logic
717        self.widget.data = item
718        category_index = self.widget.cbCategory.findText("Sphere")
719        self.widget.cbCategory.setCurrentIndex(category_index)
720
721        self.widget.show()
722
723        # Assing fitting params
724        self.widget.parameters_to_fit = ['scale']
725
726        # Spying on status update signal
727        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
728
729        with threads.deferToThread as MagicMock:
730            self.widget.onFit()
731            # thread called
732            self.assertTrue(threads.deferToThread.called)
733            # thread method is 'compute'
734            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
735
736            # the fit button changed caption and got disabled
737            self.assertEqual(self.widget.cmdFit.text(), 'Running...')
738            self.assertFalse(self.widget.cmdFit.isEnabled())
739
740            # Signal pushed up
741            self.assertEqual(update_spy.count(), 1)
742
743        self.widget.close()
744
745    def testOnFit2D(self):
746        """
747        Test the threaded fitting call
748        """
749        # Set data
750        test_data = Data2D(image=[1.0, 2.0, 3.0],
751                           err_image=[0.01, 0.02, 0.03],
752                           qx_data=[0.1, 0.2, 0.3],
753                           qy_data=[0.1, 0.2, 0.3],
754                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
755                           mask=[True, True, True])
756
757        # Force same data into logic
758        item = QtGui.QStandardItem()
759        updateModelItem(item, [test_data], "test")
760        # Force same data into logic
761        self.widget.data = item
762        category_index = self.widget.cbCategory.findText("Sphere")
763        self.widget.cbCategory.setCurrentIndex(category_index)
764
765        self.widget.show()
766
767        # Assing fitting params
768        self.widget.parameters_to_fit = ['scale']
769
770        # Spying on status update signal
771        update_spy = QtSignalSpy(self.widget, self.widget.communicate.statusBarUpdateSignal)
772
773        with threads.deferToThread as MagicMock:
774            self.widget.onFit()
775            # thread called
776            self.assertTrue(threads.deferToThread.called)
777            # thread method is 'compute'
778            self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'compute')
779
780            # the fit button changed caption and got disabled
781            self.assertEqual(self.widget.cmdFit.text(), 'Running...')
782            self.assertFalse(self.widget.cmdFit.isEnabled())
783
784            # Signal pushed up
785            self.assertEqual(update_spy.count(), 1)
786
787    # test disabled until pyqt5 deals with html properly
788    def notestOnHelp(self):
789        """
790        Test various help pages shown in this widget
791        """
792        #Mock the QWebView method
793        QtWebKit.QWebView.show = MagicMock()
794        QtWebKit.QWebView.load = MagicMock()
795
796        # Invoke the action on default tab
797        self.widget.onHelp()
798        # Check if show() got called
799        self.assertTrue(QtWebKit.QWebView.show.called)
800        # Assure the filename is correct
801        self.assertIn("fitting_help.html", QtWebKit.QWebView.load.call_args[0][0].toString())
802
803        # Change the tab to options
804        self.widget.tabFitting.setCurrentIndex(1)
805        self.widget.onHelp()
806        # Check if show() got called
807        self.assertEqual(QtWebKit.QWebView.show.call_count, 2)
808        # Assure the filename is correct
809        self.assertIn("residuals_help.html", QtWebKit.QWebView.load.call_args[0][0].toString())
810
811        # Change the tab to smearing
812        self.widget.tabFitting.setCurrentIndex(2)
813        self.widget.onHelp()
814        # Check if show() got called
815        self.assertEqual(QtWebKit.QWebView.show.call_count, 3)
816        # Assure the filename is correct
817        self.assertIn("sm_help.html", QtWebKit.QWebView.load.call_args[0][0].toString())
818
819        # Change the tab to poly
820        self.widget.tabFitting.setCurrentIndex(3)
821        self.widget.onHelp()
822        # Check if show() got called
823        self.assertEqual(QtWebKit.QWebView.show.call_count, 4)
824        # Assure the filename is correct
825        self.assertIn("pd_help.html", QtWebKit.QWebView.load.call_args[0][0].toString())
826
827        # Change the tab to magnetism
828        self.widget.tabFitting.setCurrentIndex(4)
829        self.widget.onHelp()
830        # Check if show() got called
831        self.assertEqual(QtWebKit.QWebView.show.call_count, 5)
832        # Assure the filename is correct
833        self.assertIn("mag_help.html", QtWebKit.QWebView.load.call_args[0][0].toString())
834
835    def testReadFitPage(self):
836        """
837        Read in the fitpage object and restore state
838        """
839        # Set data
840        test_data = Data1D(x=[1,2], y=[1,2])
841
842        # Force same data into logic
843        self.widget.logic.data = test_data
844        self.widget.data_is_loaded = True
845        category_index = self.widget.cbCategory.findText('Sphere')
846        self.widget.cbCategory.setCurrentIndex(category_index)
847        self.widget.parameters_to_fit = ['scale']
848        # Invoke the tested method
849        fp = self.widget.currentState()
850
851        # Prepare modified fit page
852        fp.current_model = 'onion'
853        fp.is_polydisperse = True
854
855        # Read in modified state
856        self.widget.readFitPage(fp)
857
858        # Check if the widget got updated accordingly
859        self.assertEqual(self.widget.cbModel.currentText(), 'onion')
860        self.assertTrue(self.widget.chkPolydispersity.isChecked())
861        #Check if polidispersity tab is available
862        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
863
864        #Check if magnetism box and tab are disabled when 1D data is loaded
865        self.assertFalse(self.widget.chkMagnetism.isEnabled())
866        self.assertFalse(self.widget.tabFitting.isTabEnabled(4))
867
868    def testReadFitPage2D(self):
869        """
870        Read in the fitpage object and restore state
871        """
872        # Set data
873
874        test_data = Data2D(image=[1.0, 2.0, 3.0],
875                           err_image=[0.01, 0.02, 0.03],
876                           qx_data=[0.1, 0.2, 0.3],
877                           qy_data=[0.1, 0.2, 0.3],
878                           xmin=0.1, xmax=0.3, ymin=0.1, ymax=0.3,
879                           mask=[True, True, True])
880
881        # Force same data into logic
882        self.widget.logic.data = test_data
883        self.widget.data_is_loaded = True
884
885        #item = QtGui.QStandardItem()
886        #updateModelItem(item, [test_data], "test")
887        # Force same data into logic
888        #self.widget.logic.data = item
889        #self.widget.data_is_loaded = True
890
891        category_index = self.widget.cbCategory.findText("Cylinder")
892        self.widget.cbCategory.setCurrentIndex(category_index)
893
894        # Test no fitting params
895        self.widget.parameters_to_fit = ['scale']
896
897        # Invoke the tested method
898        fp = self.widget.currentState()
899
900        # Prepare modified fit page
901        fp.current_model = 'cylinder'
902        fp.is_polydisperse = True
903        fp.is_magnetic = True
904        fp.is2D = True
905
906        # Read in modified state
907        self.widget.readFitPage(fp)
908
909        # Check if the widget got updated accordingly
910        self.assertEqual(self.widget.cbModel.currentText(), 'cylinder')
911        self.assertTrue(self.widget.chkPolydispersity.isChecked())
912        self.assertTrue(self.widget.chkPolydispersity.isEnabled())
913        #Check if polidispersity tab is available
914        self.assertTrue(self.widget.tabFitting.isTabEnabled(3))
915
916        #Check if magnetism box and tab are disabled when 1D data is loaded
917        self.assertTrue(self.widget.chkMagnetism.isChecked())
918        self.assertTrue(self.widget.chkMagnetism.isEnabled())
919        self.assertTrue(self.widget.tabFitting.isTabEnabled(4))
920
921    def testCurrentState(self):
922        """
923        Set up the fitpage with current state
924        """
925        # Set data
926        test_data = Data1D(x=[1,2], y=[1,2])
927
928        # Force same data into logic
929        self.widget.logic.data = test_data
930        self.widget.data_is_loaded = True
931        category_index = self.widget.cbCategory.findText("Sphere")
932        self.widget.cbCategory.setCurrentIndex(category_index)
933        self.widget.parameters_to_fit = ['scale']
934
935        # Invoke the tested method
936        fp = self.widget.currentState()
937
938        # Test some entries. (Full testing of fp is done in FitPageTest)
939        self.assertIsInstance(fp.data, Data1D)
940        self.assertListEqual(list(fp.data.x), [1,2])
941        self.assertTrue(fp.data_is_loaded)
942        self.assertEqual(fp.current_category, "Sphere")
943        self.assertEqual(fp.current_model, "adsorbed_layer")
944        self.assertListEqual(fp.parameters_to_fit, ['scale'])
945
946    def testPushFitPage(self):
947        """
948        Push current state of fitpage onto stack
949        """
950        # Set data
951        test_data = Data1D(x=[1,2], y=[1,2])
952
953        # Force same data into logic
954        self.widget.logic.data = test_data
955        self.widget.data_is_loaded = True
956        category_index = self.widget.cbCategory.findText("Sphere")
957
958        # Asses the initial state of stack
959        self.assertEqual(self.widget.page_stack, [])
960
961        # Set the undo flag
962        self.widget.undo_supported = True
963        self.widget.cbCategory.setCurrentIndex(category_index)
964        self.widget.parameters_to_fit = ['scale']
965
966        # Check that the stack is updated
967        self.assertEqual(len(self.widget.page_stack), 1)
968
969        # Change another parameter
970        self.widget._model_model.item(2, 1).setText("3.0")
971        # Check that the stack is updated
972        self.assertEqual(len(self.widget.page_stack), 2)
973
974    def testPopFitPage(self):
975        """
976        Pop current state of fitpage from stack
977        """
978        # TODO: to be added when implementing UNDO/REDO
979        pass
980
981    def testOnMainPageChange(self):
982        """
983        Test update  values of modified parameters in models
984        """
985        # select model: cylinder / cylinder
986        category_index = self.widget.cbCategory.findText("Cylinder")
987        self.widget.cbCategory.setCurrentIndex(category_index)
988
989        model_index = self.widget.cbModel.findText("cylinder")
990        self.widget.cbModel.setCurrentIndex(model_index)
991
992        # modify the initial value of length (different from default)
993        # print self.widget.kernel_module.details['length']
994
995        new_value = "333.0"
996        self.widget._model_model.item(5, 1).setText(new_value)
997
998        # name of modified parameter
999        name_modified_param = str(self.widget._model_model.item(5, 0).text())
1000
1001         # Check the model
1002        self.assertEqual(self.widget._model_model.rowCount(), 6)
1003        self.assertEqual(self.widget._model_model.columnCount(), 5)
1004
1005        # Test the header
1006        #self.assertEqual(self.widget.lstParams.horizontalHeader().count(), 5)
1007        #self.assertFalse(self.widget.lstParams.horizontalHeader().stretchLastSection())
1008
1009        self.assertEqual(len(self.widget._model_model.header_tooltips), 5)
1010        header_tooltips = ['Select parameter for fitting',
1011                             'Enter parameter value',
1012                             'Enter minimum value for parameter',
1013                             'Enter maximum value for parameter',
1014                             'Unit of the parameter']
1015        for column, tooltip in enumerate(header_tooltips):
1016             self.assertEqual(self.widget._model_model.headerData(column,
1017                QtCore.Qt.Horizontal, QtCore.Qt.ToolTipRole),
1018                         header_tooltips[column])
1019
1020        # check that the value has been modified in kernel_module
1021        self.assertEqual(new_value,
1022                         str(self.widget.kernel_module.params[name_modified_param]))
1023
1024        # check that range of variation for this parameter has NOT been changed
1025        self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] )
1026
1027
1028if __name__ == "__main__":
1029    unittest.main()
Note: See TracBrowser for help on using the repository browser.