Skip to content
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

PR: Show Help if closed when opening tutorial to avoid user confusion #6345

Merged
merged 6 commits into from
Feb 7, 2018
95 changes: 86 additions & 9 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from spyder.config.base import get_home_dir
from spyder.config.main import CONF
from spyder.plugins import TabFilter
from spyder.plugins.help import ObjectComboBox
from spyder.plugins.runconfig import RunConfiguration
from spyder.py3compat import PY2, to_text_string
from spyder.utils.ipython.kernelspec import SpyderKernelSpec
Expand All @@ -48,9 +49,9 @@
from urllib2 import urlopen, URLError


#==============================================================================
# =============================================================================
# Constants
#==============================================================================
# =============================================================================
# Location of this file
LOCATION = osp.realpath(osp.join(os.getcwd(), osp.dirname(__file__)))

Expand All @@ -60,7 +61,7 @@

# Need longer EVAL_TIMEOUT, because need to cythonize and C compile ".pyx" file
# before import and eval it
COMPILE_AND_EVAL_TIMEOUT=30000
COMPILE_AND_EVAL_TIMEOUT = 30000

# Time to wait for the IPython console to evaluate something (in
# miliseconds)
Expand All @@ -69,9 +70,9 @@
# Temporary directory
TEMP_DIRECTORY = tempfile.gettempdir()

#==============================================================================
# =============================================================================
# Utility functions
#==============================================================================
# =============================================================================
def open_file_in_editor(main_window, fname, directory=None):
"""Open a file using the Editor and its open file dialog"""
top_level_widgets = QApplication.topLevelWidgets()
Expand Down Expand Up @@ -119,9 +120,18 @@ def start_new_kernel(startup_timeout=60, kernel_name='python', spykernel=False,
return km, kc


#==============================================================================
def find_desired_tab_in_window(tab_name, window):
all_tabbars = window.findChildren(QTabBar)
for current_tabbar in all_tabbars:
for tab_index in range(current_tabbar.count()):
if current_tabbar.tabText(tab_index) == str(tab_name):
return current_tabbar, tab_index
return None, None


# =============================================================================
# Fixtures
#==============================================================================
# =============================================================================
@pytest.fixture
def main_window(request):
"""Main Window fixture"""
Expand Down Expand Up @@ -157,9 +167,9 @@ def close_window():
return window


#==============================================================================
# =============================================================================
# Tests
#==============================================================================
# =============================================================================
# IMPORTANT NOTE: Please leave this test to be the first one here to
# avoid possible timeouts in Appveyor
@pytest.mark.slow
Expand Down Expand Up @@ -1245,5 +1255,72 @@ def test_tabfilter_typeerror_full(main_window):
assert mockEvent_instance.pos.call_count == 1


@flaky(max_runs=3)
@pytest.mark.slow
def test_help_opens_when_show_tutorial_full(main_window, qtbot):
"""Test fix for #6317 : 'Show tutorial' opens the help plugin if closed."""
HELP_STR = "Help"

# Wait until the window is fully up
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)

help_pane_menuitem = None
for action in main_window.plugins_menu.actions():
if action.text() == HELP_STR:
help_pane_menuitem = action
break

# Test opening tutorial with Help plguin closed
try:
main_window.help.plugin_closed()
except Exception:
pass
qtbot.wait(500)
help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)

assert help_tabbar is None and help_index is None
assert not isinstance(main_window.focusWidget(), ObjectComboBox)
assert not help_pane_menuitem.isChecked()

main_window.help.show_tutorial()
qtbot.wait(500)

help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
assert None not in (help_tabbar, help_index)
assert help_index == help_tabbar.currentIndex()
assert isinstance(main_window.focusWidget(), ObjectComboBox)
assert help_pane_menuitem.isChecked()

# Test opening tutorial with help plugin open, but not selected
help_tabbar.setCurrentIndex((help_tabbar.currentIndex() + 1)
% help_tabbar.count())
qtbot.wait(500)
help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
assert None not in (help_tabbar, help_index)
assert help_index != help_tabbar.currentIndex()
assert not isinstance(main_window.focusWidget(), ObjectComboBox)
assert help_pane_menuitem.isChecked()

main_window.help.show_tutorial()
qtbot.wait(500)
help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
assert None not in (help_tabbar, help_index)
assert help_index == help_tabbar.currentIndex()
assert isinstance(main_window.focusWidget(), ObjectComboBox)
assert help_pane_menuitem.isChecked()

# Test opening tutorial with help plugin open and the active tab
qtbot.wait(500)
main_window.help.show_tutorial()
help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
qtbot.wait(500)
assert None not in (help_tabbar, help_index)
assert help_index == help_tabbar.currentIndex()
assert isinstance(main_window.focusWidget(), ObjectComboBox)
assert help_pane_menuitem.isChecked()


if __name__ == "__main__":
pytest.main()
4 changes: 4 additions & 0 deletions spyder/plugins/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,10 @@ def show_plain_text(self, text):

@Slot()
def show_tutorial(self):
"""Show the Spyder tutorial in the Help plugin, opening it if needed"""
if not self.dockwidget.isVisible():
self.dockwidget.show()
self.toggle_view_action.setChecked(True)
tutorial_path = get_module_source_path('spyder.utils.help')
tutorial = osp.join(tutorial_path, 'tutorial.rst')
text = open(tutorial).read()
Expand Down
124 changes: 89 additions & 35 deletions spyder/plugins/tests/test_help.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © Spyder Project Contributors
#
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
# -----------------------------------------------------------------------------
"""Test scripts for `findinfiles` plugin."""

# 3rd party imports
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © Spyder Project Contributors
#
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
# -----------------------------------------------------------------------------

"""
Tests for the Spyder `help` plugn, `help.py`.
"""

# Standard library imports
try:
from unittest.mock import Mock, MagicMock
except ImportError:
from mock import Mock, MagicMock # Python 2

# Third party imports
from qtpy.QtWebEngineWidgets import WEBENGINE
import pytest

# Local imports
from spyder.plugins.help import Help
from spyder.utils.introspection.utils import default_info_response


@pytest.fixture
def help_plugin(qtbot):
"""Help plugin fixture"""
help_plugin = Help()
import pytest
from flaky import flaky

# Local imports
from spyder.plugins.help import Help
from spyder.utils.introspection.utils import default_info_response


# =============================================================================
# Fixtures
# =============================================================================
@pytest.fixture
def help_plugin(qtbot):
"""Help plugin fixture"""
help_plugin = Help()
webview = help_plugin.rich_text.webview._webview

if WEBENGINE:
Expand All @@ -28,9 +41,12 @@ def help_plugin(qtbot):
help_plugin._webpage = webview.page().mainFrame()

qtbot.addWidget(help_plugin)
return help_plugin
return help_plugin


# =============================================================================
# Utility functions
# =============================================================================
def check_text(widget, text):
"""Check if some text is present in a widget."""
if WEBENGINE:
Expand All @@ -44,20 +60,25 @@ def callback(data):
return False
else:
return text in widget.toHtml()


def test_no_docs_message(help_plugin, qtbot):


# =============================================================================
# Tests
# =============================================================================
@flaky(max_runs=3)
def test_no_docs_message(help_plugin, qtbot):
"""
Test that no docs message is shown when instrospection plugins
can't get any info.
"""
help_plugin.render_sphinx_doc(default_info_response())
help_plugin.render_sphinx_doc(default_info_response())
qtbot.waitUntil(lambda: check_text(help_plugin._webpage,
"No documentation available"),
timeout=2000)


def test_no_further_docs_message(help_plugin, qtbot):
timeout=4000)


@flaky(max_runs=3)
def test_no_further_docs_message(help_plugin, qtbot):
"""
Test that no further docs message is shown when instrospection
plugins can get partial info.
Expand All @@ -66,11 +87,44 @@ def test_no_further_docs_message(help_plugin, qtbot):
info['name'] = 'foo'
info['argspec'] = '(x, y)'

help_plugin.render_sphinx_doc(info)
help_plugin.render_sphinx_doc(info)
qtbot.waitUntil(lambda: check_text(help_plugin._webpage,
"No further documentation available"),
timeout=2000)


if __name__ == "__main__":
timeout=3000)


def test_help_opens_when_show_tutorial_unit(help_plugin, qtbot,):
"""Test fix for #6317 : 'Show tutorial' opens the help plugin if closed."""
MockDockwidget = MagicMock()
MockDockwidget.return_value.isVisible.return_value = False
mockDockwidget_instance = MockDockwidget()

MockAction = Mock()
mock_toggle_view_action = MockAction()
mock_show_rich_text = Mock()

help_plugin.dockwidget = mockDockwidget_instance
help_plugin.toggle_view_action = mock_toggle_view_action
help_plugin.show_rich_text = mock_show_rich_text

help_plugin.show_tutorial()
qtbot.wait(100)

assert mockDockwidget_instance.show.call_count == 1
assert mock_toggle_view_action.setChecked.call_count == 1
mock_toggle_view_action.setChecked.assert_called_once_with(True)
assert mock_show_rich_text.call_count == 1

MockDockwidget.return_value.isVisible.return_value = True
mockDockwidget_instance = MockDockwidget()
help_plugin.dockwidget = mockDockwidget_instance

help_plugin.show_tutorial()
qtbot.wait(100)
assert mockDockwidget_instance.show.call_count == 1
assert mock_toggle_view_action.setChecked.call_count == 1
assert mock_show_rich_text.call_count == 2


if __name__ == "__main__":
pytest.main()