diff --git a/client/securedrop_client/gui/actions.py b/client/securedrop_client/gui/actions.py index 2858d8c13..73257dbf6 100644 --- a/client/securedrop_client/gui/actions.py +++ b/client/securedrop_client/gui/actions.py @@ -81,7 +81,7 @@ def __init__( source: Source, parent: QMenu, controller: Controller, - confirmation_dialog: Callable[[set[Source]], QDialog], + confirmation_dialog: Callable[[list[Source]], QDialog], ) -> None: self.source = source self.controller = controller @@ -91,9 +91,9 @@ def __init__( # DeleteSource Dialog can accept more than one source (bulk delete), # but when triggered from this menu, only applies to one source - self._confirmation_dialog = confirmation_dialog(set([self.source])) + self._confirmation_dialog = confirmation_dialog([self.source]) self._confirmation_dialog.accepted.connect( - lambda: self.controller.delete_sources(set([self.source])) + lambda: self.controller.delete_sources([self.source]) ) self.triggered.connect(self.trigger) diff --git a/client/securedrop_client/gui/source/delete/dialog.py b/client/securedrop_client/gui/source/delete/dialog.py index 2592a2f57..6453c23b4 100644 --- a/client/securedrop_client/gui/source/delete/dialog.py +++ b/client/securedrop_client/gui/source/delete/dialog.py @@ -27,7 +27,7 @@ class DeleteSourceDialog(ModalDialog): """Used to confirm deletion of source accounts.""" - def __init__(self, sources: set[Source]) -> None: + def __init__(self, sources: list[Source]) -> None: super().__init__(show_header=False, dangerous=True) self.sources = sources @@ -50,7 +50,7 @@ def __init__(self, sources: set[Source]) -> None: self.confirmation_label.setText(_("Are you sure this is what you want?")) self.adjustSize() - def make_body_text(self, sources: set[Source]) -> str: + def make_body_text(self, sources: list[Source]) -> str: message_tuple = ( "

", _("Delete entire account for: {source_or_sources}?"), @@ -74,7 +74,7 @@ def make_body_text(self, sources: set[Source]) -> str: source_or_sources=f"{self._get_source_names(sources)}" ) - def _get_source_names(self, sources: set[Source]) -> str: + def _get_source_names(self, sources: list[Source]) -> str: """ Helper. Return a comma-separated list of journalist designations. """ diff --git a/client/securedrop_client/gui/widgets.py b/client/securedrop_client/gui/widgets.py index 34aae742f..c8be4ae13 100644 --- a/client/securedrop_client/gui/widgets.py +++ b/client/securedrop_client/gui/widgets.py @@ -1145,7 +1145,7 @@ class SourceList(QListWidget): source_selection_cleared = pyqtSignal() # Bulk-context signal (toolbar) - selected_sources = pyqtSignal(object) # set[Source] + selected_sources = pyqtSignal(object) # list[Source] NUM_SOURCES_TO_ADD_AT_A_TIME = 32 INITIAL_UPDATE_SCROLLBAR_WIDTH = 20 @@ -1365,7 +1365,7 @@ def _on_item_selection_changed(self) -> None: self.source_selection_cleared.emit() # Update listeners (action toolbar) with current selection - self.selected_sources.emit(set(selected)) + self.selected_sources.emit(selected) class SourcePreview(SecureQLabel): diff --git a/client/securedrop_client/logic.py b/client/securedrop_client/logic.py index b92fa0ff3..f662cef7c 100644 --- a/client/securedrop_client/logic.py +++ b/client/securedrop_client/logic.py @@ -418,7 +418,7 @@ def __init__( # type: ignore[no-untyped-def] os.chmod(self.last_sync_filepath, 0o600) # Store currently-selected sources - self._selected_sources: set[db.Source] | None = None + self._selected_sources: list[db.Source] | None = None @pyqtSlot(int) def _on_main_queue_updated(self, num_items: int) -> None: @@ -1050,7 +1050,7 @@ def on_delete_source_failure(self, e: Exception) -> None: self.source_deletion_failed.emit(e.source_uuid) @login_required - def delete_sources(self, sources: set[db.Source]) -> None: + def delete_sources(self, sources: list[db.Source]) -> None: """ Performs a delete operation on one or more source records. @@ -1192,8 +1192,8 @@ def update_failed_replies(self) -> None: self.reply_failed.emit(failed_reply.uuid) @pyqtSlot(object) - def on_receive_selected_sources(self, sources: set[db.Source]) -> None: + def on_receive_selected_sources(self, sources: list[db.Source]) -> None: self._selected_sources = sources - def get_selected_sources(self) -> set[db.Source] | None: + def get_selected_sources(self) -> list[db.Source] | None: return self._selected_sources diff --git a/client/tests/functional/test_delete_source.py b/client/tests/functional/test_delete_source.py index 5c26bfcdc..dfe6e67ed 100644 --- a/client/tests/functional/test_delete_source.py +++ b/client/tests/functional/test_delete_source.py @@ -45,7 +45,7 @@ def check_for_conversation(): # Note: The qtbot object cannot interact with QAction items (as used in the delete button/menu) # so we programatically delete the source rather than using the GUI via qtbot source_count = gui.main_view.source_list.count() - controller.delete_sources(set([conversation.conversation_title_bar.source])) + controller.delete_sources([conversation.conversation_title_bar.source]) def check_source_list(): assert gui.main_view.source_list.count() == source_count - 1 diff --git a/client/tests/functional/test_offline_delete_source.py b/client/tests/functional/test_offline_delete_source.py index 25eca0f61..ef0c106e2 100644 --- a/client/tests/functional/test_offline_delete_source.py +++ b/client/tests/functional/test_offline_delete_source.py @@ -45,7 +45,7 @@ def check_for_conversation(): # Attempt to delete the selected source # Note: The qtbot object cannot interact with QAction items (as used in the delete button/menu) # so we programatically attempt to delete the source rather than using the GUI via qtbot - controller.delete_sources(set([conversation.conversation_title_bar.source])) + controller.delete_sources([conversation.conversation_title_bar.source]) def check_for_error(): msg = gui.bottom_pane.error_status_bar.status_bar.currentMessage() diff --git a/client/tests/gui/source/delete/test_dialog.py b/client/tests/gui/source/delete/test_dialog.py index 29c7417ec..dc854026c 100644 --- a/client/tests/gui/source/delete/test_dialog.py +++ b/client/tests/gui/source/delete/test_dialog.py @@ -5,7 +5,7 @@ @pytest.fixture( - params=[set(), set([factory.Source()]), set([factory.Source(), factory.Source()])], + params=[[], [factory.Source()], [factory.Source(), factory.Source()]], ) def dialog(request): """ @@ -22,7 +22,7 @@ def dialog(request): class TestDeleteSourceDialog: def test_dialog_setup(self, dialog): assert type(dialog) is DeleteSourceDialog - assert type(dialog.sources) is set + assert type(dialog.sources) is list assert dialog.dangerous def test_default_button_is_safer_choice(self, dialog): @@ -69,7 +69,7 @@ def test_correct_format_body_text(self): For n > 1 sources, ensure the warning text includes all the journalist desginators. """ - sources = set() + sources = [] names = [ "source one", "source two", @@ -82,7 +82,7 @@ def test_correct_format_body_text(self): for item in names: source = factory.Source(journalist_designation=item) - sources.update([source]) + sources.append(source) dialog = DeleteSourceDialog(sources) diff --git a/client/tests/test_logic.py b/client/tests/test_logic.py index b7c11ba0b..ec7f65fa7 100644 --- a/client/tests/test_logic.py +++ b/client/tests/test_logic.py @@ -1919,7 +1919,7 @@ def test_Controller_delete_source_not_logged_in(homedir, config, mocker, session source_db_object = mocker.MagicMock() co.on_action_requiring_login = mocker.MagicMock() co.api = None - co.delete_sources(set([source_db_object])) + co.delete_sources([source_db_object]) co.on_action_requiring_login.assert_called_with() @@ -1945,7 +1945,7 @@ def test_Controller_delete_source(homedir, config, mocker, session_maker, sessio session.add(source) session.commit() - co.delete_sources(set([source])) + co.delete_sources([source]) assert len(source_deleted_emissions) == 1 assert source_deleted_emissions[0] == [source.uuid]