def test_search_message( self, initial_narrow: List[Any], final_narrow: List[Any], controller: Controller, mocker: MockerFixture, msg_ids: Set[int], index_search_messages: Index, ) -> None: get_message = mocker.patch(MODEL + ".get_messages") create_msg = mocker.patch(MODULE + ".create_msg_box_list") mocker.patch(MODEL + ".get_message_ids_in_current_narrow", return_value=msg_ids) controller.model.index = index_search_messages # Any initial search index controller.view.message_view = mocker.patch("urwid.ListBox") controller.model.narrow = initial_narrow def set_msg_ids(*args: Any, **kwargs: Any) -> None: controller.model.index["search"].update(msg_ids) get_message.side_effect = set_msg_ids assert controller.model.index["search"] == {500} controller.search_messages("FOO") assert controller.model.narrow == final_narrow get_message.assert_called_once_with( num_after=0, num_before=30, anchor=10000000000 ) create_msg.assert_called_once_with(controller.model, msg_ids) assert controller.model.index == dict( index_search_messages, **{"search": msg_ids} )
def test_narrow_to_user( self, mocker: MockerFixture, controller: Controller, index_user: Index, user_email: str = "*****@*****.**", user_id: int = 5179, ) -> None: controller.model.narrow = [] controller.model.index = index_user controller.view.message_view = mocker.patch("urwid.ListBox") controller.model.user_id = 5140 controller.model.user_email = "some@email" controller.model.user_dict = {user_email: {"user_id": user_id}} emails = [user_email] controller.narrow_to_user(recipient_emails=emails) assert controller.model.narrow == [["pm_with", user_email]] controller.view.message_view.log.clear.assert_called_once_with() recipients = frozenset([controller.model.user_id, user_id]) assert controller.model.recipients == recipients widget = controller.view.message_view.log.extend.call_args_list[0][0][0][0] id_list = index_user["private_msg_ids_by_user_ids"][recipients] assert {widget.original_widget.message["id"]} == id_list
def test_narrow_to_all_messages( self, mocker: MockerFixture, controller: Controller, index_all_messages: Index, anchor: Optional[int], expected_final_focus_msg_id: int, ) -> None: controller.model.narrow = [["stream", "PTEST"]] controller.model.index = index_all_messages controller.view.message_view = mocker.patch("urwid.ListBox") controller.model.user_email = "some@email" controller.model.user_id = 1 controller.model.stream_dict = { 205: { "color": "#ffffff", } } controller.model.muted_streams = set() mocker.patch(MODEL + ".is_muted_topic", return_value=False) controller.narrow_to_all_messages(contextual_message_id=anchor) assert controller.model.narrow == [] controller.view.message_view.log.clear.assert_called_once_with() widgets, focus = controller.view.message_view.log.extend.call_args_list[0][0] id_list = index_all_messages["all_msg_ids"] msg_ids = {widget.original_widget.message["id"] for widget in widgets} final_focus_msg_id = widgets[focus].original_widget.message["id"] assert msg_ids == id_list assert final_focus_msg_id == expected_final_focus_msg_id
def test_narrow_to_stream( self, mocker: MockerFixture, controller: Controller, index_stream: Index, stream_id: int = 205, stream_name: str = "PTEST", ) -> None: controller.model.narrow = [] controller.model.index = index_stream controller.view.message_view = mocker.patch("urwid.ListBox") controller.model.stream_dict = { stream_id: { "color": "#ffffff", "name": stream_name, } } controller.model.muted_streams = set() mocker.patch(MODEL + ".is_muted_topic", return_value=False) controller.narrow_to_stream(stream_name=stream_name) assert controller.model.stream_id == stream_id assert controller.model.narrow == [["stream", stream_name]] controller.view.message_view.log.clear.assert_called_once_with() widget = controller.view.message_view.log.extend.call_args_list[0][0][0][0] id_list = index_stream["stream_msg_ids_by_stream_id"][stream_id] assert {widget.original_widget.message["id"]} == id_list
def test_show_typing_notification( self, mocker: MockerFixture, controller: Controller, active_conversation_info: Dict[str, str], ) -> None: set_footer_text = mocker.patch(VIEW + ".set_footer_text") sleep = mocker.patch(MODULE + ".time.sleep") controller.active_conversation_info = active_conversation_info def mock_typing() -> None: controller.active_conversation_info = {} Timer(0.1, mock_typing).start() Thread(controller.show_typing_notification()).start() if active_conversation_info: set_footer_text.assert_has_calls( [ mocker.call([("footer_contrast", " hamlet "), " is typing"]), mocker.call([("footer_contrast", " hamlet "), " is typing."]), mocker.call([("footer_contrast", " hamlet "), " is typing.."]), mocker.call([("footer_contrast", " hamlet "), " is typing..."]), ] ) set_footer_text.assert_called_with() else: set_footer_text.assert_called_once_with() assert controller.is_typing_notification_in_progress is False assert controller.active_conversation_info == {}
def test_editor_mode_exits_after_entering( self, mocker: MockerFixture, controller: Controller ) -> None: controller.enter_editor_mode_with(mocker.Mock()) controller.exit_editor_mode() assert not controller.is_in_editor_mode()
def test_editor_mode_error_on_multiple_enter( self, mocker: MockerFixture, controller: Controller ) -> None: controller.enter_editor_mode_with(mocker.Mock()) with pytest.raises(AssertionError): controller.enter_editor_mode_with(mocker.Mock())
def test_narrow_to_all_mentions( self, mocker: MockerFixture, controller: Controller, index_all_mentions: Index ) -> None: controller.model.narrow = [] controller.model.index = index_all_mentions controller.model.muted_streams = set() # FIXME Expand upon this # FIXME: Expand upon is_muted_topic(). mocker.patch(MODEL + ".is_muted_topic", return_value=False) controller.model.user_email = "some@email" controller.model.user_id = 1 controller.model.stream_dict = { 205: { "color": "#ffffff", } } controller.view.message_view = mocker.patch("urwid.ListBox") controller.narrow_to_all_mentions() # FIXME: Add id narrowing test assert controller.model.narrow == [["is", "mentioned"]] controller.view.message_view.log.clear.assert_called_once_with() id_list = index_all_mentions["mentioned_msg_ids"] widgets = controller.view.message_view.log.extend.call_args_list[0][0][0] msg_ids = {widget.original_widget.message["id"] for widget in widgets} assert msg_ids == id_list
def test_main(self, mocker: MockerFixture, controller: Controller) -> None: controller.view.palette = {"default": "theme_properties"} mock_tsk = mocker.patch(MODULE + ".Screen.tty_signal_keys") controller.loop.screen.tty_signal_keys = mocker.Mock(return_value={}) controller.main() assert controller.loop.run.call_count == 1
def test_editor_mode_entered_from_initial( self, mocker: MockerFixture, controller: Controller ) -> None: editor = mocker.Mock() controller.enter_editor_mode_with(editor) assert controller.is_in_editor_mode() assert controller.current_editor() == editor
def test_open_in_browser_fail__no_browser_controller( self, mocker: MockerFixture, controller: Controller ) -> None: os.environ["DISPLAY"] = ":0" error = "No runnable browser found" mocked_report_error = mocker.patch(MODULE + ".Controller.report_error") mocker.patch(MODULE + ".webbrowser.get").side_effect = webbrowser.Error(error) controller.open_in_browser("https://chat.zulip.org/#narrow/stream/test") mocked_report_error.assert_called_once_with(f"ERROR: {error}")
def test_open_in_browser_success( self, mocker: MockerFixture, controller: Controller, url: str ) -> None: # Set DISPLAY environ to be able to run test in CI os.environ["DISPLAY"] = ":0" mocked_report_success = mocker.patch(MODULE + ".Controller.report_success") mock_get = mocker.patch(MODULE + ".webbrowser.get") mock_open = mock_get.return_value.open controller.open_in_browser(url) mock_open.assert_called_once_with(url) mocked_report_success.assert_called_once_with( f"The link was successfully opened using {mock_get.return_value.name}" )
def test_copy_to_clipboard_exception( self, mocker: MockerFixture, controller: Controller, text_category: str = "Test" ) -> None: popup = mocker.patch(MODULE + ".Controller.show_pop_up") mocker.patch( MODULE + ".pyperclip.copy", side_effect=pyperclip.PyperclipException() ) mocker.patch( MODULE + ".Controller.maximum_popup_dimensions", return_value=(64, 64) ) controller.copy_to_clipboard("copy text", text_category) popup.assert_called_once() assert popup.call_args_list[0][0][1] == "area:error"
def test_register(self, mocker): self.config_file = 'path/to/zuliprc' self.theme = 'default' controller = Controller(self.config_file, self.theme) event_types = ['message', 'update_message', 'reaction'] controller.client.register.assert_called_once_with( event_types=event_types)
def controller(self, mocker: MockerFixture) -> Controller: # Patch these unconditionally to avoid calling in __init__ self.poll_for_events = mocker.patch(MODEL + ".poll_for_events") mocker.patch(MODULE + ".Controller.show_loading") self.main_loop = mocker.patch( MODULE + ".urwid.MainLoop", return_value=mocker.Mock() ) self.config_file = "path/to/zuliprc" self.theme_name = "zt_dark" self.theme = generate_theme("zt_dark", 256) self.in_explore_mode = False self.autohide = True # FIXME Add tests for no-autohide self.notify_enabled = False self.maximum_footlinks = 3 result = Controller( config_file=self.config_file, maximum_footlinks=self.maximum_footlinks, theme_name=self.theme_name, theme=self.theme, color_depth=256, in_explore_mode=self.in_explore_mode, debug_path=None, **dict( autohide=self.autohide, notify=self.notify_enabled, ), ) result.view.message_view = mocker.Mock() # set in View.__init__ result.model.server_url = SERVER_URL return result
def controller(self, mocker) -> None: # Patch these unconditionally to avoid calling in __init__ self.poll_for_events = mocker.patch(CORE + ".Model.poll_for_events") mocker.patch(CORE + ".Controller.show_loading") self.main_loop = mocker.patch(CORE + ".urwid.MainLoop", return_value=mocker.Mock()) self.config_file = "path/to/zuliprc" self.theme_name = "zt_dark" self.theme = "default" self.in_explore_mode = False self.autohide = True # FIXME Add tests for no-autohide self.notify_enabled = False self.maximum_footlinks = 3 result = Controller( self.config_file, self.maximum_footlinks, self.theme_name, self.theme, 256, self.in_explore_mode, self.autohide, self.notify_enabled, ) result.view.message_view = mocker.Mock() # set in View.__init__ result.model.server_url = SERVER_URL return result
def controller(self, mocker) -> None: self.config_file = 'path/to/zuliprc' self.theme = 'default' self.autohide = True # FIXME Add tests for no-autohide self.notify_enabled = False return Controller(self.config_file, self.theme, 256, self.autohide, self.notify_enabled)
def main(): """ Launch Zulip Terminal. """ args = parse_args() if args.debug: save_stdout() if args.profile: import cProfile prof = cProfile.Profile() prof.enable() try: Controller(args.config_file, args.theme).main() except Exception: # A unexpected exception occurred, open the debugger in debug mode if args.debug: import pudb pudb.post_mortem() finally: if args.debug: restore_stdout() if args.profile: prof.disable() prof.dump_stats("/tmp/profile.data") print("Profile data saved to /tmp/profile.data") print("You can visualize it using e.g." "`snakeviz /tmp/profile.data`") print("\nThanks for using the Zulip-Terminal interface.\n") sys.exit(1)
def test_narrow_to_topic( self, mocker: MockerFixture, controller: Controller, index_multiple_topic_msg: Index, initial_narrow: List[Any], initial_stream_id: Optional[int], anchor: Optional[int], expected_final_focus: int, stream_name: str = "PTEST", topic_name: str = "Test", stream_id: int = 205, ) -> None: expected_narrow = [ ["stream", stream_name], ["topic", topic_name], ] controller.model.narrow = initial_narrow controller.model.index = index_multiple_topic_msg controller.model.stream_id = initial_stream_id controller.view.message_view = mocker.patch("urwid.ListBox") controller.model.stream_dict = { stream_id: { "color": "#ffffff", "name": stream_name, } } controller.model.muted_streams = set() mocker.patch(MODEL + ".is_muted_topic", return_value=False) controller.narrow_to_topic( stream_name=stream_name, topic_name=topic_name, contextual_message_id=anchor, ) assert controller.model.stream_id == stream_id assert controller.model.narrow == expected_narrow controller.view.message_view.log.clear.assert_called_once_with() widgets, focus = controller.view.message_view.log.extend.call_args_list[0][0] id_list = index_multiple_topic_msg["topic_msg_ids"][stream_id][topic_name] msg_ids = {widget.original_widget.message["id"] for widget in widgets} final_focus_msg_id = widgets[focus].original_widget.message["id"] assert msg_ids == id_list assert final_focus_msg_id == expected_final_focus
def test_narrow_to_all_pm( self, mocker: MockerFixture, controller: Controller, index_user: Index ) -> None: controller.model.narrow = [] controller.model.index = index_user controller.view.message_view = mocker.patch("urwid.ListBox") controller.model.user_id = 1 controller.model.user_email = "some@email" controller.narrow_to_all_pm() # FIXME: Add id narrowing test assert controller.model.narrow == [["is", "private"]] controller.view.message_view.log.clear.assert_called_once_with() widgets = controller.view.message_view.log.extend.call_args_list[0][0][0] id_list = index_user["private_msg_ids"] msg_ids = {widget.original_widget.message["id"] for widget in widgets} assert msg_ids == id_list
def test_maximum_popup_dimensions( self, mocker: MockerFixture, controller: Controller, screen_size: Tuple[int, int], expected_popup_size: Tuple[int, int], ) -> None: controller.loop.screen.get_cols_rows = mocker.Mock(return_value=screen_size) popup_size = controller.maximum_popup_dimensions() assert popup_size == expected_popup_size
def test_register_initial_desired_events(self, mocker): self.config_file = 'path/to/zuliprc' self.theme = 'default' controller = Controller(self.config_file, self.theme) event_types = [ 'message', 'update_message', 'reaction', 'typing', ] controller.client.register.assert_called_once_with( event_types=event_types, apply_markdown=True)
def test_stream_muting_confirmation_popup( self, mocker: MockerFixture, controller: Controller, muted_streams: Set[int], action: str, stream_id: int = 205, stream_name: str = "PTEST", ) -> None: pop_up = mocker.patch(MODULE + ".PopUpConfirmationView") text = mocker.patch(MODULE + ".urwid.Text") partial = mocker.patch(MODULE + ".partial") controller.model.muted_streams = muted_streams controller.loop = mocker.Mock() controller.stream_muting_confirmation_popup(stream_id, stream_name) text.assert_called_with( ("bold", f"Confirm {action} of stream '{stream_name}' ?"), "center", ) pop_up.assert_called_once_with(controller, text(), partial())
def controller(self, mocker) -> None: # Patch these unconditionally to avoid calling in __init__ self.poll_for_events = mocker.patch(CORE + '.Model.poll_for_events') mocker.patch(CORE + '.Controller.show_loading') self.config_file = 'path/to/zuliprc' self.theme = 'default' self.autohide = True # FIXME Add tests for no-autohide self.notify_enabled = False self.footlinks_enabled = True result = Controller(self.config_file, self.theme, 256, self.autohide, self.notify_enabled, self.footlinks_enabled) result.view.message_view = mocker.Mock() # set in View.__init__ return result
def test_copy_to_clipboard_no_exception( self, text_to_copy: str, pasted_text: str, expected_result: str, mocker: MockerFixture, controller: Controller, text_category: str = "Test", ) -> None: mocker.patch(MODULE + ".pyperclip.copy", return_value=None) mocker.patch(MODULE + ".pyperclip.paste", return_value=pasted_text) mock_success = mocker.patch(MODULE + ".Controller.report_success") mock_warning = mocker.patch(MODULE + ".Controller.report_warning") controller.copy_to_clipboard(text_to_copy, text_category) if expected_result == "success": mock_success.assert_called_once_with(f"{text_category} copied successfully") mock_warning.assert_not_called() else: mock_success.assert_not_called() mock_warning.assert_called_once_with( f"{text_category} copied, but the clipboard text does not match" )
def main() -> None: """ Launch Zulip Terminal. """ args = parse_args() if args.config_file: zuliprc_path = args.config_file else: zuliprc_path = '~/zuliprc' zterm = parse_zuliprc(zuliprc_path) if args.profile: import cProfile prof = cProfile.Profile() prof.enable() try: Controller(zuliprc_path, zterm['theme']).main() except Exception as e: if args.debug: # A unexpected exception occurred, open the debugger in debug mode import pudb pudb.post_mortem() sys.stdout.flush() traceback.print_exc(file=sys.stderr) print("Zulip Terminal has crashed!", file=sys.stderr) print("You can ask for help at:", file=sys.stderr) print("https://chat.zulip.org/#narrow/stream/206-zulip-terminal", file=sys.stderr) print("\nThanks for using the Zulip-Terminal interface.\n") sys.stderr.flush() finally: if args.profile: prof.disable() prof.dump_stats("/tmp/profile.data") print("Profile data saved to /tmp/profile.data") print("You can visualize it using e.g." "`snakeviz /tmp/profile.data`") sys.exit(1)
def main(options: Optional[List[str]] = None) -> None: """ Launch Zulip Terminal. """ argv = options if options is not None else sys.argv[1:] args = parse_args(argv) if args.profile: import cProfile prof = cProfile.Profile() prof.enable() if args.config_file: zuliprc_path = args.config_file else: zuliprc_path = '~/zuliprc' try: zterm = parse_zuliprc(zuliprc_path) if args.theme: theme_to_use = (args.theme, 'on command line') else: theme_to_use = zterm['theme'] valid_themes = THEMES.keys() if theme_to_use[0] not in valid_themes: print("Invalid theme '{}' was specified {}.".format(*theme_to_use)) print("The following themes are available:") for theme in valid_themes: print(" ", theme) print("Specify theme in zuliprc file or override " "using -t/--theme options on command line.") sys.exit(1) valid_autohide_settings = {'autohide', 'no_autohide'} if zterm['autohide'][0] not in valid_autohide_settings: print("Invalid autohide setting '{}' was specified {}.".format( *zterm['autohide'])) print("The following options are available:") for option in valid_autohide_settings: print(" ", option) print("Specify the autohide option in zuliprc file.") sys.exit(1) autohide_setting = (zterm['autohide'][0] == 'autohide') print("Loading with:") print(" theme '{}' specified {}.".format(*theme_to_use)) print(" autohide setting '{}' specified {}.".format( *zterm['autohide'])) Controller(zuliprc_path, THEMES[theme_to_use[0]], autohide_setting).main() except ServerConnectionFailure as e: print("\n\033[91mError connecting to Zulip server: {}.".format(e)) sys.exit(1) except Exception as e: if args.debug: # A unexpected exception occurred, open the debugger in debug mode import pudb pudb.post_mortem() sys.stdout.flush() traceback.print_exc(file=sys.stderr) print("Zulip Terminal has crashed!", file=sys.stderr) print("You can ask for help at:", file=sys.stderr) print("https://chat.zulip.org/#narrow/stream/206-zulip-terminal", file=sys.stderr) print("\nThanks for using the Zulip-Terminal interface.\n") sys.stderr.flush() finally: if args.profile: prof.disable() prof.dump_stats("/tmp/profile.data") print("Profile data saved to /tmp/profile.data") print("You can visualize it using e.g." "`snakeviz /tmp/profile.data`") sys.exit(1)
def main(options: Optional[List[str]] = None) -> None: """ Launch Zulip Terminal. """ argv = options if options is not None else sys.argv[1:] args = parse_args(argv) set_encoding('utf-8') if args.profile: import cProfile prof = cProfile.Profile() prof.enable() if args.version: print('Zulip Terminal ' + ZT_VERSION) sys.exit(0) if args.config_file: zuliprc_path = args.config_file else: zuliprc_path = '~/zuliprc' try: zterm = parse_zuliprc(zuliprc_path) if args.theme: theme_to_use = (args.theme, 'on command line') else: theme_to_use = zterm['theme'] available_themes = all_themes() if theme_to_use[0] not in available_themes: print("Invalid theme '{}' was specified {}.".format(*theme_to_use)) print("The following themes are available:") for theme in available_themes: print(" ", theme) print("Specify theme in zuliprc file or override " "using -t/--theme options on command line.") sys.exit(1) valid_autohide_settings = {'autohide', 'no_autohide'} if zterm['autohide'][0] not in valid_autohide_settings: print("Invalid autohide setting '{}' was specified {}.".format( *zterm['autohide'])) print("The following options are available:") for option in valid_autohide_settings: print(" ", option) print("Specify the autohide option in zuliprc file.") sys.exit(1) autohide_setting = (zterm['autohide'][0] == 'autohide') print("Loading with:") print(" theme '{}' specified {}.".format(*theme_to_use)) complete, incomplete = complete_and_incomplete_themes() if theme_to_use[0] in incomplete: print( in_color( 'yellow', " WARNING: Incomplete theme; " "results may vary!\n" " (you could try: {})".format(", ".join(complete)))) print(" autohide setting '{}' specified {}.".format( *zterm['autohide'])) Controller(zuliprc_path, THEMES[theme_to_use[0]], autohide_setting).main() except ServerConnectionFailure as e: print( in_color('red', "\nError connecting to Zulip server: {}.".format(e))) # Acts as separator between logs logging.info("\n\n" + str(e) + "\n\n") logging.exception(e) sys.exit(1) except Exception as e: logging.info("\n\n" + str(e) + "\n\n") logging.exception(e) if args.debug: sys.stdout.flush() traceback.print_exc(file=sys.stderr) run_debugger = input("Run Debugger? (y/n): ") if run_debugger in ["y", "Y", "yes"]: # Open PUDB Debuuger import pudb pudb.post_mortem() if hasattr(e, 'extra_info'): print( "\n" + in_color("red", e.extra_info), # type: ignore file=sys.stderr) print(in_color( "red", "\nZulip Terminal has crashed!" "\nPlease refer to " + LOG_FILENAME + " for full log of" " the error."), file=sys.stderr) print("You can ask for help at:", file=sys.stderr) print("https://chat.zulip.org/#narrow/stream/206-zulip-terminal", file=sys.stderr) print("\nThanks for using the Zulip-Terminal interface.\n") sys.stderr.flush() finally: if args.profile: prof.disable() prof.dump_stats("/tmp/profile.data") print("Profile data saved to /tmp/profile.data") print("You can visualize it using e.g." "`snakeviz /tmp/profile.data`") sys.exit(1)
def test_current_editor_error_if_no_editor(self, controller: Controller) -> None: with pytest.raises(AssertionError): controller.current_editor()
def test_initial_editor_mode(self, controller: Controller) -> None: assert not controller.is_in_editor_mode()