diff --git a/server/src/qLangServer.ts b/server/src/qLangServer.ts index 9febc02e..7f52b0cf 100644 --- a/server/src/qLangServer.ts +++ b/server/src/qLangServer.ts @@ -178,7 +178,7 @@ export default class QLangServer { } } - private onCompletion(params: TextDocumentPositionParams): CompletionItem[] { + public onCompletion(params: TextDocumentPositionParams): CompletionItem[] { const keyword = this.getKeyword(params); if (!keyword) { return []; @@ -186,13 +186,13 @@ export default class QLangServer { return this.analyzer.getCompletionItems(keyword); } - private async onCompletionResolve( + public async onCompletionResolve( item: CompletionItem ): Promise { return item; } - private async onHover( + public async onHover( params: TextDocumentPositionParams ): Promise { const keyword = this.getEntireKeyword(params); @@ -203,14 +203,14 @@ export default class QLangServer { } // TODO: Document highlight - private onDocumentHighlight( + public onDocumentHighlight( params: TextDocumentPositionParams ): DocumentHighlight[] | null { const position = params.position; return []; } - private onDefinition(params: TextDocumentPositionParams): Location[] { + public onDefinition(params: TextDocumentPositionParams): Location[] { const document = this.documents.get(params.textDocument.uri); if (!document) { return []; @@ -246,7 +246,7 @@ export default class QLangServer { }); } - private onDocumentSymbol(params: DocumentSymbolParams): SymbolInformation[] { + public onDocumentSymbol(params: DocumentSymbolParams): SymbolInformation[] { const document = this.documents.get(params.textDocument.uri); if (document) { return this.analyzer.getSymbols(document); @@ -313,7 +313,7 @@ export default class QLangServer { .map((el) => ({ to: el, fromRanges: [el.range] })); } - private onReferences(params: ReferenceParams): Location[] | null { + public onReferences(params: ReferenceParams): Location[] | null { const document = this.documents.get(params.textDocument.uri); if (!document) { return []; @@ -330,7 +330,7 @@ export default class QLangServer { return this.analyzer.getReferences(word, document); } - private onRenameRequest({ + public onRenameRequest({ textDocument, position, newName, @@ -365,7 +365,7 @@ export default class QLangServer { return null; } - private onSignatureHelp({ + public onSignatureHelp({ textDocument, position, }: SignatureHelpParams): SignatureHelp | undefined { @@ -385,7 +385,7 @@ export default class QLangServer { return result; } - private onSemanticsTokens({ + public onSemanticsTokens({ textDocument, }: SemanticTokensParams): SemanticTokens { // Get the semantic tokens for the given document. @@ -394,9 +394,7 @@ export default class QLangServer { return tokens ?? { data: [] }; } - private async validateTextDocument( - textDocument: TextDocument - ): Promise { + public async validateTextDocument(textDocument: TextDocument): Promise { const settings = await this.getDocumentSettings(textDocument.uri); const text = textDocument.getText(); const pattern = /\b[A-Z]{2,}\b/g; diff --git a/test/suite/index.ts b/test/suite/index.ts index 39040291..a213e84c 100644 --- a/test/suite/index.ts +++ b/test/suite/index.ts @@ -11,8 +11,8 @@ * specific language governing permissions and limitations under the License. */ -import * as glob from "glob"; -import * as Mocha from "mocha"; +import glob from "glob"; +import Mocha from "mocha"; import * as path from "path"; export function run(): Promise { diff --git a/test/suite/qLangServer.test.ts b/test/suite/qLangServer.test.ts new file mode 100644 index 00000000..6fe3825f --- /dev/null +++ b/test/suite/qLangServer.test.ts @@ -0,0 +1,207 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import * as assert from "assert"; +import * as sinon from "sinon"; +import { + EndOfLine, + Position, + Range, + TextDocument, + TextLine, + Uri, +} from "vscode"; +import QLangServer from "../../server/src/qLangServer"; + +describe("qLangServer tests", () => { + let server: QLangServer; + + beforeEach(async () => { + const connectionMock = { + listen() {}, + onDidOpenTextDocument() {}, + onDidChangeTextDocument() {}, + onDidCloseTextDocument() {}, + onWillSaveTextDocument() {}, + onWillSaveTextDocumentWaitUntil() {}, + onDidSaveTextDocument() {}, + onNotification() {}, + onCompletion() {}, + onCompletionResolve() {}, + onHover() {}, + onDocumentHighlight() {}, + onDefinition() {}, + onDocumentSymbol() {}, + onReferences() {}, + onRenameRequest() {}, + languages: { + semanticTokens: { + on() {}, + }, + callHierarchy: { + onPrepare() {}, + onIncomingCalls() {}, + onOutgoingCalls() {}, + }, + }, + console: { + error() {}, + warn() {}, + info() {}, + }, + }; + + const paramsMock = { + processId: 0, + rootUri: "", + capabilities: {}, + workspaceFolders: [], + }; + + // @ts-ignore + server = await QLangServer.initialize(connectionMock, paramsMock); + }); + + it("capabilities should return a value", () => { + assert.ok(server.capabilities()); + }); + + it("debugWithLogs should log a warning message", () => { + const warnStub = sinon.stub(server.connection.console, "warn"); + let ok = false; + warnStub.value(() => (ok = true)); + server.debugWithLogs("request", "msg"); + assert.ok(ok); + }); + + it("writeConsoleMsg should log an error message", () => { + const errorStub = sinon.stub(server.connection.console, "error"); + let ok = false; + errorStub.value(() => (ok = true)); + server.writeConsoleMsg("msg", "error"); + assert.ok(ok); + }); + + it("writeConsoleMsg should log an info message", () => { + const infoStub = sinon.stub(server.connection.console, "info"); + let ok = false; + infoStub.value(() => (ok = true)); + server.writeConsoleMsg("msg", "info"); + assert.ok(ok); + }); + + it("onCompletionResolve should return a value", async () => { + const item = { label: "test" }; + const result = await server.onCompletionResolve(item); + assert.strictEqual(result, item); + }); + + it("onDocumentHighlight should return empty array", async () => { + const result = server.onDocumentHighlight({ + position: null, + textDocument: null, + }); + assert.strictEqual(result.length, 0); + }); + + // TODO + it("onCompletion should return empty array for no keyword", () => {}); + + // TODO + it("onDefinition should return empty array for no document", async () => { + const getKeywordStub = sinon.stub(server.documents, "get"); + getKeywordStub.value(() => undefined); + const result = server.onDefinition({ + textDocument: { uri: "" }, + position: undefined, + }); + assert.strictEqual(result.length, 0); + }); + + // TODO + it("onHover should return null for no keyword", async () => {}); + + // TODO + it("onDocumentSymbol should return empty array for no document", async () => {}); + + // TODO + it("onPrepareCallHierarchy", async () => {}); + + // TODO + it("onIncomingCallsCallHierarchy", async () => {}); + + // TODO + it("onOutgoingCallsCallHierarchy", async () => {}); + + // TODO + it("onReferences", async () => {}); + + // TODO + it("onRenameRequest", async () => {}); + + // TODO + it("onSignatureHelp", async () => {}); + + // TODO + it("onSemanticsTokens", async () => {}); + + // TODO + it("validateTextDocument", async () => {}); +}); + +class TextDocumentMock implements TextDocument { + fileName!: string; + isUntitled!: boolean; + languageId!: string; + version!: number; + isDirty!: boolean; + isClosed!: boolean; + eol!: EndOfLine; + lineCount!: number; + text: string; + + uri = Uri.parse("/test/test.q"); + + constructor(text: string) { + this.text = text; + } + + getText(range?: Range): string { + return this.text; + } + + save(): Thenable { + throw new Error("Method not implemented."); + } + lineAt(position: Position | number | any): TextLine { + throw new Error("Method not implemented."); + } + offsetAt(position: Position): number { + throw new Error("Method not implemented."); + } + positionAt(offset: number): Position { + throw new Error("Method not implemented."); + } + getWordRangeAtPosition( + position: Position, + regex?: RegExp + ): Range | undefined { + throw new Error("Method not implemented."); + } + validateRange(range: Range): Range { + throw new Error("Method not implemented."); + } + validatePosition(position: Position): Position { + throw new Error("Method not implemented."); + } +} diff --git a/test/tsconfig.json b/test/tsconfig.json index 46e1ea89..cac3c33f 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -4,7 +4,8 @@ "target": "ES2019", "outDir": "../out", "lib": ["ES2019"], - "sourceMap": true + "sourceMap": true, + "esModuleInterop": true }, "exclude": ["node_modules", ".vscode-test"] }