Skip to content

Respect PM typing notification settings #1163

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions tests/model/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
MAX_TOPIC_NAME_LENGTH,
Model,
ServerConnectionFailure,
UserSettings,
)


Expand Down Expand Up @@ -109,6 +110,36 @@ def test_init(
assert model.active_emoji_data["zulip"]["type"] == "zulip_extra_emoji"
assert model.twenty_four_hr_format == initial_data["twenty_four_hour_time"]

@pytest.mark.parametrize(
"sptn, expected_sptn_value",
[(None, True), (True, True), (False, False)],
)
def test_init_user_settings(self, mocker, initial_data, sptn, expected_sptn_value):
assert "user_settings" not in initial_data # we add it in tests

if sptn is not None:
initial_data["user_settings"] = {"send_private_typing_notifications": sptn}

mocker.patch(MODEL + ".get_messages", return_value="")
self.client.register = mocker.Mock(return_value=initial_data)

model = Model(self.controller)

assert model.user_settings() == UserSettings(
send_private_typing_notifications=expected_sptn_value
)

def test_user_settings_expected_contents(self, model):
expected_keys = {
"send_private_typing_notifications",
}
settings = model.user_settings()
assert set(settings) == expected_keys

# Ensure original settings are unchanged through accessor
settings["foo"] = "bar"
assert set(model._user_settings) == expected_keys

@pytest.mark.parametrize(
"server_response, locally_processed_data, zulip_feature_level",
[
Expand Down Expand Up @@ -196,6 +227,7 @@ def test_register_initial_desired_events(self, mocker, initial_data):
"typing",
"update_message_flags",
"update_display_settings",
"user_settings",
"realm_emoji",
]
fetch_event_types = [
Expand All @@ -209,6 +241,7 @@ def test_register_initial_desired_events(self, mocker, initial_data):
"realm_user",
"realm_user_groups",
"update_display_settings",
"user_settings",
"realm_emoji",
"zulip_version",
]
Expand Down Expand Up @@ -672,6 +705,7 @@ def test_send_typing_status_by_user_ids(
mock_api_query = mocker.patch(
CONTROLLER + ".client.set_typing_status", return_value=response
)
model._user_settings["send_private_typing_notifications"] = True

model.send_typing_status_by_user_ids(recipient_user_ids, status=status)

Expand All @@ -688,6 +722,20 @@ def test_send_typing_status_with_no_recipients(
with pytest.raises(RuntimeError):
model.send_typing_status_by_user_ids(recipient_user_ids, status=status)

@pytest.mark.parametrize("recipient_user_ids", [[5140], [5140, 5179]])
@pytest.mark.parametrize("status", ["start", "stop"])
def test_send_typing_status_avoided_due_to_user_setting(
self, mocker, model, status, recipient_user_ids
):
model._user_settings["send_private_typing_notifications"] = False

mock_api_query = mocker.patch(CONTROLLER + ".client.set_typing_status")

model.send_typing_status_by_user_ids(recipient_user_ids, status=status)

assert not mock_api_query.called
assert not self.display_error_if_present.called

@pytest.mark.parametrize(
"response, return_value",
[
Expand Down Expand Up @@ -2917,6 +2965,18 @@ def test__handle_subscription_event_subscribers_one_user_multiple_streams(
new_subscribers = model.stream_dict[stream_id]["subscribers"]
assert new_subscribers == expected_subscribers

@pytest.mark.parametrize("value", [True, False])
def test__handle_user_settings_event(self, mocker, model, value):
setting = "send_private_typing_notifications"
event = {
"type": "user_settings",
"op": "update",
"property": setting,
"value": value,
}
model._handle_user_settings_event(event)
assert model.user_settings()[setting] == value

@pytest.mark.parametrize("setting", [True, False])
def test_update_twenty_four_hour_format(self, mocker, model, setting):
event = {
Expand Down
12 changes: 12 additions & 0 deletions zulipterminal/api_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,17 @@ class UpdateRealmEmojiEvent(TypedDict):
realm_emoji: Dict[str, RealmEmojiData]


# This is specifically only those supported by ZT
SupportedUserSettings = Literal["send_private_typing_notifications"]


class UpdateUserSettingsEvent(TypedDict):
type: Literal["user_settings"]
op: Literal["update"]
property: SupportedUserSettings
value: Any


Event = Union[
MessageEvent,
UpdateMessageEvent,
Expand All @@ -226,4 +237,5 @@ class UpdateRealmEmojiEvent(TypedDict):
UpdateMessageFlagsEvent,
UpdateDisplaySettings,
UpdateRealmEmojiEvent,
UpdateUserSettingsEvent,
]
36 changes: 35 additions & 1 deletion zulipterminal/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from urllib.parse import urlparse

import zulip
from typing_extensions import Literal
from typing_extensions import Literal, TypedDict

from zulipterminal import unicode_emojis
from zulipterminal.api_types import (
Expand Down Expand Up @@ -76,6 +76,10 @@ def sort_streams(streams: List[StreamData]) -> None:
streams.sort(key=lambda s: s["name"].lower())


class UserSettings(TypedDict):
send_private_typing_notifications: bool


class Model:
"""
A class responsible for storing the data to be displayed.
Expand Down Expand Up @@ -113,6 +117,7 @@ def __init__(self, controller: Any) -> None:
"realm_user", # Enables cross_realm_bots
"realm_user_groups",
"update_display_settings",
"user_settings",
"realm_emoji",
# zulip_version and zulip_feature_level are always returned in
# POST /register from Feature level 3.
Expand All @@ -129,6 +134,7 @@ def __init__(self, controller: Any) -> None:
("typing", self._handle_typing_event),
("update_message_flags", self._handle_update_message_flags_event),
("update_display_settings", self._handle_update_display_settings_event),
("user_settings", self._handle_user_settings_event),
("realm_emoji", self._handle_update_emoji_event),
]
)
Expand Down Expand Up @@ -191,9 +197,23 @@ def __init__(self, controller: Any) -> None:
)

self.twenty_four_hr_format = self.initial_data["twenty_four_hour_time"]

# "user_settings" only present in ZFl 89+ (v5.0)
user_settings = self.initial_data.get("user_settings", None)
self._user_settings = UserSettings(
send_private_typing_notifications=(
True
if user_settings is None
else user_settings["send_private_typing_notifications"]
), # ZFL 105, Zulip 5.0
)

self.new_user_input = True
self._start_presence_updates()

def user_settings(self) -> UserSettings:
return deepcopy(self._user_settings)

def message_retention_days_response(self, days: int, org_default: bool) -> str:
suffix = " [Organization default]" if org_default else ""
return ("Indefinite" if (days == -1 or days is None) else str(days)) + suffix
Expand Down Expand Up @@ -479,6 +499,8 @@ def mark_message_ids_as_read(self, id_list: List[int]) -> None:
def send_typing_status_by_user_ids(
self, recipient_user_ids: List[int], *, status: Literal["start", "stop"]
) -> None:
if not self.user_settings()["send_private_typing_notifications"]:
return
if recipient_user_ids:
request = {"to": recipient_user_ids, "op": status}
response = self.client.set_typing_status(request)
Expand Down Expand Up @@ -1590,6 +1612,18 @@ def _update_rendered_view(self, msg_id: int) -> None:
self.controller.update_screen()
return

def _handle_user_settings_event(self, event: Event) -> None:
"""
Event when user settings have changed - from ZFL 89, v5.0
(previously "update_display_settings" and "update_global_notifications")
"""
assert event["type"] == "user_settings"
if event["op"] == "update": # Should always be the case
# Only update settings after initialization
if event["property"] in self._user_settings.keys():
setting = event["property"]
self._user_settings[setting] = event["value"]

def _handle_update_display_settings_event(self, event: Event) -> None:
"""
Handle change to user display setting (Eg: Time format)
Expand Down