import sys import time import numpy import logging import unittest from PyQt5 import QtGui, QtWidgets from PyQt5 import QtCore from PyQt5.QtTest import QTest from PyQt5.QtCore import Qt from unittest.mock import MagicMock from unittest.mock import patch from unittest.mock import create_autospec from twisted.internet import threads from sas.qtgui.Perspectives.Invariant.InvariantPerspective import InvariantWindow from sas.qtgui.Perspectives.Invariant.InvariantDetails import DetailsDialog from sas.qtgui.Perspectives.Invariant.InvariantUtils import WIDGETS from sas.qtgui.Plotting.PlotterData import Data1D from sas.qtgui.MainWindow.GuiManager import GuiManager from sas.qtgui.MainWindow.DataExplorer import DataExplorerWindow import sas.qtgui.Utilities.GuiUtils as GuiUtils #if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) BG_COLOR_ERR = 'background-color: rgb(244, 170, 164);' class InvariantPerspectiveTest(unittest.TestCase): """Test the Invariant Perspective Window""" def setUp(self): """Create the Invariant Perspective Window""" class MainWindow(object): def __init__(self): self.model = QtGui.QStandardItemModel() class dummy_manager(object): def __init__(self): self.filesWidget = MainWindow() def communicator(self): return GuiUtils.Communicate() def communicate(self): return GuiUtils.Communicate() self.widget = InvariantWindow(dummy_manager()) def tearDown(self): """Destroy the DataOperationUtility""" self.widget.close() self.widget = None def testDefaults(self): """Test the GUI in its default state""" self.assertIsInstance(self.widget, QtWidgets.QDialog) self.assertEqual(self.widget.windowTitle(), "Invariant Perspective") # name for displaying in the DataExplorer combo box self.assertEqual(self.widget.name, "Invariant") self.assertIsInstance(self.widget.model, QtGui.QStandardItemModel) self.assertIsNone(self.widget._data) self.assertEqual(self.widget._path, '') self.assertFalse(self.widget._allow_close) # disabled pushbuttons self.assertFalse(self.widget.cmdStatus.isEnabled()) self.assertFalse(self.widget.cmdCalculate.isEnabled()) # disabled, read only line edits self.assertFalse(self.widget.txtName.isEnabled()) self.assertTrue(self.widget.txtVolFract.isReadOnly()) self.assertTrue(self.widget.txtVolFractErr.isReadOnly()) self.assertTrue(self.widget.txtSpecSurf.isReadOnly()) self.assertTrue(self.widget.txtSpecSurfErr.isReadOnly()) self.assertTrue(self.widget.txtInvariantTot.isReadOnly()) self.assertTrue(self.widget.txtInvariantTotErr.isReadOnly()) self.assertFalse(self.widget.txtBackgd.isReadOnly()) self.assertFalse(self.widget.txtScale.isReadOnly()) self.assertFalse(self.widget.txtContrast.isReadOnly()) self.assertFalse(self.widget.txtPorodCst.isReadOnly()) self.assertFalse(self.widget.txtExtrapolQMin.isEnabled()) self.assertFalse(self.widget.txtExtrapolQMax.isEnabled()) self.assertFalse(self.widget.txtNptsLowQ.isReadOnly()) self.assertFalse(self.widget.txtNptsHighQ.isReadOnly()) # content of line edits self.assertEqual(self.widget.txtName.text(), '') self.assertEqual(self.widget.txtTotalQMin.text(), '0.0') self.assertEqual(self.widget.txtTotalQMax.text(), '0.0') self.assertEqual(self.widget.txtBackgd.text(), '0.0') self.assertEqual(self.widget.txtScale.text(), '1.0') self.assertEqual(self.widget.txtContrast.text(), '1.0') self.assertEqual(self.widget.txtExtrapolQMin.text(), '1e-05') self.assertEqual(self.widget.txtExtrapolQMax.text(), '10') self.assertEqual(self.widget.txtPowerLowQ.text(), '4') self.assertEqual(self.widget.txtPowerHighQ.text(), '4') # unchecked checkboxes self.assertFalse(self.widget.chkLowQ.isChecked()) self.assertFalse(self.widget.chkHighQ.isChecked()) # number of tabs self.assertEqual(self.widget.tabWidget.count(), 2) # default tab self.assertEqual(self.widget.tabWidget.currentIndex(), 0) # tab's title self.assertEqual(self.widget.tabWidget.tabText(0), 'Invariant') self.assertEqual(self.widget.tabWidget.tabText(1), 'Options') # Tooltips self.assertEqual(self.widget.cmdStatus.toolTip(), "Get more details of computation such as fraction from extrapolation" ) self.assertEqual(self.widget.txtInvariantTot.toolTip(), "Total invariant [Q*], including extrapolated regions.") self.assertEqual(self.widget.txtExtrapolQMin.toolTip(), "The minimum extrapolated q value.") self.assertEqual(self.widget.txtPowerHighQ.toolTip(),"Exponent to apply to the Power_law function.") self.assertEqual(self.widget.txtNptsHighQ.toolTip(), "Number of Q points to consider\n while extrapolating the high-Q region") self.assertEqual(self.widget.chkHighQ.toolTip(), "Check to extrapolate data at high-Q") self.assertEqual(self.widget.txtNptsLowQ.toolTip(), "Number of Q points to consider\nwhile extrapolating the low-Q region") self.assertEqual(self.widget.chkLowQ.toolTip(), "Check to extrapolate data at low-Q") self.assertEqual(self.widget.cmdCalculate.toolTip(), "Compute invariant") self.assertEqual(self.widget.txtPowerLowQ.toolTip(), "Exponent to apply to the Power_law function." ) # Validators self.assertIsInstance(self.widget.txtNptsLowQ.validator(), QtGui.QIntValidator) self.assertIsInstance(self.widget.txtNptsHighQ.validator(), QtGui.QIntValidator) self.assertIsInstance(self.widget.txtPowerLowQ.validator(), GuiUtils.DoubleValidator) self.assertIsInstance(self.widget.txtPowerHighQ.validator(), GuiUtils.DoubleValidator) self.assertIsInstance(self.widget.txtBackgd.validator(), GuiUtils.DoubleValidator) self.assertIsInstance(self.widget.txtContrast.validator(), GuiUtils.DoubleValidator) self.assertIsInstance(self.widget.txtScale.validator(), GuiUtils.DoubleValidator) self.assertIsInstance(self.widget.txtPorodCst.validator(), GuiUtils.DoubleValidator) # Test autoexclusivity of radiobuttons # Low Q self.assertFalse(self.widget.rbGuinier.autoExclusive()) self.assertFalse(self.widget.rbPowerLawLowQ.autoExclusive()) self.assertTrue(self.widget.rbFixLowQ.autoExclusive()) self.assertTrue(self.widget.rbFitLowQ.autoExclusive()) # High Q self.assertTrue(self.widget.rbFixHighQ.autoExclusive()) self.assertTrue(self.widget.rbFitHighQ.autoExclusive()) def testOnCalculate(self): """ Test onCompute function """ self.widget.calculateInvariant = MagicMock() self.widget.cmdCalculate.setEnabled(True) QTest.mouseClick(self.widget.cmdCalculate, Qt.LeftButton) self.assertTrue(self.widget.calculateInvariant.called_once()) def testCalculateInvariant(self): """ """ threads.deferToThread = MagicMock() self.widget.calculateInvariant() self.assertTrue(threads.deferToThread.called) self.assertEqual(threads.deferToThread.call_args_list[0][0][0].__name__, 'calculateThread') self.assertEqual(self.widget.cmdCalculate.text(), 'Calculating...') self.assertFalse(self.widget.cmdCalculate.isEnabled()) # TODO def testPlotResult(self): """ """ pass # create fake input # data = Data1D(x=[1, 2], y=[3, 4]) # GuiUtils.dataFromItem = MagicMock(return_value=data) # # self.widget._manager.filesWidget.model = MagicMock() # item = QtGui.QStandardItem("test") # run function # self.widget.plotResult = MagicMock(return_value=None) # (item) # self.widget.plotResult(item) # self.assertTrue(self.widget.plotResult.called_once()) # self.assertTrue(self.widget.cmdCalculate.isEnabled()) # self.assertEqual(self.widget.cmdCalculate.text(), 'Calculate') # self.assertEqual(self.widget._data.x[0], 1) # self.assertEqual(self.widget._data.x[1], 2) # self.assertEqual(self.widget._data.y[0], 3) # self.assertEqual(self.widget._data.y[1], 4) def notestHelp(self): """ Assure help file is shown """ # this should not rise self.widget.onHelp() def testAllowBatch(self): """ """ self.assertFalse(self.widget.allowBatch()) def testTitle(self): """ Test Perspective name """ self.assertEqual(self.widget.title(), "Invariant panel") def testOnStatus(self): """ Test Display of Invariant Details """ # enable click on Calculate button self.widget.cmdStatus.setEnabled(True) invariant_details_dialog = create_autospec(DetailsDialog) self.widget.detailsDialog = invariant_details_dialog # click on button QTest.mouseClick(self.widget.cmdStatus, Qt.LeftButton) invariant_details_dialog.showDialog.assert_called_once_with() def testUpdateFromModel(self): """ update the globals based on the data in the model """ self.widget.updateFromModel() self.assertEqual(self.widget._background, float(self.widget.model.item(WIDGETS.W_BACKGROUND).text())) self.assertEqual(self.widget._contrast, float(self.widget.model.item(WIDGETS.W_CONTRAST).text())) self.assertEqual(self.widget._scale, float(self.widget.model.item(WIDGETS.W_SCALE).text())) self.assertEqual(self.widget._low_extrapolate, str(self.widget.model.item(WIDGETS.W_ENABLE_LOWQ).text()) == 'true') self.assertEqual(self.widget._low_points, float(self.widget.model.item(WIDGETS.W_NPTS_LOWQ).text())) self.assertEqual(self.widget._low_guinier, str(self.widget.model.item(WIDGETS.W_LOWQ_GUINIER).text()) == 'true' ) self.assertEqual(self.widget._low_fit,str(self.widget.model.item(WIDGETS.W_LOWQ_FIT).text()) == 'true') self.assertEqual(self.widget._low_power_value, float(self.widget.model.item(WIDGETS.W_LOWQ_POWER_VALUE).text())) self.assertEqual(self.widget._high_extrapolate,str(self.widget.model.item(WIDGETS.W_ENABLE_HIGHQ).text()) == 'true') self.assertEqual(self.widget._high_points, float(self.widget.model.item(WIDGETS.W_NPTS_HIGHQ).text())) self.assertEqual(self.widget._high_fit,str(self.widget.model.item(WIDGETS.W_HIGHQ_FIT).text()) == 'true') self.assertEqual(self.widget._high_power_value, float(self.widget.model.item(WIDGETS.W_HIGHQ_POWER_VALUE).text())) def testEnabling(self): """ """ self.widget.cmdStatus.setEnabled(False) self.widget.enabling() self.assertTrue(self.widget.cmdStatus.isEnabled()) def testCheckLength(self): """ Test validator for number of points for extrapolation Error if it is larger than the distribution length """ logging.warning = MagicMock() self.widget.txtNptsLowQ.setEnabled(True) self.widget._data = Data1D(x=[1, 2], y=[1, 2]) self.widget.txtNptsLowQ.setText('9') # QTest.keyClicks(self.widget.txtNptsLowQ, '9') # QTest.keyClick(self.widget.txtNptsLowQ, QtCore.Qt.Key_Return) BG_COLOR_ERR = 'background-color: rgb(244, 170, 164);' # print 'style ',self.widget.txtNptsLowQ.styleSheet() self.assertIn(BG_COLOR_ERR, self.widget.txtNptsLowQ.styleSheet()) self.assertTrue(logging.warning.called_once_with()) self.assertFalse(self.widget.cmdCalculate.isEnabled()) def testModelChanged(self): """ """ self.widget.lowQToggle = MagicMock() status_ini = self.widget.model.item(WIDGETS.W_ENABLE_LOWQ).text() if status_ini == 'true': status_fin = 'false' else: status_fin = 'true' self.widget.model.setItem(WIDGETS.W_ENABLE_LOWQ, QtGui.QStandardItem(status_fin)) if status_fin: self.assertTrue(self.widget._low_extrapolate) else: self.assertFalse(self.widget._low_extrapolate) self.assertTrue(self.widget.lowQToggle.called_once_with()) def testUpdateFromGui(self): """ """ self.widget.txtBackgd.setText('0.22') self.assertEqual(str(self.widget.model.item(WIDGETS.W_BACKGROUND).text()), '0.22') def testLowGuinierAndPowerToggle(self): """ """ # enable all tested radiobuttons self.widget.rbGuinier.setEnabled(True) self.widget.rbPowerLawLowQ.setEnabled(True) self.widget.txtNptsLowQ.setEnabled(True) # record initial status status_ini = self.widget.rbGuinier.isChecked() # mouse click to run function QTest.mouseClick(self.widget.rbGuinier, Qt.LeftButton) # check that status changed self.assertNotEqual(self.widget.rbGuinier.isChecked(), status_ini) status_fin = self.widget.rbGuinier.isChecked() self.assertEqual(self.widget.rbPowerLawLowQ.isChecked(), not status_fin) self.assertEqual(self.widget.txtPowerLowQ.isEnabled(), all([not status_fin, not self.widget._low_fit])) def testLowFitAndFixToggle(self): """ """ status = True # run function to test self.widget.lowFitAndFixToggle(status) self.assertEqual(self.widget._low_fit, status) self.assertNotEqual(self.widget.txtPowerLowQ.isEnabled(), status) def testHiFitAndFixToggle(self): status = True self.widget.hiFitAndFixToggle(status) self.assertEqual(self.widget.txtPowerHighQ.isEnabled(), not status) def testHighQToggle(self): """ Test enabling / disabling for check box High Q extrapolation """ status_chkHighQ = self.widget.chkHighQ.isChecked() self.widget.highQToggle(status_chkHighQ) self.assertEqual(self.widget.rbFitHighQ.isEnabled(), status_chkHighQ) self.assertEqual(self.widget.rbFixHighQ.isEnabled(), status_chkHighQ) self.assertEqual(self.widget.txtNptsHighQ.isEnabled(), status_chkHighQ) self.assertEqual(self.widget.txtPowerHighQ.isEnabled(), status_chkHighQ) # change checked status of chkHighQ self.widget.chkHighQ.setChecked(True) status_chkHighQ = self.widget.chkHighQ.isChecked() self.assertEqual(self.widget.rbFitHighQ.isEnabled(), status_chkHighQ) self.assertEqual(self.widget.rbFixHighQ.isEnabled(), status_chkHighQ) self.assertEqual(self.widget.txtNptsHighQ.isEnabled(), status_chkHighQ) self.assertEqual(self.widget.txtPowerHighQ.isEnabled(), status_chkHighQ) def testLowQToggle(self): """ Test enabling / disabling for check box Low Q extrapolation """ status_chkLowQ = self.widget.chkLowQ.isChecked() self.assertEqual(self.widget.rbGuinier.isEnabled(), status_chkLowQ) self.assertEqual(self.widget.rbPowerLawLowQ.isEnabled(), status_chkLowQ) self.assertEqual(self.widget.txtNptsLowQ.isEnabled(), status_chkLowQ) self.assertEqual(self.widget.rbFitLowQ.isVisible(), self.widget.rbPowerLawLowQ.isChecked()) self.assertEqual(self.widget.rbFixLowQ.isVisible(), self.widget.rbPowerLawLowQ.isChecked()) self.assertEqual(self.widget.rbFitLowQ.isEnabled(), status_chkLowQ) self.assertEqual(self.widget.rbFixLowQ.isEnabled(), status_chkLowQ) self.assertEqual(self.widget.txtNptsLowQ.isEnabled(), all([status_chkLowQ, not self.widget._low_guinier, not self.widget._low_fit])) def testSetupModel(self): """ Test default settings of model""" self.assertEqual(self.widget.model.item(WIDGETS.W_FILENAME).text(), self.widget._path) self.assertEqual(self.widget.model.item(WIDGETS.W_QMIN).text(), '0.0') self.assertEqual(self.widget.model.item(WIDGETS.W_QMAX).text(), '0.0') self.assertEqual(self.widget.model.item(WIDGETS.W_BACKGROUND).text(), str(self.widget._background)) self.assertEqual(self.widget.model.item(WIDGETS.W_CONTRAST).text(), str(self.widget._contrast)) self.assertEqual(self.widget.model.item(WIDGETS.W_SCALE).text(), str(self.widget._scale)) self.assertIn(str(self.widget.model.item(WIDGETS.W_POROD_CST).text()), ['', str(self.widget._porod)]) self.assertEqual( str(self.widget.model.item(WIDGETS.W_ENABLE_HIGHQ).text()).lower(), 'false') self.assertEqual( str(self.widget.model.item(WIDGETS.W_ENABLE_LOWQ).text()).lower(), 'false') self.assertEqual( str(self.widget.model.item(WIDGETS.W_NPTS_LOWQ).text()), str(10)) self.assertEqual(self.widget.model.item(WIDGETS.W_NPTS_HIGHQ).text(), str(10)) self.assertEqual( str(self.widget.model.item(WIDGETS.W_LOWQ_GUINIER).text()).lower(), 'true') self.assertEqual( str(self.widget.model.item(WIDGETS.W_LOWQ_FIT).text()).lower(), 'true') self.assertEqual( str(self.widget.model.item(WIDGETS.W_LOWQ_POWER_VALUE).text()), '4') self.assertEqual( str(self.widget.model.item(WIDGETS.W_HIGHQ_FIT).text()).lower(), 'true') self.assertEqual( str(self.widget.model.item(WIDGETS.W_HIGHQ_POWER_VALUE).text()), '4') # TODO def testSetupMapper(self): """ """ self.assertIsInstance(self.widget.mapper, QtWidgets.QDataWidgetMapper) self.assertEqual(self.widget.mapper.orientation(), 2) self.assertEqual(self.widget.mapper.model(), self.widget.model) def testSetData(self): """ """ self.widget.updateGuiFromFile = MagicMock() data = Data1D(x=[1, 2], y=[1, 2]) GuiUtils.dataFromItem = MagicMock(return_value=data) item = QtGui.QStandardItem("test") self.widget.setData([item]) self.assertTrue(self.widget.updateGuiFromFile.called_once()) def TestCheckQExtrapolatedData(self): """ Test Match status of low or high-Q extrapolated data checkbox in DataExplorer with low or high-Q extrapolation checkbox in invariant panel """ # Low-Q check box ticked self.widget.chkLowQ.setCheckStatus(QtCore.Qt.Checked) GuiUtils.updateModelItemStatus = MagicMock() self.assertTrue(GuiUtils.updateModelItemStatus.called_once()) if __name__ == "__main__": unittest.main()