Skip to content

Commit e3169ac

Browse files
committed
Tests for protocol and completion, some logging cleanup
1 parent 745a655 commit e3169ac

8 files changed

+203
-9
lines changed

plugin/completion.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ def __init__(self, view):
123123
@classmethod
124124
def is_applicable(cls, settings):
125125
syntax = settings.get('syntax')
126-
return syntax and is_supported_syntax(syntax)
126+
if syntax is not None:
127+
return is_supported_syntax(syntax)
128+
else:
129+
return False
127130

128131
def initialize(self):
129132
self.initialized = True

plugin/core/clients.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ def add_window_client(window: sublime.Window, config_name: str, client: 'Client'
3535
debug("{} client registered for window {}".format(config_name, window.id()))
3636

3737

38+
def remove_window_client(window: sublime.Window, config_name: str):
39+
del clients_by_window[window.id()][config_name]
40+
41+
3842
def client_for_view(view: sublime.View) -> 'Optional[Client]':
3943
window = view.window()
4044
if not window:
@@ -90,7 +94,7 @@ def unload_old_clients(window: sublime.Window):
9094
clients_to_unload = {}
9195
for config_name, client in clients_by_config.items():
9296
if client and client.get_project_path() != project_path:
93-
debug('unload', config_name, 'project path changed from ', client.get_project_path())
97+
debug('unload', config_name, 'project path changed from', client.get_project_path(), 'to', project_path)
9498
clients_to_unload[config_name] = client
9599

96100
for config_name, client in clients_to_unload.items():

plugin/core/logging.py

-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
def debug(*args):
66
"""Print args to the console if the "debug" setting is True."""
7-
PLUGIN_NAME.capitalize()
8-
97
if settings.log_debug:
108
printf(*args)
119

plugin/hover.py

-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ def request_symbol_hover(self, point):
4646
lambda response: self.handle_response(response, point))
4747

4848
def handle_response(self, response, point):
49-
debug(response)
5049
if self.view.is_popup_visible():
5150
return
5251
contents = "No description available."

tests/test_completion.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import unittest
2+
from unittest.mock import MagicMock
3+
import sublime
4+
from LSP.plugin.completion import CompletionHandler, CompletionState
5+
from LSP.plugin.core.settings import client_configs, ClientConfig
6+
from LSP.plugin.core.clients import add_window_client, remove_window_client
7+
from os.path import dirname
8+
9+
10+
def create_completion_item(item: str):
11+
return {
12+
"label": item
13+
}
14+
15+
16+
def create_completion_response(items):
17+
return {
18+
"items": list(map(create_completion_item, items))
19+
}
20+
21+
22+
class FakeClient(object):
23+
24+
def __init__(self):
25+
self.response = None
26+
pass
27+
28+
def get_capability(self, capability_name: str):
29+
return {
30+
'triggerCharacters': ['.'],
31+
'resolveProvider': False
32+
}
33+
34+
35+
SUPPORTED_SCOPE = "text.plain"
36+
SUPPORTED_SYNTAX = "Lang.sublime-syntax"
37+
test_client_config = ClientConfig('langls', [], [SUPPORTED_SCOPE], [SUPPORTED_SYNTAX], 'lang')
38+
test_file_path = dirname(__file__) + "/testfile.txt"
39+
40+
41+
class InitializationTests(unittest.TestCase):
42+
43+
def setUp(self):
44+
self.view = sublime.active_window().new_file()
45+
self.old_configs = client_configs.all
46+
client_configs.all = [test_client_config]
47+
48+
def test_is_not_applicable(self):
49+
self.assertFalse(CompletionHandler.is_applicable(dict()))
50+
51+
def test_is_applicable(self):
52+
self.assertEquals(len(client_configs.all), 1)
53+
self.assertTrue(CompletionHandler.is_applicable(dict(syntax=SUPPORTED_SYNTAX)))
54+
55+
def test_not_enabled(self):
56+
handler = CompletionHandler(self.view)
57+
self.assertFalse(handler.initialized)
58+
self.assertFalse(handler.enabled)
59+
result = handler.on_query_completions("", [0])
60+
self.assertTrue(handler.initialized)
61+
self.assertFalse(handler.enabled)
62+
self.assertIsNone(result)
63+
64+
def tearDown(self):
65+
client_configs.all = self.old_configs
66+
if self.view:
67+
self.view.set_scratch(True)
68+
self.view.window().focus_view(self.view)
69+
self.view.window().run_command("close_file")
70+
71+
72+
class QueryCompletionsTests(unittest.TestCase):
73+
74+
def setUp(self):
75+
self.view = sublime.active_window().open_file(test_file_path)
76+
self.old_configs = client_configs.all
77+
client_configs.all = [test_client_config]
78+
self.client = FakeClient()
79+
add_window_client(sublime.active_window(), test_client_config.name, self.client)
80+
81+
def test_enabled(self):
82+
self.view.run_command('insert', {"characters": '.'})
83+
84+
self.client.send_request = MagicMock()
85+
86+
handler = CompletionHandler(self.view)
87+
self.assertEquals(handler.state, CompletionState.IDLE)
88+
89+
items, mask = handler.on_query_completions("", [1])
90+
91+
self.assertEquals(len(items), 0)
92+
self.assertEquals(mask, sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)
93+
94+
self.assertTrue(handler.initialized)
95+
self.assertTrue(handler.enabled)
96+
self.assertEquals(handler.state, CompletionState.REQUESTING)
97+
98+
self.client.send_request.assert_called_once()
99+
# time.sleep(1000)
100+
# self.assertEquals(len(handler.completions), 2)
101+
# self.assertEquals(handler.state, CompletionState.APPLYING)
102+
103+
# running auto_complete command does not work
104+
# sublime does not know about the instance we registered here.
105+
# we do it directly here
106+
# items, mask = handler.on_query_completions("", [1])
107+
108+
# self.assertEquals(len(items), 2)
109+
# self.assertEquals(mask, sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)
110+
111+
def tearDown(self):
112+
client_configs.all = self.old_configs
113+
remove_window_client(sublime.active_window(), test_client_config.name)
114+
if self.view:
115+
self.view.window().run_command("close_file")

tests/test_protocol.py

+76-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,84 @@
1-
from LSP.plugin.core.protocol import Point
1+
from LSP.plugin.core.protocol import (
2+
Point, Range, Diagnostic, DiagnosticSeverity, Request, Notification
3+
)
24
import unittest
35

4-
LSP_POSITION = {'line': 10, 'character': 4}
6+
LSP_START_POSITION = {'line': 10, 'character': 4}
7+
LSP_END_POSITION = {'line': 11, 'character': 3}
8+
LSP_RANGE = {'start': LSP_START_POSITION, 'end': LSP_END_POSITION}
9+
LSP_MINIMAL_DIAGNOSTIC = {
10+
'message': 'message',
11+
'range': LSP_RANGE
12+
}
13+
14+
LSP_FULL_DIAGNOSTIC = {
15+
'message': 'message',
16+
'range': LSP_RANGE,
17+
'severity': 2, # warning
18+
'source': 'pyls'
19+
}
520

621

722
class PointTests(unittest.TestCase):
823

9-
def test_conversion(self):
10-
point = Point.from_lsp(LSP_POSITION)
24+
def test_lsp_conversion(self):
25+
point = Point.from_lsp(LSP_START_POSITION)
1126
self.assertEqual(point.row, 10)
1227
self.assertEqual(point.col, 4)
28+
lsp_point = point.to_lsp()
29+
self.assertEquals(lsp_point['line'], 10)
30+
self.assertEquals(lsp_point['character'], 4)
31+
32+
33+
class RangeTests(unittest.TestCase):
34+
35+
def test_lsp_conversion(self):
36+
range = Range.from_lsp(LSP_RANGE)
37+
self.assertEquals(range.start.row, 10)
38+
self.assertEquals(range.start.col, 4)
39+
self.assertEquals(range.end.row, 11)
40+
self.assertEquals(range.end.col, 3)
41+
lsp_range = range.to_lsp()
42+
self.assertEquals(lsp_range['start']['line'], 10)
43+
self.assertEquals(lsp_range['start']['character'], 4)
44+
self.assertEquals(lsp_range['end']['line'], 11)
45+
self.assertEquals(lsp_range['end']['character'], 3)
46+
47+
48+
class DiagnosticTests(unittest.TestCase):
49+
50+
def test_lsp_conversion(self):
51+
diag = Diagnostic.from_lsp(LSP_MINIMAL_DIAGNOSTIC)
52+
self.assertEquals(diag.message, 'message')
53+
self.assertEquals(diag.severity, DiagnosticSeverity.Error)
54+
self.assertEquals(diag.source, None)
55+
self.assertEquals(diag.to_lsp(), LSP_MINIMAL_DIAGNOSTIC)
56+
57+
def test_full_lsp_conversion(self):
58+
diag = Diagnostic.from_lsp(LSP_FULL_DIAGNOSTIC)
59+
self.assertEquals(diag.message, 'message')
60+
self.assertEquals(diag.severity, DiagnosticSeverity.Warning)
61+
self.assertEquals(diag.source, 'pyls')
62+
self.assertEquals(diag.to_lsp(), LSP_FULL_DIAGNOSTIC)
63+
64+
65+
class RequestTests(unittest.TestCase):
66+
67+
def test_initialize(self):
68+
req = Request.initialize({"param": 1})
69+
payload = req.to_payload(1)
70+
self.assertEquals(payload["jsonrpc"], "2.0")
71+
self.assertEquals(payload["id"], 1)
72+
self.assertEquals(payload["method"], "initialize")
73+
self.assertEquals(payload["params"], {"param": 1})
74+
75+
76+
class NotificationTests(unittest.TestCase):
77+
78+
def test_initialized(self):
79+
notification = Notification.initialized()
80+
payload = notification.to_payload()
81+
self.assertEquals(payload["jsonrpc"], "2.0")
82+
self.assertNotIn("id", payload)
83+
self.assertEquals(payload["method"], "initialized")
84+
self.assertEquals(payload["params"], dict())

tests/testfile.txt

Whitespace-only changes.

unittesting.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"reload_package_on_testing": false
3+
}

0 commit comments

Comments
 (0)