def mouse_event(self, size: urwid_Size, event: str, button: int, col: int, row: int, focus: int) -> bool: if event == "mouse press": if button == 1: self.keypress(size, primary_key_for_command("ENTER")) return True return super().mouse_event(size, event, button, col, row, focus)
def __init__(self, controller: Any, width: int, count: int=0) -> None: button_text = ("All messages [" + primary_key_for_command("ALL_MESSAGES") + "]") super().__init__(controller, button_text, controller.show_all_messages, count=count, prefix_character='', width=width)
def __init__(self, controller: Any, width: int) -> None: button_text = ("Starred messages [" + primary_key_for_command("ALL_STARRED") + "]") super().__init__(controller, button_text, controller.show_all_starred, width=width, prefix_character='', count=0) # Starred messages are already marked read
def __init__(self, controller: Any, width: int, count: int=0) -> None: button_text = ("Mentions [" + primary_key_for_command("ALL_MENTIONS") + "]") super().__init__(controller, button_text, controller.show_all_mentions, width=width, count=count, prefix_character='')
def test__to_box_autocomplete_with_spaces(self, write_box, text, expected_text, widget_size): write_box.private_box_view(emails=['*****@*****.**'], recipient_user_ids=[1]) write_box.to_write_box.set_edit_text(text) write_box.to_write_box.set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert write_box.to_write_box.edit_text == expected_text
def test__stream_box_autocomplete_with_spaces(self, mocker, write_box, widget_size, text, expected_text): write_box.stream_box_view(1000) write_box.contents[0][0][0].set_edit_text(text) write_box.contents[0][0][0].set_edit_pos(len(text)) write_box.focus_position = 0 write_box.contents[0][0].focus_col = 0 size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert write_box.contents[0][0][0].edit_text == expected_text
def test__stream_box_autocomplete_with_spaces( self, mocker, write_box, widget_size, text, expected_text ): mocker.patch(BOXES + ".WriteBox._set_stream_write_box_style") write_box.stream_box_view(1000) stream_focus = write_box.FOCUS_HEADER_BOX_STREAM write_box.header_write_box[stream_focus].set_edit_text(text) write_box.header_write_box[stream_focus].set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER write_box.header_write_box.focus_col = stream_focus size = widget_size(write_box) write_box.keypress(size, primary_key_for_command("AUTOCOMPLETE")) assert write_box.header_write_box[stream_focus].edit_text == expected_text
def test__topic_box_autocomplete_with_spaces(self, mocker, write_box, widget_size, text, expected_text, topics): mocker.patch(BOXES + '.WriteBox._set_stream_write_box_style') write_box.stream_box_view(1000) write_box.model.topics_in_stream.return_value = topics topic_focus = write_box.FOCUS_HEADER_BOX_TOPIC write_box.header_write_box[topic_focus].set_edit_text(text) write_box.header_write_box[topic_focus].set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER write_box.header_write_box.focus_col = topic_focus size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert ( write_box.header_write_box[topic_focus].edit_text == expected_text)
class TestWriteBox: @pytest.fixture(autouse=True) def mock_external_classes(self, mocker, initial_index): self.view = mocker.Mock() self.view.model = mocker.Mock() @pytest.fixture() def write_box(self, mocker, users_fixture, user_groups_fixture, streams_fixture, unicode_emojis): self.view.model.active_emoji_data = unicode_emojis write_box = WriteBox(self.view) write_box.view.users = users_fixture write_box.model.user_group_names = [ groups['name'] for groups in user_groups_fixture ] write_box.view.pinned_streams = [] write_box.view.unpinned_streams = sorted( [{ 'name': stream['name'] } for stream in streams_fixture], key=lambda stream: stream['name'].lower()) return write_box def test_init(self, write_box): assert write_box.model == self.view.model assert write_box.view == self.view assert write_box.msg_edit_id is None @pytest.mark.parametrize('text, state', [ ('Plain Text', 0), ('Plain Text', 1), ]) def test_generic_autocomplete_no_prefix(self, mocker, write_box, text, state): return_val = write_box.generic_autocomplete(text, state) assert return_val == text write_box.view.set_typeahead_footer.assert_not_called() @pytest.mark.parametrize( 'text, state, footer_text', [ # no-text mentions ('@', 0, [ 'Human Myself', 'Human 1', 'Human 2', 'Group 1', 'Group 2', 'Group 3', 'Group 4' ]), ('@*', 0, ['Group 1', 'Group 2', 'Group 3', 'Group 4']), ('@**', 0, ['Human Myself', 'Human 1', 'Human 2']), # mentions ('@Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@**Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@_Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@_*Human', None, []), # NOTE: Optional single star fails ('@_**Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@Human', None, ['Human Myself', 'Human 1', 'Human 2']), ('@NoMatch', None, []), # streams ('#Stream', 0, ['Stream 1', 'Stream 2', 'Secret stream', 'Some general stream']), ('#*Stream', None, []), # NOTE: Optional single star fails ('#**Stream', 0, [ 'Stream 1', 'Stream 2', 'Secret stream', 'Some general stream' ]), # Optional 2-stars ('#Stream', None, ['Stream 1', 'Stream 2', 'Secret stream', 'Some general stream']), ('#NoMatch', None, []), # emojis (':smi', 0, ['smile', 'smiley', 'smirk']), (':smi', None, ['smile', 'smiley', 'smirk']), (':NoMatch', None, []), ]) def test_generic_autocomplete_set_footer(self, mocker, write_box, state, footer_text, text): write_box.view.set_typeahead_footer = mocker.patch( 'zulipterminal.ui.View.set_typeahead_footer') write_box.generic_autocomplete(text, state) write_box.view.set_typeahead_footer.assert_called_once_with( footer_text, state, False) @pytest.mark.parametrize( 'text, state, required_typeahead', [ ('@Human', 0, '@**Human Myself**'), ('@Human', 1, '@**Human 1**'), ('@Human', 2, '@**Human 2**'), ('@Human', -1, '@**Human 2**'), ('@Human', -2, '@**Human 1**'), ('@Human', -3, '@**Human Myself**'), ('@Human', -4, None), ('@_Human', 0, '@_**Human Myself**'), ('@_Human', 1, '@_**Human 1**'), ('@_Human', 2, '@_**Human 2**'), ('@H', 1, '@**Human 1**'), ('@Hu', 1, '@**Human 1**'), ('@Hum', 1, '@**Human 1**'), ('@Huma', 1, '@**Human 1**'), ('@Human', 1, '@**Human 1**'), ('@Human 1', 0, '@**Human 1**'), ('@_H', 1, '@_**Human 1**'), ('@_Hu', 1, '@_**Human 1**'), ('@_Hum', 1, '@_**Human 1**'), ('@_Huma', 1, '@_**Human 1**'), ('@_Human', 1, '@_**Human 1**'), ('@_Human 1', 0, '@_**Human 1**'), ('@Group', 0, '@*Group 1*'), ('@Group', 1, '@*Group 2*'), ('@G', 0, '@*Group 1*'), ('@Gr', 0, '@*Group 1*'), ('@Gro', 0, '@*Group 1*'), ('@Grou', 0, '@*Group 1*'), ('@G', 1, '@*Group 2*'), ('@Gr', 1, '@*Group 2*'), ('@Gro', 1, '@*Group 2*'), ('@Grou', 1, '@*Group 2*'), # Expected sequence of autocompletes from '@' ('@', 0, '@**Human Myself**'), ('@', 1, '@**Human 1**'), ('@', 2, '@**Human 2**'), ('@', 3, '@*Group 1*'), ('@', 4, '@*Group 2*'), ('@', 5, '@*Group 3*'), ('@', 6, '@*Group 4*'), ('@', 7, None), # Reached last match ('@', 8, None), # Beyond end # Expected sequence of autocompletes from '@**' (no groups) ('@**', 0, '@**Human Myself**'), ('@**', 1, '@**Human 1**'), ('@**', 2, '@**Human 2**'), ('@**', 3, None), # Reached last match ('@**', 4, None), # Beyond end # Expected sequence of autocompletes from '@*' (only groups) ('@*', 0, '@*Group 1*'), ('@*', 1, '@*Group 2*'), ('@*', 2, '@*Group 3*'), ('@*', 3, '@*Group 4*'), ('@*', 4, None), # Reached last match ('@*', 5, None), # Beyond end # Expected sequence of autocompletes from '@_' ('@_', 0, '@_**Human Myself**'), # NOTE: No silent group mention ('@_', 1, '@_**Human 1**'), ('@_', 2, '@_**Human 2**'), ('@_', 3, None), # Reached last match ('@_', 4, None), # Beyond end ('@_', -1, '@_**Human 2**'), # Complex autocomplete prefixes. ('(@H', 0, '(@**Human Myself**'), ('(@H', 1, '(@**Human 1**'), ('-@G', 0, '-@*Group 1*'), ('-@G', 1, '-@*Group 2*'), ('_@H', 0, '_@**Human Myself**'), ('_@G', 0, '_@*Group 1*'), ('@@H', 0, '@@**Human Myself**'), (':@H', 0, ':@**Human Myself**'), ('#@H', 0, '#@**Human Myself**'), ('@_@H', 0, '@_@**Human Myself**'), ('>@_H', 0, '>@_**Human Myself**'), ('>@_H', 1, '>@_**Human 1**'), ('@_@_H', 0, '@_@_**Human Myself**'), ('@@_H', 0, '@@_**Human Myself**'), (':@_H', 0, ':@_**Human Myself**'), ('#@_H', 0, '#@_**Human Myself**'), ('@@_H', 0, '@@_**Human Myself**'), ('@@_*H', 0, None), # Optional single star fails ('@@_**H', 0, '@@_**Human Myself**'), # Optional stars ]) def test_generic_autocomplete_mentions(self, write_box, text, required_typeahead, state): typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize('text, state, required_typeahead, recipients', [ ('@', 0, '@**Human 2**', [12]), ('@', 1, '@**Human Myself**', [12]), ('@', 2, '@**Human 1**', [12]), ('@', -1, '@*Group 4*', [12]), ('@', 0, '@**Human 1**', [11, 12]), ('@', 1, '@**Human 2**', [11, 12]), ('@', 2, '@**Human Myself**', [11, 12]), ('@', -1, '@*Group 4*', [11, 12]), ]) def test_generic_autocomplete_mentions_subscribers(self, write_box, text, required_typeahead, state, recipients): write_box.recipient_user_ids = recipients typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( 'text, state, required_typeahead, to_pin', [ # With no streams pinned. ('#Stream', 0, '#**Stream 1**', []), # 1st-word startswith match. ('#Stream', 1, '#**Stream 2**', []), # 1st-word startswith match. ('#Stream', 2, '#**Secret stream**', []), # 2nd-word startswith match. ('#Stream', 3, '#**Some general stream**', []), # 3rd-word startswith. ('#S', 0, '#**Secret stream**', []), # 1st-word startswith match. ('#S', 1, '#**Some general stream**', []), # 1st-word startswith. ('#S', 2, '#**Stream 1**', []), # 1st-word startswith match. ('#S', 3, '#**Stream 2**', []), # 1st-word startswith match. ('#S', -1, '#**Stream 2**', []), ('#S', -2, '#**Stream 1**', []), ('#S', -3, '#**Some general stream**', []), ('#S', -4, '#**Secret stream**', []), ('#S', -5, None, []), ('#So', 0, '#**Some general stream**', []), ('#So', 1, None, []), ('#Se', 0, '#**Secret stream**', []), ('#Se', 1, None, []), ('#St', 0, '#**Stream 1**', []), ('#St', 1, '#**Stream 2**', []), ('#g', 0, '#**Some general stream**', []), ('#g', 1, None, []), ('#Stream 1', 0, '#**Stream 1**', []), # Complete match. ('#nomatch', 0, None, []), ('#ene', 0, None, []), # Complex autocomplete prefixes. ('[#Stream', 0, '[#**Stream 1**', []), ('(#Stream', 1, '(#**Stream 2**', []), ('@#Stream', 0, '@#**Stream 1**', []), ('@_#Stream', 0, '@_#**Stream 1**', []), (':#Stream', 0, ':#**Stream 1**', []), ('##Stream', 0, '##**Stream 1**', []), ('##*Stream', 0, None, []), # NOTE: Optional single star fails ('##**Stream', 0, '##**Stream 1**', []), # Optional 2-stars # With 'Secret stream' pinned. ('#Stream', 0, '#**Secret stream**', [ 'Secret stream', ]), # 2nd-word startswith match (pinned). ('#Stream', 1, '#**Stream 1**', [ 'Secret stream', ]), # 1st-word startswith match (unpinned). ('#Stream', 2, '#**Stream 2**', [ 'Secret stream', ]), # 1st-word startswith match (unpinned). ('#Stream', 3, '#**Some general stream**', [ 'Secret stream', ]), # 3rd-word starstwith match (unpinned). # With 'Stream 1' and 'Secret stream' pinned. ('#Stream', 0, '#**Stream 1**', [ 'Secret stream', 'Stream 1', ]), ('#Stream', 1, '#**Secret stream**', [ 'Secret stream', 'Stream 1', ]), ('#Stream', 2, '#**Stream 2**', [ 'Secret stream', 'Stream 1', ]), ('#Stream', 3, '#**Some general stream**', [ 'Secret stream', 'Stream 1', ]), ]) def test_generic_autocomplete_streams(self, write_box, text, state, required_typeahead, to_pin): streams_to_pin = [{'name': stream_name} for stream_name in to_pin] for stream in streams_to_pin: write_box.view.unpinned_streams.remove(stream) write_box.view.pinned_streams = streams_to_pin typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( 'text, state, required_typeahead', [ (':rock_o', 0, ':rock_on:'), (':rock_o', 1, None), (':rock_o', -1, ':rock_on:'), (':rock_o', -2, None), (':smi', 0, ':smile:'), (':smi', 1, ':smiley:'), (':smi', 2, ':smirk:'), (':jo', 0, ':joker:'), (':jo', 1, ':joy_cat:'), (':jok', 0, ':joker:'), (':', 0, ':happy:'), (':', 1, ':joker:'), (':', -2, ':smiley:'), (':', -1, ':smirk:'), (':nomatch', 0, None), (':nomatch', -1, None), # Complex autocomplete prefixes. ('(:smi', 0, '(:smile:'), ('&:smi', 1, '&:smiley:'), ('@:smi', 0, '@:smile:'), ('@_:smi', 0, '@_:smile:'), ('#:smi', 0, '#:smile:'), ]) def test_generic_autocomplete_emojis(self, write_box, text, mocker, state, required_typeahead): typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize(['text', 'state', 'to_pin', 'matching_streams'], [ ('', 1, [], ['Secret stream', 'Some general stream', 'Stream 1', 'Stream 2']), ('', 1, ['Stream 2'], ['Stream 2', 'Secret stream', 'Some general stream', 'Stream 1']), ('St', 1, [], ['Stream 1', 'Stream 2', 'Secret stream', 'Some general stream']), ('St', 1, ['Stream 2'], ['Stream 2', 'Stream 1', 'Secret stream', 'Some general stream']), ], ids=[ 'no_search_text', 'no_search_text_with_pinned_stream', 'single_word_search_text', 'single_word_search_text_with_pinned_stream', ]) def test__stream_box_autocomplete(self, mocker, write_box, text, state, to_pin, matching_streams): streams_to_pin = [{'name': stream_name} for stream_name in to_pin] for stream in streams_to_pin: write_box.view.unpinned_streams.remove(stream) write_box.view.pinned_streams = streams_to_pin _process_typeaheads = mocker.patch(BOXES + '.WriteBox._process_typeaheads') write_box._stream_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with(matching_streams, state, matching_streams) @pytest.mark.parametrize('text, expected_text', [ ('Som', 'Some general stream'), ('Some gen', 'Some general stream'), ]) def test__stream_box_autocomplete_with_spaces(self, mocker, write_box, widget_size, text, expected_text): write_box.stream_box_view(1000) write_box.contents[0][0][0].set_edit_text(text) write_box.contents[0][0][0].set_edit_pos(len(text)) write_box.focus_position = 0 write_box.contents[0][0].focus_col = 0 size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert write_box.contents[0][0][0].edit_text == expected_text @pytest.mark.parametrize(['text', 'matching_topics'], [ ('', ['Topic 1', 'This is a topic', 'Hello there!']), ('Th', ['This is a topic']), ], ids=[ 'no_search_text', 'single_word_search_text', ]) def test__topic_box_autocomplete(self, mocker, write_box, text, topics, matching_topics, state=1): write_box.model.topics_in_stream.return_value = topics _process_typeaheads = mocker.patch(BOXES + '.WriteBox._process_typeaheads') write_box._topic_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with(matching_topics, state, matching_topics) @pytest.mark.parametrize('text, expected_text', [ ('Th', 'This is a topic'), ('This i', 'This is a topic'), ]) def test__topic_box_autocomplete_with_spaces(self, mocker, write_box, widget_size, text, expected_text, topics): write_box.stream_box_view(1000) write_box.model.topics_in_stream.return_value = topics write_box.contents[0][0][1].set_edit_text(text) write_box.contents[0][0][1].set_edit_pos(len(text)) write_box.focus_position = 0 write_box.contents[0][0].focus_col = 1 size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert write_box.contents[0][0][1].edit_text == expected_text @pytest.mark.parametrize([ 'suggestions', 'state', 'expected_state', 'expected_typeahead', 'is_truncated' ], [ (['zero', 'one', 'two'], 1, 1, '*one*', False), (['zero', 'one', 'two'] * 4, 1, 1, '*one*', True), (['zero', 'one', 'two'], None, None, None, False), (['zero', 'one', 'two'], 5, None, None, False), (['zero', 'one', 'two'], -5, None, None, False), ], ids=[ 'fewer_than_10_typeaheads', 'more_than_10_typeaheads', 'invalid_state-None', 'invalid_state-greater_than_possible_index', 'invalid_state-less_than_possible_index', ]) def test__process_typeaheads(self, write_box, suggestions, state, expected_state, expected_typeahead, is_truncated, mocker): write_box.view.set_typeahead_footer = mocker.patch( 'zulipterminal.ui.View.set_typeahead_footer') # Use an example formatting to differentiate between # typeaheads and suggestions. typeaheads = ['*{}*'.format(s) for s in suggestions] typeahead = write_box._process_typeaheads(typeaheads, state, suggestions) assert typeahead == expected_typeahead write_box.view.set_typeahead_footer.assert_called_once_with( suggestions[:10], expected_state, is_truncated) @pytest.mark.parametrize('topic_entered_by_user, topic_sent_to_server', [ ('', '(no topic)'), ('hello', 'hello'), (' ', '(no topic)'), ], ids=[ 'empty_topic', 'non_empty_topic', 'topic_with_whitespace', ]) @pytest.mark.parametrize('msg_edit_id', [10, None], ids=[ 'update_message', 'send_message', ]) @pytest.mark.parametrize('key', keys_for_command('SEND_MESSAGE')) def test_keypress_SEND_MESSAGE_no_topic(self, mocker, write_box, msg_edit_id, topic_entered_by_user, topic_sent_to_server, key, widget_size, propagate_mode='change_one'): write_box.stream_write_box = mocker.Mock() write_box.msg_write_box = mocker.Mock(edit_text='') write_box.title_write_box = mocker.Mock( edit_text=topic_entered_by_user) write_box.to_write_box = None size = widget_size(write_box) write_box.msg_edit_id = msg_edit_id write_box.edit_mode_button = mocker.Mock(mode=propagate_mode) write_box.keypress(size, key) if msg_edit_id: write_box.model.update_stream_message.assert_called_once_with( topic=topic_sent_to_server, content=write_box.msg_write_box.edit_text, message_id=msg_edit_id, propagate_mode=propagate_mode, ) else: write_box.model.send_stream_message.assert_called_once_with( stream=write_box.stream_write_box.edit_text, topic=topic_sent_to_server, content=write_box.msg_write_box.edit_text, ) @pytest.mark.parametrize( [ 'key', 'current_typeahead_mode', 'expected_typeahead_mode', 'expect_footer_was_reset' ], [ # footer does not reset (primary_key_for_command('AUTOCOMPLETE'), False, False, False), (primary_key_for_command('AUTOCOMPLETE_REVERSE'), False, False, False), (primary_key_for_command('AUTOCOMPLETE'), True, True, False), (primary_key_for_command('AUTOCOMPLETE_REVERSE'), True, True, False), # footer resets (primary_key_for_command('GO_BACK'), True, False, True), ('space', True, False, True), ('k', True, False, True), ]) def test_keypress_typeahead_mode_autocomplete_key( self, mocker, write_box, widget_size, current_typeahead_mode, expected_typeahead_mode, expect_footer_was_reset, key): write_box.is_in_typeahead_mode = current_typeahead_mode size = widget_size(write_box) write_box.keypress(size, key) assert write_box.is_in_typeahead_mode == expected_typeahead_mode if expect_footer_was_reset: self.view.set_footer_text.assert_called_once_with() else: self.view.set_footer_text.assert_not_called() @pytest.mark.parametrize([ "initial_focus_position", "initial_focus_col", "expected_focus_position", "expected_focus_col", "box_type", "msg_body_edit_enabled", "message_being_edited" ], [ (0, 0, 0, 1, "stream", True, False), (0, 1, 1, 0, "stream", True, False), (0, 1, 0, 2, "stream", False, True), (0, 2, 0, 1, "stream", False, True), (1, 0, 0, 0, "stream", True, False), (0, 0, 0, 1, "stream", True, True), (0, 1, 0, 2, "stream", True, True), (0, 2, 1, 0, "stream", True, True), (1, 0, 0, 0, "stream", True, True), (0, 0, 1, 0, "private", True, False), (1, 0, 0, 0, "private", True, False), ], ids=[ 'stream_name_to_topic_box', 'topic_to_message_box', 'topic_edit_only-topic_to_edit_mode_box', 'topic_edit_only-edit_mode_to_topic_box', 'message_to_stream_name_box', 'edit_box-stream_name_to_topic_box', 'edit_box-topic_to_edit_mode_box', 'edit_box-edit_mode_to_message_box', 'edit_box-message_to_stream_name_box', 'recipient_to_message_box', 'message_to_recipient_box', ]) @pytest.mark.parametrize("tab_key", keys_for_command("CYCLE_COMPOSE_FOCUS")) def test_keypress_CYCLE_COMPOSE_FOCUS(self, write_box, tab_key, initial_focus_position, expected_focus_position, initial_focus_col, expected_focus_col, box_type, msg_body_edit_enabled, message_being_edited, widget_size, mocker, stream_id=10): if box_type == "stream": if message_being_edited: mocker.patch(BOXES + ".EditModeButton") write_box.stream_box_edit_view(stream_id) write_box.msg_edit_id = 10 else: write_box.stream_box_view(stream_id) else: write_box.private_box_view() size = widget_size(write_box) write_box.focus_position = initial_focus_position write_box.msg_body_edit_enabled = msg_body_edit_enabled write_box.contents[0][0].focus_col = initial_focus_col write_box.model.get_invalid_recipient_emails.return_value = [] write_box.model.user_dict = mocker.MagicMock() write_box.keypress(size, tab_key) assert write_box.focus_position == expected_focus_position assert write_box.contents[0][0].focus_col == expected_focus_col
def test_primary_key_for_command(valid_command: str) -> None: assert keys.KEY_BINDINGS[valid_command]["keys"][ 0] == keys.primary_key_for_command(valid_command)
frozenset({1001, 11, 12}): 3, frozenset({1001, 11, 12, 13}): 2, }, "streams": { 1000: 3, 99: 1 }, } # --------------- UI Fixtures ----------------------------------------- @pytest.fixture( params=[ ("mouse press", 4, primary_key_for_command("GO_UP")), ("mouse press", 5, primary_key_for_command("GO_DOWN")), ], ids=[ "mouse_scroll_up", "mouse_scroll_down", ], ) def mouse_scroll_event(request): """ Returns required parameters for mouse_event keypress """ return request.param @pytest.fixture(
def test_primary_key_for_command(valid_command): assert (keys.KEY_BINDINGS[valid_command]['keys'][0] == keys.primary_key_for_command(valid_command))
class TestWriteBox: @pytest.fixture(autouse=True) def mock_external_classes(self, mocker, initial_index): self.view = mocker.Mock() self.view.model = mocker.Mock() @pytest.fixture() def write_box(self, mocker, users_fixture, user_groups_fixture, streams_fixture, unicode_emojis, user_dict): self.view.model.active_emoji_data = unicode_emojis write_box = WriteBox(self.view) write_box.view.users = users_fixture write_box.model.user_dict = user_dict write_box.model.user_group_names = [ groups['name'] for groups in user_groups_fixture ] write_box.view.pinned_streams = [] write_box.view.unpinned_streams = sorted( [{ 'name': stream['name'] } for stream in streams_fixture], key=lambda stream: stream['name'].lower()) return write_box def test_init(self, write_box): assert write_box.model == self.view.model assert write_box.view == self.view assert write_box.msg_edit_id is None def test_not_calling_typing_method_without_recipients( self, mocker, write_box): write_box.model.send_typing_status_by_user_ids = mocker.Mock() write_box.private_box_view(emails=[], recipient_user_ids=[]) # Set idle_status_tracking to True to avoid setting off the # idleness tracker function. write_box.idle_status_tracking = True # Changing the edit_text triggers on_type_send_status. write_box.msg_write_box.edit_text = "random text" assert not write_box.model.send_typing_status_by_user_ids.called @pytest.mark.parametrize('key', keys_for_command('SEND_MESSAGE')) def test_not_calling_send_private_message_without_recipients( self, key, mocker, write_box, widget_size): write_box.model.send_private_message = mocker.Mock() write_box.private_box_view(emails=[], recipient_user_ids=[]) write_box.msg_write_box.edit_text = "random text" size = widget_size(write_box) write_box.keypress(size, key) assert not write_box.model.send_private_message.called @pytest.mark.parametrize('text, state', [ ('Plain Text', 0), ('Plain Text', 1), ]) def test_generic_autocomplete_no_prefix(self, mocker, write_box, text, state): return_val = write_box.generic_autocomplete(text, state) assert return_val == text write_box.view.set_typeahead_footer.assert_not_called() @pytest.mark.parametrize( 'text, state, footer_text', [ # no-text mentions ('@', 0, [ 'Human Myself', 'Human 1', 'Human 2', 'Group 1', 'Group 2', 'Group 3', 'Group 4' ]), ('@*', 0, ['Group 1', 'Group 2', 'Group 3', 'Group 4']), ('@**', 0, ['Human Myself', 'Human 1', 'Human 2']), # mentions ('@Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@**Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@_Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@_*Human', None, []), # NOTE: Optional single star fails ('@_**Human', 0, ['Human Myself', 'Human 1', 'Human 2']), ('@Human', None, ['Human Myself', 'Human 1', 'Human 2']), ('@NoMatch', None, []), # streams ('#Stream', 0, ['Stream 1', 'Stream 2', 'Secret stream', 'Some general stream']), ('#*Stream', None, []), # NOTE: Optional single star fails ('#**Stream', 0, [ 'Stream 1', 'Stream 2', 'Secret stream', 'Some general stream' ]), # Optional 2-stars ('#Stream', None, ['Stream 1', 'Stream 2', 'Secret stream', 'Some general stream']), ('#NoMatch', None, []), # emojis (':smi', 0, ['smile', 'smiley', 'smirk']), (':smi', None, ['smile', 'smiley', 'smirk']), (':NoMatch', None, []), ]) def test_generic_autocomplete_set_footer(self, mocker, write_box, state, footer_text, text): write_box.view.set_typeahead_footer = mocker.patch( 'zulipterminal.ui.View.set_typeahead_footer') write_box.generic_autocomplete(text, state) write_box.view.set_typeahead_footer.assert_called_once_with( footer_text, state, False) @pytest.mark.parametrize( 'text, state, required_typeahead', [ ('@Human', 0, '@**Human Myself**'), ('@Human', 1, '@**Human 1**'), ('@Human', 2, '@**Human 2**'), ('@Human', -1, '@**Human 2**'), ('@Human', -2, '@**Human 1**'), ('@Human', -3, '@**Human Myself**'), ('@Human', -4, None), ('@_Human', 0, '@_**Human Myself**'), ('@_Human', 1, '@_**Human 1**'), ('@_Human', 2, '@_**Human 2**'), ('@H', 1, '@**Human 1**'), ('@Hu', 1, '@**Human 1**'), ('@Hum', 1, '@**Human 1**'), ('@Huma', 1, '@**Human 1**'), ('@Human', 1, '@**Human 1**'), ('@Human 1', 0, '@**Human 1**'), ('@_H', 1, '@_**Human 1**'), ('@_Hu', 1, '@_**Human 1**'), ('@_Hum', 1, '@_**Human 1**'), ('@_Huma', 1, '@_**Human 1**'), ('@_Human', 1, '@_**Human 1**'), ('@_Human 1', 0, '@_**Human 1**'), ('@Group', 0, '@*Group 1*'), ('@Group', 1, '@*Group 2*'), ('@G', 0, '@*Group 1*'), ('@Gr', 0, '@*Group 1*'), ('@Gro', 0, '@*Group 1*'), ('@Grou', 0, '@*Group 1*'), ('@G', 1, '@*Group 2*'), ('@Gr', 1, '@*Group 2*'), ('@Gro', 1, '@*Group 2*'), ('@Grou', 1, '@*Group 2*'), # Expected sequence of autocompletes from '@' ('@', 0, '@**Human Myself**'), ('@', 1, '@**Human 1**'), ('@', 2, '@**Human 2**'), ('@', 3, '@*Group 1*'), ('@', 4, '@*Group 2*'), ('@', 5, '@*Group 3*'), ('@', 6, '@*Group 4*'), ('@', 7, None), # Reached last match ('@', 8, None), # Beyond end # Expected sequence of autocompletes from '@**' (no groups) ('@**', 0, '@**Human Myself**'), ('@**', 1, '@**Human 1**'), ('@**', 2, '@**Human 2**'), ('@**', 3, None), # Reached last match ('@**', 4, None), # Beyond end # Expected sequence of autocompletes from '@*' (only groups) ('@*', 0, '@*Group 1*'), ('@*', 1, '@*Group 2*'), ('@*', 2, '@*Group 3*'), ('@*', 3, '@*Group 4*'), ('@*', 4, None), # Reached last match ('@*', 5, None), # Beyond end # Expected sequence of autocompletes from '@_' ('@_', 0, '@_**Human Myself**'), # NOTE: No silent group mention ('@_', 1, '@_**Human 1**'), ('@_', 2, '@_**Human 2**'), ('@_', 3, None), # Reached last match ('@_', 4, None), # Beyond end ('@_', -1, '@_**Human 2**'), # Complex autocomplete prefixes. ('(@H', 0, '(@**Human Myself**'), ('(@H', 1, '(@**Human 1**'), ('-@G', 0, '-@*Group 1*'), ('-@G', 1, '-@*Group 2*'), ('_@H', 0, '_@**Human Myself**'), ('_@G', 0, '_@*Group 1*'), ('@@H', 0, '@@**Human Myself**'), (':@H', 0, ':@**Human Myself**'), ('#@H', 0, '#@**Human Myself**'), ('@_@H', 0, '@_@**Human Myself**'), ('>@_H', 0, '>@_**Human Myself**'), ('>@_H', 1, '>@_**Human 1**'), ('@_@_H', 0, '@_@_**Human Myself**'), ('@@_H', 0, '@@_**Human Myself**'), (':@_H', 0, ':@_**Human Myself**'), ('#@_H', 0, '#@_**Human Myself**'), ('@@_H', 0, '@@_**Human Myself**'), ('@@_*H', 0, None), # Optional single star fails ('@@_**H', 0, '@@_**Human Myself**'), # Optional stars ]) def test_generic_autocomplete_mentions(self, write_box, text, required_typeahead, state): typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize('text, state, required_typeahead, recipients', [ ('@', 0, '@**Human 2**', [12]), ('@', 1, '@**Human Myself**', [12]), ('@', 2, '@**Human 1**', [12]), ('@', -1, '@*Group 4*', [12]), ('@', 0, '@**Human 1**', [11, 12]), ('@', 1, '@**Human 2**', [11, 12]), ('@', 2, '@**Human Myself**', [11, 12]), ('@', -1, '@*Group 4*', [11, 12]), ]) def test_generic_autocomplete_mentions_subscribers(self, write_box, text, required_typeahead, state, recipients): write_box.recipient_user_ids = recipients typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( 'text, state, required_typeahead, to_pin', [ # With no streams pinned. ('#Stream', 0, '#**Stream 1**', []), # 1st-word startswith match. ('#Stream', 1, '#**Stream 2**', []), # 1st-word startswith match. ('#Stream', 2, '#**Secret stream**', []), # 2nd-word startswith match. ('#Stream', 3, '#**Some general stream**', []), # 3rd-word startswith. ('#S', 0, '#**Secret stream**', []), # 1st-word startswith match. ('#S', 1, '#**Some general stream**', []), # 1st-word startswith. ('#S', 2, '#**Stream 1**', []), # 1st-word startswith match. ('#S', 3, '#**Stream 2**', []), # 1st-word startswith match. ('#S', -1, '#**Stream 2**', []), ('#S', -2, '#**Stream 1**', []), ('#S', -3, '#**Some general stream**', []), ('#S', -4, '#**Secret stream**', []), ('#S', -5, None, []), ('#So', 0, '#**Some general stream**', []), ('#So', 1, None, []), ('#Se', 0, '#**Secret stream**', []), ('#Se', 1, None, []), ('#St', 0, '#**Stream 1**', []), ('#St', 1, '#**Stream 2**', []), ('#g', 0, '#**Some general stream**', []), ('#g', 1, None, []), ('#Stream 1', 0, '#**Stream 1**', []), # Complete match. ('#nomatch', 0, None, []), ('#ene', 0, None, []), # Complex autocomplete prefixes. ('[#Stream', 0, '[#**Stream 1**', []), ('(#Stream', 1, '(#**Stream 2**', []), ('@#Stream', 0, '@#**Stream 1**', []), ('@_#Stream', 0, '@_#**Stream 1**', []), (':#Stream', 0, ':#**Stream 1**', []), ('##Stream', 0, '##**Stream 1**', []), ('##*Stream', 0, None, []), # NOTE: Optional single star fails ('##**Stream', 0, '##**Stream 1**', []), # Optional 2-stars # With 'Secret stream' pinned. ('#Stream', 0, '#**Secret stream**', [ 'Secret stream', ]), # 2nd-word startswith match (pinned). ('#Stream', 1, '#**Stream 1**', [ 'Secret stream', ]), # 1st-word startswith match (unpinned). ('#Stream', 2, '#**Stream 2**', [ 'Secret stream', ]), # 1st-word startswith match (unpinned). ('#Stream', 3, '#**Some general stream**', [ 'Secret stream', ]), # 3rd-word starstwith match (unpinned). # With 'Stream 1' and 'Secret stream' pinned. ('#Stream', 0, '#**Stream 1**', [ 'Secret stream', 'Stream 1', ]), ('#Stream', 1, '#**Secret stream**', [ 'Secret stream', 'Stream 1', ]), ('#Stream', 2, '#**Stream 2**', [ 'Secret stream', 'Stream 1', ]), ('#Stream', 3, '#**Some general stream**', [ 'Secret stream', 'Stream 1', ]), ]) def test_generic_autocomplete_streams(self, write_box, text, state, required_typeahead, to_pin): streams_to_pin = [{'name': stream_name} for stream_name in to_pin] for stream in streams_to_pin: write_box.view.unpinned_streams.remove(stream) write_box.view.pinned_streams = streams_to_pin typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( 'text, state, required_typeahead', [ (':rock_o', 0, ':rock_on:'), (':rock_o', 1, None), (':rock_o', -1, ':rock_on:'), (':rock_o', -2, None), (':smi', 0, ':smile:'), (':smi', 1, ':smiley:'), (':smi', 2, ':smirk:'), (':jo', 0, ':joker:'), (':jo', 1, ':joy_cat:'), (':jok', 0, ':joker:'), (':', 0, ':happy:'), (':', 1, ':joker:'), (':', -2, ':smiley:'), (':', -1, ':smirk:'), (':nomatch', 0, None), (':nomatch', -1, None), # Complex autocomplete prefixes. ('(:smi', 0, '(:smile:'), ('&:smi', 1, '&:smiley:'), ('@:smi', 0, '@:smile:'), ('@_:smi', 0, '@_:smile:'), ('#:smi', 0, '#:smile:'), ]) def test_generic_autocomplete_emojis(self, write_box, text, mocker, state, required_typeahead): typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( ['text', 'matching_users', 'matching_users_info'], [ ('', ['Human Myself', 'Human 1', 'Human 2'], [ 'Human Myself <*****@*****.**>', 'Human 1 <*****@*****.**>', 'Human 2 <*****@*****.**>' ]), ('My', ['Human Myself'], ['Human Myself <*****@*****.**>']), ], ids=[ 'no_search_text', 'single_word_search_text', ]) def test__to_box_autocomplete(self, mocker, write_box, text, matching_users, matching_users_info, state=1): _process_typeaheads = mocker.patch(BOXES + '.WriteBox._process_typeaheads') write_box._to_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with(matching_users_info, state, matching_users) @pytest.mark.parametrize( 'text, expected_text', [('Hu', 'Human Myself <*****@*****.**>'), ('Human M', 'Human Myself <*****@*****.**>'), ('Human Myself <FOOBOO', 'Human Myself <*****@*****.**>')]) def test__to_box_autocomplete_with_spaces(self, write_box, text, expected_text, widget_size): write_box.private_box_view(emails=['*****@*****.**'], recipient_user_ids=[1]) write_box.to_write_box.set_edit_text(text) write_box.to_write_box.set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert write_box.to_write_box.edit_text == expected_text @pytest.mark.parametrize( ['text', 'matching_users', 'matching_users_info'], [('Welcome Bot <*****@*****.**>, Human', ['Human Myself', 'Human 1', 'Human 2'], [ 'Welcome Bot <*****@*****.**>, ' 'Human Myself <*****@*****.**>', 'Welcome Bot <*****@*****.**>, ' 'Human 1 <*****@*****.**>', 'Welcome Bot <*****@*****.**>, ' 'Human 2 <*****@*****.**>' ]), ('Welcome Bot <*****@*****.**>, Notification Bot ' '<*****@*****.**>, person2', ['Human 2'], [ 'Welcome Bot <*****@*****.**>, Notification Bot ' '<*****@*****.**>, Human 2 <*****@*****.**>' ]), ('Email Gateway <*****@*****.**>,Human', ['Human Myself', 'Human 1', 'Human 2'], [ 'Email Gateway <*****@*****.**>, ' 'Human Myself <*****@*****.**>', 'Email Gateway <*****@*****.**>, ' 'Human 1 <*****@*****.**>', 'Email Gateway <*****@*****.**>, ' 'Human 2 <*****@*****.**>' ]), ('Human 1 <*****@*****.**>, Notification Bot ' '<*****@*****.**>,person2', ['Human 2'], [ 'Human 1 <*****@*****.**>, Notification Bot ' '<*****@*****.**>, Human 2 <*****@*****.**>' ])], ids=[ 'name_search_text_with_space_after_separator', 'email_search_text_with_space_after_separator', 'name_search_text_without_space_after_separator', 'email_search_text_without_space_after_separator' ]) def test__to_box_autocomplete_with_multiple_recipients( self, mocker, write_box, text, matching_users, matching_users_info, state=1): _process_typeaheads = mocker.patch(BOXES + '.WriteBox._process_typeaheads') write_box._to_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with(matching_users_info, state, matching_users) @pytest.mark.parametrize(['text', 'state', 'to_pin', 'matching_streams'], [ ('', 1, [], ['Secret stream', 'Some general stream', 'Stream 1', 'Stream 2']), ('', 1, ['Stream 2'], ['Stream 2', 'Secret stream', 'Some general stream', 'Stream 1']), ('St', 1, [], ['Stream 1', 'Stream 2', 'Secret stream', 'Some general stream']), ('St', 1, ['Stream 2'], ['Stream 2', 'Stream 1', 'Secret stream', 'Some general stream']), ], ids=[ 'no_search_text', 'no_search_text_with_pinned_stream', 'single_word_search_text', 'single_word_search_text_with_pinned_stream', ]) def test__stream_box_autocomplete(self, mocker, write_box, text, state, to_pin, matching_streams): streams_to_pin = [{'name': stream_name} for stream_name in to_pin] for stream in streams_to_pin: write_box.view.unpinned_streams.remove(stream) write_box.view.pinned_streams = streams_to_pin _process_typeaheads = mocker.patch(BOXES + '.WriteBox._process_typeaheads') write_box._stream_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with(matching_streams, state, matching_streams) @pytest.mark.parametrize([ 'stream_name', 'stream_id', 'is_valid_stream', 'expected_marker', 'expected_color' ], [ ('Secret stream', 99, True, STREAM_MARKER_PRIVATE, '#ccc'), ('Stream 1', 1, True, STREAM_MARKER_PUBLIC, '#b0a5fd'), ('Stream 0', 0, False, STREAM_MARKER_INVALID, 'general_bar'), ], ids=[ 'private_stream', 'public_stream', 'invalid_stream_name', ]) def test__set_stream_write_box_style_markers(self, write_box, stream_id, stream_name, is_valid_stream, expected_marker, stream_dict, mocker, expected_color): # FIXME: Refactor when we have ~ Model.is_private_stream write_box.model.stream_dict = stream_dict write_box.model.is_valid_stream.return_value = is_valid_stream write_box.model.stream_id_from_name.return_value = stream_id write_box.stream_box_view(stream_id) write_box._set_stream_write_box_style(write_box, stream_name) stream_marker = ( write_box.header_write_box[write_box.FOCUS_HEADER_PREFIX_STREAM]) assert stream_marker.text == expected_marker assert stream_marker.attrib[0][0] == expected_color @pytest.mark.parametrize('text, expected_text', [ ('Som', 'Some general stream'), ('Some gen', 'Some general stream'), ]) def test__stream_box_autocomplete_with_spaces(self, mocker, write_box, widget_size, text, expected_text): mocker.patch(BOXES + '.WriteBox._set_stream_write_box_style') write_box.stream_box_view(1000) stream_focus = write_box.FOCUS_HEADER_BOX_STREAM write_box.header_write_box[stream_focus].set_edit_text(text) write_box.header_write_box[stream_focus].set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER write_box.header_write_box.focus_col = stream_focus size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert (write_box.header_write_box[stream_focus].edit_text == expected_text) @pytest.mark.parametrize(['text', 'matching_topics'], [ ('', ['Topic 1', 'This is a topic', 'Hello there!']), ('Th', ['This is a topic']), ], ids=[ 'no_search_text', 'single_word_search_text', ]) def test__topic_box_autocomplete(self, mocker, write_box, text, topics, matching_topics, state=1): write_box.model.topics_in_stream.return_value = topics _process_typeaheads = mocker.patch(BOXES + '.WriteBox._process_typeaheads') write_box._topic_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with(matching_topics, state, matching_topics) @pytest.mark.parametrize('text, expected_text', [ ('Th', 'This is a topic'), ('This i', 'This is a topic'), ]) def test__topic_box_autocomplete_with_spaces(self, mocker, write_box, widget_size, text, expected_text, topics): mocker.patch(BOXES + '.WriteBox._set_stream_write_box_style') write_box.stream_box_view(1000) write_box.model.topics_in_stream.return_value = topics topic_focus = write_box.FOCUS_HEADER_BOX_TOPIC write_box.header_write_box[topic_focus].set_edit_text(text) write_box.header_write_box[topic_focus].set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER write_box.header_write_box.focus_col = topic_focus size = widget_size(write_box) write_box.keypress(size, primary_key_for_command('AUTOCOMPLETE')) assert ( write_box.header_write_box[topic_focus].edit_text == expected_text) @pytest.mark.parametrize([ 'suggestions', 'state', 'expected_state', 'expected_typeahead', 'is_truncated' ], [ (['zero', 'one', 'two'], 1, 1, '*one*', False), (['zero', 'one', 'two'] * 4, 1, 1, '*one*', True), (['zero', 'one', 'two'], None, None, None, False), (['zero', 'one', 'two'], 5, None, None, False), (['zero', 'one', 'two'], -5, None, None, False), ], ids=[ 'fewer_than_10_typeaheads', 'more_than_10_typeaheads', 'invalid_state-None', 'invalid_state-greater_than_possible_index', 'invalid_state-less_than_possible_index', ]) def test__process_typeaheads(self, write_box, suggestions, state, expected_state, expected_typeahead, is_truncated, mocker): write_box.view.set_typeahead_footer = mocker.patch( 'zulipterminal.ui.View.set_typeahead_footer') # Use an example formatting to differentiate between # typeaheads and suggestions. typeaheads = [f"*{s}*" for s in suggestions] typeahead = write_box._process_typeaheads(typeaheads, state, suggestions) assert typeahead == expected_typeahead write_box.view.set_typeahead_footer.assert_called_once_with( suggestions[:10], expected_state, is_truncated) @pytest.mark.parametrize('topic_entered_by_user, topic_sent_to_server', [ ('', '(no topic)'), ('hello', 'hello'), (' ', '(no topic)'), ], ids=[ 'empty_topic', 'non_empty_topic', 'topic_with_whitespace', ]) @pytest.mark.parametrize('msg_edit_id', [10, None], ids=[ 'update_message', 'send_message', ]) @pytest.mark.parametrize('key', keys_for_command('SEND_MESSAGE')) def test_keypress_SEND_MESSAGE_no_topic(self, mocker, write_box, msg_edit_id, topic_entered_by_user, topic_sent_to_server, key, widget_size, propagate_mode='change_one'): write_box.stream_write_box = mocker.Mock() write_box.msg_write_box = mocker.Mock(edit_text='') write_box.title_write_box = mocker.Mock( edit_text=topic_entered_by_user) write_box.to_write_box = None size = widget_size(write_box) write_box.msg_edit_id = msg_edit_id write_box.edit_mode_button = mocker.Mock(mode=propagate_mode) write_box.keypress(size, key) if msg_edit_id: write_box.model.update_stream_message.assert_called_once_with( topic=topic_sent_to_server, content=write_box.msg_write_box.edit_text, message_id=msg_edit_id, propagate_mode=propagate_mode, ) else: write_box.model.send_stream_message.assert_called_once_with( stream=write_box.stream_write_box.edit_text, topic=topic_sent_to_server, content=write_box.msg_write_box.edit_text, ) @pytest.mark.parametrize( [ 'key', 'current_typeahead_mode', 'expected_typeahead_mode', 'expect_footer_was_reset' ], [ # footer does not reset (primary_key_for_command('AUTOCOMPLETE'), False, False, False), (primary_key_for_command('AUTOCOMPLETE_REVERSE'), False, False, False), (primary_key_for_command('AUTOCOMPLETE'), True, True, False), (primary_key_for_command('AUTOCOMPLETE_REVERSE'), True, True, False), # footer resets (primary_key_for_command('GO_BACK'), True, False, True), ('space', True, False, True), ('k', True, False, True), ]) def test_keypress_typeahead_mode_autocomplete_key( self, mocker, write_box, widget_size, current_typeahead_mode, expected_typeahead_mode, expect_footer_was_reset, key): write_box.is_in_typeahead_mode = current_typeahead_mode size = widget_size(write_box) write_box.keypress(size, key) assert write_box.is_in_typeahead_mode == expected_typeahead_mode if expect_footer_was_reset: self.view.set_footer_text.assert_called_once_with() else: self.view.set_footer_text.assert_not_called() @pytest.mark.parametrize([ "initial_focus_name", "initial_focus_col_name", "box_type", "msg_body_edit_enabled", "message_being_edited", "expected_focus_name", "expected_focus_col_name" ], [ ('CONTAINER_HEADER', 'HEADER_BOX_STREAM', "stream", True, False, 'CONTAINER_HEADER', 'HEADER_BOX_TOPIC'), ('CONTAINER_HEADER', 'HEADER_BOX_TOPIC', "stream", True, False, 'CONTAINER_MESSAGE', 'MESSAGE_BOX_BODY'), ('CONTAINER_HEADER', 'HEADER_BOX_TOPIC', "stream", False, True, 'CONTAINER_HEADER', 'HEADER_BOX_EDIT'), ('CONTAINER_HEADER', 'HEADER_BOX_EDIT', "stream", False, True, 'CONTAINER_HEADER', 'HEADER_BOX_TOPIC'), ('CONTAINER_MESSAGE', 'MESSAGE_BOX_BODY', "stream", True, False, 'CONTAINER_HEADER', 'HEADER_BOX_STREAM'), ('CONTAINER_HEADER', 'HEADER_BOX_STREAM', "stream", True, True, 'CONTAINER_HEADER', 'HEADER_BOX_TOPIC'), ('CONTAINER_HEADER', 'HEADER_BOX_TOPIC', "stream", True, True, 'CONTAINER_HEADER', 'HEADER_BOX_EDIT'), ('CONTAINER_HEADER', 'HEADER_BOX_EDIT', "stream", True, True, 'CONTAINER_MESSAGE', 'MESSAGE_BOX_BODY'), ('CONTAINER_MESSAGE', 'MESSAGE_BOX_BODY', "stream", True, True, 'CONTAINER_HEADER', 'HEADER_BOX_STREAM'), ('CONTAINER_HEADER', 'HEADER_BOX_RECIPIENT', "private", True, False, 'CONTAINER_MESSAGE', 'MESSAGE_BOX_BODY'), ('CONTAINER_MESSAGE', 'MESSAGE_BOX_BODY', "private", True, False, 'CONTAINER_HEADER', 'HEADER_BOX_RECIPIENT'), ], ids=[ 'stream_name_to_topic_box', 'topic_to_message_box', 'topic_edit_only-topic_to_edit_mode_box', 'topic_edit_only-edit_mode_to_topic_box', 'message_to_stream_name_box', 'edit_box-stream_name_to_topic_box', 'edit_box-topic_to_edit_mode_box', 'edit_box-edit_mode_to_message_box', 'edit_box-message_to_stream_name_box', 'recipient_to_message_box', 'message_to_recipient_box', ]) @pytest.mark.parametrize("tab_key", keys_for_command("CYCLE_COMPOSE_FOCUS")) def test_keypress_CYCLE_COMPOSE_FOCUS(self, write_box, tab_key, initial_focus_name, expected_focus_name, initial_focus_col_name, expected_focus_col_name, box_type, msg_body_edit_enabled, message_being_edited, widget_size, mocker, stream_id=10): mocker.patch(BOXES + '.WriteBox._set_stream_write_box_style') if box_type == "stream": if message_being_edited: mocker.patch(BOXES + ".EditModeButton") write_box.stream_box_edit_view(stream_id) write_box.msg_edit_id = 10 else: write_box.stream_box_view(stream_id) else: write_box.private_box_view() size = widget_size(write_box) def focus_val(x: str) -> int: return getattr(write_box, 'FOCUS_' + x) write_box.focus_position = focus_val(initial_focus_name) write_box.msg_body_edit_enabled = msg_body_edit_enabled if write_box.focus_position == write_box.FOCUS_CONTAINER_HEADER: write_box.header_write_box.focus_col = ( focus_val(initial_focus_col_name)) write_box.model.get_invalid_recipient_emails.return_value = [] write_box.model.user_dict = mocker.MagicMock() write_box.keypress(size, tab_key) assert write_box.focus_position == focus_val(expected_focus_name) # FIXME: Needs refactoring? if write_box.focus_position == write_box.FOCUS_CONTAINER_HEADER: assert (write_box.header_write_box.focus_col == focus_val( expected_focus_col_name)) else: assert (write_box.FOCUS_MESSAGE_BOX_BODY == focus_val( expected_focus_col_name)) @pytest.mark.parametrize(["msg_type", "expected_box_size"], [ ('private', 1), ('stream', 4), ('stream_edit', 5), ], ids=[ 'private_message', 'stream_message', 'stream_edit_message', ]) def test_write_box_header_contents(self, write_box, expected_box_size, mocker, msg_type): mocker.patch(BOXES + '.WriteBox._set_stream_write_box_style') mocker.patch(BOXES + '.WriteBox.set_editor_mode') if msg_type == 'stream': write_box.stream_box_view(1000) elif msg_type == 'stream_edit': write_box.stream_box_edit_view(1000) else: write_box.private_box_view(emails=['*****@*****.**'], recipient_user_ids=[1]) assert len(write_box.header_write_box.widget_list) == expected_box_size
def _handle_message_event(self, event: Event) -> None: """ Handle new messages (eg. add message to the end of the view) """ message = event['message'] # sometimes `flags` are missing in `event` so initialize # an empty list of flags in that case. message['flags'] = event.get('flags', []) # We need to update the topic order in index, unconditionally. if message['type'] == 'stream': # NOTE: The subsequent helper only updates the topic index based # on the message event not the UI (the UI is updated in a # consecutive block independently). However, it is critical to keep # the topics index synchronized as it used whenever the topics list # view is reconstructed later. self._update_topic_index(message['stream_id'], message['subject']) # If the topic view is toggled for incoming message's # recipient stream, then we re-arrange topic buttons # with most recent at the top. if hasattr(self.controller, 'view'): view = self.controller.view if (view.left_panel.is_in_topic_view_with_stream_id( message['stream_id'])): view.topic_w.update_topics_list( message['stream_id'], message['subject'], message['sender_id']) self.controller.update_screen() # We can notify user regardless of whether UI is rendered or not, # but depend upon the UI to indicate failures. failed_command = self.notify_user(message) if (failed_command and hasattr(self.controller, 'view') and not self._notified_user_of_notification_failure): notice_template = ( "You have enabled notifications, but your notification " "command '{}' could not be found." "\n\n" "The application will continue attempting to run this command " "in this session, but will not notify you again." "\n\n" "Press '{}' to close this window." ) notice = notice_template.format(failed_command, primary_key_for_command("GO_BACK")) self.controller.popup_with_message(notice, width=50) self.controller.update_screen() self._notified_user_of_notification_failure = True # Index messages before calling set_count. self.index = index_messages([message], self, self.index) if 'read' not in message['flags']: set_count([message['id']], self.controller, 1) if (hasattr(self.controller, 'view') and self._have_last_message[repr(self.narrow)]): msg_log = self.controller.view.message_view.log if msg_log: last_message = msg_log[-1].original_widget.message else: last_message = None msg_w_list = create_msg_box_list(self, [message['id']], last_message=last_message) if not msg_w_list: return else: msg_w = msg_w_list[0] if not self.narrow: msg_log.append(msg_w) elif (self.narrow[0][1] == 'mentioned' and 'mentioned' in message['flags']): msg_log.append(msg_w) elif (self.narrow[0][1] == message['type'] and len(self.narrow) == 1): msg_log.append(msg_w) elif (message['type'] == 'stream' and self.narrow[0][0] == "stream"): recipient_stream = message['display_recipient'] narrow_stream = self.narrow[0][1] append_to_stream = recipient_stream == narrow_stream if (append_to_stream and (len(self.narrow) == 1 or (len(self.narrow) == 2 and self.narrow[1][1] == message['subject']))): msg_log.append(msg_w) elif (message['type'] == 'private' and len(self.narrow) == 1 and self.narrow[0][0] == "pm_with"): narrow_recipients = self.recipients message_recipients = frozenset( [user['id'] for user in message['display_recipient']]) if narrow_recipients == message_recipients: msg_log.append(msg_w) self.controller.update_screen()
class TestWriteBox: @pytest.fixture(autouse=True) def mock_external_classes(self, mocker, initial_index): self.view = mocker.Mock() self.view.model = mocker.Mock() @pytest.fixture() def write_box( self, mocker, users_fixture, user_groups_fixture, streams_fixture, unicode_emojis, user_dict, ): self.view.model.active_emoji_data = unicode_emojis write_box = WriteBox(self.view) write_box.view.users = users_fixture write_box.model.user_dict = user_dict write_box.model.max_stream_name_length = 60 write_box.model.max_topic_length = 60 write_box.model.max_message_length = 10000 write_box.model.user_group_names = [ groups["name"] for groups in user_groups_fixture ] write_box.view.pinned_streams = [] write_box.view.unpinned_streams = sorted( [{"name": stream["name"]} for stream in streams_fixture], key=lambda stream: stream["name"].lower(), ) return write_box def test_init(self, write_box): assert write_box.model == self.view.model assert write_box.view == self.view assert write_box.msg_edit_id is None def test_not_calling_typing_method_without_recipients(self, mocker, write_box): write_box.model.send_typing_status_by_user_ids = mocker.Mock() write_box.private_box_view(emails=[], recipient_user_ids=[]) # Set idle_status_tracking to True to avoid setting off the # idleness tracker function. write_box.idle_status_tracking = True # Changing the edit_text triggers on_type_send_status. write_box.msg_write_box.edit_text = "random text" assert not write_box.model.send_typing_status_by_user_ids.called @pytest.mark.parametrize("key", keys_for_command("SEND_MESSAGE")) def test_not_calling_send_private_message_without_recipients( self, key, mocker, write_box, widget_size ): write_box.model.send_private_message = mocker.Mock() write_box.private_box_view(emails=[], recipient_user_ids=[]) write_box.msg_write_box.edit_text = "random text" size = widget_size(write_box) write_box.keypress(size, key) assert not write_box.model.send_private_message.called @pytest.mark.parametrize( "text, state", [ ("Plain Text", 0), ("Plain Text", 1), ], ) def test_generic_autocomplete_no_prefix(self, mocker, write_box, text, state): return_val = write_box.generic_autocomplete(text, state) assert return_val == text write_box.view.set_typeahead_footer.assert_not_called() @pytest.mark.parametrize( "text, state, footer_text", [ # no-text mentions ( "@", 0, [ "Human Myself", "Human 1", "Human 2", "Group 1", "Group 2", "Group 3", "Group 4", ], ), ("@*", 0, ["Group 1", "Group 2", "Group 3", "Group 4"]), ("@**", 0, ["Human Myself", "Human 1", "Human 2"]), # mentions ("@Human", 0, ["Human Myself", "Human 1", "Human 2"]), ("@**Human", 0, ["Human Myself", "Human 1", "Human 2"]), ("@_Human", 0, ["Human Myself", "Human 1", "Human 2"]), ("@_*Human", None, []), # NOTE: Optional single star fails ("@_**Human", 0, ["Human Myself", "Human 1", "Human 2"]), ("@Human", None, ["Human Myself", "Human 1", "Human 2"]), ("@NoMatch", None, []), # streams ( "#Stream", 0, ["Stream 1", "Stream 2", "Secret stream", "Some general stream"], ), ("#*Stream", None, []), # NOTE: Optional single star fails ( "#**Stream", 0, ["Stream 1", "Stream 2", "Secret stream", "Some general stream"], ), # Optional 2-stars ( "#Stream", None, ["Stream 1", "Stream 2", "Secret stream", "Some general stream"], ), ("#NoMatch", None, []), # emojis (":smi", 0, ["smile", "smiley", "smirk"]), (":smi", None, ["smile", "smiley", "smirk"]), (":NoMatch", None, []), ], ) def test_generic_autocomplete_set_footer( self, mocker, write_box, state, footer_text, text ): write_box.view.set_typeahead_footer = mocker.patch( "zulipterminal.ui.View.set_typeahead_footer" ) write_box.generic_autocomplete(text, state) write_box.view.set_typeahead_footer.assert_called_once_with( footer_text, state, False ) @pytest.mark.parametrize( "text, state, required_typeahead", [ ("@Human", 0, "@**Human Myself**"), ("@Human", 1, "@**Human 1**"), ("@Human", 2, "@**Human 2**"), ("@Human", -1, "@**Human 2**"), ("@Human", -2, "@**Human 1**"), ("@Human", -3, "@**Human Myself**"), ("@Human", -4, None), ("@_Human", 0, "@_**Human Myself**"), ("@_Human", 1, "@_**Human 1**"), ("@_Human", 2, "@_**Human 2**"), ("@H", 1, "@**Human 1**"), ("@Hu", 1, "@**Human 1**"), ("@Hum", 1, "@**Human 1**"), ("@Huma", 1, "@**Human 1**"), ("@Human", 1, "@**Human 1**"), ("@Human 1", 0, "@**Human 1**"), ("@_H", 1, "@_**Human 1**"), ("@_Hu", 1, "@_**Human 1**"), ("@_Hum", 1, "@_**Human 1**"), ("@_Huma", 1, "@_**Human 1**"), ("@_Human", 1, "@_**Human 1**"), ("@_Human 1", 0, "@_**Human 1**"), ("@Group", 0, "@*Group 1*"), ("@Group", 1, "@*Group 2*"), ("@G", 0, "@*Group 1*"), ("@Gr", 0, "@*Group 1*"), ("@Gro", 0, "@*Group 1*"), ("@Grou", 0, "@*Group 1*"), ("@G", 1, "@*Group 2*"), ("@Gr", 1, "@*Group 2*"), ("@Gro", 1, "@*Group 2*"), ("@Grou", 1, "@*Group 2*"), # Expected sequence of autocompletes from '@' ("@", 0, "@**Human Myself**"), ("@", 1, "@**Human 1**"), ("@", 2, "@**Human 2**"), ("@", 3, "@*Group 1*"), ("@", 4, "@*Group 2*"), ("@", 5, "@*Group 3*"), ("@", 6, "@*Group 4*"), ("@", 7, None), # Reached last match ("@", 8, None), # Beyond end # Expected sequence of autocompletes from '@**' (no groups) ("@**", 0, "@**Human Myself**"), ("@**", 1, "@**Human 1**"), ("@**", 2, "@**Human 2**"), ("@**", 3, None), # Reached last match ("@**", 4, None), # Beyond end # Expected sequence of autocompletes from '@*' (only groups) ("@*", 0, "@*Group 1*"), ("@*", 1, "@*Group 2*"), ("@*", 2, "@*Group 3*"), ("@*", 3, "@*Group 4*"), ("@*", 4, None), # Reached last match ("@*", 5, None), # Beyond end # Expected sequence of autocompletes from '@_' ("@_", 0, "@_**Human Myself**"), # NOTE: No silent group mention ("@_", 1, "@_**Human 1**"), ("@_", 2, "@_**Human 2**"), ("@_", 3, None), # Reached last match ("@_", 4, None), # Beyond end ("@_", -1, "@_**Human 2**"), # Complex autocomplete prefixes. ("(@H", 0, "(@**Human Myself**"), ("(@H", 1, "(@**Human 1**"), ("-@G", 0, "-@*Group 1*"), ("-@G", 1, "-@*Group 2*"), ("_@H", 0, "_@**Human Myself**"), ("_@G", 0, "_@*Group 1*"), ("@@H", 0, "@@**Human Myself**"), (":@H", 0, ":@**Human Myself**"), ("#@H", 0, "#@**Human Myself**"), ("@_@H", 0, "@_@**Human Myself**"), (">@_H", 0, ">@_**Human Myself**"), (">@_H", 1, ">@_**Human 1**"), ("@_@_H", 0, "@_@_**Human Myself**"), ("@@_H", 0, "@@_**Human Myself**"), (":@_H", 0, ":@_**Human Myself**"), ("#@_H", 0, "#@_**Human Myself**"), ("@@_H", 0, "@@_**Human Myself**"), ("@@_*H", 0, None), # Optional single star fails ("@@_**H", 0, "@@_**Human Myself**"), # Optional stars ], ) def test_generic_autocomplete_mentions( self, write_box, text, required_typeahead, state ): typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( "text, state, required_typeahead, recipients", [ ("@", 0, "@**Human 2**", [12]), ("@", 1, "@**Human Myself**", [12]), ("@", 2, "@**Human 1**", [12]), ("@", -1, "@*Group 4*", [12]), ("@", 0, "@**Human 1**", [11, 12]), ("@", 1, "@**Human 2**", [11, 12]), ("@", 2, "@**Human Myself**", [11, 12]), ("@", -1, "@*Group 4*", [11, 12]), ], ) def test_generic_autocomplete_mentions_subscribers( self, write_box, text, required_typeahead, state, recipients ): write_box.recipient_user_ids = recipients typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( "text, state, required_typeahead, to_pin", [ # With no streams pinned. ("#Stream", 0, "#**Stream 1**", []), # 1st-word startswith match. ("#Stream", 1, "#**Stream 2**", []), # 1st-word startswith match. ("#Stream", 2, "#**Secret stream**", []), # 2nd-word startswith match. ("#Stream", 3, "#**Some general stream**", []), # 3rd-word startswith. ("#S", 0, "#**Secret stream**", []), # 1st-word startswith match. ("#S", 1, "#**Some general stream**", []), # 1st-word startswith. ("#S", 2, "#**Stream 1**", []), # 1st-word startswith match. ("#S", 3, "#**Stream 2**", []), # 1st-word startswith match. ("#S", -1, "#**Stream 2**", []), ("#S", -2, "#**Stream 1**", []), ("#S", -3, "#**Some general stream**", []), ("#S", -4, "#**Secret stream**", []), ("#S", -5, None, []), ("#So", 0, "#**Some general stream**", []), ("#So", 1, None, []), ("#Se", 0, "#**Secret stream**", []), ("#Se", 1, None, []), ("#St", 0, "#**Stream 1**", []), ("#St", 1, "#**Stream 2**", []), ("#g", 0, "#**Some general stream**", []), ("#g", 1, None, []), ("#Stream 1", 0, "#**Stream 1**", []), # Complete match. ("#nomatch", 0, None, []), ("#ene", 0, None, []), # Complex autocomplete prefixes. ("[#Stream", 0, "[#**Stream 1**", []), ("(#Stream", 1, "(#**Stream 2**", []), ("@#Stream", 0, "@#**Stream 1**", []), ("@_#Stream", 0, "@_#**Stream 1**", []), (":#Stream", 0, ":#**Stream 1**", []), ("##Stream", 0, "##**Stream 1**", []), ("##*Stream", 0, None, []), # NOTE: Optional single star fails ("##**Stream", 0, "##**Stream 1**", []), # Optional 2-stars # With 'Secret stream' pinned. ( "#Stream", 0, "#**Secret stream**", ["Secret stream"], ), # 2nd-word startswith match (pinned). ( "#Stream", 1, "#**Stream 1**", ["Secret stream"], ), # 1st-word startswith match (unpinned). ( "#Stream", 2, "#**Stream 2**", ["Secret stream"], ), # 1st-word startswith match (unpinned). ( "#Stream", 3, "#**Some general stream**", ["Secret stream"], ), # 3rd-word starstwith match (unpinned). # With 'Stream 1' and 'Secret stream' pinned. ("#Stream", 0, "#**Stream 1**", ["Secret stream", "Stream 1"]), ("#Stream", 1, "#**Secret stream**", ["Secret stream", "Stream 1"]), ("#Stream", 2, "#**Stream 2**", ["Secret stream", "Stream 1"]), ("#Stream", 3, "#**Some general stream**", ["Secret stream", "Stream 1"]), ], ) def test_generic_autocomplete_streams( self, write_box, text, state, required_typeahead, to_pin ): streams_to_pin = [{"name": stream_name} for stream_name in to_pin] for stream in streams_to_pin: write_box.view.unpinned_streams.remove(stream) write_box.view.pinned_streams = streams_to_pin typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( "text, state, required_typeahead", [ (":rock_o", 0, ":rock_on:"), (":rock_o", 1, None), (":rock_o", -1, ":rock_on:"), (":rock_o", -2, None), (":smi", 0, ":smile:"), (":smi", 1, ":smiley:"), (":smi", 2, ":smirk:"), (":jo", 0, ":joker:"), (":jo", 1, ":joy_cat:"), (":jok", 0, ":joker:"), (":", 0, ":happy:"), (":", 1, ":joker:"), (":", -2, ":smiley:"), (":", -1, ":smirk:"), (":nomatch", 0, None), (":nomatch", -1, None), # Complex autocomplete prefixes. ("(:smi", 0, "(:smile:"), ("&:smi", 1, "&:smiley:"), ("@:smi", 0, "@:smile:"), ("@_:smi", 0, "@_:smile:"), ("#:smi", 0, "#:smile:"), ], ) def test_generic_autocomplete_emojis( self, write_box, text, mocker, state, required_typeahead ): typeahead_string = write_box.generic_autocomplete(text, state) assert typeahead_string == required_typeahead @pytest.mark.parametrize( "text, matching_users, matching_users_info", [ ( "", ["Human Myself", "Human 1", "Human 2"], [ "Human Myself <*****@*****.**>", "Human 1 <*****@*****.**>", "Human 2 <*****@*****.**>", ], ), ("My", ["Human Myself"], ["Human Myself <*****@*****.**>"]), ], ids=[ "no_search_text", "single_word_search_text", ], ) def test__to_box_autocomplete( self, mocker, write_box, text, matching_users, matching_users_info, state=1 ): _process_typeaheads = mocker.patch(BOXES + ".WriteBox._process_typeaheads") write_box._to_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with( matching_users_info, state, matching_users ) @pytest.mark.parametrize( "text, expected_text", [ ("Hu", "Human Myself <*****@*****.**>"), ("Human M", "Human Myself <*****@*****.**>"), ("Human Myself <FOOBOO", "Human Myself <*****@*****.**>"), ], ) def test__to_box_autocomplete_with_spaces( self, write_box, text, expected_text, widget_size ): write_box.private_box_view( emails=["*****@*****.**"], recipient_user_ids=[1] ) write_box.to_write_box.set_edit_text(text) write_box.to_write_box.set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER size = widget_size(write_box) write_box.keypress(size, primary_key_for_command("AUTOCOMPLETE")) assert write_box.to_write_box.edit_text == expected_text @pytest.mark.parametrize( "text, matching_users, matching_users_info", [ ( "Welcome Bot <*****@*****.**>, Human", ["Human Myself", "Human 1", "Human 2"], [ "Welcome Bot <*****@*****.**>, " "Human Myself <*****@*****.**>", "Welcome Bot <*****@*****.**>, " "Human 1 <*****@*****.**>", "Welcome Bot <*****@*****.**>, " "Human 2 <*****@*****.**>", ], ), ( "Welcome Bot <*****@*****.**>, Notification Bot " "<*****@*****.**>, person2", ["Human 2"], [ "Welcome Bot <*****@*****.**>, Notification Bot " "<*****@*****.**>, Human 2 <*****@*****.**>" ], ), ( "Email Gateway <*****@*****.**>,Human", ["Human Myself", "Human 1", "Human 2"], [ "Email Gateway <*****@*****.**>, " "Human Myself <*****@*****.**>", "Email Gateway <*****@*****.**>, " "Human 1 <*****@*****.**>", "Email Gateway <*****@*****.**>, " "Human 2 <*****@*****.**>", ], ), ( "Human 1 <*****@*****.**>, Notification Bot " "<*****@*****.**>,person2", ["Human 2"], [ "Human 1 <*****@*****.**>, Notification Bot " "<*****@*****.**>, Human 2 <*****@*****.**>" ], ), ], ids=[ "name_search_text_with_space_after_separator", "email_search_text_with_space_after_separator", "name_search_text_without_space_after_separator", "email_search_text_without_space_after_separator", ], ) def test__to_box_autocomplete_with_multiple_recipients( self, mocker, write_box, text, matching_users, matching_users_info, state=1 ): _process_typeaheads = mocker.patch(BOXES + ".WriteBox._process_typeaheads") write_box._to_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with( matching_users_info, state, matching_users ) @pytest.mark.parametrize( "text, state, to_pin, matching_streams", [ ( "", 1, [], ["Secret stream", "Some general stream", "Stream 1", "Stream 2"], ), ( "", 1, ["Stream 2"], ["Stream 2", "Secret stream", "Some general stream", "Stream 1"], ), ( "St", 1, [], ["Stream 1", "Stream 2", "Secret stream", "Some general stream"], ), ( "St", 1, ["Stream 2"], ["Stream 2", "Stream 1", "Secret stream", "Some general stream"], ), ], ids=[ "no_search_text", "no_search_text_with_pinned_stream", "single_word_search_text", "single_word_search_text_with_pinned_stream", ], ) def test__stream_box_autocomplete( self, mocker, write_box, text, state, to_pin, matching_streams ): streams_to_pin = [{"name": stream_name} for stream_name in to_pin] for stream in streams_to_pin: write_box.view.unpinned_streams.remove(stream) write_box.view.pinned_streams = streams_to_pin _process_typeaheads = mocker.patch(BOXES + ".WriteBox._process_typeaheads") write_box._stream_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with( matching_streams, state, matching_streams ) @pytest.mark.parametrize( "stream_name, stream_id, is_valid_stream, expected_marker, expected_color", [ ("Secret stream", 99, True, STREAM_MARKER_PRIVATE, "#ccc"), ("Stream 1", 1, True, STREAM_MARKER_PUBLIC, "#b0a5fd"), ("Stream 0", 0, False, INVALID_MARKER, "general_bar"), ], ids=[ "private_stream", "public_stream", "invalid_stream_name", ], ) def test__set_stream_write_box_style_markers( self, write_box, stream_id, stream_name, is_valid_stream, expected_marker, stream_dict, mocker, expected_color, ): # FIXME: Refactor when we have ~ Model.is_private_stream write_box.model.stream_dict = stream_dict write_box.model.is_valid_stream.return_value = is_valid_stream write_box.model.stream_id_from_name.return_value = stream_id write_box.stream_box_view(stream_id) write_box._set_stream_write_box_style(write_box, stream_name) stream_marker = write_box.header_write_box[write_box.FOCUS_HEADER_PREFIX_STREAM] assert stream_marker.text == expected_marker assert stream_marker.attrib[0][0] == expected_color @pytest.mark.parametrize( "text, expected_text", [ ("Som", "Some general stream"), ("Some gen", "Some general stream"), ], ) def test__stream_box_autocomplete_with_spaces( self, mocker, write_box, widget_size, text, expected_text ): mocker.patch(BOXES + ".WriteBox._set_stream_write_box_style") write_box.stream_box_view(1000) stream_focus = write_box.FOCUS_HEADER_BOX_STREAM write_box.header_write_box[stream_focus].set_edit_text(text) write_box.header_write_box[stream_focus].set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER write_box.header_write_box.focus_col = stream_focus size = widget_size(write_box) write_box.keypress(size, primary_key_for_command("AUTOCOMPLETE")) assert write_box.header_write_box[stream_focus].edit_text == expected_text @pytest.mark.parametrize( "text, matching_topics", [ ("", ["Topic 1", "This is a topic", "Hello there!"]), ("Th", ["This is a topic"]), ], ids=[ "no_search_text", "single_word_search_text", ], ) def test__topic_box_autocomplete( self, mocker, write_box, text, topics, matching_topics, state=1 ): write_box.model.topics_in_stream.return_value = topics _process_typeaheads = mocker.patch(BOXES + ".WriteBox._process_typeaheads") write_box._topic_box_autocomplete(text, state) _process_typeaheads.assert_called_once_with( matching_topics, state, matching_topics ) @pytest.mark.parametrize( "text, expected_text", [ ("Th", "This is a topic"), ("This i", "This is a topic"), ], ) def test__topic_box_autocomplete_with_spaces( self, mocker, write_box, widget_size, text, expected_text, topics ): mocker.patch(BOXES + ".WriteBox._set_stream_write_box_style") write_box.stream_box_view(1000) write_box.model.topics_in_stream.return_value = topics topic_focus = write_box.FOCUS_HEADER_BOX_TOPIC write_box.header_write_box[topic_focus].set_edit_text(text) write_box.header_write_box[topic_focus].set_edit_pos(len(text)) write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER write_box.header_write_box.focus_col = topic_focus size = widget_size(write_box) write_box.keypress(size, primary_key_for_command("AUTOCOMPLETE")) assert write_box.header_write_box[topic_focus].edit_text == expected_text @pytest.mark.parametrize( "suggestions, state, expected_state, expected_typeahead, is_truncated", [ (["zero", "one", "two"], 1, 1, "*one*", False), (["zero", "one", "two"] * 4, 1, 1, "*one*", True), (["zero", "one", "two"], None, None, None, False), (["zero", "one", "two"], 5, None, None, False), (["zero", "one", "two"], -5, None, None, False), ], ids=[ "fewer_than_10_typeaheads", "more_than_10_typeaheads", "invalid_state-None", "invalid_state-greater_than_possible_index", "invalid_state-less_than_possible_index", ], ) def test__process_typeaheads( self, write_box, suggestions, state, expected_state, expected_typeahead, is_truncated, mocker, ): write_box.view.set_typeahead_footer = mocker.patch( "zulipterminal.ui.View.set_typeahead_footer" ) # Use an example formatting to differentiate between # typeaheads and suggestions. typeaheads = [f"*{s}*" for s in suggestions] typeahead = write_box._process_typeaheads(typeaheads, state, suggestions) assert typeahead == expected_typeahead write_box.view.set_typeahead_footer.assert_called_once_with( suggestions[:10], expected_state, is_truncated ) @pytest.mark.parametrize( "topic_entered_by_user, topic_sent_to_server", [ ("", "(no topic)"), ("hello", "hello"), (" ", "(no topic)"), ], ids=[ "empty_topic", "non_empty_topic", "topic_with_whitespace", ], ) @pytest.mark.parametrize( "msg_edit_id", [10, None], ids=["update_message", "send_message"] ) @pytest.mark.parametrize("key", keys_for_command("SEND_MESSAGE")) def test_keypress_SEND_MESSAGE_no_topic( self, mocker, write_box, msg_edit_id, topic_entered_by_user, topic_sent_to_server, key, widget_size, propagate_mode="change_one", ): write_box.stream_write_box = mocker.Mock() write_box.msg_write_box = mocker.Mock(edit_text="") write_box.title_write_box = mocker.Mock(edit_text=topic_entered_by_user) write_box.to_write_box = None size = widget_size(write_box) write_box.msg_edit_id = msg_edit_id write_box.edit_mode_button = mocker.Mock(mode=propagate_mode) write_box.keypress(size, key) if msg_edit_id: write_box.model.update_stream_message.assert_called_once_with( topic=topic_sent_to_server, content=write_box.msg_write_box.edit_text, message_id=msg_edit_id, propagate_mode=propagate_mode, ) else: write_box.model.send_stream_message.assert_called_once_with( stream=write_box.stream_write_box.edit_text, topic=topic_sent_to_server, content=write_box.msg_write_box.edit_text, ) @pytest.mark.parametrize( "key, current_typeahead_mode, expected_typeahead_mode, expect_footer_was_reset", [ # footer does not reset (primary_key_for_command("AUTOCOMPLETE"), False, False, False), (primary_key_for_command("AUTOCOMPLETE_REVERSE"), False, False, False), (primary_key_for_command("AUTOCOMPLETE"), True, True, False), (primary_key_for_command("AUTOCOMPLETE_REVERSE"), True, True, False), # footer resets (primary_key_for_command("GO_BACK"), True, False, True), ("space", True, False, True), ("k", True, False, True), ], ) def test_keypress_typeahead_mode_autocomplete_key( self, mocker, write_box, widget_size, current_typeahead_mode, expected_typeahead_mode, expect_footer_was_reset, key, ): write_box.is_in_typeahead_mode = current_typeahead_mode size = widget_size(write_box) write_box.keypress(size, key) assert write_box.is_in_typeahead_mode == expected_typeahead_mode if expect_footer_was_reset: self.view.set_footer_text.assert_called_once_with() else: self.view.set_footer_text.assert_not_called() @pytest.mark.parametrize( [ "initial_focus_name", "initial_focus_col_name", "box_type", "msg_body_edit_enabled", "message_being_edited", "expected_focus_name", "expected_focus_col_name", ], [ case( "CONTAINER_HEADER", "HEADER_BOX_STREAM", "stream", True, False, "CONTAINER_HEADER", "HEADER_BOX_TOPIC", id="stream_name_to_topic_box", ), case( "CONTAINER_HEADER", "HEADER_BOX_TOPIC", "stream", True, False, "CONTAINER_MESSAGE", "MESSAGE_BOX_BODY", id="topic_to_message_box", ), case( "CONTAINER_HEADER", "HEADER_BOX_TOPIC", "stream", False, True, "CONTAINER_HEADER", "HEADER_BOX_EDIT", id="topic_edit_only-topic_to_edit_mode_box", ), case( "CONTAINER_HEADER", "HEADER_BOX_EDIT", "stream", False, True, "CONTAINER_HEADER", "HEADER_BOX_TOPIC", id="topic_edit_only-edit_mode_to_topic_box", ), case( "CONTAINER_MESSAGE", "MESSAGE_BOX_BODY", "stream", True, False, "CONTAINER_HEADER", "HEADER_BOX_STREAM", id="message_to_stream_name_box", ), case( "CONTAINER_HEADER", "HEADER_BOX_STREAM", "stream", True, True, "CONTAINER_HEADER", "HEADER_BOX_TOPIC", id="edit_box-stream_name_to_topic_box", ), case( "CONTAINER_HEADER", "HEADER_BOX_TOPIC", "stream", True, True, "CONTAINER_HEADER", "HEADER_BOX_EDIT", id="edit_box-topic_to_edit_mode_box", ), case( "CONTAINER_HEADER", "HEADER_BOX_EDIT", "stream", True, True, "CONTAINER_MESSAGE", "MESSAGE_BOX_BODY", id="edit_box-edit_mode_to_message_box", ), case( "CONTAINER_MESSAGE", "MESSAGE_BOX_BODY", "stream", True, True, "CONTAINER_HEADER", "HEADER_BOX_STREAM", id="edit_box-message_to_stream_name_box", ), case( "CONTAINER_HEADER", "HEADER_BOX_RECIPIENT", "private", True, False, "CONTAINER_MESSAGE", "MESSAGE_BOX_BODY", id="recipient_to_message_box", ), case( "CONTAINER_MESSAGE", "MESSAGE_BOX_BODY", "private", True, False, "CONTAINER_HEADER", "HEADER_BOX_RECIPIENT", id="message_to_recipient_box", ), ], )