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 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)
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)