Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable cycling to recipients when editing PM #1504

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
64 changes: 57 additions & 7 deletions tests/ui_tools/test_boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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"))
Expand All @@ -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:
Expand All @@ -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 <[email protected]>"),
(
[11, 12],
"To: Human 1 <[email protected]>, Human 2 <[email protected]>",
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test ids could be added, I assume.

],
)
def test_private_box_recipient_editing(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learnt that sometimes using double underscores in the test function names helps, not sure about this test name in particular. Would test_private_box__recipient_editing be better? We don't have any other tests for private box, yet this is easier to read?

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]
Expand Down
92 changes: 56 additions & 36 deletions zulipterminal/ui_tools/boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
)
Expand Down Expand Up @@ -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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that this naming using 'in_widget' occurs only in this function in the codebase. Does it actually help us to differentiate it from valid emails, or is it redundant?

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]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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":
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is a bit tricky to read. I was thinking about refactoring the whole CYCLE_COMPOSE_FOCUS section, but that might be overkill for this PR.

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
Expand Down
4 changes: 3 additions & 1 deletion zulipterminal/ui_tools/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't have a test. It didn't have a test in the PR that I refined, but maybe it's worth thinking about adding one? How would we test this? (didn't look into it yet)

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,
Expand Down
Loading