diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py
index 66d406bc55df5213b3bd2f17cbb0b18f7e3d4b4e..40621e2032b4da330a29ccedb5b8a6f44670147d 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py
@@ -104,6 +104,10 @@ class MockSpectrum:
 class MockWorkspace:
     TEST_NAME = "THISISAtestWORKSPACE"
 
+    NUMBER_OF_ROWS_AND_COLUMNS = 5
+    COLS = NUMBER_OF_ROWS_AND_COLUMNS
+    ROWS = COLS
+
     @staticmethod
     def _return_MockSpectrumInfo():
         return MockSpectrumInfo()
@@ -136,16 +140,17 @@ class MockWorkspace:
 
         self.setCell = Mock()
 
-        self.name = None
+        self.name = Mock(return_value="MOCK_WORKSPACE_TEST")
 
-    def rowCount(self):
-        pass
+        self._column_names = []
+        for i in range(self.COLS):
+            self._column_names.append("col{0}".format(i))
 
-    def columnCount(self):
-        pass
+        self.getColumnNames = Mock(return_value=self._column_names)
+        self.column_count = self.COLS
+        self.columnCount = Mock(return_value=self.column_count)
 
-    def getColumnNames(self):
-        pass
+        self.row_count = self.ROWS
+        self.rowCount = Mock(return_value=self.row_count)
 
-    def column(self, index):
-        pass
+        self.column = Mock(return_value=[1] * self.row_count)
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py
index d3856441e64e699b6baa7fc2adce46d80aff1600..bbf0884186fee7e3dfb0eeb9609b64c2cc358206 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py
@@ -32,8 +32,8 @@ class MockQItemRange(object):
 
 
 class MockQSelectionModel:
-    def __init__(self):
-        self.hasSelection = Mock()
+    def __init__(self, has_selection=True):
+        self.hasSelection = Mock(return_value=has_selection)
         self.selectedRows = None
         self.selectedColumns = None
         self.currentIndex = None
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py
index 481a8b98b75cff82ec00b75e08365b8b0d5607d6..60d5796e619a21faa63970fc761e7d2759ac26f9 100644
--- a/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py
@@ -10,26 +10,29 @@
 
 
 class ErrorColumn:
-    def __init__(self, column, error_for_column, label_index):
+    CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE = "Cannot set Y column to be its own YErr"
+    UNHANDLED_COMPARISON_LOGIC_MESSAGE = "Unhandled comparison logic with type {}"
+
+    def __init__(self, column, related_y_column, label_index):
         self.column = column
-        self.error_for_column = error_for_column
-        if self.column == self.error_for_column:
-            raise ValueError("Cannot set Y column to be its own YErr")
+        self.related_y_column = related_y_column
+        if self.column == self.related_y_column:
+            raise ValueError(self.CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE)
 
         self.label_index = label_index
 
     def __eq__(self, other):
         if isinstance(other, ErrorColumn):
-            return self.error_for_column == other.error_for_column or self.column == other.column
+            return self.related_y_column == other.related_y_column or self.column == other.column
         elif isinstance(other, int):
             return self.column == other
         else:
-            raise RuntimeError("Unhandled comparison logic with type {}".format(type(other)))
+            raise RuntimeError(self.UNHANDLED_COMPARISON_LOGIC_MESSAGE.format(type(other)))
 
     def __cmp__(self, other):
         if isinstance(other, ErrorColumn):
-            return self.column == other.column or self.error_for_column == other.error_for_column
+            return self.column == other.column or self.related_y_column == other.related_y_column
         elif isinstance(other, int):
             return self.column == other
         else:
-            raise RuntimeError("Unhandled comparison logic with type {}".format(type(other)))
+            raise RuntimeError(self.UNHANDLED_COMPARISON_LOGIC_MESSAGE.format(type(other)))
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py
index 8c30c53d3e2fdd01d721a199cefed8c6d27b4493..d2894bb946d6ceff73d4daba2fd808c895031868 100644
--- a/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py
@@ -52,9 +52,9 @@ class MarkedColumns:
         self._add(col_index, self.as_y, [self.as_x, self.as_y_err])
 
     def add_y_err(self, err_column):
-        if err_column.error_for_column in self.as_x:
+        if err_column.related_y_column in self.as_x:
             raise ValueError("Trying to add YErr for column marked as X.")
-        elif err_column.error_for_column in self.as_y_err:
+        elif err_column.related_y_column in self.as_y_err:
             raise ValueError("Trying to add YErr for column marked as YErr.")
         # remove all labels for the column index
         len_before_remove = len(self.as_y)
@@ -64,7 +64,7 @@ class MarkedColumns:
         # -> This means that columns have been removed, and the label_index is now _wrong_
         # and has to be decremented to match the new label index correctly
         len_after_remove = len(self.as_y)
-        if err_column.error_for_column > err_column.column and len_after_remove < len_before_remove:
+        if err_column.related_y_column > err_column.column and len_after_remove < len_before_remove:
             err_column.label_index -= (len_before_remove - len_after_remove)
         self.as_y_err.append(err_column)
 
@@ -75,7 +75,7 @@ class MarkedColumns:
         # we can only have 1 Y Err for Y, so iterating and removing's iterator invalidation is not an
         # issue as the code will exit immediately after the removal
         for col in self.as_y_err:
-            if col.error_for_column == col_index:
+            if col.related_y_column == col_index:
                 self.as_y_err.remove(col)
                 break
 
@@ -105,7 +105,7 @@ class MarkedColumns:
             for yerr_col in self.as_y_err:
                 # if found append the YErr's source column - so that the data from the columns
                 # can be retrieved for plotting the errors
-                if yerr_col.error_for_column == col:
+                if yerr_col.related_y_column == col:
                     yerr_for_col[col] = yerr_col.column
 
         return yerr_for_col
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py
index 1c05e2e6091ee22375c5505e9d18791fdb23dc24..a8597856db30d938035b740fe6d37f0c07b9c11d 100644
--- a/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py
@@ -12,6 +12,7 @@ from __future__ import absolute_import, division, print_function
 from functools import partial
 
 from qtpy.QtCore import Qt
+from mantid.kernel import logger
 
 from mantid.simpleapi import DeleteTableRows, StatisticsOfTableWorkspace
 from mantidqt.widgets.common.table_copying import copy_cells, show_no_selection_to_copy_toast
@@ -29,6 +30,14 @@ class TableWorkspaceDisplay(object):
     TOO_MANY_SELECTED_FOR_PLOT = "Too many columns are selected to plot. Please select only 1."
     NUM_SELECTED_FOR_CONFIRMATION = 10
     NO_COLUMN_MARKED_AS_X = "No columns marked as X."
+    ITEM_CHANGED_INVALID_DATA_MESSAGE = "Error: Trying to set invalid data for the column."
+    ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE = "Unknown error occurred: {}"
+    TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE = "Too many selected to set as Y Error"
+    CANNOT_PLOT_AGAINST_SELF_MESSAGE = "Cannot plot column against itself."
+    NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE = "There is no associated YErr for each selected Y column."
+    PLOT_FUNCTION_ERROR_MESSAGE = "One or more of the columns being plotted contain invalid data for Matplotlib.\n\nError message:\n{}"
+    INVALID_DATA_WINDOW_TITLE = "Invalid data - Mantid Workbench"
+    COLUMN_DISPLAY_LABEL = 'Column {}'
 
     def __init__(self, ws, plot=None, parent=None, model=None, view=None, name=None):
         """
@@ -36,7 +45,7 @@ class TableWorkspaceDisplay(object):
 
         :param ws: Workspace to be displayed
         :param parent: Parent of the widget
-        :param plot: Plotting function that will be used to plot workspaces. This requires MatPlotLib directly.
+        :param plot: Plotting function that will be used to plot workspaces. This requires Matplotlib directly.
                      Passed in as parameter to allow mocking
         :param model: Model to be used by the widget. Passed in as parameter to allow mocking
         :param view: View to be used by the widget. Passed in as parameter to allow mocking
@@ -76,9 +85,9 @@ class TableWorkspaceDisplay(object):
             self.model.set_cell_data(item.row(), item.column(), item.data(Qt.DisplayRole), item.is_v3d)
             item.update()
         except ValueError:
-            self.view.show_warning("Error: Trying to set invalid data for the column.")
+            self.view.show_warning(self.ITEM_CHANGED_INVALID_DATA_MESSAGE)
         except Exception as x:
-            self.view.show_warning("Unknown error occurred: {}".format(x))
+            self.view.show_warning(self.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(x))
         finally:
             item.reset()
 
@@ -202,23 +211,23 @@ class TableWorkspaceDisplay(object):
     def action_set_as_y(self):
         self._action_set_as(self.model.marked_columns.add_y)
 
-    def action_set_as_y_err(self, error_for_column, label_index):
+    def action_set_as_y_err(self, related_y_column, label_index):
         """
 
-        :param error_for_column: The real index of the column for which the error is being marked
+        :param related_y_column: The real index of the column for which the error is being marked
         :param label_index: The index present in the label of the column for which the error is being marked
                             This will be the number in <ColumnName>[Y10] -> the 10
         """
         try:
-            selected_columns = self._get_selected_columns(1, "Too many selected to set as Y Error")
+            selected_columns = self._get_selected_columns(1, self.TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE)
         except ValueError:
             return
 
         selected_column = selected_columns[0]
         try:
-            err_column = ErrorColumn(selected_column, error_for_column, label_index)
+            err_column = ErrorColumn(selected_column, related_y_column, label_index)
         except ValueError as e:
-            self.view.show_warning(e.message)
+            self.view.show_warning(str(e))
             return
 
         self.model.marked_columns.add_y_err(err_column)
@@ -227,7 +236,7 @@ class TableWorkspaceDisplay(object):
     def action_set_as_none(self):
         self._action_set_as(self.model.marked_columns.remove)
 
-    def action_sort_ascending(self, order):
+    def action_sort(self, order):
         try:
             selected_columns = self._get_selected_columns(1, self.TOO_MANY_SELECTED_TO_SORT)
         except ValueError:
@@ -270,7 +279,7 @@ class TableWorkspaceDisplay(object):
             pass
 
         if len(selected_columns) == 0:
-            self.view.show_warning("Cannot plot column against itself.")
+            self.view.show_warning(self.CANNOT_PLOT_AGAINST_SELF_MESSAGE)
             return
 
         self._do_plot(selected_columns, selected_x, plot_type)
@@ -279,7 +288,7 @@ class TableWorkspaceDisplay(object):
         if plot_type == PlotType.LINEAR_WITH_ERR:
             yerr = self.model.marked_columns.find_yerr(selected_columns)
             if len(yerr) != len(selected_columns):
-                self.view.show_warning("There is no associated YErr for each selected Y column.")
+                self.view.show_warning(self.NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE)
                 return
         x = self.model.get_column(selected_x)
 
@@ -298,12 +307,11 @@ class TableWorkspaceDisplay(object):
             y = self.model.get_column(column)
             column_label = self.model.get_column_header(column)
             try:
-                plot_func(x, y, label='Column {}'.format(column_label), **kwargs)
+                plot_func(x, y, label=self.COLUMN_DISPLAY_LABEL.format(column_label), **kwargs)
             except ValueError as e:
-                #     TODO log error?
-                self.view.show_warning(
-                    "One or more of the columns being plotted contain invalid data for MatPlotLib."
-                    "\n\nError message:\n{}".format(e), "Invalid data - Mantid Workbench")
+                error_message = self.PLOT_FUNCTION_ERROR_MESSAGE.format(e)
+                logger.error(error_message)
+                self.view.show_warning(error_message, self.INVALID_DATA_WINDOW_TITLE)
                 return
 
             ax.set_ylabel(column_label)
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py
index 2d91244c05efa799f6b375048228fca58e48bac3..a4fdbd9d731a4f9e807ffdb1c72386cd53b027c2 100644
--- a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py
@@ -123,19 +123,19 @@ class MarkedColumnsTest(unittest.TestCase):
         -> The new YErr must replace the old one
         """
         mc = MarkedColumns()
-        ec = ErrorColumn(column=2, error_for_column=4, label_index=0)
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
         mc.add_y_err(ec)
         self.assertEqual(1, len(mc.as_y_err))
         self.assertEqual(2, mc.as_y_err[0].column)
-        self.assertEqual(4, mc.as_y_err[0].error_for_column)
+        self.assertEqual(4, mc.as_y_err[0].related_y_column)
 
         # different source column but contains error for the same column
         # adding this one should replace the first one
-        ec2 = ErrorColumn(column=2, error_for_column=5, label_index=0)
+        ec2 = ErrorColumn(column=2, related_y_column=5, label_index=0)
         mc.add_y_err(ec2)
         self.assertEqual(1, len(mc.as_y_err))
         self.assertEqual(2, mc.as_y_err[0].column)
-        self.assertEqual(5, mc.as_y_err[0].error_for_column)
+        self.assertEqual(5, mc.as_y_err[0].related_y_column)
 
     def test_add_y_err_duplicate_column_different_reference_col(self):
         """
@@ -143,19 +143,19 @@ class MarkedColumnsTest(unittest.TestCase):
         -> The new YErr must replace the old one
         """
         mc = MarkedColumns()
-        ec = ErrorColumn(column=2, error_for_column=4, label_index=0)
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
         mc.add_y_err(ec)
         self.assertEqual(1, len(mc.as_y_err))
         self.assertEqual(2, mc.as_y_err[0].column)
-        self.assertEqual(4, mc.as_y_err[0].error_for_column)
+        self.assertEqual(4, mc.as_y_err[0].related_y_column)
 
         # different source column but contains error for the same column
         # adding this one should replace the first one
-        ec2 = ErrorColumn(column=3, error_for_column=4, label_index=0)
+        ec2 = ErrorColumn(column=3, related_y_column=4, label_index=0)
         mc.add_y_err(ec2)
         self.assertEqual(1, len(mc.as_y_err))
         self.assertEqual(3, mc.as_y_err[0].column)
-        self.assertEqual(4, mc.as_y_err[0].error_for_column)
+        self.assertEqual(4, mc.as_y_err[0].related_y_column)
 
     def test_changing_y_to_x_removes_associated_yerr_columns(self):
         """
@@ -164,7 +164,7 @@ class MarkedColumnsTest(unittest.TestCase):
         """
         mc = MarkedColumns()
         mc.add_y(4)
-        ec = ErrorColumn(column=2, error_for_column=4, label_index=0)
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
         mc.add_y_err(ec)
 
         # check that we have both a Y col and an associated YErr
@@ -184,7 +184,7 @@ class MarkedColumnsTest(unittest.TestCase):
         """
         mc = MarkedColumns()
         mc.add_y(4)
-        ec = ErrorColumn(column=2, error_for_column=4, label_index=0)
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
         mc.add_y_err(ec)
 
         # check that we have both a Y col and an associated YErr
@@ -201,7 +201,7 @@ class MarkedColumnsTest(unittest.TestCase):
         mc = MarkedColumns()
         mc.add_y(4)
         mc.add_x(3)
-        ec = ErrorColumn(column=2, error_for_column=6, label_index=0)
+        ec = ErrorColumn(column=2, related_y_column=6, label_index=0)
         mc.add_y_err(ec)
 
         self.assertEqual(1, len(mc.as_x))
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py
index 9fdd788ab0bceddab2ade8e0e3f7d73956223011..85a104e04d9716038a739ece1ab3d3e23aaf8354 100644
--- a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py
@@ -7,21 +7,552 @@
 #  This file is part of the mantid workbench.
 #
 #
-# from __future__ import (absolute_import, division, print_function)
+from __future__ import (absolute_import, division, print_function)
 
-# import unittest
+import unittest
 
-# from mock import Mock
+from mock import Mock, call, patch
 
-# from mantidqt.widgets.matrixworkspacedisplay.test_helpers.matrixworkspacedisplay_common import MockQModelIndex, \
-#     MockWorkspace
-# from mantidqt.widgets.matrixworkspacedisplay.test_helpers.mock_matrixworkspacedisplay import MockQTableView
-# from mantidqt.widgets.tableworkspacedisplay.presenter import TableWorkspaceDisplay
-# from mantidqt.widgets.tableworkspacedisplay.test_helpers import MockTableWorkspaceDisplayView
+from mantidqt.widgets.matrixworkspacedisplay.test_helpers.matrixworkspacedisplay_common import MockQModelIndex, \
+    MockWorkspace
+from mantidqt.widgets.matrixworkspacedisplay.test_helpers.mock_matrixworkspacedisplay import MockQSelectionModel
+from mantidqt.widgets.tableworkspacedisplay.error_column import ErrorColumn
+from mantidqt.widgets.tableworkspacedisplay.model import TableWorkspaceDisplayModel
+from mantidqt.widgets.tableworkspacedisplay.plot_type import PlotType
+from mantidqt.widgets.tableworkspacedisplay.presenter import TableWorkspaceDisplay
+from mantidqt.widgets.tableworkspacedisplay.test_helpers.mock_plotlib import MockAx, MockPlotLib
+from mantidqt.widgets.tableworkspacedisplay.view import TableWorkspaceDisplayView
+from mantidqt.widgets.tableworkspacedisplay.workbench_table_widget_item import WorkbenchTableWidgetItem
 
 
-# class TableWorkspaceDisplayPresenterTest(unittest.TestCase):
-#     def assertNotCalled(self, mock):
-#         self.assertEqual(0, mock.call_count)
-# if __name__ == '__main__':
-#     unittest.main()
+class MockQTable:
+    """
+    Mocks the necessary functions to replace a QTableView on which data is being set.
+    """
+
+    def __init__(self):
+        self.setItem = Mock()
+        self.setRowCount = Mock()
+
+
+def with_mock_presenter(add_selection_model=False, add_plot=False):
+    """
+    Decorators with Arguments are a load of callback fun. Sources that were used for reference:
+
+    https://stackoverflow.com/a/5929165/2823526
+
+    And an answer with a little more description of the logic behind it all
+    https://stackoverflow.com/a/25827070/2823526
+
+    :param add_selection_model:
+    """
+
+    def real_decorator(func, *args, **kwargs):
+        def wrapper(self, *args):
+            ws = MockWorkspace()
+            view = Mock(spec=TableWorkspaceDisplayView)
+            if add_selection_model:
+                mock_selection_model = MockQSelectionModel(has_selection=True)
+                mock_selection_model.selectedRows = Mock(
+                    return_value=[MockQModelIndex(1, 1), MockQModelIndex(2, 2), MockQModelIndex(3, 3)])
+                mock_selection_model.selectedColumns = Mock(
+                    return_value=[MockQModelIndex(1, 1), MockQModelIndex(2, 2), MockQModelIndex(3, 3)])
+                view.mock_selection_model = mock_selection_model
+                view.selectionModel.return_value = mock_selection_model
+            twd = TableWorkspaceDisplay(ws, view=view)
+            if add_plot:
+                twd.plot = MockPlotLib()
+            return func(self, ws, view, twd, *args)
+
+        return wrapper
+
+    return real_decorator
+
+
+class TableWorkspaceDisplayPresenterTest(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # Allow the MockWorkspace to work within the model
+        TableWorkspaceDisplayModel.ALLOWED_WORKSPACE_TYPES.append(MockWorkspace)
+
+    def assertNotCalled(self, mock):
+        self.assertEqual(0, mock.call_count)
+
+    def test_supports(self):
+        ws = MockWorkspace()
+        # the test will fail if the support check fails - an exception is raised
+        TableWorkspaceDisplay.supports(ws)
+
+    def test_handleItemChanged(self):
+        ws = MockWorkspace()
+        view = Mock(spec=TableWorkspaceDisplayView)
+        twd = TableWorkspaceDisplay(ws, view=view)
+        item = Mock(spec=WorkbenchTableWidgetItem)
+        item.row.return_value = 5
+        item.column.return_value = 5
+        item.data.return_value = "magic parameter"
+        item.is_v3d = False
+
+        twd.handleItemChanged(item)
+
+        item.row.assert_called_once_with()
+        item.column.assert_called_once_with()
+        ws.setCell.assert_called_once_with(5, 5, "magic parameter")
+        item.update.assert_called_once_with()
+        item.reset.assert_called_once_with()
+
+    @with_mock_presenter
+    def test_handleItemChanged_raises_ValueError(self, ws, view, twd):
+        item = Mock(spec=WorkbenchTableWidgetItem)
+        item.row.return_value = 5
+        item.column.return_value = 5
+        item.data.return_value = "magic parameter"
+        item.is_v3d = False
+
+        # setCell will throw an exception as a side effect
+        ws.setCell.side_effect = ValueError
+
+        twd.handleItemChanged(item)
+
+        item.row.assert_called_once_with()
+        item.column.assert_called_once_with()
+        ws.setCell.assert_called_once_with(5, 5, "magic parameter")
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.ITEM_CHANGED_INVALID_DATA_MESSAGE)
+        self.assertNotCalled(item.update)
+        item.reset.assert_called_once_with()
+
+    @with_mock_presenter
+    def test_handleItemChanged_raises_Exception(self, ws, view, twd):
+        item = Mock(spec=WorkbenchTableWidgetItem)
+
+        item.row.return_value = ws.ROWS
+        item.column.return_value = ws.COLS
+        item.data.return_value = "magic parameter"
+        item.is_v3d = False
+
+        # setCell will throw an exception as a side effect
+        error_message = "TEST_EXCEPTION_MESSAGE"
+        ws.setCell.side_effect = Exception(error_message)
+
+        twd.handleItemChanged(item)
+
+        item.row.assert_called_once_with()
+        item.column.assert_called_once_with()
+        ws.setCell.assert_called_once_with(ws.ROWS, ws.COLS, "magic parameter")
+        view.show_warning.assert_called_once_with(
+            TableWorkspaceDisplay.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(error_message))
+        self.assertNotCalled(item.update)
+        item.reset.assert_called_once_with()
+
+    @with_mock_presenter
+    def test_update_column_headers(self, ws, view, twd):
+        twd.update_column_headers()
+
+        # setColumnCount is done twice - once in the TableWorkspaceDisplay initialisation, and once in the call
+        # to update_column_headers above
+        view.setColumnCount.assert_has_calls([call(ws.ROWS), call(ws.ROWS)])
+
+    @with_mock_presenter
+    def test_load_data(self, ws, _, twd):
+        mock_table = MockQTable()
+        twd.load_data(mock_table)
+
+        mock_table.setRowCount.assert_called_once_with(ws.ROWS)
+        ws.columnCount.assert_called_once_with()
+        # set item is called on every item of the table
+        self.assertEqual(ws.ROWS * ws.COLS, mock_table.setItem.call_count)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.copy_cells')
+    @with_mock_presenter
+    def test_action_copying(self, ws, view, twd, mock_copy_cells):
+        twd.action_copy_cells()
+        self.assertEqual(1, mock_copy_cells.call_count)
+        twd.action_copy_bin_values()
+        self.assertEqual(2, mock_copy_cells.call_count)
+        twd.action_copy_spectrum_values()
+        self.assertEqual(3, mock_copy_cells.call_count)
+        twd.action_keypress_copy()
+        self.assertEqual(4, mock_copy_cells.call_count)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.DeleteTableRows')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_delete_row(self, ws, view, twd, mock_DeleteTableRows):
+        twd.action_delete_row()
+        mock_DeleteTableRows.assert_called_once_with(ws, "1,2,3")
+        view.mock_selection_model.hasSelection.assert_called_once_with()
+        view.mock_selection_model.selectedRows.assert_called_once_with()
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_delete_row_no_selection(self, ws, view, twd, mock_no_selection_toast):
+        view.mock_selection_model.hasSelection = Mock(return_value=False)
+        twd.action_delete_row()
+        view.mock_selection_model.hasSelection.assert_called_once_with()
+        self.assertEqual(1, mock_no_selection_toast.call_count)
+        self.assertNotCalled(view.mock_selection_model.selectedRows)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns(self, ws, view, twd):
+        result = twd._get_selected_columns()
+        self.assertEqual([1, 2, 3], result)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns_no_selection(self, ws, view, twd, mock_no_selection_toast):
+        view.mock_selection_model.hasSelection = Mock(return_value=False)
+        self.assertRaises(ValueError, twd._get_selected_columns)
+        self.assertEqual(1, mock_no_selection_toast.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns_over_max_selected(self, ws, view, twd):
+        mock_message = "Hi."
+        self.assertRaises(ValueError, twd._get_selected_columns, max_selected=1, message_if_over_max=mock_message)
+        view.show_warning.assert_called_once_with(mock_message)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns_has_selected_but_no_columns(self, ws, view, twd, mock_no_selection_toast):
+        """
+        There is a case where the user could have a selection (of cells or rows), but not columns.
+        """
+        view.mock_selection_model.selectedColumns = Mock(return_value=[])
+        self.assertRaises(ValueError, twd._get_selected_columns)
+        self.assertEqual(1, mock_no_selection_toast.call_count)
+        view.mock_selection_model.selectedColumns.assert_called_once_with()
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay')
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.StatisticsOfTableWorkspace')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_statistics_on_columns(self, ws, view, twd, mock_StatisticsOfTableWorkspace,
+                                          mock_TableWorkspaceDisplay):
+        twd.action_statistics_on_columns()
+
+        mock_StatisticsOfTableWorkspace.assert_called_once_with(ws, [1, 2, 3])
+        # check that there was an attempt to make a new TableWorkspaceDisplay window
+        self.assertEqual(1, mock_TableWorkspaceDisplay.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_hide_selected(self, ws, view, twd):
+        twd.action_hide_selected()
+        view.hideColumn.assert_has_calls([call(1), call(2), call(3)])
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_show_all_columns(self, ws, view, twd):
+        view.columnCount = Mock(return_value=15)
+        twd.action_show_all_columns()
+        self.assertEqual(15, view.showColumn.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as(self, ws, view, twd):
+        mock_func = Mock()
+        twd._action_set_as(mock_func)
+
+        self.assertEqual(3, mock_func.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_x(self, ws, view, twd):
+        twd.action_set_as_x()
+
+        self.assertEqual(3, len(twd.model.marked_columns.as_x))
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y(self, ws, view, twd):
+        twd.action_set_as_y()
+
+        self.assertEqual(3, len(twd.model.marked_columns.as_y))
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_none(self, ws, view, twd):
+        twd.action_set_as_none()
+
+        self.assertEqual(0, len(twd.model.marked_columns.as_x))
+        self.assertEqual(0, len(twd.model.marked_columns.as_y))
+        self.assertEqual(0, len(twd.model.marked_columns.as_y_err))
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y_err(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(1, 1)])
+        twd.action_set_as_y_err(2, "0")
+        self.assertEqual(1, len(twd.model.marked_columns.as_y_err))
+        err_col = twd.model.marked_columns.as_y_err[0]
+        self.assertEqual(1, err_col.column)
+        self.assertEqual(2, err_col.related_y_column)
+        self.assertEqual("0", err_col.label_index)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y_err_too_many_selected(self, ws, view, twd):
+        twd.action_set_as_y_err(2, "0")
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y_err_failed_to_create_ErrorColumn(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(1, 1)])
+        # this will fail as we're trying to set an YErr column for itself -> (try to set col 1 to be YERR for col 1)
+        twd.action_set_as_y_err(1, "0")
+        view.show_warning.assert_called_once_with(ErrorColumn.CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_sort(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(0, 4444)])
+        order = 1
+        twd.action_sort(order)
+        view.sortByColumn.assert_called_once_with(4444, order)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_sort_too_many(self, ws, view, twd):
+        twd.action_sort(1)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_SELECTED_TO_SORT)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_plot_function_from_type(self, ws, view, twd):
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.LINEAR)
+        self.assertEqual(result, mock_ax.plot)
+
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.SCATTER)
+        self.assertEqual(result, mock_ax.scatter)
+
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.LINE_AND_SYMBOL)
+        # the function created for LINE_AND_SYMBOL is a decorated ax.plot
+        self.assertTrue("functools.partial" in str(type(result)))
+        self.assertIsNotNone(result)
+
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.LINEAR_WITH_ERR)
+        self.assertEqual(result, mock_ax.errorbar)
+
+        invalid_plot_type = 48903479
+        self.assertRaises(ValueError, twd._get_plot_function_from_type, None, invalid_plot_type)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_no_selected_columns(self, ws, view, twd, mock_show_no_selection_to_copy_toast):
+        view.mock_selection_model.selectedColumns.return_value = []
+        twd.action_plot(PlotType.LINEAR)
+        mock_show_no_selection_to_copy_toast.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_more_than_one_x(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1), MockQModelIndex(1, 2)]
+        twd.action_set_as_x()
+        twd.action_plot(PlotType.LINEAR)
+        view.mock_selection_model.selectedColumns.assert_has_calls([call(), call()])
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_SELECTED_FOR_X)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_x_in_selection(self, ws, view, twd, mock_do_plot):
+        """
+        Test that the plot is successful if there is an X in the selection,
+        and an unmarked column: which is used as the Y data
+        """
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        # set only the first column to be X
+        twd.action_set_as_x()
+        # add a second selected column, that should be used for Y
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1), MockQModelIndex(1, 2)]
+        twd.action_plot(PlotType.LINEAR)
+        mock_do_plot.assert_called_once_with([2], 1, PlotType.LINEAR)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_x_marked_but_not_selected(self, ws, view, twd, mock_do_plot):
+        """
+        Test that the plot is successful if there is no X in the selection, but a column is marked X.
+        The selection contains only an unmarked column, which is used as the Y data
+        """
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        # set only the first column to be X
+        twd.action_set_as_x()
+        # change the selection to a second column, that should be used for Y, but is not marked as anything
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)]
+        twd.action_plot(PlotType.LINEAR)
+        mock_do_plot.assert_called_once_with([2], 1, PlotType.LINEAR)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_selection_without_x(self, ws, view, twd, mock_do_plot):
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        twd.action_plot(PlotType.LINEAR)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.NO_COLUMN_MARKED_AS_X)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_column_against_itself(self, ws, view, twd):
+        """
+        For example: mark a column as X and then try to do Right click -> plot -> line on it, using it as Y
+        this will fail as it's the same column
+        """
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        # set only the first column to be X
+        twd.action_set_as_x()
+        # change the selection to a second column, that should be used for Y, but is not marked as anything
+        twd.action_plot(PlotType.LINEAR)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.CANNOT_PLOT_AGAINST_SELF_MESSAGE)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_do_action_plot_with_errors_missing_yerr_for_y_column(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)]
+        twd.action_set_as_y()
+
+        twd.action_plot(PlotType.LINEAR_WITH_ERR)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.logger.error')
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot__plot_func_throws_error(self, ws, view, twd, mock_logger_error):
+        mock_plot_function = Mock()
+        error_message = "See bottom of keyboard for HEALTH WARNING"
+        mock_plot_function.side_effect = ValueError(error_message)
+        with patch(
+                'mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._get_plot_function_from_type') \
+                as mock_get_plot_function_from_type:
+            mock_get_plot_function_from_type.return_value = mock_plot_function
+            view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+            twd.action_set_as_x()
+
+            view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)]
+            twd.action_plot(PlotType.LINEAR)
+
+            view.show_warning.assert_called_once_with(
+                TableWorkspaceDisplay.PLOT_FUNCTION_ERROR_MESSAGE.format(error_message),
+                TableWorkspaceDisplay.INVALID_DATA_WINDOW_TITLE)
+            mock_logger_error.assert_called_once_with(
+                TableWorkspaceDisplay.PLOT_FUNCTION_ERROR_MESSAGE.format(error_message))
+        self.assertNotCalled(twd.plot.mock_fig.show)
+        self.assertNotCalled(twd.plot.mock_ax.legend)
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_success(self, ws, view, twd):
+        col_as_x = 1
+        col_as_y = 2
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y_data = twd.model.get_column(col_as_y)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y)]
+        twd.action_plot(PlotType.LINEAR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+        col_y_name = twd.model.get_column_header(col_as_y)
+        twd.plot.mock_ax.set_ylabel.assert_called_once_with(col_y_name)
+        twd.plot.mock_ax.plot.assert_called_once_with(expected_x_data, expected_y_data,
+                                                      label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(
+                                                          col_y_name))
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_multiple_y_success(self, ws, view, twd):
+        col_as_x = 1
+        col_as_y1 = 2
+        col_as_y2 = 3
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y1_data = twd.model.get_column(col_as_y1)
+        expected_y2_data = twd.model.get_column(col_as_y2)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1),
+                                                                  MockQModelIndex(1, col_as_y2)]
+        twd.action_plot(PlotType.LINEAR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+
+        col_y1_name = twd.model.get_column_header(col_as_y1)
+        col_y2_name = twd.model.get_column_header(col_as_y2)
+        twd.plot.mock_ax.set_ylabel.assert_has_calls([call(col_y1_name), call(col_y2_name)])
+
+        twd.plot.mock_ax.plot.assert_has_calls([
+            call(expected_x_data, expected_y1_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y1_name)),
+            call(expected_x_data, expected_y2_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y2_name))])
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_success_error_plot(self, ws, view, twd):
+        col_as_x = 1
+        col_as_y = 2
+        col_as_y_err = 3
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y_data = twd.model.get_column(col_as_y)
+        expected_y_err_data = twd.model.get_column(col_as_y_err)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y_err)]
+        twd.action_set_as_y_err(col_as_y, '0')
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y)]
+        twd.action_plot(PlotType.LINEAR_WITH_ERR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+        col_y_name = twd.model.get_column_header(col_as_y)
+        twd.plot.mock_ax.set_ylabel.assert_called_once_with(col_y_name)
+        twd.plot.mock_ax.errorbar.assert_called_once_with(expected_x_data, expected_y_data,
+                                                          label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(
+                                                              col_y_name), yerr=expected_y_err_data)
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_multiple_y_success_error_plot(self, ws, view, twd):
+        col_as_x = 0
+        col_as_y1 = 1
+        col_as_y1_err = 2
+        col_as_y2 = 3
+        col_as_y2_err = 4
+
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y1_data = twd.model.get_column(col_as_y1)
+        expected_y1_err_data = twd.model.get_column(col_as_y1_err)
+        expected_y2_data = twd.model.get_column(col_as_y2)
+        expected_y2_err_data = twd.model.get_column(col_as_y2_err)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1_err)]
+        twd.action_set_as_y_err(col_as_y1, '0')
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y2_err)]
+        twd.action_set_as_y_err(col_as_y2, '1')
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1),
+                                                                  MockQModelIndex(1, col_as_y2)]
+        twd.action_plot(PlotType.LINEAR_WITH_ERR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+
+        col_y1_name = twd.model.get_column_header(col_as_y1)
+        col_y2_name = twd.model.get_column_header(col_as_y2)
+        twd.plot.mock_ax.set_ylabel.assert_has_calls([call(col_y1_name), call(col_y2_name)])
+
+        twd.plot.mock_ax.errorbar.assert_has_calls([
+            call(expected_x_data, expected_y1_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y1_name), yerr=expected_y1_err_data),
+            call(expected_x_data, expected_y2_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y2_name), yerr=expected_y2_err_data)])
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test_helpers/mock_plotlib.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test_helpers/mock_plotlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..762a5a5b73feb4940fc9666f5e2767d9748e1fcd
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test_helpers/mock_plotlib.py
@@ -0,0 +1,59 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+
+"""
+The aim of this module is to provide a swap-in object for
+the matplotlib.pyplot module, which has the same interface.
+Every function is instantiated as a mock, which allows to record
+calls to the plotting functions, without executing any real
+matplotlib code that requires a running GUI application.
+
+The mocking follows the real matplotlib structure so that it can be easily
+swapped in when necessary. There are two ways to do that - either
+by injecting the plotting dependency in the constructor or
+by using mock.patch to replace the 'matplotlib.pyplot'.
+
+Note that not all matplotlib.pyplot functions have been added,
+only the ones that have been necessary so far. If another function
+needs to be mocked it can be freely added in the relevant class below
+and it should not break any existing tests.
+"""
+
+
+from mock import Mock
+
+
+class MockAx:
+    def __init__(self):
+        self.plot = Mock()
+        self.scatter = Mock()
+        self.errorbar = Mock()
+        self.legend = Mock()
+        self.set_xlabel = Mock()
+        self.set_ylabel = Mock()
+
+
+class MockCanvas:
+    def __init__(self):
+        self.set_window_title = Mock()
+
+
+class MockFig:
+    def __init__(self):
+        self.show = Mock()
+        self.mock_canvas = MockCanvas()
+        self.canvas = Mock(return_value=self.mock_canvas)
+
+
+class MockPlotLib:
+    def __init__(self):
+        self.mock_ax = MockAx()
+        self.mock_fig = MockFig()
+        self.subplots = Mock(return_value=[self.mock_fig, self.mock_ax])
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py
index 264535848db926b78f565e7ec1c5af6712156d41..f834523a4c2d6525130dd3b14d3579908a6e750a 100644
--- a/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py
@@ -57,9 +57,6 @@ class TableWorkspaceDisplayView(QTableWidget):
         self.resize(600, 400)
         self.show()
 
-    def doubleClickedHeader(self):
-        print("Double clicked WOO")
-
     def keyPressEvent(self, event):
         if event.matches(QKeySequence.Copy):
             self.presenter.action_keypress_copy()
@@ -144,10 +141,10 @@ class TableWorkspaceDisplayView(QTableWidget):
         show_all_columns.triggered.connect(self.presenter.action_show_all_columns)
 
         sort_ascending = QAction("Sort Ascending", menu_main)
-        sort_ascending.triggered.connect(partial(self.presenter.action_sort_ascending, Qt.AscendingOrder))
+        sort_ascending.triggered.connect(partial(self.presenter.action_sort, Qt.AscendingOrder))
 
         sort_descending = QAction("Sort Descending", menu_main)
-        sort_descending.triggered.connect(partial(self.presenter.action_sort_ascending, Qt.DescendingOrder))
+        sort_descending.triggered.connect(partial(self.presenter.action_sort, Qt.DescendingOrder))
 
         menu_main.addAction(copy_bin_values)
         menu_main.addAction(self.make_separator(menu_main))
@@ -160,14 +157,18 @@ class TableWorkspaceDisplayView(QTableWidget):
         # If any columns are marked as Y then generate the set error menu
         if num_y_cols > 0:
             menu_set_as_y_err = QMenu("Set error for Y...")
-            for col in range(num_y_cols):
-                set_as_y_err = QAction("Y{}".format(col), menu_main)
-                # the column index of the column relative to the whole table, this is necessary
+            for label_index in range(num_y_cols):
+                set_as_y_err = QAction("Y{}".format(label_index), menu_main)
+
+                # This is the column index of the Y column for which a YERR column is being added.
+                # The column index is relative to the whole table, this is necessary
                 # so that later the data of the column marked as error can be retrieved
-                real_column_index = marked_y_cols[col]
-                # col here holds the index in the LABEL (multiple Y columns have labels Y0, Y1, YN...)
+                related_y_column = marked_y_cols[label_index]
+
+                # label_index here holds the index in the LABEL (multiple Y columns have labels Y0, Y1, YN...)
                 # this is NOT the same as the column relative to the WHOLE table
-                set_as_y_err.triggered.connect(partial(self.presenter.action_set_as_y_err, real_column_index, col))
+                set_as_y_err.triggered.connect(
+                    partial(self.presenter.action_set_as_y_err, related_y_column, label_index))
                 menu_set_as_y_err.addAction(set_as_y_err)
             menu_main.addMenu(menu_set_as_y_err)