From b3a01fcf77912090c82206c05b50d4a452450d35 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Wed, 8 Jul 2020 11:06:32 -0700 Subject: [PATCH] Remove ApiWrapper from notebook extension (#11224) * Remove ApiWrapper in notebook extension * delete file * Remove copyrights --- extensions/notebook/package.json | 2 + extensions/notebook/src/book/bookModel.ts | 6 +- extensions/notebook/src/book/bookTreeView.ts | 17 ++- .../notebook/src/book/bookTrustManager.ts | 13 +-- extensions/notebook/src/common/apiWrapper.ts | 105 ------------------ extensions/notebook/src/common/appContext.ts | 6 +- .../notebook/src/common/notebookUtils.ts | 25 ++--- .../src/dialog/configurePython/basePage.ts | 4 +- .../configurePython/configurePathPage.ts | 2 +- .../configurePython/configurePythonDialog.ts | 7 +- .../configurePython/configurePythonWizard.ts | 11 +- .../dialog/managePackages/addNewPackageTab.ts | 2 +- .../managePackages/installedPackagesTab.ts | 4 +- extensions/notebook/src/extension.ts | 7 +- .../notebookIntegration.test.ts | 20 ++-- .../notebook/src/jupyter/jupyterController.ts | 49 ++++---- .../src/jupyter/jupyterNotebookManager.ts | 5 +- .../src/jupyter/jupyterServerInstallation.ts | 71 ++++++------ .../src/jupyter/jupyterServerManager.ts | 8 +- extensions/notebook/src/prompts/adapter.ts | 12 +- extensions/notebook/src/prompts/confirm.ts | 8 +- .../notebook/src/prompts/escapeException.ts | 5 +- extensions/notebook/src/prompts/factory.ts | 2 - extensions/notebook/src/prompts/prompt.ts | 5 +- extensions/notebook/src/prompts/question.ts | 7 +- .../notebook/src/test/book/book.test.ts | 42 ++----- .../src/test/book/bookTrustManager.test.ts | 76 ++++++------- .../src/test/common/notebookUtils.test.ts | 71 ++++++------ .../notebook/src/test/common/prompt.test.ts | 32 +++--- .../notebook/src/test/configurePython.test.ts | 14 +-- .../managePackagesDialog.test.ts | 2 +- .../managePackagesDialogModel.test.ts | 2 +- .../test/model/completionItemProvider.test.ts | 7 +- .../src/test/model/jupyterController.test.ts | 32 +++--- .../src/test/model/notebookManager.test.ts | 8 +- .../src/test/model/serverInstance.test.ts | 5 - .../src/test/model/serverManager.test.ts | 6 - extensions/notebook/yarn.lock | 104 +++++++++++++++++ 38 files changed, 359 insertions(+), 445 deletions(-) delete mode 100644 extensions/notebook/src/common/apiWrapper.ts diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json index 9bc8b3ee70..7aef4e4873 100644 --- a/extensions/notebook/package.json +++ b/extensions/notebook/package.json @@ -561,6 +561,7 @@ "@types/node": "^12.11.7", "@types/request": "^2.48.1", "@types/rimraf": "^2.0.2", + "@types/sinon": "^9.0.4", "@types/temp-write": "^3.3.0", "@types/uuid": "^3.4.5", "assert": "^1.4.1", @@ -568,6 +569,7 @@ "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "should": "^13.2.3", + "sinon": "^9.0.2", "typemoq": "^2.1.0", "vscodetestcover": "^1.0.9" }, diff --git a/extensions/notebook/src/book/bookModel.ts b/extensions/notebook/src/book/bookModel.ts index d0dbd4f78b..2ad2ce63e7 100644 --- a/extensions/notebook/src/book/bookModel.ts +++ b/extensions/notebook/src/book/bookModel.ts @@ -11,7 +11,6 @@ import * as fileServices from 'fs'; import * as fs from 'fs-extra'; import * as loc from '../common/localizedConstants'; import { IJupyterBookToc, IJupyterBookSection } from '../contracts/content'; -import { ApiWrapper } from '../common/apiWrapper'; const fsPromises = fileServices.promises; @@ -22,7 +21,6 @@ export class BookModel { private _tableOfContentsPath: string; private _errorMessage: string; - private apiWrapper: ApiWrapper = new ApiWrapper(); constructor( public readonly bookPath: string, @@ -130,7 +128,7 @@ export class BookModel { this._bookItems.push(book); } catch (e) { this._errorMessage = loc.readBookError(this.bookPath, e instanceof Error ? e.message : e); - this.apiWrapper.showErrorMessage(this._errorMessage); + vscode.window.showErrorMessage(this._errorMessage); } } return this._bookItems; @@ -216,7 +214,7 @@ export class BookModel { notebooks.push(markdown); } else { this._errorMessage = loc.missingFileError(sections[i].title); - this.apiWrapper.showErrorMessage(this._errorMessage); + vscode.window.showErrorMessage(this._errorMessage); } } } else { diff --git a/extensions/notebook/src/book/bookTreeView.ts b/extensions/notebook/src/book/bookTreeView.ts index 1906b9962c..78ea7e382f 100644 --- a/extensions/notebook/src/book/bookTreeView.ts +++ b/extensions/notebook/src/book/bookTreeView.ts @@ -15,7 +15,6 @@ import { BookModel } from './bookModel'; import { Deferred } from '../common/promise'; import { IBookTrustManager, BookTrustManager } from './bookTrustManager'; import * as loc from '../common/localizedConstants'; -import { ApiWrapper } from '../common/apiWrapper'; import * as glob from 'fast-glob'; import { isNullOrUndefined } from 'util'; import { debounce } from '../common/utils'; @@ -43,14 +42,14 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider console.error(e)); this.viewId = view; this.prompter = new CodeAdapter(); - this._bookTrustManager = new BookTrustManager(this.books, _apiWrapper); + this._bookTrustManager = new BookTrustManager(this.books); this._extensionContext.subscriptions.push(azdata.nb.registerNavigationProvider(this)); } @@ -83,7 +82,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { @@ -93,9 +92,9 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { let openDocument = azdata.nb.activeNotebookEditor; let notebookPath = openDocument?.document.uri; @@ -236,7 +235,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { diff --git a/extensions/notebook/src/book/bookTrustManager.ts b/extensions/notebook/src/book/bookTrustManager.ts index c41b277ce8..1fa8c236c9 100644 --- a/extensions/notebook/src/book/bookTrustManager.ts +++ b/extensions/notebook/src/book/bookTrustManager.ts @@ -7,7 +7,6 @@ import * as vscode from 'vscode'; import * as constants from './../common/constants'; import { BookTreeItem } from './bookTreeItem'; import { BookModel } from './bookModel'; -import { ApiWrapper } from '../common/apiWrapper'; export interface IBookTrustManager { isNotebookTrustedByDefault(notebookUri: string): boolean; @@ -20,7 +19,7 @@ enum TrustBookOperation { } export class BookTrustManager implements IBookTrustManager { - constructor(private books: BookModel[], private apiWrapper: ApiWrapper) { } + constructor(private books: BookModel[]) { } isNotebookTrustedByDefault(notebookUri: string): boolean { let normalizedNotebookUri: string = path.normalize(notebookUri); @@ -38,7 +37,7 @@ export class BookTrustManager implements IBookTrustManager { let bookPathsInConfig: string[] = this.getTrustedBookPathsInConfig(); if (this.hasWorkspaceFolders()) { - let workspaceFolders = this.apiWrapper.getWorkspaceFolders(); + let workspaceFolders = vscode.workspace.workspaceFolders; trustablePaths = bookPathsInConfig .map(trustableBookPath => workspaceFolders @@ -63,21 +62,21 @@ export class BookTrustManager implements IBookTrustManager { } getTrustedBookPathsInConfig(): string[] { - let config: vscode.WorkspaceConfiguration = this.apiWrapper.getConfiguration(constants.notebookConfigKey); + let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(constants.notebookConfigKey); let trustedBookDirectories: string[] = config.get(constants.trustedBooksConfigKey); return trustedBookDirectories; } setTrustedBookPathsInConfig(bookPaths: string[]) { - let config: vscode.WorkspaceConfiguration = this.apiWrapper.getConfiguration(constants.notebookConfigKey); + let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(constants.notebookConfigKey); let storeInWorspace: boolean = this.hasWorkspaceFolders(); config.update(constants.trustedBooksConfigKey, bookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global); } hasWorkspaceFolders(): boolean { - let workspaceFolders = this.apiWrapper.getWorkspaceFolders(); + let workspaceFolders = vscode.workspace.workspaceFolders; return workspaceFolders && workspaceFolders.length > 0; } @@ -86,7 +85,7 @@ export class BookTrustManager implements IBookTrustManager { let bookPathToChange: string = path.join(bookPath, path.sep); if (this.hasWorkspaceFolders()) { - let workspaceFolders = this.apiWrapper.getWorkspaceFolders(); + let workspaceFolders = vscode.workspace.workspaceFolders; let matchingWorkspaceFolder: vscode.WorkspaceFolder = workspaceFolders .find(ws => bookPathToChange.startsWith(path.normalize(ws.uri.fsPath))); diff --git a/extensions/notebook/src/common/apiWrapper.ts b/extensions/notebook/src/common/apiWrapper.ts deleted file mode 100644 index 147dbb06b8..0000000000 --- a/extensions/notebook/src/common/apiWrapper.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import * as azdata from 'azdata'; - -/** - * Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into - * this API from our code - */ -export class ApiWrapper { - public getWorkspaceFolders(): vscode.WorkspaceFolder[] { - return [].concat(vscode.workspace.workspaceFolders || []); - } - - public createOutputChannel(name: string): vscode.OutputChannel { - return vscode.window.createOutputChannel(name); - } - - public createTerminalWithOptions(options: vscode.TerminalOptions): vscode.Terminal { - return vscode.window.createTerminal(options); - } - - public getCurrentConnection(): Thenable { - return azdata.connection.getCurrentConnection(); - } - - public getWorkspacePathFromUri(uri: vscode.Uri): string | undefined { - let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri); - return workspaceFolder ? workspaceFolder.uri.fsPath : undefined; - } - - public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable { - return vscode.commands.registerCommand(command, callback, thisArg); - } - - public registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider(selector, provider, ...triggerCharacters); - } - - public registerTaskHandler(taskId: string, handler: (profile: azdata.IConnectionProfile) => void): void { - azdata.tasks.registerTask(taskId, handler); - } - - public showErrorMessage(message: string, ...items: string[]): Thenable { - return vscode.window.showErrorMessage(message, ...items); - } - - public showInfoMessage(message: string, ...items: string[]): Thenable { - return vscode.window.showInformationMessage(message, ...items); - } - - public showOpenDialog(options: vscode.OpenDialogOptions): Thenable { - return vscode.window.showOpenDialog(options); - } - - public startBackgroundOperation(operationInfo: azdata.BackgroundOperationInfo): void { - azdata.tasks.startBackgroundOperation(operationInfo); - } - - public getNotebookDocuments() { - return azdata.nb.notebookDocuments; - } - - public getActiveNotebookEditor(): azdata.nb.NotebookEditor { - return azdata.nb.activeNotebookEditor; - } - - public showNotebookDocument(uri: vscode.Uri, showOptions?: azdata.nb.NotebookShowOptions): Thenable { - return azdata.nb.showNotebookDocument(uri, showOptions); - } - - /** - * Get the configuration for a extensionName - * @param extensionName The string name of the extension to get the configuration for - * @param resource The optional URI, as a URI object or a string, to use to get resource-scoped configurations - */ - public getConfiguration(extensionName?: string, resource?: vscode.Uri | string): vscode.WorkspaceConfiguration { - if (typeof resource === 'string') { - try { - resource = this.parseUri(resource); - } catch (e) { - resource = undefined; - } - } else if (!resource) { - // Fix to avoid adding lots of errors to debug console. Expects a valid resource or null, not undefined - resource = null; - } - return vscode.workspace.getConfiguration(extensionName, resource as vscode.Uri); - } - - public parseUri(uri: string): vscode.Uri { - return vscode.Uri.parse(uri); - } - - public createTreeView(viewId: string, options: vscode.TreeViewOptions): vscode.TreeView { - return vscode.window.createTreeView(viewId, options); - } - - public showQuickPick(items: string[] | Thenable, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): Thenable { - return vscode.window.showQuickPick(items, options, token); - } -} diff --git a/extensions/notebook/src/common/appContext.ts b/extensions/notebook/src/common/appContext.ts index 7487d4715e..d36b62c6ad 100644 --- a/extensions/notebook/src/common/appContext.ts +++ b/extensions/notebook/src/common/appContext.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ApiWrapper } from './apiWrapper'; import { NotebookUtils } from './notebookUtils'; /** @@ -14,8 +13,7 @@ export class AppContext { public readonly notebookUtils: NotebookUtils; - constructor(public readonly extensionContext: vscode.ExtensionContext, public readonly apiWrapper: ApiWrapper) { - this.apiWrapper = apiWrapper || new ApiWrapper(); - this.notebookUtils = new NotebookUtils(apiWrapper); + constructor(public readonly extensionContext: vscode.ExtensionContext) { + this.notebookUtils = new NotebookUtils(); } } diff --git a/extensions/notebook/src/common/notebookUtils.ts b/extensions/notebook/src/common/notebookUtils.ts index 15f9a0bec0..2aa73f6b17 100644 --- a/extensions/notebook/src/common/notebookUtils.ts +++ b/extensions/notebook/src/common/notebookUtils.ts @@ -8,7 +8,6 @@ import * as os from 'os'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { getErrorMessage, isEditorTitleFree } from '../common/utils'; -import { ApiWrapper } from './apiWrapper'; const localize = nls.loadMessageBundle(); @@ -18,7 +17,7 @@ const noNotebookVisible = localize('noNotebookVisible', "No notebook editor is a export class NotebookUtils { - constructor(private _apiWrapper: ApiWrapper) { } + constructor() { } public async newNotebook(connectionProfile?: azdata.IConnectionProfile): Promise { const title = this.findNextUntitledEditorName(); @@ -51,59 +50,59 @@ export class NotebookUtils { let filter: { [key: string]: Array } = {}; // TODO support querying valid notebook file types filter[localize('notebookFiles', "Notebooks")] = ['ipynb']; - let file = await this._apiWrapper.showOpenDialog({ + let file = await vscode.window.showOpenDialog({ filters: filter }); if (file && file.length > 0) { await azdata.nb.showNotebookDocument(file[0]); } } catch (err) { - this._apiWrapper.showErrorMessage(getErrorMessage(err)); + vscode.window.showErrorMessage(getErrorMessage(err)); } } public async runActiveCell(): Promise { try { - let notebook = this._apiWrapper.getActiveNotebookEditor(); + let notebook = azdata.nb.activeNotebookEditor; if (notebook) { await notebook.runCell(); } else { throw new Error(noNotebookVisible); } } catch (err) { - this._apiWrapper.showErrorMessage(getErrorMessage(err)); + vscode.window.showErrorMessage(getErrorMessage(err)); } } public async clearActiveCellOutput(): Promise { try { - let notebook = this._apiWrapper.getActiveNotebookEditor(); + let notebook = azdata.nb.activeNotebookEditor; if (notebook) { await notebook.clearOutput(); } else { throw new Error(noNotebookVisible); } } catch (err) { - this._apiWrapper.showErrorMessage(getErrorMessage(err)); + vscode.window.showErrorMessage(getErrorMessage(err)); } } public async runAllCells(startCell?: azdata.nb.NotebookCell, endCell?: azdata.nb.NotebookCell): Promise { try { - let notebook = this._apiWrapper.getActiveNotebookEditor(); + let notebook = azdata.nb.activeNotebookEditor; if (notebook) { await notebook.runAllCells(startCell, endCell); } else { throw new Error(noNotebookVisible); } } catch (err) { - this._apiWrapper.showErrorMessage(getErrorMessage(err)); + vscode.window.showErrorMessage(getErrorMessage(err)); } } public async addCell(cellType: azdata.nb.CellType): Promise { try { - let notebook = this._apiWrapper.getActiveNotebookEditor(); + let notebook = azdata.nb.activeNotebookEditor; if (notebook) { await notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => { // TODO should prompt and handle cell placement @@ -116,7 +115,7 @@ export class NotebookUtils { throw new Error(noNotebookVisible); } } catch (err) { - this._apiWrapper.showErrorMessage(getErrorMessage(err)); + vscode.window.showErrorMessage(getErrorMessage(err)); } } @@ -126,7 +125,7 @@ export class NotebookUtils { let title = this.findNextUntitledEditorName(); let untitledUri = vscode.Uri.parse(`untitled:${title}`); - let editor = await this._apiWrapper.showNotebookDocument(untitledUri, { + let editor = await azdata.nb.showNotebookDocument(untitledUri, { connectionProfile: oeContext ? oeContext.connectionProfile : undefined, providerId: JUPYTER_NOTEBOOK_PROVIDER, preview: false, diff --git a/extensions/notebook/src/dialog/configurePython/basePage.ts b/extensions/notebook/src/dialog/configurePython/basePage.ts index 07f85b568d..f92738432f 100644 --- a/extensions/notebook/src/dialog/configurePython/basePage.ts +++ b/extensions/notebook/src/dialog/configurePython/basePage.ts @@ -5,12 +5,10 @@ import * as azdata from 'azdata'; import { ConfigurePythonModel, ConfigurePythonWizard } from './configurePythonWizard'; -import { ApiWrapper } from '../../common/apiWrapper'; export abstract class BasePage { - constructor(protected readonly apiWrapper: ApiWrapper, - protected readonly instance: ConfigurePythonWizard, + constructor(protected readonly instance: ConfigurePythonWizard, protected readonly wizardPage: azdata.window.WizardPage, protected readonly model: ConfigurePythonModel, protected readonly view: azdata.ModelView) { diff --git a/extensions/notebook/src/dialog/configurePython/configurePathPage.ts b/extensions/notebook/src/dialog/configurePython/configurePathPage.ts index 00ffce2e12..a84db25f67 100644 --- a/extensions/notebook/src/dialog/configurePython/configurePathPage.ts +++ b/extensions/notebook/src/dialog/configurePython/configurePathPage.ts @@ -236,7 +236,7 @@ export class ConfigurePathPage extends BasePage { openLabel: this.SelectFileLabel }; - let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options); + let fileUris: vscode.Uri[] = await vscode.window.showOpenDialog(options); if (fileUris?.length > 0 && fileUris[0]) { let existingValues = this.pythonLocationDropdown.values; let filePath = fileUris[0].fsPath; diff --git a/extensions/notebook/src/dialog/configurePython/configurePythonDialog.ts b/extensions/notebook/src/dialog/configurePython/configurePythonDialog.ts index 664db5c9fa..4a5de79eea 100644 --- a/extensions/notebook/src/dialog/configurePython/configurePythonDialog.ts +++ b/extensions/notebook/src/dialog/configurePython/configurePythonDialog.ts @@ -10,7 +10,6 @@ import { promises as fs } from 'fs'; import * as utils from '../../common/utils'; import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; -import { ApiWrapper } from '../../common/apiWrapper'; import { Deferred } from '../../common/promise'; import { PythonPathLookup, PythonPathInfo } from '../pythonPathLookup'; @@ -39,7 +38,7 @@ export class ConfigurePythonDialog { private pythonPathsPromise: Promise; private usingCustomPath: boolean; - constructor(private apiWrapper: ApiWrapper, private jupyterInstallation: JupyterServerInstallation) { + constructor(private jupyterInstallation: JupyterServerInstallation) { this.setupComplete = new Deferred(); this.pythonPathsPromise = (new PythonPathLookup()).getSuggestions(); this.usingCustomPath = false; @@ -108,7 +107,7 @@ export class ConfigurePythonDialog { } }); - let useExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper); + let useExistingPython = JupyterServerInstallation.getExistingPythonSetting(); this.createInstallRadioButtons(view.modelBuilder, useExistingPython); let formModel = view.modelBuilder.formContainer() @@ -271,7 +270,7 @@ export class ConfigurePythonDialog { openLabel: this.SelectFileLabel }; - let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options); + let fileUris: vscode.Uri[] = await vscode.window.showOpenDialog(options); if (fileUris && fileUris[0]) { let existingValues = this.pythonLocationDropdown.values; let filePath = fileUris[0].fsPath; diff --git a/extensions/notebook/src/dialog/configurePython/configurePythonWizard.ts b/extensions/notebook/src/dialog/configurePython/configurePythonWizard.ts index bb99107a3d..f0b82c5b09 100644 --- a/extensions/notebook/src/dialog/configurePython/configurePythonWizard.ts +++ b/extensions/notebook/src/dialog/configurePython/configurePythonWizard.ts @@ -13,7 +13,6 @@ import * as utils from '../../common/utils'; import { promises as fs } from 'fs'; import { Deferred } from '../../common/promise'; import { PythonPathInfo, PythonPathLookup } from '../pythonPathLookup'; -import { ApiWrapper } from '../../common/apiWrapper'; const localize = nls.loadMessageBundle(); @@ -37,7 +36,7 @@ export class ConfigurePythonWizard { private _setupComplete: Deferred; private pythonPathsPromise: Promise; - constructor(private apiWrapper: ApiWrapper, private jupyterInstallation: JupyterServerInstallation) { + constructor(private jupyterInstallation: JupyterServerInstallation) { this._setupComplete = new Deferred(); this.pythonPathsPromise = (new PythonPathLookup()).getSuggestions(); } @@ -55,8 +54,8 @@ export class ConfigurePythonWizard { kernelName: kernelName, pythonPathsPromise: this.pythonPathsPromise, installation: this.jupyterInstallation, - pythonLocation: JupyterServerInstallation.getPythonPathSetting(this.apiWrapper), - useExistingPython: JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper) + pythonLocation: JupyterServerInstallation.getPythonPathSetting(), + useExistingPython: JupyterServerInstallation.getExistingPythonSetting() }; let pages: Map = new Map(); @@ -72,14 +71,14 @@ export class ConfigurePythonWizard { let page1 = azdata.window.createWizardPage(localize('configurePython.page1Name', "Install Dependencies")); page0.registerContent(async (view) => { - let configurePathPage = new ConfigurePathPage(this.apiWrapper, this, page0, this.model, view); + let configurePathPage = new ConfigurePathPage(this, page0, this.model, view); pages.set(0, configurePathPage); await configurePathPage.initialize(); await configurePathPage.onPageEnter(); }); page1.registerContent(async (view) => { - let pickPackagesPage = new PickPackagesPage(this.apiWrapper, this, page1, this.model, view); + let pickPackagesPage = new PickPackagesPage(this, page1, this.model, view); pages.set(1, pickPackagesPage); await pickPackagesPage.initialize(); }); diff --git a/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts b/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts index c111e9b27e..33cb3fd71c 100644 --- a/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts +++ b/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts @@ -190,7 +190,7 @@ export class AddNewPackageTab { "Installing {0} {1}", packageName, packageVersion); - this.jupyterInstallation.apiWrapper.startBackgroundOperation({ + azdata.tasks.startBackgroundOperation({ displayName: taskName, description: taskName, isCancelable: false, diff --git a/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts b/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts index d9d91aeb9a..3e57af18e6 100644 --- a/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts +++ b/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts @@ -225,7 +225,7 @@ export class InstalledPackagesTab { type: confirm, message: localize('managePackages.confirmUninstall', "Are you sure you want to uninstall the specified packages?"), default: false - }, this.jupyterInstallation?.apiWrapper); + }); if (doUninstall) { try { @@ -244,7 +244,7 @@ export class InstalledPackagesTab { "Uninstalling {0}", packagesStr); - this.jupyterInstallation.apiWrapper.startBackgroundOperation({ + azdata.tasks.startBackgroundOperation({ displayName: taskName, description: taskName, isCancelable: false, diff --git a/extensions/notebook/src/extension.ts b/extensions/notebook/src/extension.ts index 53a7cb2ad0..cd6a4193b4 100644 --- a/extensions/notebook/src/extension.ts +++ b/extensions/notebook/src/extension.ts @@ -10,7 +10,6 @@ import * as path from 'path'; import { JupyterController } from './jupyter/jupyterController'; import { AppContext } from './common/appContext'; -import { ApiWrapper } from './common/apiWrapper'; import { IExtensionApi, IPackageManageProvider } from './types'; import { CellType } from './contracts/content'; import { NotebookUriHandler } from './protocol/notebookUriHandler'; @@ -25,7 +24,7 @@ let controller: JupyterController; type ChooseCellType = { label: string, id: CellType }; export async function activate(extensionContext: vscode.ExtensionContext): Promise { - const appContext = new AppContext(extensionContext, new ApiWrapper()); + const appContext = new AppContext(extensionContext); const createBookPath: string = path.posix.join(extensionContext.extensionPath, 'resources', 'notebooks', 'JupyterBooksCreate.ipynb'); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openBook', (bookPath: string, openAsUntitled: boolean, urlToOpen?: string) => openAsUntitled ? providedBookTreeViewProvider.openBook(bookPath, urlToOpen, true) : bookTreeViewProvider.openBook(bookPath, urlToOpen, true))); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource))); @@ -117,9 +116,9 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi } let workspaceFolders = vscode.workspace.workspaceFolders?.slice() ?? []; - const bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, workspaceFolders, extensionContext, false, BOOKS_VIEWID, NavigationProviders.NotebooksNavigator); + const bookTreeViewProvider = new BookTreeViewProvider(workspaceFolders, extensionContext, false, BOOKS_VIEWID, NavigationProviders.NotebooksNavigator); await bookTreeViewProvider.initialized; - const providedBookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], extensionContext, true, PROVIDED_BOOKS_VIEWID, NavigationProviders.ProvidedBooksNavigator); + const providedBookTreeViewProvider = new BookTreeViewProvider([], extensionContext, true, PROVIDED_BOOKS_VIEWID, NavigationProviders.ProvidedBooksNavigator); await providedBookTreeViewProvider.initialized; azdata.nb.onDidChangeActiveNotebookEditor(e => { diff --git a/extensions/notebook/src/integrationTest/notebookIntegration.test.ts b/extensions/notebook/src/integrationTest/notebookIntegration.test.ts index 07529b7b33..36f866cc54 100644 --- a/extensions/notebook/src/integrationTest/notebookIntegration.test.ts +++ b/extensions/notebook/src/integrationTest/notebookIntegration.test.ts @@ -46,13 +46,12 @@ describe('Notebook Extension Python Installation', function () { it('Verify Python Installation', async function () { should(installComplete).be.true('Python setup did not complete.'); - let apiWrapper = jupyterController.jupyterInstallation.apiWrapper; - let jupyterPath = JupyterServerInstallation.getPythonInstallPath(apiWrapper); + let jupyterPath = JupyterServerInstallation.getPythonInstallPath(); console.log(`Expected python path: '${pythonInstallDir}'; actual: '${jupyterPath}'`); should(jupyterPath).be.equal(pythonInstallDir); - should(JupyterServerInstallation.isPythonInstalled(apiWrapper)).be.true(); - should(JupyterServerInstallation.getExistingPythonSetting(apiWrapper)).be.false(); + should(JupyterServerInstallation.isPythonInstalled()).be.true(); + should(JupyterServerInstallation.getExistingPythonSetting()).be.false(); }); it('Use Existing Python Installation', async function () { @@ -68,17 +67,16 @@ describe('Notebook Extension Python Installation', function () { console.log('Start Existing Python Installation'); let existingPythonPath = path.join(pythonInstallDir, pythonBundleVersion); await install.startInstallProcess(false, { installPath: existingPythonPath, existingPython: true }); - let apiWrapper = install.apiWrapper; - should(JupyterServerInstallation.isPythonInstalled(apiWrapper)).be.true(); - should(JupyterServerInstallation.getPythonInstallPath(apiWrapper)).be.equal(existingPythonPath); - should(JupyterServerInstallation.getExistingPythonSetting(apiWrapper)).be.true(); + should(JupyterServerInstallation.isPythonInstalled()).be.true(); + should(JupyterServerInstallation.getPythonInstallPath()).be.equal(existingPythonPath); + should(JupyterServerInstallation.getExistingPythonSetting()).be.true(); // Redo "new" install to restore original settings. // The actual install should get skipped since it already exists. await install.startInstallProcess(false, { installPath: pythonInstallDir, existingPython: false }); - should(JupyterServerInstallation.isPythonInstalled(apiWrapper)).be.true(); - should(JupyterServerInstallation.getPythonInstallPath(apiWrapper)).be.equal(pythonInstallDir); - should(JupyterServerInstallation.getExistingPythonSetting(apiWrapper)).be.false(); + should(JupyterServerInstallation.isPythonInstalled()).be.true(); + should(JupyterServerInstallation.getPythonInstallPath()).be.equal(pythonInstallDir); + should(JupyterServerInstallation.getExistingPythonSetting()).be.false(); console.log('Existing Python Installation is done'); }); diff --git a/extensions/notebook/src/jupyter/jupyterController.ts b/extensions/notebook/src/jupyter/jupyterController.ts index a7fb732520..91dd72d591 100644 --- a/extensions/notebook/src/jupyter/jupyterController.ts +++ b/extensions/notebook/src/jupyter/jupyterController.ts @@ -18,7 +18,6 @@ import * as utils from '../common/utils'; import { IPrompter, IQuestion, confirm } from '../prompts/question'; import { AppContext } from '../common/appContext'; -import { ApiWrapper } from '../common/apiWrapper'; import { LocalJupyterServerManager, ServerInstanceFactory } from './jupyterServerManager'; import { NotebookCompletionItemProvider } from '../intellisense/completionItemProvider'; import { JupyterNotebookProvider } from './jupyterNotebookProvider'; @@ -46,11 +45,7 @@ export class JupyterController implements vscode.Disposable { constructor(private appContext: AppContext) { this.prompter = new CodeAdapter(); - this.outputChannel = this.appContext.apiWrapper.createOutputChannel(constants.extensionOutputChannel); - } - - private get apiWrapper(): ApiWrapper { - return this.appContext.apiWrapper; + this.outputChannel = vscode.window.createOutputChannel(constants.extensionOutputChannel); } public get extensionContext(): vscode.ExtensionContext { @@ -69,33 +64,32 @@ export class JupyterController implements vscode.Disposable { public async activate(): Promise { this._jupyterInstallation = new JupyterServerInstallation( this.extensionContext.extensionPath, - this.outputChannel, - this.apiWrapper); + this.outputChannel); await this._jupyterInstallation.configurePackagePaths(); // Add command/task handlers - this.apiWrapper.registerTaskHandler(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => { + azdata.tasks.registerTask(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => { return this.handleOpenNotebookTask(profile); }); - this.apiWrapper.registerTaskHandler(constants.jupyterNewNotebookTask, (profile: azdata.IConnectionProfile) => { + azdata.tasks.registerTask(constants.jupyterNewNotebookTask, (profile: azdata.IConnectionProfile) => { return this.saveProfileAndCreateNotebook(profile); }); - this.apiWrapper.registerCommand(constants.jupyterNewNotebookCommand, (explorerContext: azdata.ObjectExplorerContext) => { + vscode.commands.registerCommand(constants.jupyterNewNotebookCommand, (explorerContext: azdata.ObjectExplorerContext) => { return this.saveProfileAndCreateNotebook(explorerContext ? explorerContext.connectionProfile : undefined); }); - this.apiWrapper.registerCommand(constants.jupyterAnalyzeCommand, (explorerContext: azdata.ObjectExplorerContext) => { + vscode.commands.registerCommand(constants.jupyterAnalyzeCommand, (explorerContext: azdata.ObjectExplorerContext) => { return this.saveProfileAndAnalyzeNotebook(explorerContext); }); - this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); }); - this.apiWrapper.registerCommand(constants.jupyterManagePackages, async (args) => { return this.doManagePackages(args); }); - this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); }); + vscode.commands.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); }); + vscode.commands.registerCommand(constants.jupyterManagePackages, async (args) => { return this.doManagePackages(args); }); + vscode.commands.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); }); let supportedFileFilter: vscode.DocumentFilter[] = [ { scheme: 'untitled', language: '*' } ]; this.registerNotebookProvider(); - this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(this._notebookProvider))); + this.extensionContext.subscriptions.push(vscode.languages.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(this._notebookProvider))); this.registerDefaultPackageManageProviders(); return true; @@ -106,7 +100,6 @@ export class JupyterController implements vscode.Disposable { documentPath: documentUri.fsPath, jupyterInstallation: this._jupyterInstallation, extensionContext: this.extensionContext, - apiWrapper: this.apiWrapper, factory: this._serverInstanceFactory })); azdata.nb.registerNotebookProvider(this._notebookProvider); @@ -127,14 +120,14 @@ export class JupyterController implements vscode.Disposable { // EVENT HANDLERS ////////////////////////////////////////////////////// public async getDefaultConnection(): Promise { - return await this.apiWrapper.getCurrentConnection(); + return await azdata.connection.getCurrentConnection(); } private async handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promise { let notebookFileTypeName = localize('notebookFileType', "Notebooks"); let filter: { [key: string]: Array } = {}; filter[notebookFileTypeName] = ['ipynb']; - let uris = await this.apiWrapper.showOpenDialog({ + let uris = await vscode.window.showOpenDialog({ filters: filter, canSelectFiles: true, canSelectMany: false @@ -144,7 +137,7 @@ export class JupyterController implements vscode.Disposable { // Verify this is a .ipynb file since this isn't actually filtered on Mac/Linux if (path.extname(fileUri.fsPath) !== '.ipynb') { // in the future might want additional supported types - this.apiWrapper.showErrorMessage(localize('unsupportedFileType', "Only .ipynb Notebooks are supported")); + vscode.window.showErrorMessage(localize('unsupportedFileType', "Only .ipynb Notebooks are supported")); } else { await azdata.nb.showNotebookDocument(fileUri, { connectionProfile: profile, @@ -195,7 +188,7 @@ export class JupyterController implements vscode.Disposable { await this._jupyterInstallation.startInstallProcess(true); } } catch (err) { - this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); + vscode.window.showErrorMessage(utils.getErrorMessage(err)); } } @@ -205,7 +198,7 @@ export class JupyterController implements vscode.Disposable { type: confirm, message: localize('confirmReinstall', "Are you sure you want to reinstall?"), default: true - }, this.apiWrapper); + }); } public async doManagePackages(options?: ManagePackageDialogOptions): Promise { @@ -223,7 +216,7 @@ export class JupyterController implements vscode.Disposable { packagesDialog.showDialog(); } catch (error) { let message = utils.getErrorMessage(error); - this.apiWrapper.showErrorMessage(message); + vscode.window.showErrorMessage(message); } } @@ -256,17 +249,17 @@ export class JupyterController implements vscode.Disposable { public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void { if (jupyterInstaller.previewFeaturesEnabled) { - let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, jupyterInstaller); + let pythonWizard = new ConfigurePythonWizard(jupyterInstaller); pythonWizard.start().catch((err: any) => { - this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); + vscode.window.showErrorMessage(utils.getErrorMessage(err)); }); pythonWizard.setupComplete.catch((err: any) => { - this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); + vscode.window.showErrorMessage(utils.getErrorMessage(err)); }); } else { - let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller); + let pythonDialog = new ConfigurePythonDialog(jupyterInstaller); pythonDialog.showDialog().catch((err: any) => { - this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); + vscode.window.showErrorMessage(utils.getErrorMessage(err)); }); } } diff --git a/extensions/notebook/src/jupyter/jupyterNotebookManager.ts b/extensions/notebook/src/jupyter/jupyterNotebookManager.ts index 2cacd13ebc..d1e326b185 100644 --- a/extensions/notebook/src/jupyter/jupyterNotebookManager.ts +++ b/extensions/notebook/src/jupyter/jupyterNotebookManager.ts @@ -8,14 +8,13 @@ import * as vscode from 'vscode'; import { ServerConnection, SessionManager } from '@jupyterlab/services'; import { JupyterSessionManager } from './jupyterSessionManager'; -import { ApiWrapper } from '../common/apiWrapper'; import { LocalJupyterServerManager } from './jupyterServerManager'; export class JupyterNotebookManager implements nb.NotebookManager, vscode.Disposable { protected _serverSettings: ServerConnection.ISettings; private _sessionManager: JupyterSessionManager; - constructor(private _serverManager: LocalJupyterServerManager, sessionManager?: JupyterSessionManager, private apiWrapper: ApiWrapper = new ApiWrapper()) { + constructor(private _serverManager: LocalJupyterServerManager, sessionManager?: JupyterSessionManager) { let pythonEnvVarPath = this._serverManager && this._serverManager.jupyterServerInstallation && this._serverManager.jupyterServerInstallation.pythonEnvVarPath; this._sessionManager = sessionManager || new JupyterSessionManager(pythonEnvVarPath); this._serverManager.onServerStarted(() => { @@ -49,7 +48,7 @@ export class JupyterNotebookManager implements nb.NotebookManager, vscode.Dispos this._sessionManager.shutdownAll().then(() => this._sessionManager.dispose()); } if (this._serverManager) { - this._serverManager.stopServer().then(success => undefined, error => this.apiWrapper.showErrorMessage(error)); + this._serverManager.stopServer().then(success => undefined, error => vscode.window.showErrorMessage(error)); } } } diff --git a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts index 58aeac8e1c..b7b84ddeb6 100644 --- a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts +++ b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts @@ -13,7 +13,6 @@ import * as request from 'request'; import * as zip from 'adm-zip'; import * as tar from 'tar'; -import { ApiWrapper } from '../common/apiWrapper'; import * as constants from '../common/constants'; import * as utils from '../common/utils'; import { Deferred } from '../common/promise'; @@ -62,7 +61,6 @@ export interface IJupyterServerInstallation { installPythonPackage(backgroundOperation: azdata.BackgroundOperation, usingExistingPython: boolean, pythonInstallationPath: string, outputChannel: vscode.OutputChannel): Promise; } export class JupyterServerInstallation implements IJupyterServerInstallation { - public apiWrapper: ApiWrapper; public extensionPath: string; public pythonBinPath: string; public outputChannel: vscode.OutputChannel; @@ -114,10 +112,9 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { private readonly _runningOnSAW: boolean; - constructor(extensionPath: string, outputChannel: vscode.OutputChannel, apiWrapper: ApiWrapper) { + constructor(extensionPath: string, outputChannel: vscode.OutputChannel) { this.extensionPath = extensionPath; this.outputChannel = outputChannel; - this.apiWrapper = apiWrapper; this._runningOnSAW = vscode.env.appName.toLowerCase().indexOf('saw') > 0; vscode.commands.executeCommand(constants.BuiltInCommands.SetContext, 'notebook:runningOnSAW', this._runningOnSAW); @@ -126,8 +123,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { this._pythonInstallationPath = `${vscode.env.appRoot}\\ads-python`; this._usingExistingPython = true; } else { - this._pythonInstallationPath = JupyterServerInstallation.getPythonInstallPath(this.apiWrapper); - this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper); + this._pythonInstallationPath = JupyterServerInstallation.getPythonInstallPath(); + this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting(); } this._usingConda = false; this._installInProgress = false; @@ -427,7 +424,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { } if (this._installInProgress) { - this.apiWrapper.showInfoMessage(msgWaitingForInstall); + vscode.window.showInformationMessage(msgWaitingForInstall); return this._installCompletion.promise; } @@ -438,14 +435,14 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { this._usingExistingPython = installSettings.existingPython; await this.configurePackagePaths(); - this.apiWrapper.startBackgroundOperation({ + azdata.tasks.startBackgroundOperation({ displayName: msgTaskName, description: msgTaskName, isCancelable: false, operation: op => { this.installDependencies(op, forceInstall, installSettings.specificPackages) .then(async () => { - let notebookConfig = this.apiWrapper.getConfiguration(constants.notebookConfigKey); + let notebookConfig = vscode.workspace.getConfiguration(constants.notebookConfigKey); await notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, vscode.ConfigurationTarget.Global); await notebookConfig.update(constants.existingPythonConfigKey, this._usingExistingPython, vscode.ConfigurationTarget.Global); await this.configurePackagePaths(); @@ -472,21 +469,21 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { return Promise.resolve(); } if (this._installInProgress) { - this.apiWrapper.showInfoMessage(msgWaitingForInstall); + vscode.window.showInformationMessage(msgWaitingForInstall); return this._installCompletion.promise; } - let isPythonInstalled = JupyterServerInstallation.isPythonInstalled(this.apiWrapper); + let isPythonInstalled = JupyterServerInstallation.isPythonInstalled(); let areRequiredPackagesInstalled = await this.areRequiredPackagesInstalled(kernelDisplayName); if (!isPythonInstalled || !areRequiredPackagesInstalled) { if (this.previewFeaturesEnabled) { - let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, this); + let pythonWizard = new ConfigurePythonWizard(this); await pythonWizard.start(kernelDisplayName, true); return pythonWizard.setupComplete.then(() => { this._kernelSetupCache.set(kernelDisplayName, true); }); } else { - let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this); + let pythonDialog = new ConfigurePythonDialog(this); return pythonDialog.showDialog(true); } } @@ -500,7 +497,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { return Promise.resolve(); } if (this._installInProgress) { - this.apiWrapper.showInfoMessage(msgWaitingForInstall); + vscode.window.showInformationMessage(msgWaitingForInstall); return this._installCompletion.promise; } @@ -607,7 +604,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { type: confirm, message: localize('confirmPackageUpgrade', "Some required python packages need to be installed. Would you like to install them now?"), default: true - }, this.apiWrapper); + }); if (!doUpgrade) { throw new Error(localize('configurePython.packageInstallDeclined', "Package installation was declined.")); } @@ -637,7 +634,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { packagesStr); let backgroundTaskComplete = new Deferred(); - this.apiWrapper.startBackgroundOperation({ + azdata.tasks.startBackgroundOperation({ displayName: taskName, description: taskName, isCancelable: false, @@ -667,7 +664,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { if (!fs.existsSync(pythonExePath)) { return []; } - } else if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) { + } else if (!JupyterServerInstallation.isPythonInstalled()) { return []; } @@ -801,16 +798,15 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { /** * Checks if a python executable exists at the "notebook.pythonPath" defined in the user's settings. - * @param apiWrapper An ApiWrapper to use when retrieving user settings info. */ - public static isPythonInstalled(apiWrapper: ApiWrapper): boolean { + public static isPythonInstalled(): boolean { // Don't use _pythonExecutable here, since it could be populated with a default value - let pathSetting = JupyterServerInstallation.getPythonPathSetting(apiWrapper); + let pathSetting = JupyterServerInstallation.getPythonPathSetting(); if (!pathSetting) { return false; } - let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper); + let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(); let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting, useExistingInstall); // eslint-disable-next-line no-sync return fs.existsSync(pythonExe); @@ -819,34 +815,29 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { /** * Returns the Python installation path defined in "notebook.pythonPath" in the user's settings. * Returns a default path if the setting is not defined. - * @param apiWrapper An ApiWrapper to use when retrieving user settings info. */ - public static getPythonInstallPath(apiWrapper: ApiWrapper): string { - let userPath = JupyterServerInstallation.getPythonPathSetting(apiWrapper); + public static getPythonInstallPath(): string { + let userPath = JupyterServerInstallation.getPythonPathSetting(); return userPath ? userPath : JupyterServerInstallation.DefaultPythonLocation; } - public static getExistingPythonSetting(apiWrapper: ApiWrapper): boolean { + public static getExistingPythonSetting(): boolean { let useExistingPython = false; - if (apiWrapper) { - let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey); - if (notebookConfig) { - useExistingPython = !!notebookConfig[constants.existingPythonConfigKey]; - } + let notebookConfig = vscode.workspace.getConfiguration(constants.notebookConfigKey); + if (notebookConfig) { + useExistingPython = !!notebookConfig[constants.existingPythonConfigKey]; } return useExistingPython; } - public static getPythonPathSetting(apiWrapper: ApiWrapper): string { + public static getPythonPathSetting(): string { let path = undefined; - if (apiWrapper) { - let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey); - if (notebookConfig) { - let configPythonPath = notebookConfig[constants.pythonPathConfigKey]; - // eslint-disable-next-line no-sync - if (configPythonPath && fs.existsSync(configPythonPath)) { - path = configPythonPath; - } + let notebookConfig = vscode.workspace.getConfiguration(constants.notebookConfigKey); + if (notebookConfig) { + let configPythonPath = notebookConfig[constants.pythonPathConfigKey]; + // eslint-disable-next-line no-sync + if (configPythonPath && fs.existsSync(configPythonPath)) { + path = configPythonPath; } } return path; @@ -888,7 +879,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { } public get previewFeaturesEnabled(): boolean { - return this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures'); + return vscode.workspace.getConfiguration('workbench').get('enablePreviewFeatures'); } } diff --git a/extensions/notebook/src/jupyter/jupyterServerManager.ts b/extensions/notebook/src/jupyter/jupyterServerManager.ts index c45241316b..462fc5638c 100644 --- a/extensions/notebook/src/jupyter/jupyterServerManager.ts +++ b/extensions/notebook/src/jupyter/jupyterServerManager.ts @@ -10,7 +10,6 @@ import { ServerConnection } from '@jupyterlab/services'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -import { ApiWrapper } from '../common/apiWrapper'; import { JupyterServerInstallation } from './jupyterServerInstallation'; import * as utils from '../common/utils'; import { IServerInstance } from './common'; @@ -21,18 +20,15 @@ export interface IServerManagerOptions { documentPath: string; jupyterInstallation: JupyterServerInstallation; extensionContext: vscode.ExtensionContext; - apiWrapper?: ApiWrapper; factory?: ServerInstanceFactory; } export class LocalJupyterServerManager implements nb.ServerManager, vscode.Disposable { private _serverSettings: Partial; private _onServerStarted = new vscode.EventEmitter(); private _instanceOptions: IInstanceOptions; - private _apiWrapper: ApiWrapper; private _jupyterServer: IServerInstance; factory: ServerInstanceFactory; constructor(private options: IServerManagerOptions) { - this._apiWrapper = options.apiWrapper || new ApiWrapper(); this.factory = options.factory || new ServerInstanceFactory(); } @@ -74,7 +70,7 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo public dispose(): void { this.stopServer().catch(err => { let msg = utils.getErrorMessage(err); - this._apiWrapper.showErrorMessage(localize('shutdownError', "Shutdown of Notebook server failed: {0}", msg)); + vscode.window.showErrorMessage(localize('shutdownError', "Shutdown of Notebook server failed: {0}", msg)); }); } @@ -124,7 +120,7 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo // /path2/nb2.ipynb // /path2/nb3.ipynb // ... will result in 2 notebook servers being started, one for /path1/ and one for /path2/ - let notebookDir = this._apiWrapper.getWorkspacePathFromUri(vscode.Uri.file(this.documentPath)); + let notebookDir = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(this.documentPath))?.uri.fsPath; if (!notebookDir) { let docDir; // If a folder is passed in for documentPath, use the folder instead of calling dirname diff --git a/extensions/notebook/src/prompts/adapter.ts b/extensions/notebook/src/prompts/adapter.ts index 0aa38b3167..4e14a5b38d 100644 --- a/extensions/notebook/src/prompts/adapter.ts +++ b/extensions/notebook/src/prompts/adapter.ts @@ -4,14 +4,14 @@ import PromptFactory from './factory'; import EscapeException from './escapeException'; import { IQuestion, IPrompter } from './question'; -import { ApiWrapper } from '../common/apiWrapper'; +import * as vscode from 'vscode'; // Supports simple pattern for prompting for user input and acting on this export default class CodeAdapter implements IPrompter { - public promptSingle(question: IQuestion, apiWrapper?: ApiWrapper): Promise { + public promptSingle(question: IQuestion): Promise { let questions: IQuestion[] = [question]; - return this.prompt(questions, apiWrapper).then((answers: { [key: string]: T }) => { + return this.prompt(questions).then((answers: { [key: string]: T }) => { if (answers) { let response: T = answers[question.name]; return response || undefined; @@ -20,7 +20,7 @@ export default class CodeAdapter implements IPrompter { }); } - public prompt(questions: IQuestion[], apiWrapper = new ApiWrapper()): Promise<{ [key: string]: T }> { + public prompt(questions: IQuestion[]): Promise<{ [key: string]: T }> { let answers: { [key: string]: T } = {}; // Collapse multiple questions into a set of prompt steps @@ -36,7 +36,7 @@ export default class CodeAdapter implements IPrompter { // } if (!question.shouldPrompt || question.shouldPrompt(answers) === true) { - return prompt.render(apiWrapper).then((result: any) => { + return prompt.render().then((result: any) => { answers[question.name] = result; if (question.onAnswered) { @@ -54,7 +54,7 @@ export default class CodeAdapter implements IPrompter { return undefined; } - apiWrapper.showErrorMessage(err.message); + vscode.window.showErrorMessage(err.message); }); } } diff --git a/extensions/notebook/src/prompts/confirm.ts b/extensions/notebook/src/prompts/confirm.ts index 6764408904..c3c1da282f 100644 --- a/extensions/notebook/src/prompts/confirm.ts +++ b/extensions/notebook/src/prompts/confirm.ts @@ -1,12 +1,10 @@ -'use strict'; - // This code is originally from https://github.com/DonJayamanne/bowerVSCode // License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE import Prompt from './prompt'; import LocalizedConstants = require('../common/localizedConstants'); import EscapeException from './escapeException'; -import { ApiWrapper } from '../common/apiWrapper'; +import * as vscode from 'vscode'; export default class ConfirmPrompt extends Prompt { @@ -14,7 +12,7 @@ export default class ConfirmPrompt extends Prompt { super(question); } - public render(apiWrapper: ApiWrapper): any { + public render(): any { let choices: { [id: string]: boolean } = {}; choices[LocalizedConstants.msgYes] = true; choices[LocalizedConstants.msgNo] = false; @@ -22,7 +20,7 @@ export default class ConfirmPrompt extends Prompt { let options = this.defaultQuickPickOptions; options.placeHolder = this._question.message; - return apiWrapper.showQuickPick(Object.keys(choices), options) + return vscode.window.showQuickPick(Object.keys(choices), options) .then(result => { if (result === undefined) { throw new EscapeException(); diff --git a/extensions/notebook/src/prompts/escapeException.ts b/extensions/notebook/src/prompts/escapeException.ts index e405084cbe..d6d2636d0a 100644 --- a/extensions/notebook/src/prompts/escapeException.ts +++ b/extensions/notebook/src/prompts/escapeException.ts @@ -1,3 +1,6 @@ -'use strict'; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ export default require('error-ex')('EscapeException'); diff --git a/extensions/notebook/src/prompts/factory.ts b/extensions/notebook/src/prompts/factory.ts index bd86421bd0..c99f749e78 100644 --- a/extensions/notebook/src/prompts/factory.ts +++ b/extensions/notebook/src/prompts/factory.ts @@ -1,5 +1,3 @@ -'use strict'; - // This code is originally from https://github.com/DonJayamanne/bowerVSCode // License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE diff --git a/extensions/notebook/src/prompts/prompt.ts b/extensions/notebook/src/prompts/prompt.ts index e3b26c67c4..493b2aac70 100644 --- a/extensions/notebook/src/prompts/prompt.ts +++ b/extensions/notebook/src/prompts/prompt.ts @@ -1,10 +1,7 @@ -'use strict'; - // This code is originally from https://github.com/DonJayamanne/bowerVSCode // License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE import { InputBoxOptions, QuickPickOptions } from 'vscode'; -import { ApiWrapper } from '../common/apiWrapper'; abstract class Prompt { @@ -16,7 +13,7 @@ abstract class Prompt { this._ignoreFocusOut = ignoreFocusOut ? ignoreFocusOut : false; } - public abstract render(apiWrapper: ApiWrapper): any; + public abstract render(): any; protected get defaultQuickPickOptions(): QuickPickOptions { return { diff --git a/extensions/notebook/src/prompts/question.ts b/extensions/notebook/src/prompts/question.ts index 343f15c38b..b32ee53433 100644 --- a/extensions/notebook/src/prompts/question.ts +++ b/extensions/notebook/src/prompts/question.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import vscode = require('vscode'); -import { ApiWrapper } from '../common/apiWrapper'; +import * as vscode from 'vscode'; export const confirm = 'confirm'; @@ -48,14 +47,14 @@ export interface IQuestionHandler { } export interface IPrompter { - promptSingle(question: IQuestion, apiWrapper?: ApiWrapper): Promise; + promptSingle(question: IQuestion): Promise; /** * Prompts for multiple questions * * @returns Map of question IDs to results, or undefined if * the user canceled the question session */ - prompt(questions: IQuestion[], apiWrapper?: ApiWrapper): Promise<{ [questionId: string]: any }>; + prompt(questions: IQuestion[]): Promise<{ [questionId: string]: any }>; } export interface IPromptCallback { diff --git a/extensions/notebook/src/test/book/book.test.ts b/extensions/notebook/src/test/book/book.test.ts index c6346b571b..08fe6a1e86 100644 --- a/extensions/notebook/src/test/book/book.test.ts +++ b/extensions/notebook/src/test/book/book.test.ts @@ -15,8 +15,6 @@ import { BookTreeItem, BookTreeItemType } from '../../book/bookTreeItem'; import { promisify } from 'util'; import { MockExtensionContext } from '../common/stubs'; import { exists } from '../../common/utils'; -import { AppContext } from '../../common/appContext'; -import { ApiWrapper } from '../../common/apiWrapper'; import { BookModel } from '../../book/bookModel'; import { BookTrustManager } from '../../book/bookTrustManager'; import { NavigationProviders } from '../../common/constants'; @@ -56,7 +54,6 @@ describe('BookTreeViewProviderTests', function () { let expectedMarkdown: IExpectedBookItem; let expectedExternalLink: IExpectedBookItem; let expectedBook: IExpectedBookItem; - let appContext: AppContext; this.beforeAll(async () => { mockExtensionContext = new MockExtensionContext(); @@ -103,7 +100,6 @@ describe('BookTreeViewProviderTests', function () { sections: [expectedNotebook1, expectedMarkdown, expectedExternalLink], title: 'Test Book' }; - appContext = new AppContext(mockExtensionContext, new ApiWrapper()); await fs.mkdir(rootFolderPath); await fs.mkdir(bookFolderPath); @@ -116,11 +112,10 @@ describe('BookTreeViewProviderTests', function () { await fs.writeFile(notebook2File, ''); await fs.writeFile(notebook3File, ''); await fs.writeFile(markdownFile, ''); - appContext = new AppContext(undefined, new ApiWrapper()); }); it('should initialize correctly with empty workspace array', async () => { - const bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + const bookTreeViewProvider = new BookTreeViewProvider([], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); await bookTreeViewProvider.initialized; }); @@ -130,7 +125,7 @@ describe('BookTreeViewProviderTests', function () { name: '', index: 0 }; - const bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + const bookTreeViewProvider = new BookTreeViewProvider([folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); await bookTreeViewProvider.initialized; }); @@ -145,7 +140,7 @@ describe('BookTreeViewProviderTests', function () { name: '', index: 0 }; - const bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [book, nonBook], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + const bookTreeViewProvider = new BookTreeViewProvider([book, nonBook], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); await bookTreeViewProvider.initialized; should(bookTreeViewProvider.books.length).equal(1, 'Expected book was not initialized'); }); @@ -162,8 +157,8 @@ describe('BookTreeViewProviderTests', function () { name: '', index: 0 }; - bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); - providedbookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], mockExtensionContext, true, 'providedBooksView', NavigationProviders.ProvidedBooksNavigator); + bookTreeViewProvider = new BookTreeViewProvider([folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + providedbookTreeViewProvider = new BookTreeViewProvider([], mockExtensionContext, true, 'providedBooksView', NavigationProviders.ProvidedBooksNavigator); let errorCase = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000)); await Promise.race([bookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('BookTreeViewProvider did not initialize in time'); })]); await Promise.race([providedbookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('ProvidedBooksTreeViewProvider did not initialize in time'); })]); @@ -202,7 +197,7 @@ describe('BookTreeViewProviderTests', function () { it('bookTreeViewProvider should set notebooks trusted to true on trustBook', async () => { let notebook1Path = path.join(rootFolderPath, 'Book', 'content', 'notebook1.ipynb'); - let bookTrustManager: BookTrustManager = new BookTrustManager(bookTreeViewProvider.books, appContext.apiWrapper); + let bookTrustManager: BookTrustManager = new BookTrustManager(bookTreeViewProvider.books); let isTrusted = bookTrustManager.isNotebookTrustedByDefault(vscode.Uri.file(notebook1Path).fsPath); should(isTrusted).equal(false, 'Notebook should not be trusted by default'); @@ -280,7 +275,6 @@ describe('BookTreeViewProviderTests', function () { let tableOfContentsFile: string; let bookTreeViewProvider: BookTreeViewProvider; let folder: vscode.WorkspaceFolder; - let appContext: AppContext; this.beforeAll(async () => { rootFolderPath = path.join(os.tmpdir(), `BookTestData_${uuid.v4()}`); @@ -297,11 +291,9 @@ describe('BookTreeViewProviderTests', function () { name: '', index: 0 }; - appContext = new AppContext(mockExtensionContext, new ApiWrapper()); - bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + bookTreeViewProvider = new BookTreeViewProvider([folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); let errorCase = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000)); await Promise.race([bookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('BookTreeViewProvider did not initialize in time'); })]); - appContext = new AppContext(undefined, new ApiWrapper()); }); it('should ignore toc.yml files not in _data folder', async () => { @@ -324,7 +316,6 @@ describe('BookTreeViewProviderTests', function () { let folder: vscode.WorkspaceFolder; let bookTreeViewProvider: BookTreeViewProvider; let tocFile: string; - let appContext: AppContext; this.beforeAll(async () => { rootFolderPath = path.join(os.tmpdir(), `BookTestData_${uuid.v4()}`); @@ -340,11 +331,9 @@ describe('BookTreeViewProviderTests', function () { name: '', index: 0 }; - appContext = new AppContext(mockExtensionContext, new ApiWrapper()); - bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + bookTreeViewProvider = new BookTreeViewProvider([folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); let errorCase = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000)); await Promise.race([bookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('BookTreeViewProvider did not initialize in time'); })]); - appContext = new AppContext(undefined, new ApiWrapper()); }); it('should show error message if config.yml file not found', async () => { @@ -372,7 +361,6 @@ describe('BookTreeViewProviderTests', function () { let bookTreeViewProvider: BookTreeViewProvider; let folder: vscode.WorkspaceFolder; let expectedNotebook2: IExpectedBookItem; - let appContext: AppContext; this.beforeAll(async () => { rootFolderPath = path.join(os.tmpdir(), `BookTestData_${uuid.v4()}`); @@ -400,11 +388,9 @@ describe('BookTreeViewProviderTests', function () { name: '', index: 0 }; - appContext = new AppContext(mockExtensionContext, new ApiWrapper()); - bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + bookTreeViewProvider = new BookTreeViewProvider([folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); let errorCase = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000)); await Promise.race([bookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('BookTreeViewProvider did not initialize in time'); })]); - appContext = new AppContext(undefined, new ApiWrapper()); }); it('should show error if notebook or markdown file is missing', async function (): Promise { @@ -426,7 +412,6 @@ describe('BookTreeViewProviderTests', function () { let rootFolderPath: string; let tableOfContentsFile: string; let bookTreeViewProvider: BookTreeViewProvider; - let appContext: AppContext; this.beforeAll(async () => { rootFolderPath = path.join(os.tmpdir(), `BookTestData_${uuid.v4()}`); @@ -443,11 +428,9 @@ describe('BookTreeViewProviderTests', function () { await fs.writeFile(notebook2File, ''); const mockExtensionContext = new MockExtensionContext(); - appContext = new AppContext(mockExtensionContext, new ApiWrapper()); - bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + bookTreeViewProvider = new BookTreeViewProvider([], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); let errorCase = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000)); await Promise.race([bookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('BookTreeViewProvider did not initialize in time'); })]); - appContext = new AppContext(undefined, new ApiWrapper()); }); it('should add book and initialize book on openBook', async () => { @@ -502,7 +485,6 @@ describe('BookTreeViewProviderTests', function () { let standaloneNotebookTitle: string; let standaloneNotebookFile: string; let bookTreeViewProvider: BookTreeViewProvider; - let appContext: AppContext; this.beforeAll(async () => { rootFolderPath = path.join(os.tmpdir(), `BookFolderTest_${uuid.v4()}`); @@ -527,11 +509,9 @@ describe('BookTreeViewProviderTests', function () { await fs.writeFile(standaloneNotebookFile, ''); const mockExtensionContext = new MockExtensionContext(); - appContext = new AppContext(mockExtensionContext, new ApiWrapper()); - bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); + bookTreeViewProvider = new BookTreeViewProvider([], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); let errorCase = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000)); await Promise.race([bookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('BookTreeViewProvider did not initialize in time'); })]); - appContext = new AppContext(undefined, new ApiWrapper()); }); it('should include books and notebooks when opening parent folder', async () => { diff --git a/extensions/notebook/src/test/book/bookTrustManager.test.ts b/extensions/notebook/src/test/book/bookTrustManager.test.ts index f964a65f7e..07ffa185c8 100644 --- a/extensions/notebook/src/test/book/bookTrustManager.test.ts +++ b/extensions/notebook/src/test/book/bookTrustManager.test.ts @@ -9,9 +9,9 @@ import * as TypeMoq from 'typemoq'; import * as constants from '../../common/constants'; import { IBookTrustManager, BookTrustManager } from '../../book/bookTrustManager'; import { BookTreeItem, BookTreeItemFormat, BookTreeItemType } from '../../book/bookTreeItem'; -import { ApiWrapper } from '../../common/apiWrapper'; -import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode'; +import * as vscode from 'vscode'; import { BookModel } from '../../book/bookModel'; +import * as sinon from 'sinon'; describe('BookTrustManagerTests', function () { @@ -20,36 +20,36 @@ describe('BookTrustManagerTests', function () { let trustedSubFolders: string[]; let books: BookModel[]; + afterEach(function (): void { + sinon.restore(); + }); + beforeEach(() => { trustedSubFolders = ['/SubFolder/']; // Mock Workspace Configuration - let workspaceConfigurtionMock: TypeMoq.IMock = TypeMoq.Mock.ofType(); + let workspaceConfigurtionMock: TypeMoq.IMock = TypeMoq.Mock.ofType(); workspaceConfigurtionMock.setup(config => config.get(TypeMoq.It.isValue(constants.trustedBooksConfigKey))).returns(() => [].concat(trustedSubFolders)); workspaceConfigurtionMock.setup(config => config.update(TypeMoq.It.isValue(constants.trustedBooksConfigKey), TypeMoq.It.isAny(), TypeMoq.It.isValue(false))).returns((key: string, newValues: string[]) => { trustedSubFolders.splice(0, trustedSubFolders.length, ...newValues); // Replace return Promise.resolve(); }); - // Mock Api Wrapper - let apiWrapperMock: TypeMoq.IMock = TypeMoq.Mock.ofType(); - - apiWrapperMock.setup(api => api.getWorkspaceFolders()).returns(() => [ - { - // @ts-ignore - Don't need all URI properties for this tests + sinon.replaceGetter(vscode.workspace, 'workspaceFolders', () => { + return [{ uri: { fsPath: '/temp/' }, }, { - // @ts-ignore - Don't need all URI properties for this tests uri: { fsPath: '/temp2/' } }, - ]); + ]; + }); - apiWrapperMock.setup(api => api.getConfiguration(TypeMoq.It.isValue(constants.notebookConfigKey))).returns(() => workspaceConfigurtionMock.object); + sinon.stub(vscode.workspace, 'getConfiguration').returns(workspaceConfigurtionMock.object); // Mock Book Data let bookTreeItemFormat1: BookTreeItemFormat = { @@ -108,24 +108,24 @@ describe('BookTrustManagerTests', function () { let bookModel1Mock: TypeMoq.IMock = TypeMoq.Mock.ofType(); bookModel1Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat1, undefined), new BookTreeItem(bookTreeItemFormat2, undefined)]); - bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content','sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); - bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content','sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); - bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder2','content','sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp', 'SubFolder2', 'content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined); let bookModel2Mock: TypeMoq.IMock = TypeMoq.Mock.ofType(); bookModel2Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat3, undefined)]); - bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp2','SubFolder','content','sample','notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp2', 'SubFolder', 'content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined); books = [bookModel1Mock.object, bookModel2Mock.object]; - bookTrustManager = new BookTrustManager(books, apiWrapperMock.object); + bookTrustManager = new BookTrustManager(books); }); it('should trust notebooks in a trusted book within a workspace', async () => { - let notebookUri1 = path.join(path.sep,'temp','SubFolder','content','sample', 'notebook.ipynb'); - let notebookUri2 = path.join(path.sep,'temp','SubFolder','content','sample', 'notebook2.ipynb'); + let notebookUri1 = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook.ipynb'); + let notebookUri2 = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook2.ipynb'); let isNotebook1Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri1); let isNotebook2Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri2); @@ -136,14 +136,14 @@ describe('BookTrustManagerTests', function () { }); it('should NOT trust a notebook in an untrusted book within a workspace', async () => { - let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb'); + let notebookUri = path.join(path.sep, 'temp', 'SubFolder2', 'content', 'sample', 'notebook.ipynb'); let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri); should(isNotebookTrusted).be.false('Notebook should be trusted'); }); it('should trust notebook after book has been trusted within a workspace', async () => { - let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb'); + let notebookUri = path.join(path.sep, 'temp', 'SubFolder2', 'content', 'sample', 'notebook.ipynb'); let isNotebookTrustedBeforeChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri); should(isNotebookTrustedBeforeChange).be.false('Notebook should NOT be trusted'); @@ -157,7 +157,7 @@ describe('BookTrustManagerTests', function () { }); it('should NOT trust a notebook when untrusting a book within a workspace', async () => { - let notebookUri = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb'); + let notebookUri = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook.ipynb'); let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri); should(isNotebookTrusted).be.true('Notebook should be trusted'); @@ -195,19 +195,13 @@ describe('BookTrustManagerTests', function () { beforeEach(() => { // Mock Workspace Configuration - let workspaceConfigurtionMock: TypeMoq.IMock = TypeMoq.Mock.ofType(); + let workspaceConfigurtionMock: TypeMoq.IMock = TypeMoq.Mock.ofType(); workspaceConfigurtionMock.setup(config => config.get(TypeMoq.It.isValue(constants.trustedBooksConfigKey))).returns(() => [].concat(trustedFolders)); - workspaceConfigurtionMock.setup(config => config.update(TypeMoq.It.isValue(constants.trustedBooksConfigKey), TypeMoq.It.isAny(), TypeMoq.It.isValue(ConfigurationTarget.Global))).returns((key: string, newValues: string[], target: ConfigurationTarget) => { + workspaceConfigurtionMock.setup(config => config.update(TypeMoq.It.isValue(constants.trustedBooksConfigKey), TypeMoq.It.isAny(), TypeMoq.It.isValue(vscode.ConfigurationTarget.Global))).returns((key: string, newValues: string[], target: vscode.ConfigurationTarget) => { trustedFolders.splice(0, trustedFolders.length, ...newValues); // Replace return Promise.resolve(); }); - - // Mock Api Wrapper - let apiWrapperMock: TypeMoq.IMock = TypeMoq.Mock.ofType(); - - apiWrapperMock.setup(api => api.getWorkspaceFolders()).returns(() => []); - apiWrapperMock.setup(api => api.getConfiguration(TypeMoq.It.isValue(constants.notebookConfigKey))).returns(() => workspaceConfigurtionMock.object); let bookTreeItemFormat1: BookTreeItemFormat = { contentPath: undefined, root: '/temp/SubFolder/', @@ -250,26 +244,26 @@ describe('BookTrustManagerTests', function () { let bookModel1Mock: TypeMoq.IMock = TypeMoq.Mock.ofType(); bookModel1Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat1, undefined)]); - bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); - bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined); let bookModel2Mock: TypeMoq.IMock = TypeMoq.Mock.ofType(); bookModel2Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat2, undefined)]); - bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); - bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp', 'SubFolder2', 'content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); + bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep, 'temp', 'SubFolder2', 'content', 'sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType().object); bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined); books = [bookModel1Mock.object, bookModel2Mock.object]; - bookTrustManager = new BookTrustManager(books, apiWrapperMock.object); + bookTrustManager = new BookTrustManager(books); }); it('should trust notebooks in a trusted book in a folder', async () => { bookTrustManager.setBookAsTrusted('/temp/SubFolder/'); - let notebookUri1 = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb'); - let notebookUri2 = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook2.ipynb'); + let notebookUri1 = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook.ipynb'); + let notebookUri2 = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook2.ipynb'); let isNotebook1Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri1); let isNotebook2Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri2); @@ -280,14 +274,14 @@ describe('BookTrustManagerTests', function () { }); it('should NOT trust a notebook in an untrusted book in a folder', async () => { - let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb'); + let notebookUri = path.join(path.sep, 'temp', 'SubFolder2', 'content', 'sample', 'notebook.ipynb'); let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri); should(isNotebookTrusted).be.false('Notebook should be trusted'); }); it('should trust notebook after book has been added to a folder', async () => { - let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample','notebook.ipynb'); + let notebookUri = path.join(path.sep, 'temp', 'SubFolder2', 'content', 'sample', 'notebook.ipynb'); let isNotebookTrustedBeforeChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri); should(isNotebookTrustedBeforeChange).be.false('Notebook should NOT be trusted'); @@ -301,7 +295,7 @@ describe('BookTrustManagerTests', function () { it('should NOT trust a notebook when untrusting a book in folder', async () => { bookTrustManager.setBookAsTrusted('/temp/SubFolder/'); - let notebookUri = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb'); + let notebookUri = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notebook.ipynb'); let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri); should(isNotebookTrusted).be.true('Notebook should be trusted'); @@ -323,7 +317,7 @@ describe('BookTrustManagerTests', function () { it('should NOT trust notebook inside trusted subfolder when absent in table of contents ', async () => { bookTrustManager.setBookAsTrusted('/temp/SubFolder/'); - let notebookUri = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notInToc.ipynb'); + let notebookUri = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notInToc.ipynb'); let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri); should(isNotebookTrusted).be.false('Notebook should NOT be trusted'); diff --git a/extensions/notebook/src/test/common/notebookUtils.test.ts b/extensions/notebook/src/test/common/notebookUtils.test.ts index fe4641abb4..ef88ca59b9 100644 --- a/extensions/notebook/src/test/common/notebookUtils.test.ts +++ b/extensions/notebook/src/test/common/notebookUtils.test.ts @@ -8,25 +8,24 @@ import { NotebookUtils } from '../../common/notebookUtils'; import * as should from 'should'; import * as vscode from 'vscode'; import * as TypeMoq from 'typemoq'; +import * as sinon from 'sinon'; import * as os from 'os'; import * as path from 'path'; import * as uuid from 'uuid'; import { promises as fs } from 'fs'; -import { ApiWrapper } from '../../common/apiWrapper'; import { tryDeleteFile } from './testUtils'; import { CellTypes } from '../../contracts/content'; describe('notebookUtils Tests', function (): void { - let notebookUtils: NotebookUtils; - let apiWrapperMock: TypeMoq.IMock; + let notebookUtils: NotebookUtils = new NotebookUtils(); + let showErrorMessageSpy: sinon.SinonSpy; - this.beforeAll(async function(): Promise { - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + beforeEach(function(): void { + showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); }); - beforeEach(function (): void { - apiWrapperMock = TypeMoq.Mock.ofInstance(new ApiWrapper()); - notebookUtils = new NotebookUtils(apiWrapperMock.object); + afterEach(function(): void { + sinon.restore(); }); this.afterAll(async function (): Promise { @@ -74,7 +73,7 @@ describe('notebookUtils Tests', function (): void { const notebookUri = vscode.Uri.file(notebookPath); try { await fs.writeFile(notebookPath, ''); - apiWrapperMock.setup(x => x.showOpenDialog(TypeMoq.It.isAny())).returns(() => Promise.resolve([notebookUri])); + sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([notebookUri])); await notebookUtils.openNotebook(); should(azdata.nb.notebookDocuments.find(doc => doc.fileName === notebookUri.fsPath)).not.be.undefined(); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -84,88 +83,86 @@ describe('notebookUtils Tests', function (): void { }); it('shows error if unexpected error is thrown', async function (): Promise { - apiWrapperMock.setup(x => x.showOpenDialog(TypeMoq.It.isAny())).throws(new Error('Unexpected error')); - apiWrapperMock.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns(() => Promise.resolve('')); + sinon.stub(vscode.window, 'showOpenDialog').throws(new Error('Unexpected error')); await notebookUtils.openNotebook(); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should have been called'); }); }); describe('runActiveCell', function () { it('shows error when no notebook visible', async function (): Promise { - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => undefined); await notebookUtils.runActiveCell(); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should have been called'); }); it('does not show error when notebook visible', async function (): Promise { let mockNotebookEditor = TypeMoq.Mock.ofType(); - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => mockNotebookEditor.object); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => mockNotebookEditor.object); await notebookUtils.runActiveCell(); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.never()); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); mockNotebookEditor.verify(x => x.runCell(TypeMoq.It.isAny()), TypeMoq.Times.once()); }); }); describe('clearActiveCellOutput', function () { it('shows error when no notebook visible', async function (): Promise { - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => undefined); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => undefined); await notebookUtils.clearActiveCellOutput(); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); - }); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should be called exactly once'); +}); it('does not show error when notebook visible', async function (): Promise { let mockNotebookEditor = TypeMoq.Mock.ofType(); - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => mockNotebookEditor.object); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => mockNotebookEditor.object); await notebookUtils.clearActiveCellOutput(); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.never()); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); mockNotebookEditor.verify(x => x.clearOutput(TypeMoq.It.isAny()), TypeMoq.Times.once()); }); }); describe('runAllCells', function () { it('shows error when no notebook visible', async function (): Promise { - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => undefined); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => undefined); await notebookUtils.runAllCells(); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should be called exactly once'); }); it('does not show error when notebook visible', async function (): Promise { let mockNotebookEditor = TypeMoq.Mock.ofType(); - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => mockNotebookEditor.object); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => mockNotebookEditor.object); await notebookUtils.runAllCells(); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.never()); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); mockNotebookEditor.verify(x => x.runAllCells(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); }); }); describe('addCell', function () { it('shows error when no notebook visible for code cell', async function (): Promise { - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => undefined); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => undefined); await notebookUtils.addCell('code'); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should be called exactly once'); }); it('shows error when no notebook visible for markdown cell', async function (): Promise { - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => undefined); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => undefined); await notebookUtils.addCell('markdown'); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should be called exactly once'); }); it('does not show error when notebook visible for code cell', async function (): Promise { const notebookEditor = await notebookUtils.newNotebook(undefined); - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => notebookEditor); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => notebookEditor); await notebookUtils.addCell('code'); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.never()); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should never be called'); should(notebookEditor.document.cells.length).equal(1); should(notebookEditor.document.cells[0].contents.cell_type).equal(CellTypes.Code); }); it('does not show error when notebook visible for markdown cell', async function (): Promise { const notebookEditor = await notebookUtils.newNotebook(undefined); - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => notebookEditor); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => notebookEditor); await notebookUtils.addCell('markdown'); - apiWrapperMock.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.never()); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should never be called'); should(notebookEditor.document.cells.length).equal(1); should(notebookEditor.document.cells[0].contents.cell_type).equal(CellTypes.Markdown); }); @@ -174,8 +171,8 @@ describe('notebookUtils Tests', function (): void { describe('analyzeNotebook', function () { it('creates cell when oeContext exists', async function (): Promise { const notebookEditor = await notebookUtils.newNotebook(undefined); - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => notebookEditor); - apiWrapperMock.setup(x => x.showNotebookDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(notebookEditor)); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => notebookEditor); + sinon.stub(azdata.nb, 'showNotebookDocument').returns(Promise.resolve(notebookEditor)); const oeContext: azdata.ObjectExplorerContext = { connectionProfile: undefined, isConnectionNode: true, @@ -197,8 +194,8 @@ describe('notebookUtils Tests', function (): void { it('does not create new cell when oeContext does not exist', async function (): Promise { const notebookEditor = await notebookUtils.newNotebook(undefined); - apiWrapperMock.setup(x => x.getActiveNotebookEditor()).returns(() => notebookEditor); - apiWrapperMock.setup(x => x.showNotebookDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(notebookEditor)); + sinon.replaceGetter(azdata.nb, 'activeNotebookEditor', () => notebookEditor); + sinon.stub(azdata.nb, 'showNotebookDocument').returns(Promise.resolve(notebookEditor)); await notebookUtils.analyzeNotebook(); should(notebookEditor.document.cells.length).equal(0, 'No cells should exist'); }); diff --git a/extensions/notebook/src/test/common/prompt.test.ts b/extensions/notebook/src/test/common/prompt.test.ts index 7e0d008895..2fe8996019 100644 --- a/extensions/notebook/src/test/common/prompt.test.ts +++ b/extensions/notebook/src/test/common/prompt.test.ts @@ -3,47 +3,51 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as TypeMoq from 'typemoq'; +import * as should from 'should'; +import * as sinon from 'sinon'; +import * as vscode from 'vscode'; import { IPrompter, confirm, IQuestion } from '../../prompts/question'; import CodeAdapter from '../../prompts/adapter'; -import { ApiWrapper } from '../../common/apiWrapper'; describe('Prompt', () => { let prompter: IPrompter; - let mockApiWrapper: TypeMoq.IMock; - + let showErrorMessageSpy: sinon.SinonSpy; before(function () { prompter = new CodeAdapter(); }); - beforeEach(function () { - mockApiWrapper = TypeMoq.Mock.ofType(); - mockApiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())); + beforeEach(function(): void { + showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); + }); + + afterEach(function () { + sinon.restore(); }); it('Should not find prompt for invalid question type', async function (): Promise { + const showQuickPickSpy = sinon.spy(vscode.window, 'showQuickPick'); await prompter.promptSingle({ type: 'invalidType', message: 'sample message', default: false - }, mockApiWrapper.object); - mockApiWrapper.verify(s => s.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); - mockApiWrapper.verify(s => s.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + }); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should be called exactly once'); + should(showQuickPickSpy.notCalled).be.true('showQuickPick should never have been called'); }); it('Should find prompt for confirm type', async function (): Promise { - mockApiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('Yes')); + const quickPickSpy = sinon.stub(vscode.window, 'showQuickPick').returns(Promise.resolve('Yes') as any); await prompter.promptSingle({ type: confirm, message: 'sample message', default: false - }, mockApiWrapper.object); - mockApiWrapper.verify(s => s.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.never()); - mockApiWrapper.verify(s => s.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); + }); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should never be called'); + should(quickPickSpy.calledOnce).be.true('showQuickPick should have been called once'); }); }); diff --git a/extensions/notebook/src/test/configurePython.test.ts b/extensions/notebook/src/test/configurePython.test.ts index 8c6c5e7187..9c9463bda8 100644 --- a/extensions/notebook/src/test/configurePython.test.ts +++ b/extensions/notebook/src/test/configurePython.test.ts @@ -5,7 +5,6 @@ import * as azdata from 'azdata'; import * as TypeMoq from 'typemoq'; -import { ApiWrapper } from '../common/apiWrapper'; import { ConfigurePythonWizard, ConfigurePythonModel } from '../dialog/configurePython/configurePythonWizard'; import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation'; import { ConfigurePathPage } from '../dialog/configurePython/configurePathPage'; @@ -16,7 +15,6 @@ import { TestContext, createViewContext, TestButton } from './common'; import { EventEmitter } from 'vscode'; describe('Configure Python Wizard', function () { - let apiWrapper: ApiWrapper = new ApiWrapper(); let testWizard: ConfigurePythonWizard; let viewContext: TestContext; let testInstallation: JupyterServerInstallation; @@ -45,21 +43,21 @@ describe('Configure Python Wizard', function () { // These wizard tests are disabled due to errors with disposable objects // // it('Start wizard test', async () => { - // let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation); + // let wizard = new ConfigurePythonWizard(testInstallation); // await wizard.start(); // await wizard.close(); // await should(wizard.setupComplete).be.resolved(); // }); // it('Reject setup on cancel test', async () => { - // let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation); + // let wizard = new ConfigurePythonWizard(testInstallation); // await wizard.start(undefined, true); // await wizard.close(); // await should(wizard.setupComplete).be.rejected(); // }); // it('Error message test', async () => { - // let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation); + // let wizard = new ConfigurePythonWizard(testInstallation); // await wizard.start(); // should(wizard.wizard.message).be.undefined(); @@ -86,7 +84,7 @@ describe('Configure Python Wizard', function () { }; let page = azdata.window.createWizardPage('Page 1'); - let configurePathPage = new ConfigurePathPage(apiWrapper, testWizard, page, model, viewContext.view); + let configurePathPage = new ConfigurePathPage(testWizard, page, model, viewContext.view); should(await configurePathPage.initialize()).be.true(); @@ -108,7 +106,7 @@ describe('Configure Python Wizard', function () { }; let page = azdata.window.createWizardPage('Page 2'); - let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view); + let pickPackagesPage = new PickPackagesPage(testWizard, page, model, viewContext.view); should(await pickPackagesPage.initialize()).be.true(); @@ -132,7 +130,7 @@ describe('Configure Python Wizard', function () { }; let page = azdata.window.createWizardPage('Page 2'); - let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view); + let pickPackagesPage = new PickPackagesPage(testWizard, page, model, viewContext.view); should(await pickPackagesPage.initialize()).be.true(); diff --git a/extensions/notebook/src/test/managePackages/managePackagesDialog.test.ts b/extensions/notebook/src/test/managePackages/managePackagesDialog.test.ts index 58dafc17ee..9155e875b1 100644 --- a/extensions/notebook/src/test/managePackages/managePackagesDialog.test.ts +++ b/extensions/notebook/src/test/managePackages/managePackagesDialog.test.ts @@ -5,12 +5,12 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as TypeMoq from 'typemoq'; +import * as should from 'should'; import { ManagePackagesDialog } from '../../dialog/managePackages/managePackagesDialog'; import { ManagePackagesDialogModel } from '../../dialog/managePackages/managePackagesDialogModel'; import { IPackageManageProvider, IPackageLocation } from '../../types'; import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider'; import { InstalledPackagesTab } from '../../dialog/managePackages/installedPackagesTab'; -import should = require('should'); interface TestContext { view: azdata.ModelView; diff --git a/extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts b/extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts index f5cd8f7c25..152eb66dd7 100644 --- a/extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts +++ b/extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts @@ -19,7 +19,7 @@ interface TestContext { describe('Manage Packages', () => { let jupyterServerInstallation: JupyterServerInstallation; beforeEach(() => { - jupyterServerInstallation = new JupyterServerInstallation(undefined, undefined, undefined); + jupyterServerInstallation = new JupyterServerInstallation(undefined, undefined); }); it('Should throw exception given undefined providers', async function (): Promise { diff --git a/extensions/notebook/src/test/model/completionItemProvider.test.ts b/extensions/notebook/src/test/model/completionItemProvider.test.ts index 69cad03d73..f70a774ea7 100644 --- a/extensions/notebook/src/test/model/completionItemProvider.test.ts +++ b/extensions/notebook/src/test/model/completionItemProvider.test.ts @@ -11,7 +11,6 @@ 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'; @@ -22,7 +21,6 @@ describe('Completion Item Provider', function () { 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; @@ -31,8 +29,7 @@ describe('Completion Item Provider', function () { let token: vscode.CancellationToken; this.beforeAll(async () => { - mockApiWrapper = TypeMoq.Mock.ofType(); - notebookUtils = new NotebookUtils(new ApiWrapper()); + notebookUtils = new NotebookUtils(); mockServerManager = TypeMoq.Mock.ofType(); testEvent = new vscode.EventEmitter(); token = { @@ -47,7 +44,7 @@ describe('Completion Item Provider', function () { mockSessionManager = TypeMoq.Mock.ofType(); mockJupyterSession = TypeMoq.Mock.ofType(); kernel = new TestKernel(true, true); - notebookManager = new JupyterNotebookManager(mockServerManager.object, mockSessionManager.object, mockApiWrapper.object); + notebookManager = new JupyterNotebookManager(mockServerManager.object, mockSessionManager.object); notebookProviderMock = TypeMoq.Mock.ofType(); notebookProviderMock.setup(n => n.getNotebookManager(TypeMoq.It.isAny())).returns(() => Promise.resolve(notebookManager)); completionItemProvider = new NotebookCompletionItemProvider(notebookProviderMock.object); diff --git a/extensions/notebook/src/test/model/jupyterController.test.ts b/extensions/notebook/src/test/model/jupyterController.test.ts index 74e80659b2..c5919ea5b2 100644 --- a/extensions/notebook/src/test/model/jupyterController.test.ts +++ b/extensions/notebook/src/test/model/jupyterController.test.ts @@ -7,8 +7,8 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as should from 'should'; import * as TypeMoq from 'typemoq'; +import * as sinon from 'sinon'; -import { ApiWrapper } from '../../common/apiWrapper'; import { AppContext } from '../../common/appContext'; import { JupyterController } from '../../jupyter/jupyterController'; import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider'; @@ -16,24 +16,24 @@ import { MockExtensionContext } from '../common/stubs'; import { NotebookUtils } from '../../common/notebookUtils'; describe('Jupyter Controller', function () { - let mockExtensionContext: vscode.ExtensionContext; - let appContext: AppContext; + let mockExtensionContext: vscode.ExtensionContext = new MockExtensionContext(); + let appContext = new AppContext(mockExtensionContext); let controller: JupyterController; - let mockApiWrapper: TypeMoq.IMock; - let connection: azdata.connection.ConnectionProfile; - - this.beforeAll(() => { - mockExtensionContext = new MockExtensionContext(); - connection = new azdata.connection.ConnectionProfile(); - }); + let connection: azdata.connection.ConnectionProfile = new azdata.connection.ConnectionProfile(); + let showErrorMessageSpy: sinon.SinonSpy; this.beforeEach(() => { - mockApiWrapper = TypeMoq.Mock.ofType(); - mockApiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); }); - appContext = new AppContext(mockExtensionContext, mockApiWrapper.object); + showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); + sinon.stub(azdata.connection, 'getCurrentConnection').returns(Promise.resolve(connection)); + sinon.stub(azdata.tasks, 'registerTask'); + sinon.stub(vscode.commands, 'registerCommand'); controller = new JupyterController(appContext); }); + this.afterEach(function (): void { + sinon.restore(); + }); + it('should activate new JupyterController successfully', async () => { should(controller.extensionContext).deepEqual(appContext.extensionContext, 'Extension context should be passed through'); should(controller.jupyterInstallation).equal(undefined, 'JupyterInstallation should be undefined before controller activation'); @@ -65,13 +65,13 @@ describe('Jupyter Controller', function () { it('should show error message for doManagePackages before activation', async () => { await controller.doManagePackages(); - mockApiWrapper.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should be called'); }); it('should not show error message for doManagePackages after activation', async () => { await controller.activate(); await controller.doManagePackages(); - mockApiWrapper.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.never()); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not be called'); }); it('Returns expected values from notebook provider', async () => { @@ -85,7 +85,7 @@ describe('Jupyter Controller', function () { it('Returns notebook manager for real notebook editor', async () => { await controller.activate(); - let notebookUtils = new NotebookUtils(mockApiWrapper.object); + let notebookUtils = new NotebookUtils(); const notebookEditor = await notebookUtils.newNotebook(undefined); let notebookManager = await controller.notebookProvider.getNotebookManager(notebookEditor.document.uri); should(controller.notebookProvider.notebookManagerCount).equal(1); diff --git a/extensions/notebook/src/test/model/notebookManager.test.ts b/extensions/notebook/src/test/model/notebookManager.test.ts index 494edd26cb..04b6566c59 100644 --- a/extensions/notebook/src/test/model/notebookManager.test.ts +++ b/extensions/notebook/src/test/model/notebookManager.test.ts @@ -12,7 +12,6 @@ import 'mocha'; import { LocalJupyterServerManager, ServerInstanceFactory, IServerManagerOptions } from '../../jupyter/jupyterServerManager'; import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; import { Deferred } from '../../common/promise'; -import { ApiWrapper } from '../../common/apiWrapper'; import { MockExtensionContext } from '../common/stubs'; import { JupyterSessionManager } from '../../jupyter/jupyterSessionManager'; import { JupyterNotebookManager } from '../../jupyter/jupyterNotebookManager'; @@ -28,15 +27,11 @@ describe('Jupyter Notebook Manager', function (): void { let sessionManager: JupyterSessionManager; let notebookManager: JupyterNotebookManager; let deferredInstall: Deferred; - let mockApiWrapper: TypeMoq.IMock; let mockExtensionContext: MockExtensionContext; let mockFactory: TypeMoq.IMock; let serverManagerOptions: IServerManagerOptions; beforeEach(() => { mockExtensionContext = new MockExtensionContext(); - mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper); - mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny())); - mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined); mockFactory = TypeMoq.Mock.ofType(ServerInstanceFactory); deferredInstall = new Deferred(); @@ -48,7 +43,6 @@ describe('Jupyter Notebook Manager', function (): void { documentPath: expectedPath, jupyterInstallation: mockInstall.object, extensionContext: mockExtensionContext, - apiWrapper: mockApiWrapper.object, factory: mockFactory.object }; serverManager = new LocalJupyterServerManager(serverManagerOptions); @@ -90,7 +84,7 @@ describe('Jupyter Notebook Manager', function (): void { it('Session and server managers should be shutdown/stopped on dispose', async function(): Promise { let sessionManager = TypeMoq.Mock.ofType(); let serverManager = TypeMoq.Mock.ofType(); - notebookManager = new JupyterNotebookManager(serverManager.object, sessionManager.object, mockApiWrapper.object); + notebookManager = new JupyterNotebookManager(serverManager.object, sessionManager.object); sessionManager.setup(s => s.shutdownAll()).returns(() => new Promise((resolve) => resolve())); serverManager.setup(s => s.stopServer()).returns(() => new Promise((resolve) => resolve())); diff --git a/extensions/notebook/src/test/model/serverInstance.test.ts b/extensions/notebook/src/test/model/serverInstance.test.ts index 99f4596892..72eb800a3b 100644 --- a/extensions/notebook/src/test/model/serverInstance.test.ts +++ b/extensions/notebook/src/test/model/serverInstance.test.ts @@ -10,7 +10,6 @@ import { ChildProcess } from 'child_process'; import 'mocha'; import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; -import { ApiWrapper } from '../..//common/apiWrapper'; import { PerFolderServerInstance, ServerInstanceUtils } from '../../jupyter/serverInstance'; import { MockOutputChannel } from '../common/stubs'; import * as testUtils from '../common/testUtils'; @@ -25,14 +24,10 @@ describe('Jupyter server instance', function (): void { let expectedPath = 'mydir/notebook.ipynb'; let mockInstall: TypeMoq.IMock; let mockOutputChannel: TypeMoq.IMock; - let mockApiWrapper: TypeMoq.IMock; let mockUtils: TypeMoq.IMock; let serverInstance: PerFolderServerInstance; beforeEach(() => { - mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper); - mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny())); - mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined); mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root'); mockOutputChannel = TypeMoq.Mock.ofType(MockOutputChannel); mockInstall.setup(i => i.outputChannel).returns(() => mockOutputChannel.object); diff --git a/extensions/notebook/src/test/model/serverManager.test.ts b/extensions/notebook/src/test/model/serverManager.test.ts index fbc076b3ed..1ec59ed1c8 100644 --- a/extensions/notebook/src/test/model/serverManager.test.ts +++ b/extensions/notebook/src/test/model/serverManager.test.ts @@ -13,7 +13,6 @@ import { JupyterServerInstanceStub } from '../common'; import { LocalJupyterServerManager, ServerInstanceFactory } from '../../jupyter/jupyterServerManager'; import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; import { Deferred } from '../../common/promise'; -import { ApiWrapper } from '../../common/apiWrapper'; import * as testUtils from '../common/testUtils'; import { IServerInstance } from '../../jupyter/common'; import { MockExtensionContext } from '../common/stubs'; @@ -26,14 +25,10 @@ describe('Local Jupyter Server Manager', function (): void { let expectedPath = 'my/notebook.ipynb'; let serverManager: LocalJupyterServerManager; let deferredInstall: Deferred; - let mockApiWrapper: TypeMoq.IMock; let mockExtensionContext: MockExtensionContext; let mockFactory: TypeMoq.IMock; beforeEach(() => { mockExtensionContext = new MockExtensionContext(); - mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper); - mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny())); - mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined); mockFactory = TypeMoq.Mock.ofType(ServerInstanceFactory); deferredInstall = new Deferred(); @@ -45,7 +40,6 @@ describe('Local Jupyter Server Manager', function (): void { documentPath: expectedPath, jupyterInstallation: mockInstall.object, extensionContext: mockExtensionContext, - apiWrapper: mockApiWrapper.object, factory: mockFactory.object }); }); diff --git a/extensions/notebook/yarn.lock b/extensions/notebook/yarn.lock index ec2a186a59..c1ccd4e65a 100644 --- a/extensions/notebook/yarn.lock +++ b/extensions/notebook/yarn.lock @@ -281,6 +281,42 @@ dependencies: "@phosphor/algorithm" "^1.1.2" +"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d" + integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@sinonjs/formatio@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089" + integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ== + dependencies: + "@sinonjs/commons" "^1" + "@sinonjs/samsam" "^5.0.2" + +"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.0.3.tgz#86f21bdb3d52480faf0892a480c9906aa5a52938" + integrity sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + "@types/adm-zip@^0.4.32": version "0.4.32" resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.4.32.tgz#6de01309af60677065d2e52b417a023303220931" @@ -371,6 +407,18 @@ "@types/glob" "*" "@types/node" "*" +"@types/sinon@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1" + integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" + integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== + "@types/tar@^4.0.3": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.3.tgz#e2cce0b8ff4f285293243f5971bd7199176ac489" @@ -663,6 +711,11 @@ diff@3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -948,6 +1001,11 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -1089,6 +1147,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +just-extend@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4" + integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA== + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.4: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -1250,6 +1318,17 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +nise@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd" + integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^6.0.0" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + node-fetch@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" @@ -1282,6 +1361,13 @@ path-posix@~1.0.0: resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f" integrity sha1-BrJhE/Vr6rBCVFojv6iAA8ysJg8= +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -1461,6 +1547,19 @@ should@^13.2.3: should-type-adaptors "^1.0.1" should-util "^1.0.0" +sinon@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d" + integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A== + dependencies: + "@sinonjs/commons" "^1.7.2" + "@sinonjs/fake-timers" "^6.0.1" + "@sinonjs/formatio" "^5.0.1" + "@sinonjs/samsam" "^5.0.3" + diff "^4.0.2" + nise "^4.0.1" + supports-color "^7.1.0" + source-map@^0.5.0: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -1580,6 +1679,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-detect@4.0.8, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + typemoq@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8"