diff --git a/tests/ui_tools/test_buttons.py b/tests/ui_tools/test_buttons.py index 45894e6c63..217a2cf36e 100644 --- a/tests/ui_tools/test_buttons.py +++ b/tests/ui_tools/test_buttons.py @@ -103,16 +103,23 @@ def test__decode_stream_data(self, stream_data, expected_response): (SERVER_URL + '/#narrow/stream/Stream.201', {'narrow': 'stream', 'stream': {'stream_id': None, 'stream_name': 'Stream 1'}}), + (SERVER_URL + '/#narrow/stream/1-Stream-1/topic/foo.20bar', + {'narrow': 'stream:topic', 'topic_name': 'foo bar', + 'stream': {'stream_id': 1, 'stream_name': None}}), (SERVER_URL + '/#narrow/foo', {}), (SERVER_URL + '/#narrow/stream/', {}), + (SERVER_URL + '/#narrow/stream/1-Stream-1/topic/', + {}), ], ids=[ 'modern_stream_narrow_link', 'deprecated_stream_narrow_link', + 'topic_narrow_link', 'invalid_narrow_link_1', 'invalid_narrow_link_2', + 'invalid_narrow_link_3', ] ) def test__parse_narrow_link(self, link, expected_parsed_link): @@ -125,6 +132,7 @@ def test__parse_narrow_link(self, link, expected_parsed_link): 'parsed_link', 'is_user_subscribed_to_stream', 'is_valid_stream', + 'topics_in_stream', 'expected_error' ], [ @@ -132,23 +140,40 @@ def test__parse_narrow_link(self, link, expected_parsed_link): 'stream': {'stream_id': 1, 'stream_name': None}}, True, None, + None, ''), ({'narrow': 'stream', 'stream': {'stream_id': 462, 'stream_name': None}}, False, None, + None, 'The stream seems to be either unknown or unsubscribed'), ({'narrow': 'stream', 'stream': {'stream_id': None, 'stream_name': 'Stream 1'}}, None, True, + None, ''), ({'narrow': 'stream', 'stream': {'stream_id': None, 'stream_name': 'foo'}}, None, False, + None, 'The stream seems to be either unknown or unsubscribed'), + ({'narrow': 'stream:topic', 'topic_name': 'Valid', + 'stream': {'stream_id': 1, 'stream_name': None}}, + True, + None, + ['Valid'], + ''), + ({'narrow': 'stream:topic', 'topic_name': 'Invalid', + 'stream': {'stream_id': 1, 'stream_name': None}}, + True, + None, + [], + 'Invalid topic name'), ({}, + None, None, None, 'The narrow link seems to be either broken or unsupported'), @@ -158,18 +183,22 @@ def test__parse_narrow_link(self, link, expected_parsed_link): 'invalid_modern_stream_narrow_parsed_link', 'valid_deprecated_stream_narrow_parsed_link', 'invalid_deprecated_stream_narrow_parsed_link', + 'valid_topic_narrow_parsed_link', + 'invalid_topic_narrow_parsed_link', 'invalid_narrow_link', ] ) def test__validate_narrow_link(self, stream_dict, parsed_link, is_user_subscribed_to_stream, is_valid_stream, + topics_in_stream, expected_error): self.controller.model.stream_dict = stream_dict self.controller.model.is_user_subscribed_to_stream.return_value = ( is_user_subscribed_to_stream ) self.controller.model.is_valid_stream.return_value = is_valid_stream + self.controller.model.topics_in_stream.return_value = topics_in_stream mocked_button = self.message_link_button() return_value = mocked_button._validate_narrow_link(parsed_link) @@ -237,17 +266,25 @@ def test__validate_and_patch_stream_data(self, stream_dict, parsed_link, @pytest.mark.parametrize([ 'parsed_link', 'narrow_to_stream_called', + 'narrow_to_topic_called', ], [ ({'narrow': 'stream', 'stream': {'stream_id': 1, 'stream_name': 'Stream 1'}}, + True, + False), + ({'narrow': 'stream:topic', 'topic_name': 'Foo', + 'stream': {'stream_id': 1, 'stream_name': 'Stream 1'}}, + False, True), ], ids=[ 'stream_narrow', + 'topic_narrow', ] ) def test__switch_narrow_to(self, parsed_link, narrow_to_stream_called, + narrow_to_topic_called, ): mocked_button = self.message_link_button() @@ -255,6 +292,8 @@ def test__switch_narrow_to(self, parsed_link, narrow_to_stream_called, assert (mocked_button.controller.narrow_to_stream.called == narrow_to_stream_called) + assert (mocked_button.controller.narrow_to_topic.called + == narrow_to_topic_called) @pytest.mark.parametrize(['error', 'set_footer_text_called', '_switch_narrow_to_called', diff --git a/zulipterminal/ui_tools/buttons.py b/zulipterminal/ui_tools/buttons.py index c5c8874ad5..81ac1ba7df 100644 --- a/zulipterminal/ui_tools/buttons.py +++ b/zulipterminal/ui_tools/buttons.py @@ -261,6 +261,7 @@ def __init__(self, user_id: int, email: str) -> None: ParsedNarrowLink = TypedDict('ParsedNarrowLink', { 'narrow': str, 'stream': StreamData, + 'topic_name': str, }, total=False) @@ -333,6 +334,13 @@ def _parse_narrow_link(cls, link: str) -> ParsedNarrowLink: stream_data = cls._decode_stream_data(fragments[2]) parsed_link = dict(narrow='stream', stream=stream_data) + elif (len_fragments == 5 and fragments[1] == 'stream' + and fragments[3] == 'topic'): + stream_data = cls._decode_stream_data(fragments[2]) + topic_name = hash_util_decode(fragments[4]) + parsed_link = dict(narrow='stream:topic', stream=stream_data, + topic_name=topic_name) + return parsed_link def _validate_and_patch_stream_data(self, @@ -380,6 +388,14 @@ def _validate_narrow_link(self, parsed_link: ParsedNarrowLink) -> str: if error: return error + # Validate topic name. + if 'topic_name' in parsed_link: + topic_name = parsed_link['topic_name'] + stream_id = parsed_link['stream']['stream_id'] + + if topic_name not in self.model.topics_in_stream(stream_id): + return 'Invalid topic name' + return '' def _switch_narrow_to(self, parsed_link: ParsedNarrowLink) -> None: @@ -391,6 +407,11 @@ def _switch_narrow_to(self, parsed_link: ParsedNarrowLink) -> None: self.stream_id = parsed_link['stream']['stream_id'] self.stream_name = parsed_link['stream']['stream_name'] self.controller.narrow_to_stream(self) + elif 'stream:topic' == narrow: + self.stream_id = parsed_link['stream']['stream_id'] + self.stream_name = parsed_link['stream']['stream_name'] + self.topic_name = parsed_link['topic_name'] + self.controller.narrow_to_topic(self) def handle_narrow_link(self) -> None: """