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/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) diff --git a/spyder/plugins/editor/widgets/tests/conftest.py b/spyder/plugins/editor/widgets/tests/conftest.py index 980b9f76ccf..217e38c041e 100644 --- a/spyder/plugins/editor/widgets/tests/conftest.py +++ b/spyder/plugins/editor/widgets/tests/conftest.py @@ -126,6 +126,40 @@ def teardown(): return completions +@pytest.fixture +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_response = Mock() + + def perform_request(lang, method, params): + resp = mock_response(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_response + + @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..349c8fe9aa2 100644 --- a/spyder/plugins/editor/widgets/tests/test_introspection.py +++ b/spyder/plugins/editor/widgets/tests/test_introspection.py @@ -28,6 +28,9 @@ 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 +767,55 @@ 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_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_response = mock_completions_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 my_dict. -> my_dict["dict-key"] + mock_response.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_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) + 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'])