diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py index 8fc050391f..302f6ab38e 100644 --- a/tests/ui_tools/test_boxes.py +++ b/tests/ui_tools/test_boxes.py @@ -5,7 +5,8 @@ import pytest from pytest import param as case from pytest_mock import MockerFixture -from urwid import Widget +from urwid import Text, Widget +from urwid_readline import ReadlineEdit from zulipterminal.api_types import ( TYPING_STARTED_EXPIRY_PERIOD, @@ -420,7 +421,7 @@ def test_footer_notification_on_invalid_recipients( ), ], ) - def test_update_recipients( + def test_update_recipients_from_emails_in_widget( self, write_box: WriteBox, header: str, @@ -431,7 +432,7 @@ def test_update_recipients( assert write_box.to_write_box is not None write_box.to_write_box.edit_text = header - write_box.update_recipients(write_box.to_write_box) + write_box.update_recipients_from_emails_in_widget(write_box.to_write_box) assert write_box.recipient_emails == expected_recipient_emails assert write_box.recipient_user_ids == expected_recipient_user_ids @@ -1662,6 +1663,16 @@ def test_keypress_typeahead_mode_autocomplete_key( "HEADER_BOX_RECIPIENT", id="message_to_recipient_box", ), + case( + "CONTAINER_MESSAGE", + "MESSAGE_BOX_BODY", + "private", + True, + True, + "CONTAINER_MESSAGE", + "MESSAGE_BOX_BODY", + id="private_edit_box-no_cycle", + ), ], ) @pytest.mark.parametrize("tab_key", keys_for_command("CYCLE_COMPOSE_FOCUS")) @@ -1682,17 +1693,21 @@ def test_keypress_CYCLE_COMPOSE_FOCUS( ) -> None: mocker.patch(WRITEBOX + "._set_stream_write_box_style") + if message_being_edited: + write_box.msg_edit_state = _MessageEditState( + message_id=10, old_topic="some old topic" + ) if box_type == "stream": if message_being_edited: mocker.patch(MODULE + ".EditModeButton") write_box.stream_box_edit_view(stream_id) - write_box.msg_edit_state = _MessageEditState( - message_id=10, old_topic="some old topic" - ) else: write_box.stream_box_view(stream_id) else: - write_box.private_box_view() + if message_being_edited: + write_box.private_box_edit_view() + else: + write_box.private_box_view() size = widget_size(write_box) def focus_val(x: str) -> int: @@ -1718,6 +1733,41 @@ def focus_val(x: str) -> int: expected_focus_col_name ) + @pytest.mark.parametrize("message_being_edited", [(True), (False)]) + @pytest.mark.parametrize( + "recipient_ids, expected_recipient_text", + [ + ([], "To: "), + ([11], "To: Human 1 "), + ( + [11, 12], + "To: Human 1 , Human 2 ", + ), + ], + ) + def test_private_box_recipient_editing( + self, + mocker: MockerFixture, + write_box: WriteBox, + user_dict: List[Dict[str, Any]], + user_id_email_dict: Dict[int, str], + recipient_ids: List[int], + expected_recipient_text: str, + message_being_edited: bool, + ) -> None: + write_box.model.user_id_email_dict = user_id_email_dict + write_box.model.user_dict = user_dict + mocker.patch("urwid.connect_signal") + + if message_being_edited: + write_box.private_box_edit_view(recipient_user_ids=recipient_ids) + assert isinstance(write_box.to_write_box, Text) + else: + write_box.private_box_view(recipient_user_ids=recipient_ids) + assert isinstance(write_box.to_write_box, ReadlineEdit) + + assert write_box.to_write_box.text == expected_recipient_text + @pytest.mark.parametrize("key", keys_for_command("MARKDOWN_HELP")) def test_keypress_MARKDOWN_HELP( self, write_box: WriteBox, key: str, widget_size: Callable[[Widget], urwid_Size] diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index f6d3294241..eb2f41dc09 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -173,41 +173,11 @@ def send_stop_typing_status(self) -> None: self.idle_status_tracking = False self.sent_start_typing_status = False - def private_box_view( - self, - *, - recipient_user_ids: Optional[List[int]] = None, - ) -> None: + def _setup_common_private_compose(self) -> None: self.set_editor_mode() self.compose_box_status = "open_with_private" - if recipient_user_ids: - self._set_regular_and_typing_recipient_user_ids(recipient_user_ids) - self.recipient_emails = [ - self.model.user_id_email_dict[user_id] - for user_id in self.recipient_user_ids - ] - recipient_info = ", ".join( - [ - f"{self.model.user_dict[email]['full_name']} <{email}>" - for email in self.recipient_emails - ] - ) - else: - self._set_regular_and_typing_recipient_user_ids(None) - self.recipient_emails = [] - recipient_info = "" - - self.send_next_typing_update = datetime.now() - self.to_write_box = ReadlineEdit("To: ", edit_text=recipient_info) - self.to_write_box.enable_autocomplete( - func=self._to_box_autocomplete, - key=primary_key_for_command("AUTOCOMPLETE"), - key_reverse=primary_key_for_command("AUTOCOMPLETE_REVERSE"), - ) - self.to_write_box.set_completer_delims("") - self.msg_write_box = ReadlineEdit( multiline=True, max_char=self.model.max_message_length ) @@ -232,6 +202,50 @@ def private_box_view( ] self.focus_position = self.FOCUS_CONTAINER_MESSAGE + def update_recipients_from_user_ids( + self, recipient_user_ids: Optional[List[int]] = None + ) -> str: + if recipient_user_ids: + self._set_regular_and_typing_recipient_user_ids(recipient_user_ids) + self.recipient_emails = [ + self.model.user_id_email_dict[user_id] + for user_id in self.recipient_user_ids + ] + return ", ".join( + [ + f"{self.model.user_dict[email]['full_name']} <{email}>" + for email in self.recipient_emails + ] + ) + else: + self._set_regular_and_typing_recipient_user_ids(None) + self.recipient_emails = [] + return "" + + def private_box_edit_view( + self, + *, + recipient_user_ids: Optional[List[int]] = None, + ) -> None: + self.recipient_info = self.update_recipients_from_user_ids(recipient_user_ids) + self.to_write_box = urwid.Text("To: " + self.recipient_info) + self._setup_common_private_compose() + + def private_box_view( + self, + *, + recipient_user_ids: Optional[List[int]] = None, + ) -> None: + self.send_next_typing_update = datetime.now() + recipient_info = self.update_recipients_from_user_ids(recipient_user_ids) + self.to_write_box = ReadlineEdit("To: ", edit_text=recipient_info) + self.to_write_box.enable_autocomplete( + func=self._to_box_autocomplete, + key=primary_key_for_command("AUTOCOMPLETE"), + key_reverse=primary_key_for_command("AUTOCOMPLETE_REVERSE"), + ) + self.to_write_box.set_completer_delims("") + self._setup_common_private_compose() start_period_delta = timedelta( milliseconds=self.model.typing_started_wait_period ) @@ -265,7 +279,7 @@ def track_idleness_and_update_status() -> None: urwid.connect_signal(self.msg_write_box, "change", on_type_send_status) - def update_recipients(self, write_box: ReadlineEdit) -> None: + def update_recipients_from_emails_in_widget(self, write_box: ReadlineEdit) -> None: self.recipient_emails = re.findall(REGEX_RECIPIENT_EMAIL, write_box.edit_text) self._set_regular_and_typing_recipient_user_ids( [self.model.user_dict[email]["user_id"] for email in self.recipient_emails] @@ -761,7 +775,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: ) if not all_valid: return key - self.update_recipients(self.to_write_box) + self.update_recipients_from_emails_in_widget(self.to_write_box) if self.recipient_user_ids: success = self.model.send_private_message( recipients=self.recipient_user_ids, @@ -815,7 +829,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: ) if not all_valid: return key - self.update_recipients(self.to_write_box) + self.update_recipients_from_emails_in_widget(self.to_write_box) this_draft: Composition = PrivateComposition( type="private", to=self.recipient_user_ids, @@ -893,14 +907,20 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: # We extract recipients' user_ids and emails only once we know # that all the recipients are valid, to avoid including any # invalid ones. - self.update_recipients(self.to_write_box) + self.update_recipients_from_emails_in_widget(self.to_write_box) if not self.msg_body_edit_enabled: return key if self.focus_position == self.FOCUS_CONTAINER_HEADER: self.focus_position = self.FOCUS_CONTAINER_MESSAGE else: - self.focus_position = self.FOCUS_CONTAINER_HEADER + if self.compose_box_status == "open_with_stream": + self.focus_position = self.FOCUS_CONTAINER_HEADER + if ( + self.compose_box_status == "open_with_private" + and self.msg_edit_state is None + ): + self.focus_position = self.FOCUS_CONTAINER_HEADER if self.compose_box_status == "open_with_stream": if self.msg_edit_state is not None: header.focus_col = self.FOCUS_HEADER_BOX_TOPIC diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index 3ddfa10a70..ade1407351 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -1096,7 +1096,9 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: ) if self.message["type"] == "private": - self.keypress(size, primary_key_for_command("REPLY_MESSAGE")) + self.model.controller.view.write_box.private_box_edit_view( + recipient_user_ids=self.recipient_ids, + ) elif self.message["type"] == "stream": self.model.controller.view.write_box.stream_box_edit_view( stream_id=self.stream_id,