From df2f3d4d767b0ad8b876dbc1ee70b1267e704d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 21 Mar 2024 19:58:22 +0100 Subject: [PATCH] narrowing it :) --- tests/test_file_watcher.py | 261 ------------------- tests/test_mocks.py | 82 ------ tests/test_server_notifications.py | 81 ------ tests/test_server_requests.py | 224 ---------------- tests/test_single_document.py | 400 ----------------------------- 5 files changed, 1048 deletions(-) delete mode 100644 tests/test_file_watcher.py delete mode 100644 tests/test_mocks.py delete mode 100644 tests/test_server_notifications.py delete mode 100644 tests/test_server_requests.py delete mode 100644 tests/test_single_document.py diff --git a/tests/test_file_watcher.py b/tests/test_file_watcher.py deleted file mode 100644 index b3281e3ee..000000000 --- a/tests/test_file_watcher.py +++ /dev/null @@ -1,261 +0,0 @@ -from LSP.plugin import FileWatcher -from LSP.plugin import FileWatcherEvent -from LSP.plugin import FileWatcherEventType -from LSP.plugin import FileWatcherProtocol -from LSP.plugin.core.file_watcher import file_watcher_event_type_to_lsp_file_change_type -from LSP.plugin.core.file_watcher import register_file_watcher_implementation -from LSP.plugin.core.protocol import WatchKind -from LSP.plugin.core.types import ClientConfig -from LSP.plugin.core.types import sublime_pattern_to_glob -from LSP.plugin.core.typing import Generator, List, Optional -from os.path import join -from setup import expand -from setup import TextDocumentTestCase -import sublime -import unittest - - -def setup_workspace_folder() -> str: - window = sublime.active_window() - folder_path = expand(join('$packages', 'LSP', 'tests'), window) - window.set_project_data({ - 'folders': [ - { - 'name': 'folder', - 'path': folder_path, - } - ] - }) - return folder_path - - -class TestFileWatcher(FileWatcher): - - # The list of watchers created by active sessions. - _active_watchers = [] # type: List[TestFileWatcher] - - @classmethod - def create( - cls, - root_path: str, - patterns: List[str], - events: List[FileWatcherEventType], - ignores: List[str], - handler: FileWatcherProtocol - ) -> 'TestFileWatcher': - watcher = TestFileWatcher(root_path, patterns, events, ignores, handler) - cls._active_watchers.append(watcher) - return watcher - - def __init__( - self, - root_path: str, - patterns: List[str], - events: List[FileWatcherEventType], - ignores: List[str], - handler: FileWatcherProtocol - ) -> None: - self.root_path = root_path - self.patterns = patterns - self.events = events - self.ignores = ignores - self.handler = handler - - def destroy(self) -> None: - self.handler = None - self._active_watchers.remove(self) - - def trigger_event(self, events: List[FileWatcherEvent]) -> None: - - def trigger_async(): - if self.handler: - self.handler.on_file_event_async(events) - - sublime.set_timeout_async(trigger_async) - - -class FileWatcherDocumentTestCase(TextDocumentTestCase): - """ - Changes TextDocumentTestCase behavior so that the initialization and destroy of the config - and the view happens before and after every test rather than per-testsuite. - """ - - @classmethod - def setUpClass(cls) -> None: - # Don't call the superclass. - register_file_watcher_implementation(TestFileWatcher) - - @classmethod - def tearDownClass(cls) -> None: - # Don't call the superclass. - pass - - def setUp(self) -> Generator: - self.assertEqual(len(TestFileWatcher._active_watchers), 0) - # Watchers are only registered when there are workspace folders so add a folder. - self.folder_root_path = setup_workspace_folder() - yield from super().setUpClass() - yield from super().setUp() - - def tearDown(self) -> Generator: - yield from super().tearDownClass() - self.assertEqual(len(TestFileWatcher._active_watchers), 0) - # Restore original project data. - window = sublime.active_window() - window.set_project_data({}) - - -class FileWatcherStaticTests(FileWatcherDocumentTestCase): - - @classmethod - def get_stdio_test_config(cls) -> ClientConfig: - return ClientConfig.from_config( - super().get_stdio_test_config(), - { - 'file_watcher': { - 'patterns': ['*.js'], - 'events': ['change'], - 'ignores': ['.git'], - } - } - ) - - def test_initialize_params_includes_capability(self) -> None: - self.assertIn('didChangeWatchedFiles', self.initialize_params['capabilities']['workspace']) - - def test_creates_static_watcher(self) -> None: - # Starting a session should have created a watcher. - self.assertEqual(len(TestFileWatcher._active_watchers), 1) - watcher = TestFileWatcher._active_watchers[0] - self.assertEqual(watcher.patterns, ['*.js']) - self.assertEqual(watcher.events, ['change']) - self.assertEqual(watcher.ignores, ['.git']) - self.assertEqual(watcher.root_path, self.folder_root_path) - - def test_handles_file_event(self) -> Generator: - watcher = TestFileWatcher._active_watchers[0] - filepath = join(self.folder_root_path, 'file.js') - watcher.trigger_event([('change', filepath)]) - sent_notification = yield from self.await_message('workspace/didChangeWatchedFiles') - self.assertIs(type(sent_notification['changes']), list) - self.assertEqual(len(sent_notification['changes']), 1) - change = sent_notification['changes'][0] - self.assertEqual(change['type'], file_watcher_event_type_to_lsp_file_change_type('change')) - self.assertTrue(change['uri'].endswith('file.js')) - - -class FileWatcherDynamicTests(FileWatcherDocumentTestCase): - - def test_handles_dynamic_watcher_registration(self) -> Generator: - registration_params = { - 'registrations': [ - { - 'id': '111', - 'method': 'workspace/didChangeWatchedFiles', - 'registerOptions': { - 'watchers': [ - { - 'globPattern': '*.py', - 'kind': WatchKind.Create | WatchKind.Change | WatchKind.Delete, - } - ] - } - } - ] - } - yield self.make_server_do_fake_request('client/registerCapability', registration_params) - self.assertEqual(len(TestFileWatcher._active_watchers), 1) - watcher = TestFileWatcher._active_watchers[0] - self.assertEqual(watcher.patterns, ['*.py']) - self.assertEqual(watcher.events, ['create', 'change', 'delete']) - self.assertEqual(watcher.root_path, self.folder_root_path) - # Trigger the file event - filepath = join(self.folder_root_path, 'file.py') - watcher.trigger_event([('create', filepath), ('change', filepath)]) - sent_notification = yield from self.await_message('workspace/didChangeWatchedFiles') - self.assertIs(type(sent_notification['changes']), list) - self.assertEqual(len(sent_notification['changes']), 2) - change1 = sent_notification['changes'][0] - self.assertEqual(change1['type'], file_watcher_event_type_to_lsp_file_change_type('create')) - self.assertTrue(change1['uri'].endswith('file.py')) - change2 = sent_notification['changes'][1] - self.assertEqual(change2['type'], file_watcher_event_type_to_lsp_file_change_type('change')) - self.assertTrue(change2['uri'].endswith('file.py')) - - -class PatternToGlobTests(unittest.TestCase): - - def test_basic_directory_patterns(self): - patterns = [ - '.git', - 'CVS', - '.Trash-*', - ] - self._verify_patterns( - patterns, - [ - '**/.git/**', - '**/CVS/**', - '**/.Trash-*/**', - ], - is_directory_pattern=True) - - def test_complex_directory_patterns(self): - patterns = [ - '*/foo', - 'foo/bar', - 'foo/bar/', - '/foo', - ] - self._verify_patterns( - patterns, - [ - '**/foo/**', - '**/foo/bar/**', - '**/foo/bar/**', - '/foo/**', - ], - is_directory_pattern=True) - - def test_basic_file_patterns(self): - self._verify_patterns( - [ - '*.pyc', - ".DS_Store", - - ], - [ - '**/*.pyc', - '**/.DS_Store', - ], - is_directory_pattern=False) - - def test_complex_file_patterns(self): - self._verify_patterns( - [ - "/*.pyo", - ], - [ - '/*.pyo', - ], - is_directory_pattern=False) - - def test_project_relative_patterns(self): - self._verify_patterns(['//foo'], ['/Users/me/foo/**'], is_directory_pattern=True, root_path='/Users/me') - self._verify_patterns(['//*.pyo'], ['/Users/me/*.pyo'], is_directory_pattern=False, root_path='/Users/me') - # Without root_path those will be treated as absolute paths even when starting with multiple slashes. - self._verify_patterns(['//foo'], ['//foo/**'], is_directory_pattern=True) - self._verify_patterns(['//*.pyo'], ['//*.pyo'], is_directory_pattern=False) - - def _verify_patterns( - self, - patterns: List[str], - expected: List[str], - is_directory_pattern: bool, - root_path: Optional[str] = None - ) -> None: - glob_patterns = [ - sublime_pattern_to_glob(pattern, is_directory_pattern=is_directory_pattern, root_path=root_path) - for pattern in patterns - ] - self.assertEqual(glob_patterns, expected) diff --git a/tests/test_mocks.py b/tests/test_mocks.py deleted file mode 100644 index 177c4f9a2..000000000 --- a/tests/test_mocks.py +++ /dev/null @@ -1,82 +0,0 @@ -from LSP.plugin.core.logging import debug -from LSP.plugin.core.protocol import Notification -from LSP.plugin.core.protocol import Request -from LSP.plugin.core.protocol import Response -from LSP.plugin.core.types import ClientConfig -from LSP.plugin.core.typing import List, Any, Callable - - -TEST_CONFIG = ClientConfig(name="test", command=[], selector="text.plain", tcp_port=None) -DISABLED_CONFIG = ClientConfig("test", command=[], selector="text.plain", tcp_port=None, enabled=False) - -basic_responses = { - 'initialize': { - 'capabilities': { - 'testing': True, - 'hoverProvider': True, - 'completionProvider': { - 'triggerCharacters': ['.'], - 'resolveProvider': True - }, - 'textDocumentSync': { - "openClose": True, - "change": 2, - "save": True - }, - 'definitionProvider': True, - 'typeDefinitionProvider': True, - 'declarationProvider': True, - 'implementationProvider': True, - 'documentFormattingProvider': True, - 'selectionRangeProvider': True, - 'renameProvider': True, - 'workspace': { - 'workspaceFolders': { - 'supported': True - } - } - } - } -} - - -class MockSession(object): - def __init__(self, async_response=None) -> None: - self.responses = basic_responses - self._notifications = [] # type: List[Notification] - self._async_response_callback = async_response - - def send_request(self, request: Request, on_success: Callable, on_error: Callable = None) -> None: - response = self.responses.get(request.method) - debug("TEST: responding to", request.method, "with", response) - if self._async_response_callback: - self._async_response_callback(lambda: on_success(response)) - else: - on_success(response) - - def execute_request(self, request: Request) -> Any: - return self.responses.get(request.method) - - def send_notification(self, notification: Notification) -> None: - self._notifications.append(notification) - - def on_notification(self, name, handler: Callable) -> None: - pass - - def on_request(self, name, handler: Callable) -> None: - pass - - def set_error_display_handler(self, handler: Callable) -> None: - pass - - def set_crash_handler(self, handler: Callable) -> None: - pass - - def set_log_payload_handler(self, handler: Callable) -> None: - pass - - def exit(self) -> None: - pass - - def send_response(self, response: Response) -> None: - pass diff --git a/tests/test_server_notifications.py b/tests/test_server_notifications.py deleted file mode 100644 index e49d655c6..000000000 --- a/tests/test_server_notifications.py +++ /dev/null @@ -1,81 +0,0 @@ -from LSP.plugin.core.protocol import DiagnosticSeverity -from LSP.plugin.core.protocol import DiagnosticTag -from LSP.plugin.core.protocol import PublishDiagnosticsParams -from LSP.plugin.core.typing import Generator -from LSP.plugin.core.url import filename_to_uri -from setup import TextDocumentTestCase -import sublime - - -class ServerNotifications(TextDocumentTestCase): - - def test_publish_diagnostics(self) -> Generator: - self.insert_characters("a b c\n") - params = { - 'uri': filename_to_uri(self.view.file_name() or ''), - 'diagnostics': [ - { - 'message': "foo", - 'severity': DiagnosticSeverity.Error, - 'source': 'qux', - 'range': {'end': {'character': 1, 'line': 0}, 'start': {'character': 0, 'line': 0}} - }, - { - 'message': 'bar', - 'severity': DiagnosticSeverity.Warning, - 'source': 'qux', - 'range': {'end': {'character': 3, 'line': 0}, 'start': {'character': 2, 'line': 0}} - }, - { - 'message': "baz", - 'severity': DiagnosticSeverity.Information, - 'source': 'qux', - 'range': {'end': {'character': 5, 'line': 0}, 'start': {'character': 4, 'line': 0}}, - 'tags': [DiagnosticTag.Unnecessary] - } - ] - } # type: PublishDiagnosticsParams - yield from self.await_client_notification("textDocument/publishDiagnostics", params) - errors_icon_regions = self.view.get_regions("lspTESTds1_icon") - errors_underline_regions = self.view.get_regions("lspTESTds1_underline") - warnings_icon_regions = self.view.get_regions("lspTESTds2_icon") - warnings_underline_regions = self.view.get_regions("lspTESTds2_underline") - info_icon_regions = self.view.get_regions("lspTESTds3_icon") - info_underline_regions = self.view.get_regions("lspTESTds3_underline") - yield lambda: len(errors_icon_regions) == len(errors_underline_regions) == 1 - yield lambda: len(warnings_icon_regions) == len(warnings_underline_regions) == 1 - yield lambda: len(info_icon_regions) == len(info_underline_regions) == 1 - yield lambda: len(self.view.get_regions("lspTESTds3_tags")) == 0 - self.assertEqual(errors_underline_regions[0], sublime.Region(0, 1)) - self.assertEqual(warnings_underline_regions[0], sublime.Region(2, 3)) - self.assertEqual(info_underline_regions[0], sublime.Region(4, 5)) - - # Testing whether the cursor position moves along with lsp_next_diagnostic - - self.view.window().run_command("lsp_next_diagnostic") - self.assertEqual(self.view.sel()[0].a, self.view.sel()[0].b) - self.assertEqual(self.view.sel()[0].b, 0) - - self.view.window().run_command("lsp_next_diagnostic") - self.assertEqual(self.view.sel()[0].a, self.view.sel()[0].b) - self.assertEqual(self.view.sel()[0].b, 2) - - self.view.window().run_command("lsp_next_diagnostic") - self.assertEqual(self.view.sel()[0].a, self.view.sel()[0].b) - self.assertEqual(self.view.sel()[0].b, 4) - - # lsp_prev_diagnostic should work as well - - self.view.window().run_command("lsp_prev_diagnostic") - self.assertEqual(self.view.sel()[0].a, self.view.sel()[0].b) - self.assertEqual(self.view.sel()[0].b, 2) - - self.view.window().run_command("lsp_prev_diagnostic") - self.assertEqual(self.view.sel()[0].a, self.view.sel()[0].b) - self.assertEqual(self.view.sel()[0].b, 0) - - # Testing to wrap around if there are no more diagnostics in the direction - - self.view.window().run_command("lsp_prev_diagnostic") - self.assertEqual(self.view.sel()[0].a, self.view.sel()[0].b) - self.assertEqual(self.view.sel()[0].b, 4) diff --git a/tests/test_server_requests.py b/tests/test_server_requests.py deleted file mode 100644 index 2ac9d5814..000000000 --- a/tests/test_server_requests.py +++ /dev/null @@ -1,224 +0,0 @@ -from LSP.plugin.core.protocol import ErrorCodes -from LSP.plugin.core.protocol import TextDocumentSyncKind -from LSP.plugin.core.sessions import SessionBufferProtocol -from LSP.plugin.core.types import ClientConfig -from LSP.plugin.core.typing import Any, Dict, Generator, Optional, List -from LSP.plugin.core.url import filename_to_uri -from setup import TextDocumentTestCase -import os -import sublime -import tempfile - - -def get_auto_complete_trigger(sb: SessionBufferProtocol) -> Optional[List[Dict[str, str]]]: - for sv in sb.session_views: - triggers = sv.view.settings().get("auto_complete_triggers") - for trigger in triggers: - if "server" in trigger and "registration_id" in trigger: - return trigger - return None - - -def verify(testcase: TextDocumentTestCase, method: str, input_params: Any, expected_output_params: Any) -> Generator: - promise = testcase.make_server_do_fake_request(method, input_params) - yield from testcase.await_promise(promise) - testcase.assertEqual(promise.result(), expected_output_params) - - -class ServerRequests(TextDocumentTestCase): - - def test_unknown_method(self) -> Generator: - yield from verify(self, "foobar/qux", {}, {"code": ErrorCodes.MethodNotFound, "message": "foobar/qux"}) - - def test_m_workspace_workspaceFolders(self) -> Generator: - expected_output = [{"name": os.path.basename(f), "uri": filename_to_uri(f)} - for f in sublime.active_window().folders()] - self.maxDiff = None - yield from verify(self, "workspace/workspaceFolders", {}, expected_output) - - def test_m_workspace_configuration(self) -> Generator: - self.session.config.settings.set("foo.bar", "$hello") - self.session.config.settings.set("foo.baz", "$world") - self.session.config.settings.set("foo.a", 1) - self.session.config.settings.set("foo.b", None) - self.session.config.settings.set("foo.c", ["asdf ${hello} ${world}"]) - - class TempPlugin: - - @classmethod - def additional_variables(cls) -> Optional[Dict[str, str]]: - return {"hello": "X", "world": "Y"} - - self.session._plugin_class = TempPlugin # type: ignore - method = "workspace/configuration" - params = {"items": [{"section": "foo"}]} - expected_output = [{"bar": "X", "baz": "Y", "a": 1, "b": None, "c": ["asdf X Y"]}] - yield from verify(self, method, params, expected_output) - self.session.config.settings.clear() - - def test_m_workspace_applyEdit(self) -> Generator: - old_change_count = self.insert_characters("hello\nworld\n") - edit = { - "newText": "there", - "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}} - params = {"edit": {"changes": {filename_to_uri(self.view.file_name()): [edit]}}} - yield from verify(self, "workspace/applyEdit", params, {"applied": True}) - yield lambda: self.view.change_count() > old_change_count - self.assertEqual(self.view.substr(sublime.Region(0, self.view.size())), "hello\nthere\n") - - def test_m_workspace_applyEdit_with_nontrivial_promises(self) -> Generator: - with tempfile.TemporaryDirectory() as dirpath: - initial_text = ["a b", "c d"] - file_paths = [] - for i in range(0, 2): - file_paths.append(os.path.join(dirpath, "file{}.txt".format(i))) - with open(file_paths[-1], "w") as fp: - fp.write(initial_text[i]) - yield from verify( - self, - "workspace/applyEdit", - { - "edit": { - "changes": { - filename_to_uri(file_paths[0]): - [ - { - "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 1}}, - "newText": "hello" - }, - { - "range": {"start": {"line": 0, "character": 2}, "end": {"line": 0, "character": 3}}, - "newText": "there" - } - ], - filename_to_uri(file_paths[1]): - [ - { - "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 1}}, - "newText": "general" - }, - { - "range": {"start": {"line": 0, "character": 2}, "end": {"line": 0, "character": 3}}, - "newText": "kenobi" - } - ] - } - } - }, - {"applied": True} - ) - # Changes should have been applied - expected = ["hello there", "general kenobi"] - for i in range(0, 2): - view = self.view.window().find_open_file(file_paths[i]) - self.assertTrue(view) - view.set_scratch(True) - self.assertTrue(view.is_valid()) - self.assertFalse(view.is_loading()) - self.assertEqual(view.substr(sublime.Region(0, view.size())), expected[i]) - view.close() - - def test_m_client_registerCapability(self) -> Generator: - yield from verify( - self, - "client/registerCapability", - { - "registrations": - [ - {"method": "foo/bar", "id": "hello"}, - {"method": "bar/baz", "id": "world", "registerOptions": {"frobnicatable": True}}, - {"method": "workspace/didChangeWorkspaceFolders", "id": "asdf"}, - {"method": "textDocument/didOpen", "id": "1"}, - {"method": "textDocument/willSaveWaitUntil", "id": "2", - "registerOptions": {"documentSelector": [{"language": "plaintext"}]}}, - {"method": "textDocument/didChange", "id": "adsf", - "registerOptions": {"syncKind": TextDocumentSyncKind.Full, "documentSelector": [ - {"language": "plaintext"} - ]}}, - {"method": "textDocument/completion", "id": "myCompletionRegistrationId", - "registerOptions": {"triggerCharacters": ["!", "@", "#"], "documentSelector": [ - {"language": "plaintext"} - ]}} - ] - }, - None) - self.assertIn("barProvider", self.session.capabilities) - self.assertEqual(self.session.capabilities.get("barProvider.id"), "hello") - self.assertIn("bazProvider", self.session.capabilities) - self.assertEqual(self.session.capabilities.get("bazProvider"), {"id": "world", "frobnicatable": True}) - self.assertEqual(self.session.capabilities.get("workspace.workspaceFolders.changeNotifications"), "asdf") - self.assertEqual(self.session.capabilities.get("textDocumentSync.didOpen"), {"id": "1"}) - self.assertFalse(self.session.capabilities.get("textDocumentSync.didClose")) - - # willSaveWaitUntil is *only* registered on the buffer - self.assertFalse(self.session.capabilities.get("textDocumentSync.willSaveWaitUntil")) - sb = next(self.session.session_buffers_async()) - self.assertEqual(sb.capabilities.text_sync_kind(), TextDocumentSyncKind.Full) - self.assertEqual(sb.capabilities.get("textDocumentSync.willSaveWaitUntil"), {"id": "2"}) - self.assertEqual(self.session.capabilities.text_sync_kind(), TextDocumentSyncKind.Incremental) - - # Check that textDocument/completion was registered onto the SessionBuffer, and check that the trigger - # characters for each view were updated - self.assertEqual(sb.capabilities.get("completionProvider.id"), "myCompletionRegistrationId") - self.assertEqual(sb.capabilities.get("completionProvider.triggerCharacters"), ["!", "@", "#"]) - trigger = get_auto_complete_trigger(sb) - self.assertTrue(trigger) - self.assertEqual(trigger.get("characters"), "!@#") - - def test_m_client_unregisterCapability(self) -> Generator: - yield from verify( - self, - "client/registerCapability", - {"registrations": [{"method": "foo/bar", "id": "hello"}]}, - None) - self.assertIn("barProvider", self.session.capabilities) - yield from verify( - self, - "client/unregisterCapability", - {"unregisterations": [{"method": "foo/bar", "id": "hello"}]}, - None) - self.assertNotIn("barProvider", self.session.capabilities) - - -class ServerRequestsWithAutoCompleteSelector(TextDocumentTestCase): - - @classmethod - def get_stdio_test_config(cls) -> ClientConfig: - return ClientConfig.from_config( - super().get_stdio_test_config(), - { - "auto_complete_selector": "punctuation.section", - "disabled_capabilities": { - "completionProvider": { - "triggerCharacters": True - } - } - } - ) - - def test_m_client_registerCapability(self) -> Generator: - yield from verify( - self, - "client/registerCapability", - { - "registrations": - [ - # Note that the triggerCharacters are disabled in the configuration. - {"method": "textDocument/completion", "id": "anotherCompletionRegistrationId", - "registerOptions": {"triggerCharacters": ["!", "@", "#"], "documentSelector": [ - {"language": "plaintext"} - ]}} - ] - }, - None) - sb = next(self.session.session_buffers_async()) - # Check that textDocument/completion was registered onto the SessionBuffer - self.assertEqual(sb.capabilities.get("completionProvider.id"), "anotherCompletionRegistrationId") - # Trigger characters should not have been registered - self.assertFalse(sb.capabilities.get("completionProvider.triggerCharacters")) - trigger = get_auto_complete_trigger(sb) - self.assertTrue(trigger) - # No triggers should have been assigned - self.assertFalse(trigger.get("characters")) - # The selector should have been set - self.assertEqual(trigger.get("selector"), "punctuation.section") diff --git a/tests/test_single_document.py b/tests/test_single_document.py deleted file mode 100644 index 4d1bb10d3..000000000 --- a/tests/test_single_document.py +++ /dev/null @@ -1,400 +0,0 @@ -from copy import deepcopy -from LSP.plugin import apply_text_edits, Request -from LSP.plugin.core.protocol import UINT_MAX -from LSP.plugin.core.url import filename_to_uri -from LSP.plugin.core.views import entire_content -from LSP.plugin.hover import _test_contents -from setup import TextDocumentTestCase -from setup import TIMEOUT_TIME -from setup import YieldPromise -import os -import sublime - - -try: - from typing import Generator, Optional, Iterable, Tuple, List - assert Generator and Optional and Iterable and Tuple and List -except ImportError: - pass - -SELFDIR = os.path.dirname(__file__) -TEST_FILE_PATH = os.path.join(SELFDIR, 'testfile.txt') -GOTO_RESPONSE = [ - { - 'uri': filename_to_uri(TEST_FILE_PATH), - 'range': - { - 'start': - { - # Put the cursor at the capital letter "F". - 'character': 5, - 'line': 1 - }, - 'end': - { - 'character': 5, - 'line': 1 - } - } - } -] -GOTO_RESPONSE_LOCATION_LINK = [ - { - 'originSelectionRange': {'start': {'line': 0, 'character': 0}}, - 'targetUri': GOTO_RESPONSE[0]['uri'], - 'targetRange': GOTO_RESPONSE[0]['range'], - 'targetSelectionRange': GOTO_RESPONSE[0]['range'] - } -] -GOTO_CONTENT = r'''abcdefghijklmnopqrstuvwxyz -ABCDEFGHIJKLMNOPQRSTUVWXYZ -0123456789 -''' - - -class SingleDocumentTestCase(TextDocumentTestCase): - - def test_did_open(self) -> None: - # Just the existence of this method checks "initialize" -> "initialized" -> "textDocument/didOpen" - # -> "shutdown" -> client shut down - pass - - def test_out_of_bounds_column_for_text_document_edit(self) -> None: - self.insert_characters("a\nb\nc\n") - apply_text_edits(self.view, [ - { - 'newText': 'hello there', - 'range': { - 'start': { - 'line': 1, - 'character': 0, - }, - 'end': { - 'line': 1, - 'character': 10000, - } - } - }, - ]) - self.assertEqual(entire_content(self.view), "a\nhello there\nc\n") - - def test_did_close(self) -> 'Generator': - self.assertTrue(self.view) - self.assertTrue(self.view.is_valid()) - self.view.close() - yield from self.await_message("textDocument/didClose") - - def test_did_change(self) -> 'Generator': - assert self.view - self.maxDiff = None - self.insert_characters("A") - yield from self.await_message("textDocument/didChange") - # multiple changes are batched into one didChange notification - self.insert_characters("B\n") - self.insert_characters("🙂\n") - self.insert_characters("D") - promise = YieldPromise() - yield from self.await_message("textDocument/didChange", promise) - self.assertEqual(promise.result(), { - 'contentChanges': [ - {'rangeLength': 0, 'range': {'start': {'line': 0, 'character': 1}, 'end': {'line': 0, 'character': 1}}, 'text': 'B'}, # noqa - {'rangeLength': 0, 'range': {'start': {'line': 0, 'character': 2}, 'end': {'line': 0, 'character': 2}}, 'text': '\n'}, # noqa - {'rangeLength': 0, 'range': {'start': {'line': 1, 'character': 0}, 'end': {'line': 1, 'character': 0}}, 'text': '🙂'}, # noqa - # Note that this is character offset (2) is correct (UTF-16). - {'rangeLength': 0, 'range': {'start': {'line': 1, 'character': 2}, 'end': {'line': 1, 'character': 2}}, 'text': '\n'}, # noqa - {'rangeLength': 0, 'range': {'start': {'line': 2, 'character': 0}, 'end': {'line': 2, 'character': 0}}, 'text': 'D'}], # noqa - 'textDocument': { - 'version': self.view.change_count(), - 'uri': filename_to_uri(TEST_FILE_PATH) - } - }) - - def test_sends_save_with_purge(self) -> 'Generator': - assert self.view - self.view.settings().set("lsp_format_on_save", False) - self.insert_characters("A") - self.view.run_command("lsp_save", {'async': True}) - yield from self.await_message("textDocument/didChange") - yield from self.await_message("textDocument/didSave") - yield from self.await_clear_view_and_save() - - def test_formats_on_save(self) -> 'Generator': - assert self.view - self.view.settings().set("lsp_format_on_save", True) - self.insert_characters("A") - yield from self.await_message("textDocument/didChange") - self.set_response('textDocument/formatting', [{ - 'newText': "BBB", - 'range': { - 'start': {'line': 0, 'character': 0}, - 'end': {'line': 0, 'character': 1} - } - }]) - self.view.run_command("lsp_save", {'async': True}) - yield from self.await_message("textDocument/formatting") - yield from self.await_message("textDocument/didChange") - yield from self.await_message("textDocument/didSave") - text = self.view.substr(sublime.Region(0, self.view.size())) - self.assertEquals("BBB", text) - yield from self.await_clear_view_and_save() - - def test_hover_info(self) -> 'Generator': - assert self.view - self.set_response('textDocument/hover', {"contents": "greeting"}) - self.view.run_command('insert', {"characters": "Hello Wrld"}) - self.assertFalse(self.view.is_popup_visible()) - self.view.run_command('lsp_hover', {'point': 3}) - yield lambda: self.view.is_popup_visible() - last_content = _test_contents[-1] - self.assertTrue("greeting" in last_content) - - def test_remove_line_and_then_insert_at_that_line_at_end(self) -> 'Generator': - original = ( - 'a\n' - 'b\n' - 'c' - ) - file_changes = [ - ((2, 0), (3, 0), ''), # out-of-bounds end position, but this is fine - ((3, 0), (3, 0), 'c\n') # out-of-bounds start and end, this line doesn't exist - ] - expected = ( - 'a\n' - 'b\n' - 'c\n' - ) - # Old behavior: - # 1) first we end up with ('a\n', 'b\n', 'cc\n') - # 2) then we end up with ('a\n', 'b\n', '') - # New behavior: - # 1) line index 3 is "created" ('a\n', 'b\n', 'c\n', c\n')) - # 2) deletes line index 2. - yield from self.__run_formatting_test(original, expected, file_changes) - - def test_apply_formatting(self) -> 'Generator': - original = ( - '\n' - '\n' - '\n' - '\n' - ) - file_changes = [ - ((0, 28), (1, 0), ''), # delete first \n - ((1, 0), (1, 15), ''), # delete second line (but not the \n) - ((2, 10), (2, 10), '\n '), # insert after