Skip to content

bugfix: view: Fix Zulip crash during search. #977

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

Closed
Closed
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
28 changes: 21 additions & 7 deletions tests/ui/test_ui_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ def test_init(self, mocker, stream_view):
('FOO', ['foo', '', 'FOO', 'FOOBAR'], ['foo', ]),
# With 'bar' pinned.
('bar', ['bar'], ['bar', ]),
('baar', 'search error', []),
])
def test_update_streams(self, mocker, stream_view, new_text, expected_log,
to_pin):
Expand All @@ -510,14 +511,17 @@ def test_update_streams(self, mocker, stream_view, new_text, expected_log,
stream_names.sort(key=lambda stream_name: stream_name in [
stream for stream in to_pin], reverse=True)
self.view.controller.is_in_editor_mode = lambda: True
search_box = "SEARCH_BOX"
search_box = stream_view.stream_search_box
stream_view.streams_btn_list = [
mocker.Mock(stream_name=stream_name)
for stream_name in stream_names
]
stream_view.update_streams(search_box, new_text)
assert [stream.stream_name for stream in stream_view.log
] == expected_log
if expected_log != 'search error':
assert [stream.stream_name for stream in stream_view.log
] == expected_log
else:
assert hasattr(stream_view.log[0].original_widget, 'text')
self.view.controller.update_screen.assert_called_once_with()

def test_mouse_event(self, mocker, stream_view, widget_size):
Expand All @@ -539,8 +543,10 @@ def test_keypress_SEARCH_STREAMS(self, mocker, stream_view, key,
widget_size):
size = widget_size(stream_view)
mocker.patch.object(stream_view, 'set_focus')
mocker.patch.object(stream_view.stream_search_box, 'set_caption')
stream_view.keypress(size, key)
stream_view.set_focus.assert_called_once_with("header")
stream_view.stream_search_box.set_caption.assert_called_once_with('')

@pytest.mark.parametrize('key', keys_for_command('GO_BACK'))
def test_keypress_GO_BACK(self, mocker, stream_view, key, widget_size):
Expand Down Expand Up @@ -624,6 +630,7 @@ def test_init(self, mocker, topic_view):
('FOO', ['FOO', 'FOOBAR', 'foo']),
('(no', ['(no topic)']),
('topic', ['(no topic)']),
('c', 'search error'),
])
def test_update_topics(self, mocker, topic_view, new_text, expected_log):
topic_names = [
Expand All @@ -638,8 +645,11 @@ def test_update_topics(self, mocker, topic_view, new_text, expected_log):
for topic_name in topic_names
]
topic_view.update_topics(search_box, new_text)
assert [topic.topic_name for topic in topic_view.log
] == expected_log
if expected_log != 'search error':
assert [topic.topic_name for topic in topic_view.log
] == expected_log
else:
assert hasattr(topic_view.log[0].original_widget, 'text')
self.view.controller.update_screen.assert_called_once_with()

@pytest.mark.parametrize(['topic_name', 'topic_initial_log',
Expand Down Expand Up @@ -695,9 +705,11 @@ def test_keypress_SEARCH_TOPICS(self, mocker, topic_view, key,
widget_size):
size = widget_size(topic_view)
mocker.patch(VIEWS + '.TopicsView.set_focus')
mocker.patch.object(topic_view.topic_search_box, 'set_caption')
topic_view.keypress(size, key)
topic_view.set_focus.assert_called_once_with('header')
topic_view.header_list.set_focus.assert_called_once_with(2)
topic_view.topic_search_box.set_caption.assert_called_once_with('')

@pytest.mark.parametrize('key', keys_for_command('GO_BACK'))
def test_keypress_GO_BACK(self, mocker, topic_view, key, widget_size):
Expand Down Expand Up @@ -1097,8 +1109,8 @@ def test_update_user_list(self, right_col_view, mocker,
set_body = mocker.patch(VIEWS + ".urwid.Frame.set_body")

right_col_view.update_user_list("SEARCH_BOX", search_string)

right_col_view.users_view.assert_called_with(assert_list)
if assert_list:
right_col_view.users_view.assert_called_with(assert_list)
set_body.assert_called_once_with(right_col_view.body)

def test_update_user_presence(self, right_col_view, mocker,
Expand Down Expand Up @@ -1149,8 +1161,10 @@ def test_keypress_SEARCH_PEOPLE(self, right_col_view, mocker, key,
widget_size):
size = widget_size(right_col_view)
mocker.patch(VIEWS + ".RightColumnView.set_focus")
mocker.patch.object(right_col_view.user_search, 'set_caption')
right_col_view.keypress(size, key)
right_col_view.set_focus.assert_called_once_with('header')
right_col_view.user_search.set_caption.assert_called_once_with('')

@pytest.mark.parametrize('key', keys_for_command('GO_BACK'))
def test_keypress_GO_BACK(self, right_col_view, mocker, key, widget_size):
Expand Down
61 changes: 40 additions & 21 deletions tests/ui_tools/test_boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from zulipterminal.config.keys import keys_for_command, primary_key_for_command
from zulipterminal.config.symbols import (
STREAM_MARKER_INVALID,
INVALID_MARKER,
STREAM_MARKER_PRIVATE,
STREAM_MARKER_PUBLIC,
)
Expand Down Expand Up @@ -453,7 +453,7 @@ def test__stream_box_autocomplete(self, mocker, write_box, text, state,
], [
('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'),
('Stream 0', 0, False, INVALID_MARKER, 'general_bar'),
], ids=[
'private_stream',
'public_stream',
Expand Down Expand Up @@ -802,41 +802,60 @@ def test_valid_char(self, panel_search_box,

assert result == expected_result

@pytest.mark.parametrize("log, expect_body_focus_set", [
([], False),
(["SOMETHING"], True)
])
@pytest.mark.parametrize("enter_key", keys_for_command("ENTER"))
def test_keypress_ENTER(self, panel_search_box, widget_size,
enter_key, log, expect_body_focus_set):
def test_keypress_ENTER_focus_set(self, panel_search_box, widget_size,
enter_key):
size = widget_size(panel_search_box)
panel_search_box.panel_view.view.controller.is_in_editor_mode = (
lambda: True
)
panel_search_box.panel_view.log = log
panel_search_box.panel_view.log = ["SOMETHING"]
panel_search_box.panel_view.empty_search = False
panel_search_box.set_caption("")
panel_search_box.edit_text = "key words"

panel_search_box.keypress(size, enter_key)

# Update this display
# FIXME We can't test for the styled version?
# We'd compare to [('filter_results', 'Search Results'), ' ']
assert panel_search_box.caption == self.search_caption
assert panel_search_box.edit_text == "key words"

assert panel_search_box.edit_text == "key words"
assert panel_search_box.caption == self.search_caption
# Leave editor mode
(panel_search_box.panel_view.view.controller.exit_editor_mode
.assert_called_once_with())
.assert_called_once_with())
# Switch focus to body and move to first result
panel_search_box.panel_view.set_focus.assert_called_once_with(
"body")
(panel_search_box.panel_view.body.set_focus
.assert_called_once_with(0))

# Switch focus to body; if have results, move to them
panel_search_box.panel_view.set_focus.assert_called_once_with("body")
if expect_body_focus_set:
(panel_search_box.panel_view.body.set_focus
.assert_called_once_with(0))
else:
(panel_search_box.panel_view.body.set_focus
.assert_not_called())
@pytest.mark.parametrize("enter_key", keys_for_command("ENTER"))
def test_keypress_ENTER_focus_not_set(self, panel_search_box,
widget_size, enter_key):
size = widget_size(panel_search_box)
panel_search_box.panel_view.view.controller.is_in_editor_mode = (
lambda: True
)
panel_search_box.panel_view.log = []
panel_search_box.panel_view.empty_search = True
panel_search_box.set_caption("")
panel_search_box.edit_text = "key words"
panel_search_box.keypress(size, enter_key)

# Update this display
# FIXME We can't test for the styled version?
# We'd compare to [('filter_results', 'Search Results'), ' ']

assert panel_search_box.edit_text == "key words"
assert panel_search_box.caption == ''
# Don't Leave editor mode.
(panel_search_box.panel_view.view.controller.exit_editor_mode
.assert_not_called())
# Don't switch focus to body.
panel_search_box.panel_view.set_focus.assert_not_called()
(panel_search_box.panel_view.body.set_focus
.assert_not_called())

@pytest.mark.parametrize("back_key", keys_for_command("GO_BACK"))
def test_keypress_GO_BACK(self, panel_search_box, back_key, widget_size):
Expand Down
2 changes: 1 addition & 1 deletion zulipterminal/config/symbols.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
STREAM_MARKER_INVALID = '✗'
INVALID_MARKER = '✗'
STREAM_MARKER_PRIVATE = 'P'
STREAM_MARKER_PUBLIC = '#'
STREAM_TOPIC_SEPARATOR = '▶'
Expand Down
9 changes: 9 additions & 0 deletions zulipterminal/config/themes.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
'area:msg': 'standout',
'area:stream': 'standout',
'area:error': 'standout',
'search_error': 'standout'
}


Expand Down Expand Up @@ -206,6 +207,8 @@
None, DEF['white'], DEF['dark_cyan']),
('area:error', 'white', 'dark red',
None, DEF['white'], DEF['dark_red']),
('search_error', 'light red', 'black',
None, DEF['light_red'], DEF['black']),
],
'gruvbox_dark': [
# default colorscheme on 16 colors, gruvbox colorscheme
Expand Down Expand Up @@ -298,6 +301,8 @@
None, BLACK, LIGHTBLUE),
('area:error', 'white', 'dark red',
None, WHITE, DARKRED),
('search_error', 'light red', 'black',
None, LIGHTRED, BLACK),
],
'gruvbox_dark24': [
# default colorscheme on 16 colors, gruvbox colorscheme
Expand Down Expand Up @@ -390,6 +395,8 @@
None, BLACK24, LIGHTBLUE24),
('area:error', 'white', 'dark red',
None, WHITE24, DARKRED24),
('search_error', 'light red', 'black',
None, LIGHTRED, BLACK),
],
'zt_light': [
(None, 'black', 'white'),
Expand Down Expand Up @@ -436,6 +443,7 @@
('area:stream', 'black', 'light blue'),
('area:msg', 'black', 'yellow'),
('area:error', 'black', 'light red'),
('search_error', 'light red', 'white'),
],
'zt_blue': [
(None, 'black', 'light blue'),
Expand Down Expand Up @@ -482,6 +490,7 @@
('area:stream', 'white', 'dark cyan'),
('area:msg', 'white', 'brown'),
('area:error', 'white', 'dark red'),
('search_error', 'light red', 'light blue'),
]
}

Expand Down
12 changes: 6 additions & 6 deletions zulipterminal/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ def _set_count_in_view(controller: Any, new_count: int,
additionally set the current count in the model and make use of the
same in the UI.
"""
stream_buttons_log = controller.view.stream_w.log
stream_buttons_list = controller.view.stream_w.streams_btn_list
is_open_topic_view = controller.view.left_panel.is_in_topic_view
if is_open_topic_view:
topic_buttons_log = controller.view.topic_w.log
topic_buttons_list = controller.view.topic_w.topics_btn_list
toggled_stream_id = controller.view.topic_w.stream_button.stream_id
user_buttons_log = controller.view.user_w.log
user_buttons_list = controller.view.user_w.users_btn_list
all_msg = controller.view.home_button
all_pm = controller.view.pm_button
all_mentioned = controller.view.mentioned_button
Expand All @@ -190,7 +190,7 @@ def _set_count_in_view(controller: Any, new_count: int,
if controller.model.is_muted_stream(stream_id):
add_to_counts = False # if muted, don't add to eg. all_msg
else:
for stream_button in stream_buttons_log:
for stream_button in stream_buttons_list:
if stream_button.stream_id == stream_id:
stream_button.update_count(stream_button.count
+ new_count)
Expand All @@ -201,12 +201,12 @@ def _set_count_in_view(controller: Any, new_count: int,
if is_open_topic_view and stream_id == toggled_stream_id:
# If topic_view is open for incoming messages's stream,
# We update the respective TopicButton count accordingly.
for topic_button in topic_buttons_log:
for topic_button in topic_buttons_list:
if topic_button.topic_name == msg_topic:
topic_button.update_count(topic_button.count
+ new_count)
else:
for user_button in user_buttons_log:
for user_button in user_buttons_list:
if user_button.user_id == user_id:
user_button.update_count(user_button.count + new_count)
break
Expand Down
12 changes: 8 additions & 4 deletions zulipterminal/ui_tools/boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
primary_key_for_command,
)
from zulipterminal.config.symbols import (
INVALID_MARKER,
MESSAGE_CONTENT_MARKER,
MESSAGE_HEADER_DIVIDER,
QUOTED_TEXT_MARKER,
STREAM_MARKER_INVALID,
STREAM_MARKER_PRIVATE,
STREAM_MARKER_PUBLIC,
STREAM_TOPIC_SEPARATOR,
Expand Down Expand Up @@ -274,7 +274,7 @@ def stream_box_edit_view(self, stream_id: int, caption: str='',
def _set_stream_write_box_style(self, widget: ReadlineEdit,
new_text: str) -> None:
# FIXME: Refactor when we have ~ Model.is_private_stream
stream_marker = STREAM_MARKER_INVALID
stream_marker = INVALID_MARKER
color = 'general_bar'
if self.model.is_valid_stream(new_text):
stream = self.model.stream_dict[
Expand Down Expand Up @@ -1620,6 +1620,10 @@ def __init__(self, panel_view: Any, search_command: str,
self.search_text = (
f"Search [{', '.join(keys_for_command(search_command))}]: "
)
self.search_error = urwid.AttrMap(
urwid.Text([' ', INVALID_MARKER, ' No Results']),
'search_error'
)
urwid.connect_signal(self, 'change', update_function)
super().__init__(caption='', edit_text=self.search_text)

Expand Down Expand Up @@ -1648,10 +1652,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
self.reset_search_text()
self.panel_view.set_focus("body")
self.panel_view.keypress(size, 'esc')
elif is_command_key('ENTER', key):
elif is_command_key('ENTER', key) and not self.panel_view.empty_search:
self.panel_view.view.controller.exit_editor_mode()
self.set_caption([('filter_results', 'Search Results'), ' '])
self.panel_view.set_focus("body")
if hasattr(self.panel_view, 'log') and len(self.panel_view.log):
if hasattr(self.panel_view, 'log'):
self.panel_view.body.set_focus(0)
return super().keypress(size, key)
Loading