def setUp(self):
        self._qapp = mock_widget.mockQapp()
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QtGui.QWidget()

        self.context = MuonDataContext()
        self.model = GroupingTabModel(data=self.context)

        self.grouping_table_view = GroupingTableView(parent=self.obj)
        self.grouping_table_widget = GroupingTablePresenter(self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView(parent=self.obj)
        self.pairing_table_widget = PairingTablePresenter(self.pairing_table_view, self.model)

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view, self.pairing_table_view, parent=self.obj)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget)

        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()
    def setUp(self):
        self._qapp = mock_widget.mockQapp()
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QtGui.QWidget()

        self.data = MuonDataContext()
        self.model = GroupingTableModel(data=self.data)
        self.view = GroupingTableView(parent=self.obj)
        self.presenter = GroupingTablePresenter(self.view, self.model)

        self.view.warning_popup = mock.Mock()
Example #3
0
    def setUp(self):
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QWidget()

        setup_context_for_tests(self)

        self.gui_variable_observer = Observer()

        self.gui_context.gui_variables_notifier.add_subscriber(self.gui_variable_observer)
        self.model = GroupingTabModel(context=self.context)
        self.view = GroupingTableView(parent=self.obj)
        self.presenter = GroupingTablePresenter(self.view, self.model)

        self.view.enter_group_name = mock.Mock(side_effect=group_name())
        self.view.warning_popup = mock.Mock()
        self.gui_variable_observer.update = mock.MagicMock()
    def setUp(self):
        self._qapp = mock_widget.mockQapp()
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QtGui.QWidget()

        self.loaded_data = MuonLoadData()
        self.data_context = MuonDataContext(self.loaded_data)
        self.gui_context = MuonGuiContext()
        self.group_context = MuonGroupPairContext(self.data_context.check_group_contains_valid_detectors)
        self.context = MuonContext(muon_data_context=self.data_context, muon_group_context=self.group_context,
                                   muon_gui_context=self.gui_context)

        self.model = GroupingTabModel(context=self.context)

        self.grouping_table_view = GroupingTableView(parent=self.obj)
        self.grouping_table_widget = GroupingTablePresenter(self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView(parent=self.obj)
        self.pairing_table_widget = PairingTablePresenter(self.pairing_table_view, self.model)

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view, self.pairing_table_view, parent=self.obj)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget)

        self.presenter.create_update_thread = mock.MagicMock(return_value=mock.MagicMock())
        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()
    def setUp(self):
        self.obj = QWidget()

        self.loaded_data = MuonLoadData()

        setup_context_for_tests(self)

        self.model = GroupingTabModel(context=self.context)

        self.grouping_table_view = GroupingTableView()
        self.grouping_table_widget = GroupingTablePresenter(
            self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView()
        self.pairing_table_widget = PairingTablePresenter(
            self.pairing_table_view, self.model)

        self.diff_widget = DifferencePresenter(self.model)
        self.diff_widget.group_view.enter_diff_name = mock.Mock(
            side_effect=diff_name())
        self.diff_widget.pair_view.enter_diff_name = mock.Mock(
            side_effect=diff_name())

        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view,
                                    self.pairing_table_view,
                                    self.diff_widget.view)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget,
                                              self.diff_widget)

        self.presenter.create_update_thread = mock.MagicMock(
            return_value=mock.MagicMock())
        self.presenter.pairing_table_widget.handle_add_pair_button_clicked = mock.MagicMock(
        )
        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()
    def setUp(self):
        self._qapp = mock_widget.mockQapp()
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QtGui.QWidget()

        self.loaded_data = MuonLoadData()
        self.data_context = MuonDataContext(self.loaded_data)
        self.gui_context = MuonGuiContext()
        self.group_context = MuonGroupPairContext(
            self.data_context.check_group_contains_valid_detectors)
        self.context = MuonContext(muon_data_context=self.data_context,
                                   muon_group_context=self.group_context,
                                   muon_gui_context=self.gui_context)

        self.model = GroupingTabModel(context=self.context)

        self.grouping_table_view = GroupingTableView(parent=self.obj)
        self.grouping_table_widget = GroupingTablePresenter(
            self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView(parent=self.obj)
        self.pairing_table_widget = PairingTablePresenter(
            self.pairing_table_view, self.model)

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view,
                                    self.pairing_table_view,
                                    parent=self.obj)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget)

        self.presenter.create_update_thread = mock.MagicMock(
            return_value=mock.MagicMock())
        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()
Example #7
0
    def setUp(self):
        self.loaded_data = MuonLoadData()

        self.context = setup_context(False)

        self.data_context = self.context.data_context
        self.gui_context = self.context.gui_context
        self.group_context = self.context.group_pair_context

        self.model = GroupingTabModel(context=self.context)

        self.grouping_table_view = GroupingTableView()
        self.grouping_table_widget = GroupingTablePresenter(
            self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView()
        self.pairing_table_widget = PairingTablePresenter(
            self.pairing_table_view, self.model)

        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view,
                                    self.pairing_table_view)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget)

        self.presenter.create_update_thread = mock.MagicMock(
            return_value=mock.MagicMock())
        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()
    def __init__(self, context):

        self.group_tab_model = GroupingTabModel(context)

        self.grouping_table_view = GroupingTableView()
        self.grouping_table_widget = GroupingTablePresenter(
            self.grouping_table_view, self.group_tab_model)

        self.pairing_table_view = PairingTableView()
        self.pairing_table_widget = PairingTablePresenter(
            self.pairing_table_view, self.group_tab_model)

        self.group_tab_view = GroupingTabView(self.grouping_table_view,
                                              self.pairing_table_view)
        self.group_tab_presenter = GroupingTabPresenter(
            self.group_tab_view, self.group_tab_model,
            self.grouping_table_widget, self.pairing_table_widget)
    def setUp(self):
        self._qapp = mock_widget.mockQapp()
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QtGui.QWidget()

        setup_context_for_tests(self)
        
        self.gui_variable_observer = Observer()

        self.gui_context.gui_variables_notifier.add_subscriber(self.gui_variable_observer)
        self.model = GroupingTabModel(context=self.context)
        self.view = GroupingTableView(parent=self.obj)
        self.presenter = GroupingTablePresenter(self.view, self.model)

        self.view.enter_group_name = mock.Mock(side_effect=group_name())
        self.view.warning_popup = mock.Mock()
        self.gui_variable_observer.update = mock.MagicMock()
Example #10
0
    def __init__(self, context):

        self.group_tab_model = GroupingTabModel(context)

        self.grouping_table_view = GroupingTableView()
        self.grouping_table_widget = GroupingTablePresenter(
            self.grouping_table_view, self.group_tab_model)

        self.pairing_table_view = PairingTableView()
        self.pairing_table_widget = PairingTablePresenter(
            self.pairing_table_view, self.group_tab_model)

        self.group_tab_view = GroupingTabView(self.grouping_table_view,
                                              self.pairing_table_view)
        self.group_tab_presenter = GroupingTabPresenter(
            self.group_tab_view, self.group_tab_model,
            self.grouping_table_widget, self.pairing_table_widget)

        context.update_view_from_model_notifier.add_subscriber(
            self.group_tab_presenter.update_view_from_model_observer)
Example #11
0
class GroupingTablePresenterTest(unittest.TestCase):
    def setUp(self):
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QWidget()

        setup_context_for_tests(self)

        self.gui_variable_observer = Observer()

        self.gui_context.gui_variables_notifier.add_subscriber(
            self.gui_variable_observer)
        self.model = GroupingTabModel(context=self.context)
        self.view = GroupingTableView(parent=self.obj)
        self.presenter = GroupingTablePresenter(self.view, self.model)

        self.view.enter_group_name = mock.Mock(side_effect=group_name())
        self.view.warning_popup = mock.Mock()
        self.gui_variable_observer.update = mock.MagicMock()

    def tearDown(self):
        self.obj = None

    def assert_model_empty(self):
        self.assertEqual(len(self.model.group_names), 0)
        self.assertEqual(len(self.model.groups), 0)

    def assert_view_empty(self):
        self.assertEqual(self.view.num_rows(), 0)

    def add_three_groups_to_table(self):
        group1 = MuonGroup(group_name="my_group_0", detector_ids=[1])
        group2 = MuonGroup(group_name="my_group_1", detector_ids=[2])
        group3 = MuonGroup(group_name="my_group_2", detector_ids=[3])
        self.presenter.add_group(group1)
        self.presenter.add_group(group2)
        self.presenter.add_group(group3)

    # ------------------------------------------------------------------------------------------------------------------
    # TESTS : Initialization
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_table_has_three_columns_when_initialized(self):
        self.assertEqual(self.view.num_cols(), 3)

    def test_that_model_is_initialized_as_empty(self):
        self.assert_model_empty()

    def test_that_view_is_initialized_as_empty(self):
        self.assert_view_empty()

    # ------------------------------------------------------------------------------------------------------------------
    # TESTS : Adding and removing groups
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_add_group_button_adds_group(self):
        self.presenter.handle_add_group_button_clicked()
        self.assertEqual(self.view.num_rows(), 1)
        self.assertEqual(len(self.model.groups), 1)

    def test_that_add_three_groups_function_adds_three_groups(self):
        self.add_three_groups_to_table()
        self.assertEqual(self.view.num_rows(), 3)
        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_0")
        self.assertEqual(self.view.get_table_item_text(1, 0), "my_group_1")
        self.assertEqual(self.view.get_table_item_text(2, 0), "my_group_2")

    def test_that_remove_group_button_removes_group(self):
        self.add_three_groups_to_table()
        self.presenter.handle_remove_group_button_clicked()
        self.assertEqual(self.view.num_rows(), 2)

    def test_that_add_group_button_adds_group_to_end_of_table(self):
        self.add_three_groups_to_table()

        self.presenter.add_group(MuonGroup(group_name="new", detector_ids=[4]))

        self.assertEqual(
            self.view.get_table_item_text(self.view.num_rows() - 1, 0), "new")

    def test_that_remove_group_button_removes_group_from_end_of_table(self):
        self.add_three_groups_to_table()

        self.presenter.handle_remove_group_button_clicked()

        self.assertEqual(
            self.view.get_table_item_text(self.view.num_rows() - 1, 0),
            "my_group_1")

    def test_that_highlighting_rows_and_clicking_remove_group_removes_the_selected_rows(
            self):
        self.add_three_groups_to_table()
        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 2])

        self.presenter.handle_remove_group_button_clicked()

        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_1")
        self.assertEqual(self.model.group_names, ["my_group_1"])

    def test_that_cannot_add_more_than_20_rows(self):
        for i in range(maximum_number_of_groups + 1):
            self.presenter.handle_add_group_button_clicked()

        self.assertEqual(self.view.num_rows(), maximum_number_of_groups)
        self.assertEqual(len(self.model.groups), maximum_number_of_groups)

    def test_that_trying_to_add_a_20th_row_gives_warning_message(self):
        for i in range(maximum_number_of_groups + 1):
            self.presenter.handle_add_group_button_clicked()

        self.assertEqual(self.view.warning_popup.call_count, 1)

    def test_that_remove_group_when_table_is_empty_does_not_throw(self):

        self.presenter.handle_remove_group_button_clicked()
        self.assertEqual(self.view.warning_popup.call_count, 0)

    # ------------------------------------------------------------------------------------------------------------------
    # Testing context menu
    # ------------------------------------------------------------------------------------------------------------------

    def test_context_menu_add_grouping_with_no_rows_selected_adds_group_to_end_of_table(
            self):
        self.presenter.handle_add_group_button_clicked()

        self.view.contextMenuEvent(0)
        self.view.add_group_action.triggered.emit(True)

        self.assertEqual(len(self.model.groups), 2)
        self.assertEqual(self.view.num_rows(), 2)
        self.assertEqual(self.view.get_table_item_text(1, 0), "group_1")

    def test_context_menu_add_grouping_with_rows_selected_does_not_add_group(
            self):
        self.presenter.handle_add_group_button_clicked()
        self.view._get_selected_row_indices = mock.Mock(return_value=[0])

        self.view.contextMenuEvent(0)

        self.assertFalse(self.view.add_group_action.isEnabled())

    def test_context_menu_remove_grouping_with_no_rows_selected_removes_last_row(
            self):
        self.add_three_groups_to_table()

        self.view.contextMenuEvent(0)
        self.view.remove_group_action.triggered.emit(True)

        self.assertEqual(len(self.model.groups), 2)
        self.assertEqual(self.view.num_rows(), 2)
        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_0")
        self.assertEqual(self.view.get_table_item_text(1, 0), "my_group_1")

    def test_context_menu_remove_grouping_removes_selected_rows(self):
        self.add_three_groups_to_table()
        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 2])

        self.view.contextMenuEvent(0)
        self.view.remove_group_action.triggered.emit(True)

        self.assertEqual(len(self.model.groups), 1)
        self.assertEqual(self.view.num_rows(), 1)
        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_1")

    def test_context_menu_remove_grouping_disabled_if_no_groups_in_table(self):
        self.view.contextMenuEvent(0)

        self.assertFalse(self.view.remove_group_action.isEnabled())

    def test_context_menu_add_pair_disabled_unless_two_groups_selected(self):
        self.add_three_groups_to_table()

        for selected_rows in [[], [0], [0, 1, 2]]:
            self.view._get_selected_row_indices = mock.Mock(
                return_value=selected_rows)
            self.view.contextMenuEvent(0)

            self.assertFalse(self.view.add_pair_action.isEnabled())

    # ------------------------------------------------------------------------------------------------------------------
    # Group name validation
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_can_change_group_name_to_valid_name_and_update_view_and_model(
            self):
        self.add_three_groups_to_table()

        self.view.grouping_table.item(0, 0).setText("new_name")

        self.assertEqual(self.view.get_table_item_text(0, 0), "new_name")
        self.assertIn("new_name", self.model.group_names)

    def test_that_if_invalid_name_given_warning_message_is_shown(self):
        self.add_three_groups_to_table()
        invalid_names = ["", "@", "name!", "+-"]

        for invalid_name in invalid_names:
            self.view.grouping_table.item(0, 0).setText(invalid_name)

            self.assertEqual(str(self.view.get_table_item_text(0, 0)),
                             "my_group_0")
            self.assertIn("my_group_0", self.model.group_names)

    def test_that_group_names_with_numbers_and_letters_and_underscores_are_valid(
            self):
        self.add_three_groups_to_table()

        valid_names = ["fwd", "fwd_1", "1234", "FWD0001", "_fwd"]

        for valid_name in valid_names:
            self.view.grouping_table.item(0, 0).setText(valid_name)

            self.assertEqual(str(self.view.get_table_item_text(0, 0)),
                             valid_name)
            self.assertIn(valid_name, self.model.group_names)

    def test_that_warning_shown_if_duplicated_group_name_used(self):
        self.add_three_groups_to_table()

        self.view.enter_group_name = mock.Mock(return_value="my_group_1")
        self.presenter.handle_add_group_button_clicked()

        self.assertEqual(self.view.warning_popup.call_count, 1)

    # ------------------------------------------------------------------------------------------------------------------
    # detector ID validation
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_if_not_entering_numbers_into_detector_IDs_the_changes_are_rejected(
            self):
        self.add_three_groups_to_table()

        invalid_id_lists = ["fwd", "a", "A", "!", "_", "(1)", "11a22", '0']

        call_count = 0
        for invalid_ids in invalid_id_lists:
            call_count += 1

            self.view.grouping_table.setCurrentCell(0, 1)
            self.view.grouping_table.item(0, 1).setText(invalid_ids)

            self.assertEqual(self.view.warning_popup.call_count, call_count)

            self.assertEqual(self.view.get_table_item_text(0, 1), "1")
            self.assertEqual(
                self.model._context.group_pair_context["my_group_0"].detectors,
                [1])

    def test_that_displayed_values_are_simplified_to_least_verbose_form(self):
        self.presenter.handle_add_group_button_clicked()

        self.view.grouping_table.setCurrentCell(0, 1)
        self.view.grouping_table.item(0, 1).setText("20-25,10,5,4,3,2,1")

        self.assertEqual(self.view.get_table_item_text(0, 1), "1-5,10,20-25")
        self.assertEqual(
            self.model._context.group_pair_context["group_0"].detectors,
            [1, 2, 3, 4, 5, 10, 20, 21, 22, 23, 24, 25])

    def test_that_if_detector_list_changed_that_number_of_detectors_updates(
            self):
        self.presenter.handle_add_group_button_clicked()
        self.assertEqual(self.view.get_table_item_text(0, 2), "1")

        self.view.grouping_table.setCurrentCell(0, 1)
        self.view.grouping_table.item(0, 1).setText("1-10")

        self.assertEqual(self.view.get_table_item_text(0, 2), "10")

    def test_that_detector_numbers_cannot_be_edited(self):
        self.presenter.handle_add_group_button_clicked()

        self.view.grouping_table.setCurrentCell(0, 2)
        self.view.grouping_table.item(0, 2).setText("25")

        self.assertEqual(self.view.get_table_item_text(0, 2), "1")

    def test_modifying_detector_ids_to_non_existent_detector_fails(self):
        self.presenter.handle_add_group_button_clicked()
        self.view.grouping_table.item(0, 1).setText("1000")

        self.view.warning_popup.assert_called_once_with(
            'Invalid detector list.')

    def test_modifying_detector_ids_to_negative_detectors_fails(self):
        self.presenter.handle_add_group_button_clicked()
        self.view.grouping_table.item(0, 1).setText("-10-10")

        self.view.warning_popup.assert_called_once_with(
            'Invalid detector list.')

    def test_range_boxes_start_out_disabled(self):
        self.assertFalse(self.view.group_range_min.isEnabled())
        self.assertFalse(self.view.group_range_max.isEnabled())

    def test_enabling_range_min_editing_creates_context_variable(self):
        number = '1.12'
        self.view.group_range_min.setText(number)
        self.view.group_range_use_first_good_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMin'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(
            self.gui_context.gui_variables_notifier, {'GroupRangeMin': 1.12})

    def test_disabling_range_min_editing_removes_context_variable(self):
        number = '1.12'
        self.view.group_range_min.setText(number)
        self.view.group_range_use_first_good_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMin'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(
            self.gui_context.gui_variables_notifier, {'GroupRangeMin': 1.12})

        self.view.group_range_use_first_good_data.setChecked(True)

        self.assertFalse('GroupRangeMin' in self.gui_context)
        self.assertEqual(self.gui_variable_observer.update.call_count, 2)

    def test_enabling_range_max_editing_creates_context_variable(self):
        number = '1.12'
        self.view.group_range_max.setText(number)
        self.view.group_range_use_last_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMax'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(
            self.gui_context.gui_variables_notifier, {'GroupRangeMax': 1.12})

    def test_disabling_range_max_editing_removes_context_variable(self):
        number = '1.12'
        self.view.group_range_max.setText(number)
        self.view.group_range_use_last_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMax'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(
            self.gui_context.gui_variables_notifier, {'GroupRangeMax': 1.12})

        self.view.group_range_use_last_data.setChecked(True)

        self.assertFalse('GroupRangeMax' in self.gui_context)
        self.assertEqual(self.gui_variable_observer.update.call_count, 2)

    def test_updating_range_min_to_be_greater_than_range_max_displays_warning_and_vice_versa(
            self):
        original_max = '1.12'
        original_min = '0.1'
        self.view.group_range_max.setText(original_max)
        self.view.group_range_min.setText(original_min)
        self.view.group_range_use_last_data.setChecked(False)
        self.view.group_range_use_first_good_data.setChecked(False)

        self.view.group_range_min.setText('2.0')
        self.view.group_range_min.editingFinished.emit()

        self.assertEqual(self.gui_context['GroupRangeMin'],
                         float(original_min))
        self.view.warning_popup.assert_called_once_with(
            'Minimum of group asymmetry range must be less than maximum')

        self.view.group_range_max.setText('0.05')
        self.view.group_range_max.editingFinished.emit()

        self.assertEqual(self.gui_context['GroupRangeMax'],
                         float(original_max))
        self.view.warning_popup.assert_called_with(
            'Maximum of group asymmetry range must be greater than minimum')
        self.assertEqual(self.view.warning_popup.call_count, 2)
Example #12
0
class GroupingTabPresenterTest(unittest.TestCase):
    def setUp(self):
        self.loaded_data = MuonLoadData()

        self.context = setup_context(False)

        self.data_context = self.context.data_context
        self.gui_context = self.context.gui_context
        self.group_context = self.context.group_pair_context

        self.model = GroupingTabModel(context=self.context)

        self.grouping_table_view = GroupingTableView()
        self.grouping_table_widget = GroupingTablePresenter(
            self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView()
        self.pairing_table_widget = PairingTablePresenter(
            self.pairing_table_view, self.model)

        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view,
                                    self.pairing_table_view)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget)

        self.presenter.create_update_thread = mock.MagicMock(
            return_value=mock.MagicMock())
        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()

    def add_three_groups(self):
        testgroup1 = MuonGroup(group_name="fwd", detector_ids=[1, 2, 3, 4, 5])
        testgroup2 = MuonGroup(group_name="bwd", detector_ids=[6, 7, 8, 9, 10])
        testgroup3 = MuonGroup(group_name="top",
                               detector_ids=[11, 12, 13, 14, 15])
        self.grouping_table_widget.add_group(testgroup1)
        self.grouping_table_widget.add_group(testgroup2)
        self.grouping_table_widget.add_group(testgroup3)

    def add_two_pairs(self):
        testpair1 = MuonPair(pair_name="long1",
                             forward_group_name="fwd",
                             backward_group_name="bwd")
        testpair2 = MuonPair(pair_name="long2",
                             forward_group_name="fwd",
                             backward_group_name="top")
        self.pairing_table_widget.add_pair(testpair1)
        self.pairing_table_widget.add_pair(testpair2)

    def tearDown(self):
        self.obj = None

    # ------------------------------------------------------------------------------------------------------------------
    # TESTS
    # ------------------------------------------------------------------------------------------------------------------
    def test_context_menu_add_pair_adds_pair_if_two_groups_selected(self):
        self.assertEqual(self.pairing_table_view.num_rows(), 2)
        self.grouping_table_view._get_selected_row_indices = mock.Mock(
            return_value=[0, 1])
        self.grouping_table_view.contextMenuEvent(0)
        self.grouping_table_view.add_pair_action.triggered.emit(True)

        self.assertEqual(self.pairing_table_view.num_rows(), 3)

    def test_context_menu_add_pair_adds_correct_pair_if_two_groups_selected(
            self):
        self.grouping_table_view._get_selected_row_indices = mock.Mock(
            return_value=[0, 1])
        self.grouping_table_view.contextMenuEvent(0)
        self.grouping_table_view.add_pair_action.triggered.emit(True)

        pair_name = "pair_0"

        self.assertEqual(self.group_context[pair_name].forward_group, "fwd")
        self.assertEqual(self.group_context[pair_name].backward_group, "bwd")

    def test_that_clear_button_clears_model_and_view(self):
        self.view.clear_grouping_button.clicked.emit(True)

        self.assertEqual(len(self.model.groups), 0)
        self.assertEqual(len(self.model.pairs), 0)
        self.assertEqual(self.grouping_table_view.num_rows(), 0)
        self.assertEqual(self.pairing_table_view.num_rows(), 0)

    @mock.patch(
        "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML"
    )
    def test_that_load_grouping_triggers_the_correct_function(self, mock_load):
        self.view.show_file_browser_and_return_selection = mock.MagicMock(
            return_value="grouping.xml")
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]
        mock_load.return_value = (groups, pairs, 'description', 'pair1')

        self.view.load_grouping_button.clicked.emit(True)

        self.assertEqual(mock_load.call_count, 1)
        self.assertEqual(mock_load.call_args[0][0], "grouping.xml")

    def test_that_load_grouping_inserts_loaded_groups_and_pairs_correctly(
            self):
        self.view.show_file_browser_and_return_selection = mock.Mock(
            return_value="grouping.xml")
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]

        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML"
        ) as mock_load:
            # mock the loading to return set groups/pairs
            mock_load.return_value = (groups, pairs, 'description', 'pair1')
            self.view.load_grouping_button.clicked.emit(True)

            six.assertCountEqual(self, self.model.group_names,
                                 ["grp1", "grp2"])
            six.assertCountEqual(self, self.model.pair_names, ["pair1"])
            self.assertEqual(self.grouping_table_view.num_rows(), 2)
            self.assertEqual(self.pairing_table_view.num_rows(), 1)
            self.assertEqual(
                self.pairing_table_view.pairing_table.cellWidget(
                    0, 1).currentText(), "grp1")
            self.assertEqual(
                self.pairing_table_view.pairing_table.cellWidget(
                    0, 2).currentText(), "grp2")

    def test_loading_does_not_insert_invalid_groups(self):
        self.view.show_file_browser_and_return_selection = mock.Mock(
            return_value="grouping.xml")
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 1000])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]
        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML"
        ) as mock_load:
            # mock the loading to return set groups/pairs
            mock_load.return_value = (groups, pairs, 'description', 'pair1')
            self.view.load_grouping_button.clicked.emit(True)

            self.view.display_warning_box.assert_called_once_with(
                'Invalid detectors in group grp2')
            six.assertCountEqual(self, self.model.group_names, ["grp1"])
            six.assertCountEqual(self, self.model.pair_names, [])
            self.assertEqual(self.grouping_table_view.num_rows(), 1)
            self.assertEqual(self.pairing_table_view.num_rows(), 0)

    def test_that_save_grouping_triggers_the_correct_function(self):
        # Save functionality is tested elsewhere
        self.view.show_file_save_browser_and_return_selection = mock.Mock(
            return_value="grouping.xml")

        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.save_grouping_to_XML"
        ) as mock_save:
            self.view.save_grouping_button.clicked.emit(True)

            self.assertEqual(mock_save.call_count, 1)
            self.assertEqual(mock_save.call_args[0][-1], "grouping.xml")

    def test_update_all_calculates_groups_and_pairs(self):
        self.presenter.handle_update_all_clicked()

        self.presenter.update_thread.threadWrapperSetUp.assert_called_once_with(
            self.presenter.disable_editing,
            self.presenter.handle_update_finished,
            self.presenter.error_callback)
        self.presenter.update_thread.start.assert_called_once_with()

    def test_removing_group_removes_linked_pairs(self):
        self.group_context.clear_pairs()
        self.group_context.clear_groups()
        self.add_three_groups()
        self.add_two_pairs()

        self.presenter.grouping_table_widget.remove_last_row_in_view_and_model(
        )

        self.assertEqual(self.model.pair_names, ['long1'])
class GroupingTabPresenterTest(unittest.TestCase):
    def setUp(self):
        self.obj = QWidget()

        self.loaded_data = MuonLoadData()

        setup_context_for_tests(self)

        self.model = GroupingTabModel(context=self.context)

        self.grouping_table_view = GroupingTableView()
        self.grouping_table_widget = GroupingTablePresenter(
            self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView()
        self.pairing_table_widget = PairingTablePresenter(
            self.pairing_table_view, self.model)

        self.diff_widget = DifferencePresenter(self.model)
        self.diff_widget.group_view.enter_diff_name = mock.Mock(
            side_effect=diff_name())
        self.diff_widget.pair_view.enter_diff_name = mock.Mock(
            side_effect=diff_name())

        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view,
                                    self.pairing_table_view,
                                    self.diff_widget.view)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget,
                                              self.diff_widget)

        self.presenter.create_update_thread = mock.MagicMock(
            return_value=mock.MagicMock())
        self.presenter.pairing_table_widget.handle_add_pair_button_clicked = mock.MagicMock(
        )
        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()

    def add_three_groups(self):
        testgroup1 = MuonGroup(group_name="fwd", detector_ids=[1, 2, 3, 4, 5])
        testgroup2 = MuonGroup(group_name="bwd", detector_ids=[6, 7, 8, 9, 10])
        testgroup3 = MuonGroup(group_name="top",
                               detector_ids=[11, 12, 13, 14, 15])
        self.grouping_table_widget.add_group(testgroup1)
        self.grouping_table_widget.add_group(testgroup2)
        self.grouping_table_widget.add_group(testgroup3)

    def add_two_pairs(self):
        testpair1 = MuonPair(pair_name="long1",
                             forward_group_name="fwd",
                             backward_group_name="bwd")
        testpair2 = MuonPair(pair_name="long2",
                             forward_group_name="fwd",
                             backward_group_name="top")
        self.pairing_table_widget.add_pair(testpair1)
        self.pairing_table_widget.add_pair(testpair2)

    def add_group_diff(self):
        self.diff_widget.group_widget.handle_add_diff_button_clicked(
            'fwd', 'top')

    def add_pair_diff(self):
        self.diff_widget.pair_widget.handle_add_diff_button_clicked(
            'long1', 'long2')

    def tearDown(self):
        self.obj = None

    # ------------------------------------------------------------------------------------------------------------------
    # TESTS
    # ------------------------------------------------------------------------------------------------------------------
    def test_context_menu_add_pair_adds_pair_if_two_groups_selected(self):
        self.assertEqual(self.pairing_table_view.num_rows(), 2)
        self.grouping_table_view._get_selected_row_indices = mock.Mock(
            return_value=[0, 1])
        self.grouping_table_view.contextMenuEvent(0)
        self.grouping_table_view.add_pair_action.triggered.emit(True)

        self.presenter.pairing_table_widget.handle_add_pair_button_clicked.assert_called_once_with(
            'fwd', 'bwd')

    def test_that_clear_button_clears_model_and_view(self):
        self.view.clear_grouping_button.clicked.emit(True)

        self.assertEqual(len(self.model.groups), 0)
        self.assertEqual(len(self.model.pairs), 0)
        self.assertEqual(self.grouping_table_view.num_rows(), 0)
        self.assertEqual(self.pairing_table_view.num_rows(), 0)

    @mock.patch(
        "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML"
    )
    def test_that_load_grouping_triggers_the_correct_function(self, mock_load):
        self.view.show_file_browser_and_return_selection = mock.MagicMock(
            return_value="grouping.xml")
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]
        mock_load.return_value = (groups, pairs, [], 'description', 'pair1')

        self.view.load_grouping_button.clicked.emit(True)

        self.assertEqual(mock_load.call_count, 1)
        self.assertEqual(mock_load.call_args[0][0], "grouping.xml")

    def test_that_load_grouping_inserts_loaded_groups_and_pairs_correctly(
            self):
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]

        self._run_handle_load_grouping_with_mocked_load(groups,
                                                        pairs,
                                                        default='pair1')

        self.assertCountEqual(self.model.group_names, ["grp1", "grp2"])
        self.assertCountEqual(self.model.pair_names, ["pair1"])
        self.assertEqual(self.grouping_table_view.num_rows(), 2)
        self.assertEqual(self.pairing_table_view.num_rows(), 1)
        self.assertEqual(
            self.pairing_table_view.pairing_table.cellWidget(0,
                                                             2).currentText(),
            "grp1")
        self.assertEqual(
            self.pairing_table_view.pairing_table.cellWidget(0,
                                                             3).currentText(),
            "grp2")

    def test_loading_does_not_insert_invalid_groups(self):
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 1000])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]

        self._run_handle_load_grouping_with_mocked_load(groups,
                                                        pairs,
                                                        default='pair1')

        self.view.display_warning_box.assert_called_once_with(
            'Invalid detectors in group grp2')
        self.assertCountEqual(self.model.group_names, ["grp1"])
        self.assertCountEqual(self.model.pair_names, [])
        self.assertEqual(self.grouping_table_view.num_rows(), 1)
        self.assertEqual(self.pairing_table_view.num_rows(), 0)

    def test_loading_selects_all_pairs_if_any_pairs_exist_and_no_default_set(
            self):
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]

        self._run_handle_load_grouping_with_mocked_load(groups,
                                                        pairs,
                                                        default='')

        self.assertEqual(self.model.selected_pairs, ["pair1"])
        self.assertEqual(self.model.selected_groups, [])

    def test_loading_selects_groups_if_no_pairs_exist_and_no_default_set(self):
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = []

        self._run_handle_load_grouping_with_mocked_load(groups,
                                                        pairs,
                                                        default='')

        self.assertEqual(self.model.selected_pairs, [])
        self.assertEqual(self.model.selected_groups, ["grp1", "grp2"])

    def test_loading_selects_default_pairs_and_groups_correctly(self):
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]

        self._run_handle_load_grouping_with_mocked_load(groups,
                                                        pairs,
                                                        default='grp2')

        self.assertEqual(self.model.selected_pairs, [])
        self.assertEqual(self.model.selected_groups, ["grp2"])

    def test_loading_selects_correctly_when_default_is_invalid(self):
        groups = [
            MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
            MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])
        ]
        pairs = [
            MuonPair(pair_name="pair1",
                     forward_group_name="grp1",
                     backward_group_name="grp2")
        ]

        self._run_handle_load_grouping_with_mocked_load(groups,
                                                        pairs,
                                                        default='grp3')

        self.assertEqual(self.model.selected_pairs, ["pair1"])
        self.assertEqual(self.model.selected_groups, [])

    def test_that_save_grouping_triggers_the_correct_function(self):
        # Save functionality is tested elsewhere
        self.view.show_file_save_browser_and_return_selection = mock.Mock(
            return_value="grouping.xml")

        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.save_grouping_to_XML"
        ) as mock_save:
            self.view.save_grouping_button.clicked.emit(True)

            self.assertEqual(mock_save.call_count, 1)
            self.assertEqual(mock_save.call_args[0][-1], "grouping.xml")

    def test_update_all_calculates_groups_and_pairs(self):
        self.presenter.handle_update_all_clicked()

        self.presenter.update_thread.threadWrapperSetUp.assert_called_once_with(
            self.presenter.disable_editing,
            self.presenter.handle_update_finished,
            self.presenter.error_callback)
        self.presenter.update_thread.start.assert_called_once_with()

    def test_that_adding_pair_with_context_menu_allows_for_name_specification(
            self):
        self.presenter.add_pair_from_grouping_table("first", "second")
        self.pairing_table_widget.handle_add_pair_button_clicked.assert_called_once_with(
            "first", "second")

    def _run_handle_load_grouping_with_mocked_load(self,
                                                   groups,
                                                   pairs,
                                                   description='description',
                                                   default=''):
        self.view.show_file_browser_and_return_selection = mock.Mock(
            return_value="grouping.xml")
        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML"
        ) as mock_load:
            # mock the loading to return set groups/pairs
            mock_load.return_value = (groups, pairs, [], 'description',
                                      default)
            self.presenter.handle_load_grouping_from_file()

    def test_default_clicked(self):
        self.presenter._model.reset_groups_and_pairs_to_default = mock.MagicMock(
            return_value="success")
        self.presenter.handle_default_grouping_button_clicked()
        self.assertEqual(self.view.display_warning_box.call_count, 0)

    def test_default_clicked_no_data(self):
        self.presenter._model.reset_groups_and_pairs_to_default = mock.MagicMock(
            return_value="failed")
        self.presenter.handle_default_grouping_button_clicked()
        self.assertEqual(self.view.display_warning_box.call_count, 1)

    def test_update_description_to_empty_on_clear_all(self):
        self.presenter.on_clear_requested()
        self.assertEqual('', self.view.get_description_text())

    def test_cannot_remove_last_row_group_table_if_used_by_pair(self):
        self.grouping_table_widget.handle_remove_group_button_clicked()

        self.assertEqual(1, self.grouping_table_view.warning_popup.call_count)
        self.assertEqual(
            'top is used by: long2',
            self.grouping_table_view.warning_popup.call_args_list[0][0][0])

    def test_cannot_remove_last_row_group_table_if_used_by_diff(self):
        self.add_group_diff()
        self.grouping_table_widget.handle_remove_group_button_clicked()

        self.assertEqual(1, self.grouping_table_view.warning_popup.call_count)
        self.assertEqual(
            'top is used by: long2, diff_1',
            self.grouping_table_view.warning_popup.call_args_list[0][0][0])

    def test_cannot_remove_a_selected_group_used_by_pair(self):
        self.grouping_table_view.get_selected_group_names_and_indexes = mock.Mock(
            return_value=[['fwd', 0]])
        self.grouping_table_widget.handle_remove_group_button_clicked()

        self.assertEqual(1, self.grouping_table_view.warning_popup.call_count)
        self.assertEqual(
            'fwd is used by: long1, long2\n',
            self.grouping_table_view.warning_popup.call_args_list[0][0][0])

    def test_cannot_remove_a_selected_group_used_by_diff(self):
        self.add_group_diff()
        self.grouping_table_view.get_selected_group_names_and_indexes = mock.Mock(
            return_value=[['fwd', 0]])
        self.grouping_table_widget.handle_remove_group_button_clicked()

        self.assertEqual(1, self.grouping_table_view.warning_popup.call_count)
        self.assertEqual(
            'fwd is used by: long1, long2, diff_1\n',
            self.grouping_table_view.warning_popup.call_args_list[0][0][0])

    def test_cannot_remove_last_row_pair_table_if_used_by_diff(self):
        self.add_pair_diff()
        self.pairing_table_widget.handle_remove_pair_button_clicked()

        self.assertEqual(1, self.pairing_table_view.warning_popup.call_count)
        self.assertEqual(
            'long2 is used by: diff_1',
            self.pairing_table_view.warning_popup.call_args_list[0][0][0])

    def test_cannot_remove_a_selected_pair_used_by_diff(self):
        self.add_pair_diff()
        self.pairing_table_view.get_selected_pair_names_and_indexes = mock.Mock(
            return_value=[['long1', 0]])
        self.pairing_table_widget.handle_remove_pair_button_clicked()

        self.assertEqual(1, self.pairing_table_view.warning_popup.call_count)
        self.assertEqual(
            'long1 is used by: diff_1\n',
            self.pairing_table_view.warning_popup.call_args_list[0][0][0])

    def test_periods_button_no_data(self):
        self.presenter._model.is_data_loaded = mock.Mock(return_value=False)
        self.presenter.period_info_widget.addInfo = mock.MagicMock()
        self.presenter.period_info_widget.show = mock.MagicMock()
        self.presenter.handle_period_information_button_clicked()

        self.assertEqual(0,
                         self.presenter.period_info_widget.addInfo.call_count)
        self.assertEqual(1, self.presenter.period_info_widget.show.call_count)

    def test_periods_button_data_added_successfully(self):
        self.presenter._model.is_data_loaded = mock.Mock(return_value=True)
        self.presenter.period_info_widget.addInfo = mock.MagicMock()
        self.presenter.period_info_widget.show = mock.MagicMock()

        self.presenter.handle_period_information_button_clicked()

        self.assertEqual(1,
                         self.presenter.period_info_widget.addInfo.call_count)
        self.assertEqual(1, self.presenter.period_info_widget.show.call_count)

    def test_periods_button_data_missing_added_successfully(self):
        self.presenter._model.is_data_loaded = mock.Mock(return_value=True)
        self.model._data.get_sample_log = mock.Mock(return_value=None)
        self.presenter.period_info_widget.addInfo = mock.MagicMock()
        self.presenter.period_info_widget.show = mock.MagicMock()

        self.presenter.handle_period_information_button_clicked()

        self.assertEqual(1,
                         self.presenter.period_info_widget.addInfo.call_count)
        self.assertEqual(1, self.presenter.period_info_widget.show.call_count)
class GroupingTabPresenterTest(unittest.TestCase):
    def setUp(self):
        self._qapp = mock_widget.mockQapp()
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QtGui.QWidget()

        self.loaded_data = MuonLoadData()
        self.data_context = MuonDataContext(self.loaded_data)
        self.gui_context = MuonGuiContext()
        self.group_context = MuonGroupPairContext(self.data_context.check_group_contains_valid_detectors)
        self.context = MuonContext(muon_data_context=self.data_context, muon_group_context=self.group_context,
                                   muon_gui_context=self.gui_context)

        self.model = GroupingTabModel(context=self.context)

        self.grouping_table_view = GroupingTableView(parent=self.obj)
        self.grouping_table_widget = GroupingTablePresenter(self.grouping_table_view, self.model)

        self.pairing_table_view = PairingTableView(parent=self.obj)
        self.pairing_table_widget = PairingTablePresenter(self.pairing_table_view, self.model)

        self.add_three_groups()
        self.add_two_pairs()

        self.view = GroupingTabView(self.grouping_table_view, self.pairing_table_view, parent=self.obj)
        self.presenter = GroupingTabPresenter(self.view, self.model,
                                              self.grouping_table_widget,
                                              self.pairing_table_widget)

        self.presenter.create_update_thread = mock.MagicMock(return_value=mock.MagicMock())
        self.view.display_warning_box = mock.MagicMock()
        self.grouping_table_view.warning_popup = mock.MagicMock()
        self.pairing_table_view.warning_popup = mock.MagicMock()

    def add_three_groups(self):
        testgroup1 = MuonGroup(group_name="fwd", detector_ids=[1, 2, 3, 4, 5])
        testgroup2 = MuonGroup(group_name="bwd", detector_ids=[6, 7, 8, 9, 10])
        testgroup3 = MuonGroup(group_name="top", detector_ids=[11, 12, 13, 14, 15])
        self.grouping_table_widget.add_group(testgroup1)
        self.grouping_table_widget.add_group(testgroup2)
        self.grouping_table_widget.add_group(testgroup3)

    def add_two_pairs(self):
        testpair1 = MuonPair(pair_name="long1", forward_group_name="fwd", backward_group_name="bwd")
        testpair2 = MuonPair(pair_name="long2", forward_group_name="fwd", backward_group_name="top")
        self.pairing_table_widget.add_pair(testpair1)
        self.pairing_table_widget.add_pair(testpair2)

    def tearDown(self):
        self.obj = None

    # ------------------------------------------------------------------------------------------------------------------
    # TESTS
    # ------------------------------------------------------------------------------------------------------------------
    def test_context_menu_add_pair_adds_pair_if_two_groups_selected(self):
        self.assertEqual(self.pairing_table_view.num_rows(), 2)
        self.grouping_table_view._get_selected_row_indices = mock.Mock(return_value=[0, 1])
        self.grouping_table_view.contextMenuEvent(0)
        self.grouping_table_view.add_pair_action.triggered.emit(True)

        self.assertEqual(self.pairing_table_view.num_rows(), 3)

    def test_context_menu_add_pair_adds_correct_pair_if_two_groups_selected(self):
        self.grouping_table_view._get_selected_row_indices = mock.Mock(return_value=[0, 1])
        self.grouping_table_view.contextMenuEvent(0)
        self.grouping_table_view.add_pair_action.triggered.emit(True)

        pair_name = "pair_0"

        self.assertEqual(self.group_context[pair_name].forward_group, "fwd")
        self.assertEqual(self.group_context[pair_name].backward_group, "bwd")

    def test_that_clear_button_clears_model_and_view(self):
        self.view.clear_grouping_button.clicked.emit(True)

        self.assertEqual(len(self.model.groups), 0)
        self.assertEqual(len(self.model.pairs), 0)
        self.assertEqual(self.grouping_table_view.num_rows(), 0)
        self.assertEqual(self.pairing_table_view.num_rows(), 0)

    @mock.patch("Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML")
    def test_that_load_grouping_triggers_the_correct_function(self, mock_load):
        self.view.show_file_browser_and_return_selection = mock.MagicMock(return_value="grouping.xml")
        groups = [MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
                  MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])]
        pairs = [MuonPair(pair_name="pair1", forward_group_name="grp1", backward_group_name="grp2")]
        mock_load.return_value = (groups, pairs, 'description')

        self.view.load_grouping_button.clicked.emit(True)

        self.assertEqual(mock_load.call_count, 1)
        self.assertEqual(mock_load.call_args[0][0], "grouping.xml")

    def test_that_load_grouping_inserts_loaded_groups_and_pairs_correctly(self):
        self.view.show_file_browser_and_return_selection = mock.Mock(return_value="grouping.xml")
        groups = [MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
                  MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 10])]
        pairs = [MuonPair(pair_name="pair1", forward_group_name="grp1", backward_group_name="grp2")]

        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML") as mock_load:
            # mock the loading to return set groups/pairs
            mock_load.return_value = (groups, pairs, 'description')
            self.view.load_grouping_button.clicked.emit(True)

            six.assertCountEqual(self, self.model.group_names, ["grp1", "grp2"])
            six.assertCountEqual(self, self.model.pair_names, ["pair1"])
            self.assertEqual(self.grouping_table_view.num_rows(), 2)
            self.assertEqual(self.pairing_table_view.num_rows(), 1)
            self.assertEqual(self.pairing_table_view.pairing_table.cellWidget(0, 1).currentText(), "grp1")
            self.assertEqual(self.pairing_table_view.pairing_table.cellWidget(0, 2).currentText(), "grp2")

    def test_loading_does_not_insert_invalid_groups(self):
        self.view.show_file_browser_and_return_selection = mock.Mock(return_value="grouping.xml")
        groups = [MuonGroup(group_name="grp1", detector_ids=[1, 2, 3, 4, 5]),
                  MuonGroup(group_name="grp2", detector_ids=[6, 7, 8, 9, 1000])]
        pairs = [MuonPair(pair_name="pair1", forward_group_name="grp1", backward_group_name="grp2")]
        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.load_grouping_from_XML") as mock_load:
            # mock the loading to return set groups/pairs
            mock_load.return_value = (groups, pairs, 'description')
            self.view.load_grouping_button.clicked.emit(True)

            self.view.display_warning_box.assert_called_once_with('Invalid detectors in group grp2')
            six.assertCountEqual(self, self.model.group_names, ["grp1"])
            six.assertCountEqual(self, self.model.pair_names, [])
            self.assertEqual(self.grouping_table_view.num_rows(), 1)
            self.assertEqual(self.pairing_table_view.num_rows(), 0)

    def test_that_save_grouping_triggers_the_correct_function(self):
        # Save functionality is tested elsewhere
        self.view.show_file_save_browser_and_return_selection = mock.Mock(return_value="grouping.xml")

        with mock.patch(
                "Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_presenter.xml_utils.save_grouping_to_XML") as mock_save:
            self.view.save_grouping_button.clicked.emit(True)

            self.assertEqual(mock_save.call_count, 1)
            self.assertEqual(mock_save.call_args[0][-1], "grouping.xml")

    def test_update_all_calculates_groups_and_pairs(self):
        self.presenter.handle_update_all_clicked()

        self.presenter.update_thread.threadWrapperSetUp.assert_called_once_with(self.presenter.disable_editing,
                                                                                self.presenter.handle_update_finished,
                                                                                self.presenter.error_callback)
        self.presenter.update_thread.start.assert_called_once_with()

    def test_removing_group_removes_linked_pairs(self):
        self.group_context.clear_pairs()
        self.group_context.clear_groups()
        self.add_three_groups()
        self.add_two_pairs()

        self.presenter.grouping_table_widget.remove_last_row_in_view_and_model()

        self.assertEqual(self.model.pair_names, ['long1'])
class GroupingTablePresenterTest(unittest.TestCase):

    def setUp(self):
        self._qapp = mock_widget.mockQapp()
        # Store an empty widget to parent all the views, and ensure they are deleted correctly
        self.obj = QtGui.QWidget()

        setup_context_for_tests(self)
        
        self.gui_variable_observer = Observer()

        self.gui_context.gui_variables_notifier.add_subscriber(self.gui_variable_observer)
        self.model = GroupingTabModel(context=self.context)
        self.view = GroupingTableView(parent=self.obj)
        self.presenter = GroupingTablePresenter(self.view, self.model)

        self.view.enter_group_name = mock.Mock(side_effect=group_name())
        self.view.warning_popup = mock.Mock()
        self.gui_variable_observer.update = mock.MagicMock()

    def tearDown(self):
        self.obj = None

    def assert_model_empty(self):
        self.assertEqual(len(self.model.group_names), 0)
        self.assertEqual(len(self.model.groups), 0)

    def assert_view_empty(self):
        self.assertEqual(self.view.num_rows(), 0)

    def add_three_groups_to_table(self):
        group1 = MuonGroup(group_name="my_group_0", detector_ids=[1])
        group2 = MuonGroup(group_name="my_group_1", detector_ids=[2])
        group3 = MuonGroup(group_name="my_group_2", detector_ids=[3])
        self.presenter.add_group(group1)
        self.presenter.add_group(group2)
        self.presenter.add_group(group3)

    # ------------------------------------------------------------------------------------------------------------------
    # TESTS : Initialization
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_table_has_three_columns_when_initialized(self):
        self.assertEqual(self.view.num_cols(), 3)

    def test_that_model_is_initialized_as_empty(self):
        self.assert_model_empty()

    def test_that_view_is_initialized_as_empty(self):
        self.assert_view_empty()

    # ------------------------------------------------------------------------------------------------------------------
    # TESTS : Adding and removing groups
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_add_group_button_adds_group(self):
        self.presenter.handle_add_group_button_clicked()
        self.assertEqual(self.view.num_rows(), 1)
        self.assertEqual(len(self.model.groups), 1)

    def test_that_add_three_groups_function_adds_three_groups(self):
        self.add_three_groups_to_table()
        self.assertEqual(self.view.num_rows(), 3)
        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_0")
        self.assertEqual(self.view.get_table_item_text(1, 0), "my_group_1")
        self.assertEqual(self.view.get_table_item_text(2, 0), "my_group_2")

    def test_that_remove_group_button_removes_group(self):
        self.add_three_groups_to_table()
        self.presenter.handle_remove_group_button_clicked()
        self.assertEqual(self.view.num_rows(), 2)

    def test_that_add_group_button_adds_group_to_end_of_table(self):
        self.add_three_groups_to_table()

        self.presenter.add_group(MuonGroup(group_name="new", detector_ids=[4]))

        self.assertEqual(self.view.get_table_item_text(self.view.num_rows() - 1, 0), "new")

    def test_that_remove_group_button_removes_group_from_end_of_table(self):
        self.add_three_groups_to_table()

        self.presenter.handle_remove_group_button_clicked()

        self.assertEqual(self.view.get_table_item_text(self.view.num_rows() - 1, 0), "my_group_1")

    def test_that_highlighting_rows_and_clicking_remove_group_removes_the_selected_rows(self):
        self.add_three_groups_to_table()
        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 2])

        self.presenter.handle_remove_group_button_clicked()

        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_1")
        self.assertEqual(self.model.group_names, ["my_group_1"])

    def test_that_cannot_add_more_than_20_rows(self):
        for i in range(maximum_number_of_groups + 1):
            self.presenter.handle_add_group_button_clicked()

        self.assertEqual(self.view.num_rows(), maximum_number_of_groups)
        self.assertEqual(len(self.model.groups), maximum_number_of_groups)

    def test_that_trying_to_add_a_20th_row_gives_warning_message(self):
        for i in range(maximum_number_of_groups + 1):
            self.presenter.handle_add_group_button_clicked()

        self.assertEqual(self.view.warning_popup.call_count, 1)

    def test_that_remove_group_when_table_is_empty_does_not_throw(self):

        self.presenter.handle_remove_group_button_clicked()
        self.assertEqual(self.view.warning_popup.call_count, 0)

    # ------------------------------------------------------------------------------------------------------------------
    # Testing context menu
    # ------------------------------------------------------------------------------------------------------------------

    def test_context_menu_add_grouping_with_no_rows_selected_adds_group_to_end_of_table(self):
        self.presenter.handle_add_group_button_clicked()

        self.view.contextMenuEvent(0)
        self.view.add_group_action.triggered.emit(True)

        self.assertEqual(len(self.model.groups), 2)
        self.assertEqual(self.view.num_rows(), 2)
        self.assertEqual(self.view.get_table_item_text(1, 0), "group_1")

    def test_context_menu_add_grouping_with_rows_selected_does_not_add_group(self):
        self.presenter.handle_add_group_button_clicked()
        self.view._get_selected_row_indices = mock.Mock(return_value=[0])

        self.view.contextMenuEvent(0)

        self.assertFalse(self.view.add_group_action.isEnabled())

    def test_context_menu_remove_grouping_with_no_rows_selected_removes_last_row(self):
        self.add_three_groups_to_table()

        self.view.contextMenuEvent(0)
        self.view.remove_group_action.triggered.emit(True)

        self.assertEqual(len(self.model.groups), 2)
        self.assertEqual(self.view.num_rows(), 2)
        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_0")
        self.assertEqual(self.view.get_table_item_text(1, 0), "my_group_1")

    def test_context_menu_remove_grouping_removes_selected_rows(self):
        self.add_three_groups_to_table()
        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 2])

        self.view.contextMenuEvent(0)
        self.view.remove_group_action.triggered.emit(True)

        self.assertEqual(len(self.model.groups), 1)
        self.assertEqual(self.view.num_rows(), 1)
        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_1")

    def test_context_menu_remove_grouping_disabled_if_no_groups_in_table(self):
        self.view.contextMenuEvent(0)

        self.assertFalse(self.view.remove_group_action.isEnabled())

    def test_context_menu_add_pair_disabled_unless_two_groups_selected(self):
        self.add_three_groups_to_table()

        for selected_rows in [[], [0], [0, 1, 2]]:
            self.view._get_selected_row_indices = mock.Mock(return_value=selected_rows)
            self.view.contextMenuEvent(0)

            self.assertFalse(self.view.add_pair_action.isEnabled())

    # ------------------------------------------------------------------------------------------------------------------
    # Group name validation
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_can_change_group_name_to_valid_name_and_update_view_and_model(self):
        self.add_three_groups_to_table()

        self.view.grouping_table.item(0, 0).setText("new_name")

        self.assertEqual(self.view.get_table_item_text(0, 0), "new_name")
        self.assertIn("new_name", self.model.group_names)

    def test_that_if_invalid_name_given_warning_message_is_shown(self):
        self.add_three_groups_to_table()
        invalid_names = ["", "@", "name!", "+-"]

        for invalid_name in invalid_names:
            self.view.grouping_table.item(0, 0).setText(invalid_name)

            self.assertEqual(str(self.view.get_table_item_text(0, 0)), "my_group_0")
            self.assertIn("my_group_0", self.model.group_names)

    def test_that_group_names_with_numbers_and_letters_and_underscores_are_valid(self):
        self.add_three_groups_to_table()

        valid_names = ["fwd", "fwd_1", "1234", "FWD0001", "_fwd"]

        for valid_name in valid_names:
            self.view.grouping_table.item(0, 0).setText(valid_name)

            self.assertEqual(str(self.view.get_table_item_text(0, 0)), valid_name)
            self.assertIn(valid_name, self.model.group_names)

    def test_that_warning_shown_if_duplicated_group_name_used(self):
        self.add_three_groups_to_table()

        self.view.enter_group_name = mock.Mock(return_value="my_group_1")
        self.presenter.handle_add_group_button_clicked()

        self.assertEqual(self.view.warning_popup.call_count, 1)

    # ------------------------------------------------------------------------------------------------------------------
    # detector ID validation
    # ------------------------------------------------------------------------------------------------------------------

    def test_that_if_not_entering_numbers_into_detector_IDs_the_changes_are_rejected(self):
        self.add_three_groups_to_table()

        invalid_id_lists = ["fwd", "a", "A", "!", "_", "(1)", "11a22", '0']

        call_count = 0
        for invalid_ids in invalid_id_lists:
            call_count += 1

            self.view.grouping_table.setCurrentCell(0, 1)
            self.view.grouping_table.item(0, 1).setText(invalid_ids)

            self.assertEqual(self.view.warning_popup.call_count, call_count)

            self.assertEqual(self.view.get_table_item_text(0, 1), "1")
            self.assertEqual(self.model._context.group_pair_context["my_group_0"].detectors, [1])

    def test_that_displayed_values_are_simplified_to_least_verbose_form(self):
        self.presenter.handle_add_group_button_clicked()

        self.view.grouping_table.setCurrentCell(0, 1)
        self.view.grouping_table.item(0, 1).setText("20-25,10,5,4,3,2,1")

        self.assertEqual(self.view.get_table_item_text(0, 1), "1-5,10,20-25")
        self.assertEqual(self.model._context.group_pair_context["group_0"].detectors, [1, 2, 3, 4, 5, 10, 20, 21, 22, 23, 24, 25])

    def test_that_if_detector_list_changed_that_number_of_detectors_updates(self):
        self.presenter.handle_add_group_button_clicked()
        self.assertEqual(self.view.get_table_item_text(0, 2), "1")

        self.view.grouping_table.setCurrentCell(0, 1)
        self.view.grouping_table.item(0, 1).setText("1-10")

        self.assertEqual(self.view.get_table_item_text(0, 2), "10")

    def test_that_detector_numbers_cannot_be_edited(self):
        self.presenter.handle_add_group_button_clicked()

        self.view.grouping_table.setCurrentCell(0, 2)
        self.view.grouping_table.item(0, 2).setText("25")

        self.assertEqual(self.view.get_table_item_text(0, 2), "1")

    def test_modifying_detector_ids_to_non_existent_detector_fails(self):
        self.presenter.handle_add_group_button_clicked()
        self.view.grouping_table.item(0, 1).setText("1000")

        self.view.warning_popup.assert_called_once_with('Invalid detector list.')

    def test_modifying_detector_ids_to_negative_detectors_fails(self):
        self.presenter.handle_add_group_button_clicked()
        self.view.grouping_table.item(0, 1).setText("-10-10")

        self.view.warning_popup.assert_called_once_with('Invalid detector list.')

    def test_range_boxes_start_out_disabled(self):
        self.assertFalse(self.view.group_range_min.isEnabled())
        self.assertFalse(self.view.group_range_max.isEnabled())

    def test_enabling_range_min_editing_creates_context_variable(self):
        number = '1.12'
        self.view.group_range_min.setText(number)
        self.view.group_range_use_first_good_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMin'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(self.gui_context.gui_variables_notifier, None)

    def test_disabling_range_min_editing_removes_context_variable(self):
        number = '1.12'
        self.view.group_range_min.setText(number)
        self.view.group_range_use_first_good_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMin'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(self.gui_context.gui_variables_notifier, None)

        self.view.group_range_use_first_good_data.setChecked(True)

        self.assertFalse('GroupRangeMin' in self.gui_context)
        self.assertEqual(self.gui_variable_observer.update.call_count, 2)

    def test_enabling_range_max_editing_creates_context_variable(self):
        number = '1.12'
        self.view.group_range_max.setText(number)
        self.view.group_range_use_last_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMax'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(self.gui_context.gui_variables_notifier, None)

    def test_disabling_range_max_editing_removes_context_variable(self):
        number = '1.12'
        self.view.group_range_max.setText(number)
        self.view.group_range_use_last_data.setChecked(False)

        self.assertEqual(self.gui_context['GroupRangeMax'], float(number))
        self.gui_variable_observer.update.assert_called_once_with(self.gui_context.gui_variables_notifier, None)

        self.view.group_range_use_last_data.setChecked(True)

        self.assertFalse('GroupRangeMax' in self.gui_context)
        self.assertEqual(self.gui_variable_observer.update.call_count, 2)

    def test_updating_range_min_to_be_greater_than_range_max_displays_warning_and_vice_versa(self):
        original_max = '1.12'
        original_min = '0.1'
        self.view.group_range_max.setText(original_max)
        self.view.group_range_min.setText(original_min)
        self.view.group_range_use_last_data.setChecked(False)
        self.view.group_range_use_first_good_data.setChecked(False)

        self.view.group_range_min.setText('2.0')
        self.view.group_range_min.editingFinished.emit()

        self.assertEqual(self.gui_context['GroupRangeMin'], float(original_min))
        self.view.warning_popup.assert_called_once_with('Minimum of group asymmetry range must be less than maximum')

        self.view.group_range_max.setText('0.05')
        self.view.group_range_max.editingFinished.emit()

        self.assertEqual(self.gui_context['GroupRangeMax'], float(original_max))
        self.view.warning_popup.assert_called_with('Maximum of group asymmetry range must be greater than minimum')
        self.assertEqual(self.view.warning_popup.call_count, 2)