From 6953dab75d587c84e42ff834b10c75da9df137b9 Mon Sep 17 00:00:00 2001 From: naman <1977419+metalogical@users.noreply.github.com> Date: Wed, 6 Nov 2019 14:27:19 -0800 Subject: [PATCH 1/5] fix bug introduced by #10605 --- spyder/plugins/editor/widgets/codeeditor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py index 149a85dd657..7953e21ac81 100644 --- a/spyder/plugins/editor/widgets/codeeditor.py +++ b/spyder/plugins/editor/widgets/codeeditor.py @@ -1071,7 +1071,8 @@ def process_completion(self, params): completions = params['params'] completions = ([] if completions is None else [completion for completion in completions - if completion['insertText']]) + if completion.get('insertText') + or completion.get('textEdit', {}).get('newText')]) replace_end = self.textCursor().position() under_cursor = self.get_current_word_and_position(completion=True) From aec48d223057100de3e6aa7c708ee2b27217907f Mon Sep 17 00:00:00 2001 From: naman <1977419+metalogical@users.noreply.github.com> Date: Wed, 6 Nov 2019 16:29:34 -0800 Subject: [PATCH 2/5] add unit test to verify Kite's completions response schema --- .../completion/kite/providers/document.py | 3 ++ .../plugins/editor/widgets/tests/conftest.py | 31 ++++++++++++ .../widgets/tests/test_introspection.py | 47 +++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/spyder/plugins/completion/kite/providers/document.py b/spyder/plugins/completion/kite/providers/document.py index bbb4d7cfd6e..aef90e76f2e 100644 --- a/spyder/plugins/completion/kite/providers/document.py +++ b/spyder/plugins/completion/kite/providers/document.py @@ -116,6 +116,9 @@ def request_document_completions(self, params): @handles(LSPRequestTypes.DOCUMENT_COMPLETION) def convert_completion_request(self, response): + # The response schema is tested via mocking in + # spyder/plugins/editor/widgets/tests/test_introspection.py + logger.debug(response) if response is None: return {'params': []} diff --git a/spyder/plugins/editor/widgets/tests/conftest.py b/spyder/plugins/editor/widgets/tests/conftest.py index 980b9f76ccf..2f37a208be1 100644 --- a/spyder/plugins/editor/widgets/tests/conftest.py +++ b/spyder/plugins/editor/widgets/tests/conftest.py @@ -126,6 +126,37 @@ def teardown(): return completions +@pytest.fixture +def mock_codeeditor(qtbot_module, request): + """CodeEditor instance with ability to mock the completions response""" + # Create a CodeEditor instance + editor = codeeditor_factory() + qtbot_module.addWidget(editor) + editor.show() + + mock = Mock() + mock.side_effect = lambda *args: None + + def perform_request(lang, method, params): + resp = mock(lang, method, params) + print("DEBUG {}".format(resp)) + if resp is not None: + editor.handle_response(method, resp) + editor.sig_perform_completion_request.connect(perform_request) + + editor.filename = 'test.py' + editor.language = 'Python' + editor.completions_available = True + qtbot_module.wait(2000) + + def teardown(): + editor.hide() + editor.completion_widget.hide() + request.addfinalizer(teardown) + + return editor, mock + + @pytest.fixture def lsp_codeeditor(lsp_plugin, qtbot_module, request): """CodeEditor instance with LSP services activated.""" diff --git a/spyder/plugins/editor/widgets/tests/test_introspection.py b/spyder/plugins/editor/widgets/tests/test_introspection.py index e1aeb0da49f..d5bd87d6e84 100644 --- a/spyder/plugins/editor/widgets/tests/test_introspection.py +++ b/spyder/plugins/editor/widgets/tests/test_introspection.py @@ -28,6 +28,8 @@ rtree_available = False # Local imports +from spyder.plugins.completion.languageserver import LSPRequestTypes, CompletionItemKind +from spyder.plugins.completion.kite.providers.document import KITE_COMPLETION from spyder.py3compat import PY2 from spyder.config.manager import CONF @@ -764,6 +766,51 @@ def test_fallback_completions(fallback_codeeditor, qtbot): code_editor.toggle_code_snippets(True) +@pytest.mark.slow +@pytest.mark.first +@flaky(max_runs=5) +def test_kite_textEdit_completions(mock_codeeditor, qtbot): + """Test on-the-fly completions.""" + code_editor, mock = mock_codeeditor + completion = code_editor.completion_widget + + code_editor.toggle_automatic_completions(False) + code_editor.toggle_code_snippets(False) + + # Set cursor to start + code_editor.go_to_line(1) + + qtbot.keyClicks(code_editor, 'my_dict.') + + # Complete f -> from + mock.side_effect = lambda lang, method, params: {'params': [{ + 'kind': CompletionItemKind.TEXT, + 'label': '["dict-key"]', + 'textEdit': { + 'newText': '["dict-key"]', + 'range': { + 'start': 7, + 'end': 8, + }, + }, + 'filterText': '', + 'sortText': '', + 'documentation': '', + 'provider': KITE_COMPLETION, + }]} if method == LSPRequestTypes.DOCUMENT_COMPLETION else None + with qtbot.waitSignal(completion.sig_show_completions, + timeout=10000) as sig: + qtbot.keyPress(code_editor, Qt.Key_Tab, delay=300) + mock.side_effect = None + + assert '["dict-key"]' in [x['label'] for x in sig.args[0]] + qtbot.keyPress(code_editor, Qt.Key_Enter, delay=300) + assert code_editor.toPlainText() == 'my_dict["dict-key"]\n' + + code_editor.toggle_automatic_completions(True) + code_editor.toggle_code_snippets(True) + + if __name__ == '__main__': pytest.main(['test_introspection.py', '--run-slow']) From 0f1ab468b48dfb938bf61a36a3cd68d66fbdb3ef Mon Sep 17 00:00:00 2001 From: naman <1977419+metalogical@users.noreply.github.com> Date: Wed, 6 Nov 2019 16:37:00 -0800 Subject: [PATCH 3/5] fix lint --- spyder/plugins/editor/widgets/tests/test_introspection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spyder/plugins/editor/widgets/tests/test_introspection.py b/spyder/plugins/editor/widgets/tests/test_introspection.py index d5bd87d6e84..5339920e676 100644 --- a/spyder/plugins/editor/widgets/tests/test_introspection.py +++ b/spyder/plugins/editor/widgets/tests/test_introspection.py @@ -28,7 +28,8 @@ rtree_available = False # Local imports -from spyder.plugins.completion.languageserver import LSPRequestTypes, CompletionItemKind +from spyder.plugins.completion.languageserver import ( + LSPRequestTypes, CompletionItemKind) from spyder.plugins.completion.kite.providers.document import KITE_COMPLETION from spyder.py3compat import PY2 from spyder.config.manager import CONF From d684558a6335c164970289880609efb892dfcd07 Mon Sep 17 00:00:00 2001 From: naman <1977419+metalogical@users.noreply.github.com> Date: Thu, 7 Nov 2019 09:24:23 -0800 Subject: [PATCH 4/5] review fixes --- spyder/plugins/editor/widgets/tests/conftest.py | 16 ++++++++++------ .../editor/widgets/tests/test_introspection.py | 8 ++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/spyder/plugins/editor/widgets/tests/conftest.py b/spyder/plugins/editor/widgets/tests/conftest.py index 2f37a208be1..2ad423ffff5 100644 --- a/spyder/plugins/editor/widgets/tests/conftest.py +++ b/spyder/plugins/editor/widgets/tests/conftest.py @@ -127,18 +127,22 @@ def teardown(): @pytest.fixture -def mock_codeeditor(qtbot_module, request): - """CodeEditor instance with ability to mock the completions response""" +def mock_completions_codeeditor(qtbot_module, request): + """CodeEditor instance with ability to mock the completions response. + + Returns a tuple of (editor, mock_response). Tests using this fixture should + set `mock_response.side_effect = lambda lang, method, params: {}`. + """ # Create a CodeEditor instance editor = codeeditor_factory() qtbot_module.addWidget(editor) editor.show() - mock = Mock() - mock.side_effect = lambda *args: None + mock_response = Mock() + mock_response.side_effect = lambda *args: None def perform_request(lang, method, params): - resp = mock(lang, method, params) + resp = mock_response(lang, method, params) print("DEBUG {}".format(resp)) if resp is not None: editor.handle_response(method, resp) @@ -154,7 +158,7 @@ def teardown(): editor.completion_widget.hide() request.addfinalizer(teardown) - return editor, mock + return editor, mock_response @pytest.fixture diff --git a/spyder/plugins/editor/widgets/tests/test_introspection.py b/spyder/plugins/editor/widgets/tests/test_introspection.py index 5339920e676..67c5413f6b5 100644 --- a/spyder/plugins/editor/widgets/tests/test_introspection.py +++ b/spyder/plugins/editor/widgets/tests/test_introspection.py @@ -771,7 +771,11 @@ def test_fallback_completions(fallback_codeeditor, qtbot): @pytest.mark.first @flaky(max_runs=5) def test_kite_textEdit_completions(mock_codeeditor, qtbot): - """Test on-the-fly completions.""" + """Test textEdit completions such as those returned by the Kite provider. + + This mocks out the completions response, and does not test the Kite + provider directly. + """ code_editor, mock = mock_codeeditor completion = code_editor.completion_widget @@ -783,7 +787,7 @@ def test_kite_textEdit_completions(mock_codeeditor, qtbot): qtbot.keyClicks(code_editor, 'my_dict.') - # Complete f -> from + # Complete my_dict. -> my_dict["dict-key"] mock.side_effect = lambda lang, method, params: {'params': [{ 'kind': CompletionItemKind.TEXT, 'label': '["dict-key"]', From 496ab260bc79920b08f131635fa6d7b833994546 Mon Sep 17 00:00:00 2001 From: naman <1977419+metalogical@users.noreply.github.com> Date: Thu, 7 Nov 2019 09:58:18 -0800 Subject: [PATCH 5/5] more fixes --- spyder/plugins/editor/widgets/tests/conftest.py | 1 - spyder/plugins/editor/widgets/tests/test_introspection.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/editor/widgets/tests/conftest.py b/spyder/plugins/editor/widgets/tests/conftest.py index 2ad423ffff5..217e38c041e 100644 --- a/spyder/plugins/editor/widgets/tests/conftest.py +++ b/spyder/plugins/editor/widgets/tests/conftest.py @@ -139,7 +139,6 @@ def mock_completions_codeeditor(qtbot_module, request): editor.show() mock_response = Mock() - mock_response.side_effect = lambda *args: None def perform_request(lang, method, params): resp = mock_response(lang, method, params) diff --git a/spyder/plugins/editor/widgets/tests/test_introspection.py b/spyder/plugins/editor/widgets/tests/test_introspection.py index 67c5413f6b5..349c8fe9aa2 100644 --- a/spyder/plugins/editor/widgets/tests/test_introspection.py +++ b/spyder/plugins/editor/widgets/tests/test_introspection.py @@ -770,13 +770,13 @@ def test_fallback_completions(fallback_codeeditor, qtbot): @pytest.mark.slow @pytest.mark.first @flaky(max_runs=5) -def test_kite_textEdit_completions(mock_codeeditor, qtbot): +def test_kite_textEdit_completions(mock_completions_codeeditor, qtbot): """Test textEdit completions such as those returned by the Kite provider. This mocks out the completions response, and does not test the Kite provider directly. """ - code_editor, mock = mock_codeeditor + code_editor, mock_response = mock_completions_codeeditor completion = code_editor.completion_widget code_editor.toggle_automatic_completions(False) @@ -788,7 +788,7 @@ def test_kite_textEdit_completions(mock_codeeditor, qtbot): qtbot.keyClicks(code_editor, 'my_dict.') # Complete my_dict. -> my_dict["dict-key"] - mock.side_effect = lambda lang, method, params: {'params': [{ + mock_response.side_effect = lambda lang, method, params: {'params': [{ 'kind': CompletionItemKind.TEXT, 'label': '["dict-key"]', 'textEdit': { @@ -806,7 +806,7 @@ def test_kite_textEdit_completions(mock_codeeditor, qtbot): with qtbot.waitSignal(completion.sig_show_completions, timeout=10000) as sig: qtbot.keyPress(code_editor, Qt.Key_Tab, delay=300) - mock.side_effect = None + mock_response.side_effect = None assert '["dict-key"]' in [x['label'] for x in sig.args[0]] qtbot.keyPress(code_editor, Qt.Key_Enter, delay=300)