Skip to content
Open
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
5 changes: 5 additions & 0 deletions docs/hotkeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@
|Show/hide stream information & modify settings|<kbd>i</kbd>|
|Show/hide stream members (from stream information)|<kbd>m</kbd>|

## Topic list actions
|Command|Key Combination|
| :--- | :---: |
|Mute/unmute Topics|<kbd>M</kbd>|

## Composing a Message
|Command|Key Combination|
| :--- | :---: |
Expand Down
21 changes: 20 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from zulipterminal.config.keys import keys_for_command
from zulipterminal.helper import initial_index as helper_initial_index
from zulipterminal.ui_tools.boxes import MessageBox
from zulipterminal.ui_tools.buttons import StreamButton, UserButton
from zulipterminal.ui_tools.buttons import (
StreamButton,
TopicButton,
UserButton,
)
from zulipterminal.version import (
MINIMUM_SUPPORTED_SERVER_VERSION,
SUPPORTED_SERVER_VERSIONS,
Expand Down Expand Up @@ -51,6 +55,21 @@ def stream_button(mocker):
return button


@pytest.fixture
def topic_button(mocker):
"""
Mocked topic button.
"""
button = TopicButton(
stream_id=1,
topic='party',
controller=mocker.patch('zulipterminal.core.Controller'),
width=27,
count=30
)
return button


@pytest.fixture
def user_button(mocker, width=38):
"""
Expand Down
28 changes: 28 additions & 0 deletions tests/core/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,34 @@ def test_stream_muting_confirmation_popup(self, mocker, controller,
+ "' ?"), "center")
pop_up.assert_called_once_with(controller, text(), partial())

@pytest.mark.parametrize('muted_topics, action', [
({
('Stream 1', 'delhi'): 1
}, 'muting'),
({
('Stream 1', 'delhi'): 1,
('Stream 1', 'party'): 2
}, 'unmuting'),
])
def test_topic_muting_confirmation_popup(self, mocker, controller,
stream_dict, topic_button,
muted_topics, action):
pop_up = mocker.patch(CORE + '.PopUpConfirmationView')
text = mocker.patch(CORE + '.urwid.Text')
partial = mocker.patch(CORE + '.partial')
controller.model._muted_topics = muted_topics
controller.model.stream_dict = stream_dict
controller.loop = mocker.Mock()

controller.topic_muting_confirmation_popup(topic_button)
text.assert_called_with(
("",
f"Confirm {action} of topic '{topic_button.topic_name}' "
f"under the '{topic_button.stream_name}' stream ?"),
"center"
)
pop_up.assert_called_once_with(controller, text(), partial())

@pytest.mark.parametrize('initial_narrow, final_narrow', [
([], [['search', 'FOO']]),
([['search', 'BOO']], [['search', 'FOO']]),
Expand Down
7 changes: 6 additions & 1 deletion tests/helper/test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,20 @@ def test_powerset(iterable, map_func, expected_powerset):
'all_msg': 8,
'streams': {99: 1},
'unread_topics': {(99, 'Some private unread topic'): 1},
'unread_muted_topics': {(1000, 'Some general unread topic'): 3},
'all_mentions': 0,
}),
([1000], [['Secret stream', 'Some private unread topic']], {
'all_msg': 8,
'streams': {1000: 3},
'unread_topics': {(1000, 'Some general unread topic'): 3},
'unread_muted_topics': {(99, 'Some private unread topic'): 1},
'all_mentions': 0,
}),
([1], [], {
'unread_muted_topics': {},
'all_mentions': 0,
}),
([1], [], {'all_mentions': 0})
], ids=['mute_private_stream_mute_general_stream_topic',
'mute_general_stream_mute_private_stream_topic',
'no_mute_some_other_stream_muted']
Expand Down
104 changes: 104 additions & 0 deletions tests/model/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def test_register_initial_desired_events(self, mocker, initial_data):
'message',
'update_message',
'reaction',
'muted_topics',
'subscription',
'typing',
'update_message_flags',
Expand Down Expand Up @@ -706,6 +707,34 @@ def test_toggle_stream_muted_status(self, mocker, model,
self.display_error_if_present.assert_called_once_with(response,
self.controller)

@pytest.mark.parametrize(['initial_muted_topics', 'op',
'topic_to_be_toggled'], [
({
('Stream 1', 'delhi'): 1
}, 'add', ('Stream 1', 'party')),
({
('Stream 1', 'delhi'): 1,
('Stream 1', 'party'): 2
}, 'remove', ('Stream 1', 'delhi'))
], ids=['muting_party', 'unmuting_delhi'])
def test_toggle_topic_muted_status(self, mocker, model,
stream_dict, topic_to_be_toggled,
initial_muted_topics, op,
response={'result': 'success'}):
Copy link
Member

Choose a reason for hiding this comment

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

We could add tests for {result: error} too for non-existent topics perhaps?

model._muted_topics = initial_muted_topics
model.stream_dict = stream_dict
model.client.mute_topic.return_value = response
model.toggle_topic_muted_status(1, topic_to_be_toggled[0],
topic_to_be_toggled[1])
request = {
'stream': topic_to_be_toggled[0],
'topic': topic_to_be_toggled[1],
'op': op
}
model.client.mute_topic.assert_called_once_with(request)
self.display_error_if_present.assert_called_once_with(response,
self.controller)

@pytest.mark.parametrize('flags_before, expected_operator', [
([], 'add'),
(['starred'], 'remove'),
Expand Down Expand Up @@ -965,6 +994,55 @@ def test__handle_message_event(self, mocker, user_profile, response,
# LOG REMAINS THE SAME IF UPDATE IS FALSE
assert self.controller.view.message_view.log == log

@pytest.mark.parametrize(['event', 'initial_muted_topics',
'toggled_topic_status'], [
({
"type": 'muted_topics',
"muted_topics": [
['Stream 1', 'delhi', 3],
['Stream 1', 'kerala', 2],
['Stream 2', 'bangalore', 1]
]
},
{
('Stream 1', 'kerala'): 2,
('Stream 2', 'bangalore'): 1
}, ('delhi', True)),
({
"type": 'muted_topics',
"muted_topics": [
['Stream 1', 'delhi', 3],
['Stream 2', 'bangalore', 1]
]
},
{
('Stream 1', 'delhi'): 3,
('Stream 1', 'kerala'): 2,
('Stream 2', 'bangalore'): 1
}, ('kerala', False))
], ids=['muting_topic_delhi', 'unmuting_topic_kerala'])
def test__handle_topic_muting_event(self, mocker, model,
event, initial_muted_topics,
toggled_topic_status):
model._muted_topics = initial_muted_topics
model._get_muted_topic = (
mocker.Mock(return_value=toggled_topic_status)
)
mark_muted = mocker.patch(
'zulipterminal.ui_tools.buttons.TopicButton.mark_muted')
mark_unmuted = mocker.patch(
'zulipterminal.ui_tools.buttons.TopicButton.mark_unmuted')
model.handle_topic_muting_event(event)

if toggled_topic_status[1]:
assert ('Stream 1', 'delhi') in model._muted_topics
mark_muted.called
else:
assert ('Stream 1', 'kerala') not in model._muted_topics
mark_unmuted.called
model._get_muted_topic.assert_called
assert model.controller.update_screen.called

@pytest.mark.parametrize(['topic_name', 'topic_order_initial',
'topic_order_final'], [
('TOPIC3', ['TOPIC2', 'TOPIC3', 'TOPIC1'],
Expand Down Expand Up @@ -2012,6 +2090,32 @@ def test_is_muted_topic(self, topic, is_muted, stream_dict, model,

assert return_value == is_muted

@pytest.mark.parametrize('muted_topics', [
[
['Stream 1', 'delhi', 4],
['Stream 1', 'kerala', 2],
['Stream 2', 'bangalore', 1]
]
])
@pytest.mark.parametrize(['initial_muted_topics', 'toggle_topic_status'], [
({
('Stream 1', 'kerala'): 2,
('Stream 2', 'bangalore'): 1
}, (['Stream 1', 'delhi', 4], True)),
({
('Stream 1', 'delhi'): 4,
('Stream 1', 'chennai'): 3,
('Stream 1', 'kerala'): 2,
('Stream 2', 'bangalore'): 1
}, (['Stream 1', 'chennai', 3], False)),
], ids=['muting_delhi', 'unmuting_chennai'])
def test__get_muted_topic(self, model, initial_muted_topics,
muted_topics, toggle_topic_status):
model._muted_topics = initial_muted_topics
return_value = model._get_muted_topic(muted_topics)

assert return_value == toggle_topic_status

@pytest.mark.parametrize('stream_id, expected_response', [
(1, True),
(462, False),
Expand Down
11 changes: 10 additions & 1 deletion tests/ui_tools/test_buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,19 @@ def test_init_calls_mark_muted(self, mocker, stream_name, title,
topic=title, controller=controller,
width=40, count=0)
if is_muted_called:
mark_muted.assert_called_once_with()
mark_muted.called
else:
mark_muted.assert_not_called()

@pytest.mark.parametrize('key', keys_for_command('TOGGLE_MUTE_TOPIC'))
def test_keypress_TOGGLE_MUTE_TOPIC(self, mocker, topic_button, key,
widget_size):
size = widget_size(topic_button)
pop_up = mocker.patch(
'zulipterminal.core.Controller.topic_muting_confirmation_popup')
topic_button.keypress(size, key)
pop_up.assert_called_once_with(topic_button)


class TestMessageLinkButton:
@pytest.fixture(autouse=True)
Expand Down
6 changes: 6 additions & 0 deletions zulipterminal/api_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ class ReactionEvent(TypedDict):
message_id: int


class TopicMutingEvent(TypedDict):
type: Literal['muted_topics']
muted_topics: List[List[str]]


class SubscriptionEvent(TypedDict):
type: Literal['subscription']
op: str
Expand Down Expand Up @@ -152,6 +157,7 @@ class UpdateDisplaySettings(TypedDict):
MessageEvent,
UpdateMessageEvent,
ReactionEvent,
TopicMutingEvent,
SubscriptionEvent,
TypingEvent,
UpdateMessageFlagsEvent,
Expand Down
6 changes: 6 additions & 0 deletions zulipterminal/config/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ class KeyBinding(TypedDict, total=False):
'help_text': 'Mute/unmute Streams',
'key_category': 'stream_list',
}),
('TOGGLE_MUTE_TOPIC', {
'keys': ['M'],
'help_text': 'Mute/unmute Topics',
'key_category': 'topic_list',
}),
('ENTER', {
'keys': ['enter'],
'help_text': 'Perform current action',
Expand Down Expand Up @@ -339,6 +344,7 @@ class KeyBinding(TypedDict, total=False):
('searching', 'Searching'),
('msg_actions', 'Message actions'),
('stream_list', 'Stream list actions'),
('topic_list', 'Topic list actions'),
('msg_compose', 'Composing a Message'),
])

Expand Down
16 changes: 16 additions & 0 deletions zulipterminal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,22 @@ def stream_muting_confirmation_popup(self, button: Any) -> None:
self.loop.widget = PopUpConfirmationView(self, question,
mute_this_stream)

def topic_muting_confirmation_popup(self, button: Any) -> None:
currently_muted = self.model.is_muted_topic(button.stream_id,
button.topic_name)
type_of_action = "unmuting" if currently_muted else "muting"
question = urwid.Text(
("",
f"Confirm {type_of_action} of topic '{button.topic_name}' under "
f"the '{button.stream_name}' stream ?"),
"center"
)
mute_this_topic = partial(self.model.toggle_topic_muted_status,
button.stream_id, button.stream_name,
button.topic_name)
self.loop.widget = PopUpConfirmationView(self, question,
mute_this_topic)

def _narrow_to(self, anchor: Optional[int], **narrow: Any) -> None:
already_narrowed = self.model.set_narrow(**narrow)
if already_narrowed:
Expand Down
Loading