diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py index bc3bc86f6..720aa7f0c 100644 --- a/securedrop_client/gui/widgets.py +++ b/securedrop_client/gui/widgets.py @@ -1574,6 +1574,13 @@ def __init__( self.setLayout(main_layout) self.update_conversation(self.source.collection) + # Refresh the session to update any replies that failed from a network timeout + self.controller.reply_succeeded.connect(self.refresh_conversation) + + def refresh_conversation(self): + self.controller.session.refresh(self.source) + self.update_conversation(self.source.collection) + def clear_conversation(self): while self.conversation_layout.count(): child = self.conversation_layout.takeAt(0) diff --git a/tests/gui/test_widgets.py b/tests/gui/test_widgets.py index 7d3b8a28e..a1bd37df2 100644 --- a/tests/gui/test_widgets.py +++ b/tests/gui/test_widgets.py @@ -1368,6 +1368,21 @@ def test_ConversationView_init(mocker, homedir): assert isinstance(cv.conversation_layout, QVBoxLayout) +def test_ConversationView_refresh_conversation(mocker, homedir): + """ + Ensure that the session refreshes whenever there is a new reply in case there are previously + failed replies. + """ + source = factory.Source() + cv = ConversationView(source, mocker.MagicMock()) + mocker.patch.object(cv, 'update_conversation') + + cv.refresh_conversation() + + cv.controller.session.refresh.assert_called_with(source) + cv.update_conversation.assert_called_once_with(source.collection) + + def test_ConversationView_update_conversation_position_follow(mocker, homedir): """ Check the signal handler sets the correct value for the scrollbar to be