Index: src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingPerspectiveTest.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingPerspectiveTest.py	(revision 676f1378496edab35bd11ed8e0ce2614c0db17b6)
+++ src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingPerspectiveTest.py	(revision 63319b0e1635f70a44346bc676740b77b0aa7102)
@@ -19,4 +19,5 @@
 if not QtWidgets.QApplication.instance():
     app = QtWidgets.QApplication(sys.argv)
+
 
 class FittingPerspectiveTest(unittest.TestCase):
@@ -146,4 +147,28 @@
         self.assertEqual(len(self.widget.tabs), 4)
 
+    def testSetBatchData(self):
+        ''' Assure that setting batch data is correct'''
+
+        # Mock the datafromitem() call from FittingWidget
+        data1 = Data1D(x=[1,2], y=[1,2])
+        data2 = Data1D(x=[1,2], y=[1,2])
+        data_batch = [data1, data2]
+        GuiUtils.dataFromItem = MagicMock(return_value=data1)
+
+        item = QtGui.QStandardItem("test")
+        self.widget.setData([item, item], is_batch=True)
+
+        # First tab should not accept data
+        self.assertEqual(len(self.widget.tabs), 2)
+
+        # Add another set of data
+        self.widget.setData([item, item], is_batch=True)
+
+        # Now we should have two batch tabs
+        self.assertEqual(len(self.widget.tabs), 3)
+
+        # Check the names of the new tabs
+        self.assertEqual(self.widget.tabText(1), "BatchPage1")
+        self.assertEqual(self.widget.tabText(2), "BatchPage2")
 
 if __name__ == "__main__":
Index: src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py	(revision 676f1378496edab35bd11ed8e0ce2614c0db17b6)
+++ src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py	(revision 63319b0e1635f70a44346bc676740b77b0aa7102)
@@ -17,4 +17,6 @@
 from sas.qtgui.Utilities.GuiUtils import *
 from sas.qtgui.Perspectives.Fitting.FittingWidget import *
+from sas.qtgui.Perspectives.Fitting.Constraints import Constraint
+
 from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy
 
@@ -661,5 +663,5 @@
         test_data = Data1D(x=[1,2], y=[1,2])
         item = QtGui.QStandardItem()
-        updateModelItem(item, [test_data], "test")
+        updateModelItem(item, test_data, "test")
         # Force same data into logic
         self.widget.data = item
@@ -687,5 +689,5 @@
         # Force same data into logic
         item = QtGui.QStandardItem()
-        updateModelItem(item, [test_data], "test")
+        updateModelItem(item, test_data, "test")
         # Force same data into logic
         self.widget.data = item
@@ -713,5 +715,5 @@
         test_data = Data1D(x=[1,2], y=[1,2])
         item = QtGui.QStandardItem()
-        updateModelItem(item, [test_data], "test")
+        updateModelItem(item, test_data, "test")
         # Force same data into logic
         self.widget.data = item
@@ -757,5 +759,5 @@
         # Force same data into logic
         item = QtGui.QStandardItem()
-        updateModelItem(item, [test_data], "test")
+        updateModelItem(item, test_data, "test")
         # Force same data into logic
         self.widget.data = item
@@ -1025,4 +1027,417 @@
         self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] )
 
+    def testModelContextMenu(self):
+        """
+        Test the right click context menu in the parameter table
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # no rows selected
+        menu = self.widget.modelContextMenu([])
+        self.assertEqual(len(menu.actions()), 0)
+
+        # 1 row selected
+        menu = self.widget.modelContextMenu([1])
+        self.assertEqual(len(menu.actions()), 4)
+
+        # 2 rows selected
+        menu = self.widget.modelContextMenu([1,3])
+        self.assertEqual(len(menu.actions()), 5)
+
+        # 3 rows selected
+        menu = self.widget.modelContextMenu([1,2,3])
+        self.assertEqual(len(menu.actions()), 4)
+
+        # over 9000
+        with self.assertRaises(AttributeError):
+            menu = self.widget.modelContextMenu([i for i in range(9001)])
+        self.assertEqual(len(menu.actions()), 4)
+
+    def testShowModelContextMenu(self):
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # No selection
+        logging.error = MagicMock()
+        self.widget.showModelDescription = MagicMock()
+        # Show the menu
+        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
+
+        # Assure the description menu is shown
+        self.assertTrue(self.widget.showModelDescription.called)
+        self.assertFalse(logging.error.called)
+
+        # "select" two rows
+        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+        selection_model.select(index2, selection_model.Select | selection_model.Rows)
+
+        QtWidgets.QMenu.exec_ = MagicMock()
+        logging.error = MagicMock()
+        # Show the menu
+        self.widget.showModelContextMenu(QtCore.QPoint(10,20))
+
+        # Assure the menu is shown
+        self.assertFalse(logging.error.called)
+        self.assertTrue(QtWidgets.QMenu.exec_.called)
+
+    def testShowMultiConstraint(self):
+        """
+        Test the widget update on new multi constraint
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # nothing selected
+        with self.assertRaises(AssertionError):
+            self.widget.showMultiConstraint()
+
+        # one row selected
+        index = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index, selection_model.Select | selection_model.Rows)
+        with self.assertRaises(AssertionError):
+            # should also throw
+            self.widget.showMultiConstraint()
+
+        # two rows selected
+        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+        selection_model.select(index2, selection_model.Select | selection_model.Rows)
+
+        # return non-OK from dialog
+        QtWidgets.QDialog.exec_ = MagicMock()
+        self.widget.showMultiConstraint()
+        # Check the dialog called
+        self.assertTrue(QtWidgets.QDialog.exec_.called)
+
+        # return OK from dialog
+        QtWidgets.QDialog.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted)
+        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
+
+        self.widget.showMultiConstraint()
+
+        # Make sure the signal has been emitted
+        self.assertEqual(spy.count(), 1)
+
+        # Check the argument value - should be row '1'
+        self.assertEqual(spy.called()[0]['args'][0], [1])
+
+    def testGetRowFromName(self):
+        """
+        Helper function for parameter table
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # several random parameters
+        self.assertEqual(self.widget.getRowFromName('scale'), 0)
+        self.assertEqual(self.widget.getRowFromName('length'), 5)
+
+    def testGetParamNames(self):
+        """
+        Helper function for parameter table
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        cylinder_params = ['scale','background','sld','sld_solvent','radius','length']
+        # assure all parameters are returned
+        self.assertEqual(self.widget.getParamNames(), cylinder_params)
+
+        # Switch to another model
+        model_index = self.widget.cbModel.findText("pringle")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # make sure the parameters are different than before
+        self.assertFalse(self.widget.getParamNames() == cylinder_params)
+
+    def testAddConstraintToRow(self):
+        """
+        Test the constraint row add operation
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # Create a constraint object
+        const = Constraint(parent=None, value=7.0)
+        row = 2
+
+        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
+
+        # call the method tested
+        self.widget.addConstraintToRow(constraint=const, row=row)
+
+        # Make sure the signal has been emitted
+        self.assertEqual(spy.count(), 1)
+
+        # Check the argument value - should be row 'row'
+        self.assertEqual(spy.called()[0]['args'][0], [row])
+
+        # Assure the row has the constraint
+        self.assertEqual(self.widget.getConstraintForRow(row), const)
+        # but not complex constraint!
+        self.assertFalse(self.widget.rowHasConstraint(row))
+
+        # assign complex constraint now
+        const = Constraint(parent=None, param='radius', func='5*sld')
+        row = 4
+        # call the method tested
+        self.widget.addConstraintToRow(constraint=const, row=row)
+
+        # Make sure the signal has been emitted
+        self.assertEqual(spy.count(), 2)
+
+        # Check the argument value - should be row 'row'
+        self.assertEqual(spy.called()[1]['args'][0], [row])
+
+        # Assure the row has the constraint
+        self.assertEqual(self.widget.getConstraintForRow(row), const)
+        # and it is a complex constraint
+        self.assertTrue(self.widget.rowHasConstraint(row))
+
+    def testAddSimpleConstraint(self):
+        """
+        Test the constraint add operation
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # select two rows
+        row1 = 1
+        row2 = 4
+        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+        selection_model.select(index2, selection_model.Select | selection_model.Rows)
+
+        # define the signal spy
+        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
+
+        # call the method tested
+        self.widget.addSimpleConstraint()
+
+        # Make sure the signal has been emitted
+        self.assertEqual(spy.count(), 2)
+
+        # Check the argument value
+        self.assertEqual(spy.called()[0]['args'][0], [row1])
+        self.assertEqual(spy.called()[1]['args'][0], [row2])
+
+        # Other properties
+        self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
+
+    def testDeleteConstraintOnParameter(self):
+        """
+        Test the constraint deletion in model/view
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # select two rows
+        row1 = 1
+        row2 = 4
+        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+        selection_model.select(index2, selection_model.Select | selection_model.Rows)
+
+        # add constraints
+        self.widget.addSimpleConstraint()
+
+        # deselect the model
+        selection_model.clear()
+
+        # select a single row
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+
+        # delete one of the constraints
+        self.widget.deleteConstraintOnParameter(param='background')
+
+        # see that the other constraint is still present
+        self.assertEqual(self.widget.getConstraintsForModel(), [('radius', '20')])
+
+        # kill the other constraint
+        self.widget.deleteConstraint()
+
+        # see that the other constraint is still present
+        self.assertEqual(self.widget.getConstraintsForModel(), [])
+
+    def testGetConstraintForRow(self):
+        """
+        Helper function for parameter table
+        """
+        # tested extensively elsewhere
+        pass
+
+    def testRowHasConstraint(self):
+        """
+        Helper function for parameter table
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # select two rows
+        row1 = 1
+        row2 = 4
+        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+        selection_model.select(index2, selection_model.Select | selection_model.Rows)
+
+        # add constraints
+        self.widget.addSimpleConstraint()
+
+        con_list = [False, True, False, False, True, False]
+        new_list = []
+        for row in range(self.widget._model_model.rowCount()):
+            new_list.append(self.widget.rowHasConstraint(row))
+
+        self.assertEqual(new_list, con_list)
+
+    def testRowHasActiveConstraint(self):
+        """
+        Helper function for parameter table
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # select two rows
+        row1 = 1
+        row2 = 4
+        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+        selection_model.select(index2, selection_model.Select | selection_model.Rows)
+
+        # add constraints
+        self.widget.addSimpleConstraint()
+
+        # deactivate the first constraint
+        constraint_objects = self.widget.getConstraintObjectsForModel()
+        constraint_objects[0].active = False
+
+        con_list = [False, False, False, False, True, False]
+        new_list = []
+        for row in range(self.widget._model_model.rowCount()):
+            new_list.append(self.widget.rowHasActiveConstraint(row))
+
+        self.assertEqual(new_list, con_list)
+
+    def testGetConstraintsForModel(self):
+        """
+        Test the constraint getter for constraint texts
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        # no constraints
+        self.assertEqual(self.widget.getConstraintsForModel(),[])
+
+        # select two rows
+        row1 = 1
+        row2 = 4
+        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
+        selection_model = self.widget.lstParams.selectionModel()
+        selection_model.select(index1, selection_model.Select | selection_model.Rows)
+        selection_model.select(index2, selection_model.Select | selection_model.Rows)
+
+        # add constraints
+        self.widget.addSimpleConstraint()
+
+        # simple constraints
+        self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
+        objects = self.widget.getConstraintObjectsForModel()
+        self.assertEqual(len(objects), 2)
+        self.assertEqual(objects[1].value, '20')
+        self.assertEqual(objects[0].param, 'background')
+
+
+        # add complex constraint
+        const = Constraint(parent=None, param='scale', func='5*sld')
+        row = 0
+        self.widget.addConstraintToRow(constraint=const, row=row)
+        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', '20')])
+        objects = self.widget.getConstraintObjectsForModel()
+        self.assertEqual(len(objects), 3)
+        self.assertEqual(objects[0].func, '5*sld')
+
+    def testReplaceConstraintName(self):
+        """
+        Test the replacement of constraint moniker
+        """
+        # select model: cylinder / cylinder
+        category_index = self.widget.cbCategory.findText("Cylinder")
+        self.widget.cbCategory.setCurrentIndex(category_index)
+
+        model_index = self.widget.cbModel.findText("cylinder")
+        self.widget.cbModel.setCurrentIndex(model_index)
+
+        old_name = 'M5'
+        new_name = 'poopy'
+        # add complex constraint
+        const = Constraint(parent=None, param='scale', func='%s.5*sld'%old_name)
+        row = 0
+        self.widget.addConstraintToRow(constraint=const, row=row)
+
+        # Replace 'm5' with 'poopy'
+        self.widget.replaceConstraintName(old_name, new_name)
+
+        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', 'poopy.5*sld')])
+
 
 if __name__ == "__main__":
