From 5f8180ebbf245ae1a6fa9d28f8d8fbc6b8bc8c7d Mon Sep 17 00:00:00 2001 From: Chris LaFreniere <40371649+chlafreniere@users.noreply.github.com> Date: Tue, 7 Jul 2020 12:48:22 -0700 Subject: [PATCH] Add Notebook Completion Item Provider Unit Tests (#11222) * completion item provider tests * Add nb completionItemProvider tests --- extensions/notebook/src/test/common.ts | 54 +++++ .../src/test/common/notebookUtils.test.ts | 6 +- .../test/model/completionItemProvider.test.ts | 201 ++++++++++++++++++ 3 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 extensions/notebook/src/test/model/completionItemProvider.test.ts diff --git a/extensions/notebook/src/test/common.ts b/extensions/notebook/src/test/common.ts index cc15e2958f..a9ff4c0e45 100644 --- a/extensions/notebook/src/test/common.ts +++ b/extensions/notebook/src/test/common.ts @@ -255,6 +255,60 @@ export class FutureStub implements Kernel.IFuture { throw new Error('Method not implemented.'); } } + +export class TestKernel implements azdata.nb.IKernel { + constructor( + private _isReady = false, + private _supportsIntellisense = false, + private _matches = ['firstMatch', 'secondMatch', 'thirdMatch'], + private _status_override: 'ok' | 'error' = 'ok' + ) { } + + get id(): string { + throw new Error('Method not implemented.'); + } + get name(): string { + throw new Error('Method not implemented.'); + } + get supportsIntellisense(): boolean { + return this._supportsIntellisense; + } + get isReady(): boolean { + return this._isReady; + } + get ready(): Thenable { + throw new Error('Method not implemented.'); + } + get info(): azdata.nb.IInfoReply { + throw new Error('Method not implemented.'); + } + getSpec(): Thenable { + throw new Error('Method not implemented.'); + } + requestExecute(content: azdata.nb.IExecuteRequest, disposeOnDone?: boolean): azdata.nb.IFuture { + throw new Error('Method not implemented.'); + } + requestComplete(content: azdata.nb.ICompleteRequest): Thenable { + let msg: azdata.nb.ICompleteReplyMsg = { + channel: 'shell', + content: { + cursor_end: 0, + cursor_start: 0, + matches: this._matches, + metadata: undefined, + status: this._status_override + }, + header: undefined, + metadata: undefined, + parent_header: undefined, + type: undefined + }; + return Promise.resolve(msg); + } + interrupt(): Thenable { + throw new Error('Method not implemented.'); + } +} //#endregion //#region test modelView components diff --git a/extensions/notebook/src/test/common/notebookUtils.test.ts b/extensions/notebook/src/test/common/notebookUtils.test.ts index 04ccc7546b..fe4641abb4 100644 --- a/extensions/notebook/src/test/common/notebookUtils.test.ts +++ b/extensions/notebook/src/test/common/notebookUtils.test.ts @@ -20,6 +20,10 @@ describe('notebookUtils Tests', function (): void { let notebookUtils: NotebookUtils; let apiWrapperMock: TypeMoq.IMock; + this.beforeAll(async function(): Promise { + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + beforeEach(function (): void { apiWrapperMock = TypeMoq.Mock.ofInstance(new ApiWrapper()); notebookUtils = new NotebookUtils(apiWrapperMock.object); @@ -185,7 +189,7 @@ describe('notebookUtils Tests', function (): void { nodeSubType: undefined, nodeType: undefined } - } + }; await notebookUtils.analyzeNotebook(oeContext); should(notebookEditor.document.cells.length).equal(1, 'One cell should exist'); should(notebookEditor.document.cells[0].contents.cell_type).equal(CellTypes.Code, 'Cell was created with incorrect type'); diff --git a/extensions/notebook/src/test/model/completionItemProvider.test.ts b/extensions/notebook/src/test/model/completionItemProvider.test.ts new file mode 100644 index 0000000000..69cad03d73 --- /dev/null +++ b/extensions/notebook/src/test/model/completionItemProvider.test.ts @@ -0,0 +1,201 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; +import * as should from 'should'; +import * as vscode from 'vscode'; +import * as TypeMoq from 'typemoq'; + +import { NotebookCompletionItemProvider } from '../../intellisense/completionItemProvider'; +import { JupyterNotebookProvider } from '../../jupyter/jupyterNotebookProvider'; +import { NotebookUtils } from '../../common/notebookUtils'; +import { ApiWrapper } from '../../common/apiWrapper'; +import { JupyterNotebookManager } from '../../jupyter/jupyterNotebookManager'; +import { JupyterSessionManager, JupyterSession } from '../../jupyter/jupyterSessionManager'; +import { LocalJupyterServerManager } from '../../jupyter/jupyterServerManager'; +import { TestKernel } from '../common'; + +describe('Completion Item Provider', function () { + let completionItemProvider: NotebookCompletionItemProvider; + let notebookProviderMock: TypeMoq.IMock; + let notebookUtils: NotebookUtils; + let notebookManager: JupyterNotebookManager; + let mockApiWrapper: TypeMoq.IMock; + let mockSessionManager: TypeMoq.IMock; + let mockServerManager: TypeMoq.IMock; + let mockJupyterSession: TypeMoq.IMock; + let kernel: TestKernel; + let testEvent: vscode.EventEmitter; + let token: vscode.CancellationToken; + + this.beforeAll(async () => { + mockApiWrapper = TypeMoq.Mock.ofType(); + notebookUtils = new NotebookUtils(new ApiWrapper()); + mockServerManager = TypeMoq.Mock.ofType(); + testEvent = new vscode.EventEmitter(); + token = { + isCancellationRequested: false, + onCancellationRequested: testEvent.event + }; + + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + this.beforeEach(() => { + mockSessionManager = TypeMoq.Mock.ofType(); + mockJupyterSession = TypeMoq.Mock.ofType(); + kernel = new TestKernel(true, true); + notebookManager = new JupyterNotebookManager(mockServerManager.object, mockSessionManager.object, mockApiWrapper.object); + notebookProviderMock = TypeMoq.Mock.ofType(); + notebookProviderMock.setup(n => n.getNotebookManager(TypeMoq.It.isAny())).returns(() => Promise.resolve(notebookManager)); + completionItemProvider = new NotebookCompletionItemProvider(notebookProviderMock.object); + }); + + it('should not return items when undefined passed in for every parameter', async () => { + let completionItems = await completionItemProvider.provideCompletionItems(undefined, undefined, undefined, undefined); + should(completionItems).deepEqual([]); + }); + + it('should not return items when no notebook provider passed in', async () => { + completionItemProvider = new NotebookCompletionItemProvider(undefined); + let completionItems = await completionItemProvider.provideCompletionItems(undefined, undefined, undefined, undefined); + should(completionItems).deepEqual([]); + }); + + it('should not provide items when session does not exist in notebook provider', async () => { + let notebook = await notebookUtils.newNotebook(); + await notebookUtils.addCell('code'); + let document = vscode.workspace.textDocuments.find(d => d.uri.path === notebook.document.cells[0].uri.path); + should(document).not.equal(undefined, 'Could not find text document that matched cell uri path'); + + let completionItems = await completionItemProvider.provideCompletionItems(document, undefined, undefined, undefined); + should(completionItems).deepEqual([]); + }); + + it('should not provide items when session list throws exception', async () => { + mockSessionManager.setup(m => m.listRunning()).throws(new Error('Test Error')); + + let notebook = await notebookUtils.newNotebook(); + await notebookUtils.addCell('code'); + let document = vscode.workspace.textDocuments.find(d => d.uri.path === notebook.document.cells[0].uri.path); + + let completionItems = await completionItemProvider.provideCompletionItems(document, undefined, undefined, undefined); + should(completionItems).deepEqual([]); + }); + + it('should not provide items when kernel does not exist in notebook provider', async () => { + mockSessionManager.setup(m => m.listRunning()).returns(() => [mockJupyterSession.object]); + + let notebook = await notebookUtils.newNotebook(); + await notebookUtils.addCell('code'); + let document = vscode.workspace.textDocuments.find(d => d.uri.path === notebook.document.cells[0].uri.path); + + mockJupyterSession.setup(s => s.path).returns(() => document.uri.path); + + let completionItems = await completionItemProvider.provideCompletionItems(document, undefined, undefined, undefined); + should(completionItems).deepEqual([]); + }); + + it('should not provide items when kernel exists but is not ready in notebook provider', async () => { + kernel = new TestKernel(); + mockJupyterSession.setup(s => s.kernel).returns(() => kernel); + mockSessionManager.setup(m => m.listRunning()).returns(() => [mockJupyterSession.object]); + mockJupyterSession.setup(s => s.path).returns(() => notebook.document.uri.path); + + let notebook = await notebookUtils.newNotebook(); + await notebookUtils.addCell('code'); + let document = vscode.workspace.textDocuments.find(d => d.uri.path === notebook.document.cells[0].uri.path); + + mockJupyterSession.setup(s => s.path).returns(() => document.uri.path); + + let completionItems = await completionItemProvider.provideCompletionItems(document, undefined, undefined, undefined); + should(completionItems).deepEqual([]); + }); + + it('should provide items source has unicode characters', async () => { + let document = await setupSessionAndNotebookCells('🌉sample code\nline 2\n'); + + let completionItems = await completionItemProvider.provideCompletionItems(document, new vscode.Position(2, 2), token, undefined); + should(Array.isArray(completionItems)); + if (Array.isArray(completionItems)) { + should(completionItems.length).equal(3); + } + }); + + it('should provide items source has no unicode characters', async () => { + let document = await setupSessionAndNotebookCells('sample code\nline 2\n'); + + let completionItems = await completionItemProvider.provideCompletionItems(document, new vscode.Position(1, 1), token, undefined); + should(Array.isArray(completionItems)); + if (Array.isArray(completionItems)) { + should(completionItems.length).equal(3); + } + }); + + it('should not provide items when no content exists in the first cell', async () => { + let document = await setupSessionAndNotebookCells(); + + let completionItems = await completionItemProvider.provideCompletionItems(document, new vscode.Position(1, 1), token, undefined); + should(completionItems).deepEqual([]); + }); + + it('should not provide items when kernel returns error status', async () => { + kernel = new TestKernel(true, true, [], 'error'); + mockJupyterSession.setup(s => s.path).returns(() => notebook.document.uri.path); + mockJupyterSession.setup(s => s.kernel).returns(() => kernel); + mockSessionManager.setup(m => m.listRunning()).returns(() => [mockJupyterSession.object]); + + let notebook = await notebookUtils.newNotebook(); + await notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => { + editBuilder.insertCell({ + cell_type: 'code', + source: 'sample text' + }); + }); + let document = vscode.workspace.textDocuments.find(d => d.uri.path === notebook.document.cells[0].uri.path); + + let completionItems = await completionItemProvider.provideCompletionItems(document, new vscode.Position(1, 1), token, undefined); + should(completionItems).deepEqual([]); + }); + + it('resolveCompletionItems returns items back', async () => { + let sampleItem: vscode.CompletionItem = { + label: 'item label' + }; + let item = await completionItemProvider.resolveCompletionItem(sampleItem, undefined); + should(item).deepEqual(sampleItem); + + item = await completionItemProvider.resolveCompletionItem(undefined, undefined); + should(item).deepEqual(undefined); + + item = await completionItemProvider.resolveCompletionItem(null, undefined); + should(item).deepEqual(null); + }); + + /** + * Setup session manager mocks, and create code cell. + * Return a document representing the first cell's editor document + * @param source Cell source to insert; if empty, create new cell with no source edit + */ + async function setupSessionAndNotebookCells(source?: string): Promise { + mockJupyterSession.setup(s => s.path).returns(() => notebook.document.uri.path); + mockJupyterSession.setup(s => s.kernel).returns(() => kernel); + mockSessionManager.setup(m => m.listRunning()).returns(() => [mockJupyterSession.object]); + + let notebook = await notebookUtils.newNotebook(); + if (source) { + await notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => { + editBuilder.insertCell({ + cell_type: 'code', + source: source + }); + }); + } else { + await notebookUtils.addCell('code'); + } + let document = vscode.workspace.textDocuments.find(d => d.uri.path === notebook.document.cells[0].uri.path); + return document; + } +});