lsp-ws-connection
Version:
Utility for adapting editors to language server protocol
424 lines • 16.4 kB
JavaScript
import { expect } from 'chai';
import * as sinon from 'sinon';
import { LspWsConnection } from '..';
var serverUri = 'ws://localhost:8080';
var mockInfo = {
uri: 'file://' + __dirname,
version: 0,
languageId: 'plaintext',
text: ''
};
// There is a library that can be used to mock WebSockets, but the API surface tested here is small
// enough that it is not necessary to use the library. This mock is a simple EventEmitter
var MockSocket = /** @class */ (function () {
function MockSocket(url, protocols) {
var _this = this;
this.listeners = {};
/**
* Mocks sending data to the server. The fake implementation needs to respond with some data
*/
this.send = sinon.stub();
this.addEventListener = sinon
.mock()
.callsFake(function (type, listener) {
var listeners = _this.listeners[type];
if (!listeners) {
_this.listeners[type] = [];
}
listeners.push(listener);
});
this.removeEventListener = sinon
.mock()
.callsFake(function (type, listener) {
var index = _this.listeners[type].indexOf(function (l) { return l === listener; });
if (index > -1) {
_this.listeners[type].splice(index, 1);
}
});
this.close = sinon.stub();
/**
* Sends a synthetic event to the client code, for example to imitate a server response
*/
this.dispatchEvent = function (event) {
var listeners = _this.listeners[event.type];
if (!listeners) {
return false;
}
listeners.forEach(function (listener) { return listener.call(null, event); });
return true;
};
// nothing here yet
}
Object.defineProperty(MockSocket.prototype, "onclose", {
set: function (handler) {
if (handler) {
this.listeners.close = [handler];
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(MockSocket.prototype, "onerror", {
set: function (handler) {
if (handler) {
this.listeners.error = [handler];
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(MockSocket.prototype, "onmessage", {
set: function (handler) {
if (handler) {
this.listeners.message = [handler];
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(MockSocket.prototype, "onopen", {
set: function (handler) {
if (handler) {
this.listeners.open = [handler];
}
},
enumerable: false,
configurable: true
});
return MockSocket;
}());
describe('LspWsConnection', function () {
var connection;
var mockSocket;
beforeEach(function () {
connection = new LspWsConnection({
languageId: 'plaintext',
rootUri: 'file://' + __dirname,
serverUri: serverUri
});
mockSocket = new MockSocket('ws://localhost:8080');
});
afterEach(function () {
sinon.restore();
});
it('initializes the connection in the right order', function (done) {
// 1. It sends initialize and expects a response with capabilities
mockSocket.send.onFirstCall().callsFake(function (str) {
var message = JSON.parse(str);
expect(message.method).equal('initialize');
// This is an actual response from the html language server
var data = JSON.stringify({
jsonrpc: '2.0',
id: message.id,
result: {
capabilities: {
textDocumentSync: 1,
hoverProvider: true,
documentHighlightProvider: true,
documentRangeFormattingProvider: false,
documentLinkProvider: {
resolveProvider: false
},
documentSymbolProvider: true,
definitionProvider: true,
signatureHelpProvider: {
triggerCharacters: ['(']
},
typeDefinitionProvider: true,
referencesProvider: true,
colorProvider: {},
foldingRangeProvider: true,
workspaceSymbolProvider: true,
completionProvider: {
resolveProvider: true,
triggerCharacters: ['.']
},
codeActionProvider: true,
renameProvider: true,
executeCommandProvider: {
commands: []
}
}
}
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
// 2. After receiving capabilities from the server, it sends more configuration options
mockSocket.send.onSecondCall().callsFake(function (str) {
var message = JSON.parse(str);
expect(message.method).equal('initialized');
setTimeout(function () {
var mock = mockSocket.send;
expect(mock.callCount).equal(3);
// 3 is sent after initialization
expect(JSON.parse(mock.getCall(2).args[0]).method).equal('workspace/didChangeConfiguration');
done();
}, 0);
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
// Send the messages
expect(mockSocket.send.callCount).equal(1);
expect(JSON.parse(mockSocket.send.firstCall.args[0]).method).equal('initialize');
});
describe('register/unregister capability', function () {
beforeEach(function () {
mockSocket.send.onFirstCall().callsFake(function (str) {
var data = JSON.stringify({
jsonrpc: '2.0',
id: 0,
result: {
capabilities: {
definitionProvider: true
}
}
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
});
it('registers a new server capability', function (done) {
mockSocket.send.onSecondCall().callsFake(function () {
expect(connection.isDefinitionSupported()).equal(true);
expect(connection.isImplementationSupported()).equal(false);
var data = JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'client/registerCapability',
params: {
registrations: [
{
id: 'id',
method: 'textDocument/implementation'
}
]
}
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
setTimeout(function () {
expect(connection.isDefinitionSupported()).equal(true);
expect(connection.isImplementationSupported()).equal(true);
done();
}, 0);
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
});
it('unregisters a server capability', function (done) {
mockSocket.send.onSecondCall().callsFake(function () {
expect(connection.isDefinitionSupported()).equal(true);
var data = JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'client/unregisterCapability',
params: {
unregisterations: [
{
id: 'id',
method: 'textDocument/definition'
}
]
}
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
setTimeout(function () {
expect(connection.isDefinitionSupported()).equal(false);
done();
});
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
});
});
describe('hover', function () {
var hoverResponse;
beforeEach(function () {
// Fake response just includes the hover provider
mockSocket.send.onFirstCall().callsFake(function (str) {
var data = JSON.stringify({
jsonrpc: '2.0',
id: 0,
result: {
capabilities: {
hoverProvider: true
}
}
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
// 2. After receiving capabilities from the server, we will send a hover
mockSocket.send.onSecondCall().callsFake(function (str) {
void connection.getHoverTooltip({
line: 1,
ch: 0
}, mockInfo);
});
});
it('emits a null hover event', function (done) {
// 3. Fake a server response for the hover
mockSocket.send.onThirdCall().callsFake(function (str) {
var message = JSON.parse(str);
var data = JSON.stringify({
jsonrpc: '2.0',
id: message.id,
result: null
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
connection.on('hover', function (response) {
expect(response).to.be.a('null');
done();
});
});
it('emits a complete hover event', function (done) {
hoverResponse = {
contents: 'Details of hover',
range: {
start: {
line: 1,
character: 0
},
end: {
line: 2,
character: 0
}
}
};
// 3. Fake a server response for the hover
mockSocket.send.onThirdCall().callsFake(function (str) {
var message = JSON.parse(str);
var data = JSON.stringify({
jsonrpc: '2.0',
id: message.id,
result: hoverResponse
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
connection.on('hover', function (response) {
expect(response).to.deep.equal(hoverResponse);
done();
});
});
});
describe('completion', function () {
var completionResponse;
beforeEach(function () {
// Fake response just includes the hover provider
mockSocket.send.onFirstCall().callsFake(function (str) {
var data = JSON.stringify({
jsonrpc: '2.0',
id: 0,
result: {
capabilities: {
completionProvider: {
triggerCharacters: ['.'],
resolveProvider: false
}
}
}
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
// 2. After receiving capabilities from the server, we will send a completion
mockSocket.send.onSecondCall().callsFake(function (str) {
void connection.getCompletion({
line: 1,
ch: 8
}, {
start: {
line: 1,
ch: 8
},
end: {
line: 1,
ch: 9
},
text: '.'
}, mockInfo);
});
});
it('emits a null completion event', function (done) {
// 3. Fake a server response for the hover
mockSocket.send.onThirdCall().callsFake(function (str) {
var message = JSON.parse(str);
var data = JSON.stringify({
jsonrpc: '2.0',
id: message.id,
result: null
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
connection.on('completion', function (response) {
expect(response).to.be.a('null');
done();
});
});
it('emits a completion event using CompletionList', function (done) {
completionResponse = {
isIncomplete: false,
items: [
{
label: 'log'
},
{
label: 'info'
}
]
};
// 3. Fake a server response for the hover
mockSocket.send.onThirdCall().callsFake(function (str) {
var message = JSON.parse(str);
var data = JSON.stringify({
jsonrpc: '2.0',
id: message.id,
result: completionResponse
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
connection.on('completion', function (response) {
expect(response).to.deep.equal(completionResponse.items);
done();
});
});
it('emits a completion event of CompletionItem[]', function (done) {
var completion = [
{
label: 'log'
},
{
label: 'info'
}
];
// 3. Fake a server response for the hover
mockSocket.send.onThirdCall().callsFake(function (str) {
var message = JSON.parse(str);
var data = JSON.stringify({
jsonrpc: '2.0',
id: message.id,
result: completion
});
mockSocket.dispatchEvent(new MessageEvent('message', { data: data }));
});
connection.connect(mockSocket);
mockSocket.dispatchEvent(new Event('open'));
connection.on('completion', function (response) {
expect(response).to.deep.equal(completion);
done();
});
});
});
it('closes the socket connection and stops sending messages', function () {
connection.connect(mockSocket);
connection.close();
connection.sendChange(mockInfo);
expect(mockSocket.send.callCount).equal(0);
});
});
//# sourceMappingURL=connection.test.js.map