From 9b1eb53665791c77729ae1a01a7781a2dee8820d Mon Sep 17 00:00:00 2001 From: Maddy <12754347+MaddyDev@users.noreply.github.com> Date: Thu, 16 Jul 2020 10:55:46 -0700 Subject: [PATCH] Test/highlight correct nb (#11348) * unit test of the method * stash changes * revealActiveDocumentInViewlet test * separate tests * test on activate * added tests * feedback changes * naming change --- extensions/notebook/src/book/bookTreeView.ts | 14 +- extensions/notebook/src/common/appContext.ts | 8 + extensions/notebook/src/common/constants.ts | 2 + extensions/notebook/src/extension.ts | 14 +- .../notebook/src/test/book/book.test.ts | 196 ++++++++++++++---- extensions/notebook/src/types.d.ts | 3 + 6 files changed, 185 insertions(+), 52 deletions(-) diff --git a/extensions/notebook/src/book/bookTreeView.ts b/extensions/notebook/src/book/bookTreeView.ts index 8f15b4f0d3..40c23e7dad 100644 --- a/extensions/notebook/src/book/bookTreeView.ts +++ b/extensions/notebook/src/book/bookTreeView.ts @@ -230,7 +230,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { + async revealActiveDocumentInViewlet(uri?: vscode.Uri, shouldReveal: boolean = true): Promise { let bookItem: BookTreeItem; let notebookPath: string; // If no uri is passed in, try to use the current active notebook editor @@ -242,15 +242,16 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { @@ -535,9 +536,4 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider itemPath.toLowerCase().indexOf(b.bookPath.toLowerCase()) > -1); - return selectedBook; - } } diff --git a/extensions/notebook/src/common/appContext.ts b/extensions/notebook/src/common/appContext.ts index d36b62c6ad..a7407d4599 100644 --- a/extensions/notebook/src/common/appContext.ts +++ b/extensions/notebook/src/common/appContext.ts @@ -5,6 +5,8 @@ import * as vscode from 'vscode'; import { NotebookUtils } from './notebookUtils'; +import { BookTreeViewProvider } from '../book/bookTreeView'; +import { NavigationProviders, BOOKS_VIEWID, PROVIDED_BOOKS_VIEWID } from './constants'; /** * Global context for the application @@ -12,8 +14,14 @@ import { NotebookUtils } from './notebookUtils'; export class AppContext { public readonly notebookUtils: NotebookUtils; + public readonly bookTreeViewProvider: BookTreeViewProvider; + public readonly providedBookTreeViewProvider: BookTreeViewProvider; constructor(public readonly extensionContext: vscode.ExtensionContext) { this.notebookUtils = new NotebookUtils(); + + let workspaceFolders = vscode.workspace.workspaceFolders?.slice() ?? []; + this.bookTreeViewProvider = new BookTreeViewProvider(workspaceFolders, extensionContext, false, BOOKS_VIEWID, NavigationProviders.NotebooksNavigator); + this.providedBookTreeViewProvider = new BookTreeViewProvider([], extensionContext, true, PROVIDED_BOOKS_VIEWID, NavigationProviders.ProvidedBooksNavigator); } } diff --git a/extensions/notebook/src/common/constants.ts b/extensions/notebook/src/common/constants.ts index 9a81283c74..6a81479f0f 100644 --- a/extensions/notebook/src/common/constants.ts +++ b/extensions/notebook/src/common/constants.ts @@ -40,6 +40,8 @@ export const sparkScalaDisplayName = 'Spark | Scala'; export const sparkRDisplayName = 'Spark | R'; export const powershellDisplayName = 'PowerShell'; export const allKernelsName = 'All Kernels'; +export const BOOKS_VIEWID = 'bookTreeView'; +export const PROVIDED_BOOKS_VIEWID = 'providedBooksView'; export const visitedNotebooksMementoKey = 'notebooks.visited'; diff --git a/extensions/notebook/src/extension.ts b/extensions/notebook/src/extension.ts index cd6a4193b4..f9597cecbf 100644 --- a/extensions/notebook/src/extension.ts +++ b/extensions/notebook/src/extension.ts @@ -13,13 +13,10 @@ import { AppContext } from './common/appContext'; import { IExtensionApi, IPackageManageProvider } from './types'; import { CellType } from './contracts/content'; import { NotebookUriHandler } from './protocol/notebookUriHandler'; -import { BookTreeViewProvider } from './book/bookTreeView'; -import { NavigationProviders, BuiltInCommands, unsavedBooksContextKey } from './common/constants'; +import { BuiltInCommands, unsavedBooksContextKey } from './common/constants'; const localize = nls.loadMessageBundle(); -const BOOKS_VIEWID = 'bookTreeView'; -const PROVIDED_BOOKS_VIEWID = 'providedBooksView'; let controller: JupyterController; type ChooseCellType = { label: string, id: CellType }; @@ -115,10 +112,10 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi return undefined; } - let workspaceFolders = vscode.workspace.workspaceFolders?.slice() ?? []; - const bookTreeViewProvider = new BookTreeViewProvider(workspaceFolders, extensionContext, false, BOOKS_VIEWID, NavigationProviders.NotebooksNavigator); + + const bookTreeViewProvider = appContext.bookTreeViewProvider; await bookTreeViewProvider.initialized; - const providedBookTreeViewProvider = new BookTreeViewProvider([], extensionContext, true, PROVIDED_BOOKS_VIEWID, NavigationProviders.ProvidedBooksNavigator); + const providedBookTreeViewProvider = appContext.providedBookTreeViewProvider; await providedBookTreeViewProvider.initialized; azdata.nb.onDidChangeActiveNotebookEditor(e => { @@ -146,6 +143,9 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi }, getPackageManagers() { return controller.packageManageProviders; + }, + getAppContext() { + return appContext; } }; } diff --git a/extensions/notebook/src/test/book/book.test.ts b/extensions/notebook/src/test/book/book.test.ts index 08fe6a1e86..a6156bc9e1 100644 --- a/extensions/notebook/src/test/book/book.test.ts +++ b/extensions/notebook/src/test/book/book.test.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as should from 'should'; import * as path from 'path'; @@ -18,6 +19,9 @@ import { exists } from '../../common/utils'; import { BookModel } from '../../book/bookModel'; import { BookTrustManager } from '../../book/bookTrustManager'; import { NavigationProviders } from '../../common/constants'; +import { readBookError } from '../../common/localizedConstants'; +import * as sinon from 'sinon'; +import { AppContext } from '../../common/appContext'; export interface IExpectedBookItem { title: string; @@ -28,23 +32,24 @@ export interface IExpectedBookItem { nextUri?: string | undefined; } -export function equalBookItems(book: BookTreeItem, expectedBook: IExpectedBookItem): void { - should(book.title).equal(expectedBook.title); +export function equalBookItems(book: BookTreeItem, expectedBook: IExpectedBookItem, errorMsg?: string): void { + should(book.title).equal(expectedBook.title, `Book titles do not match, expected ${expectedBook?.title} and got ${book?.title}`); should(path.posix.parse(book.uri)).deepEqual(path.posix.parse(expectedBook.url)); if (expectedBook.previousUri || expectedBook.nextUri) { let prevUri = book.previousUri ? book.previousUri.toLocaleLowerCase() : undefined; let expectedPrevUri = expectedBook.previousUri ? expectedBook.previousUri.replace(/\\/g, '/') : undefined; - should(prevUri).equal(expectedPrevUri); + should(prevUri).equal(expectedPrevUri, errorMsg ?? `PreviousUri\'s do not match, expected ${expectedPrevUri} and got ${prevUri}`); let nextUri = book.nextUri ? book.nextUri.toLocaleLowerCase() : undefined; let expectedNextUri = expectedBook.nextUri ? expectedBook.nextUri.replace(/\\/g, '/') : undefined; - should(nextUri).equal(expectedNextUri); + should(nextUri).equal(expectedNextUri, errorMsg ?? `NextUri\'s do not match, expected ${expectedNextUri} and got ${nextUri}`); } } -describe('BookTreeViewProviderTests', function () { +describe('BooksTreeViewTests', function () { describe('BookTreeViewProvider', () => { let mockExtensionContext: vscode.ExtensionContext; + let appContext: AppContext; let nonBookFolderPath: string; let bookFolderPath: string; let rootFolderPath: string; @@ -114,6 +119,13 @@ describe('BookTreeViewProviderTests', function () { await fs.writeFile(markdownFile, ''); }); + it('bookProviders should be initialized on extension activate', async () => { + appContext = (await vscode.extensions.getExtension('Microsoft.notebook').activate()).getAppContext(); + should(appContext).not.be.undefined(); + should(appContext.bookTreeViewProvider).not.be.undefined(); + should(appContext.providedBookTreeViewProvider).not.be.undefined(); + }); + it('should initialize correctly with empty workspace array', async () => { const bookTreeViewProvider = new BookTreeViewProvider([], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); await bookTreeViewProvider.initialized; @@ -145,27 +157,24 @@ describe('BookTreeViewProviderTests', function () { should(bookTreeViewProvider.books.length).equal(1, 'Expected book was not initialized'); }); - describe('getChildren', function (): void { + describe('bookTreeViewProvider', function (): void { let bookTreeViewProvider: BookTreeViewProvider; - let providedbookTreeViewProvider: BookTreeViewProvider; let book: BookTreeItem; let notebook1: BookTreeItem; + let notebook2: BookTreeItem; this.beforeAll(async () => { - let folder: vscode.WorkspaceFolder = { - uri: vscode.Uri.file(rootFolderPath), - name: '', - index: 0 - }; - bookTreeViewProvider = new BookTreeViewProvider([folder], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator); - providedbookTreeViewProvider = new BookTreeViewProvider([], mockExtensionContext, true, 'providedBooksView', NavigationProviders.ProvidedBooksNavigator); + bookTreeViewProvider = appContext.bookTreeViewProvider; 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'); })]); - await providedbookTreeViewProvider.openBook(bookFolderPath, undefined, false, false); + await bookTreeViewProvider.openBook(bookFolderPath, undefined, false, false); }); - it('bookTreeViewProvider should return all book nodes when element is undefined', async function (): Promise { + afterEach(function(): void { + sinon.restore(); + }); + + it('getChildren should return all book nodes when element is undefined', async function (): Promise { const children = await bookTreeViewProvider.getChildren(); should(children).be.Array(); should(children.length).equal(1); @@ -173,7 +182,7 @@ describe('BookTreeViewProviderTests', function () { should(book.title).equal(expectedBook.title); }); - it('bookTreeViewProvider should return all page nodes when element is a book', async function (): Promise { + it('getChildren should return all page nodes when element is a book', async function (): Promise { const children = await bookTreeViewProvider.getChildren(book); should(children).be.Array(); should(children.length).equal(3); @@ -185,17 +194,17 @@ describe('BookTreeViewProviderTests', function () { equalBookItems(externalLink, expectedExternalLink); }); - it('bookTreeViewProvider should return all sections when element is a notebook', async function (): Promise { + it('getChildren should return all sections when element is a notebook', async function (): Promise { const children = await bookTreeViewProvider.getChildren(notebook1); should(children).be.Array(); should(children.length).equal(2); - const notebook2 = children[0]; + notebook2 = children[0]; const notebook3 = children[1]; equalBookItems(notebook2, expectedNotebook2); equalBookItems(notebook3, expectedNotebook3); }); - it('bookTreeViewProvider should set notebooks trusted to true on trustBook', async () => { + it('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); let isTrusted = bookTrustManager.isNotebookTrustedByDefault(vscode.Uri.file(notebook1Path).fsPath); @@ -207,7 +216,7 @@ describe('BookTreeViewProviderTests', function () { }); - it('bookTreeViewProvider getNavigation should get previous and next urls correctly from the bookModel', async () => { + it('getNavigation should get previous and next urls correctly from the bookModel', async () => { let notebook1Path = path.join(rootFolderPath, 'Book', 'content', 'notebook1.ipynb'); let notebook2Path = path.join(rootFolderPath, 'Book', 'content', 'notebook2.ipynb'); let notebook3Path = path.join(rootFolderPath, 'Book', 'content', 'notebook3.ipynb'); @@ -218,7 +227,60 @@ describe('BookTreeViewProviderTests', function () { }); - it('providedBookTreeViewProvider should return all book nodes when element is undefined', async function (): Promise { + it('getParent should return when element is a valid child notebook', async () => { + let parent = await bookTreeViewProvider.getParent(); + should(parent).be.undefined(); + + parent = await bookTreeViewProvider.getParent(notebook2); + should(parent).not.be.undefined(); + equalBookItems(parent, expectedNotebook1); + }); + + it('revealActiveDocumentInViewlet should return correct bookItem for highlight', async () => { + let notebook1Path = path.join(rootFolderPath, 'Book', 'content', 'notebook1.ipynb'); + let currentSelection = await bookTreeViewProvider.findAndExpandParentNode(notebook1Path); + equalBookItems(currentSelection, expectedNotebook1); + }); + + it('revealActiveDocumentInViewlet should be called on showNotebook', async () => { + let notebook1Path = path.join(rootFolderPath, 'Book', 'content', 'notebook1.ipynb'); + let notebook2Path = path.join(rootFolderPath, 'Book', 'content', 'notebook2.ipynb'); + let notebookUri = vscode.Uri.file(notebook2Path); + + let revealActiveDocumentInViewletSpy = sinon.spy(bookTreeViewProvider, 'revealActiveDocumentInViewlet'); + await azdata.nb.showNotebookDocument(notebookUri); + should(azdata.nb.notebookDocuments.find(doc => doc.fileName === notebookUri.fsPath)).not.be.undefined(); + should(revealActiveDocumentInViewletSpy.calledOnce).be.true('revealActiveDocumentInViewlet should have been called'); + + notebookUri = vscode.Uri.file(notebook1Path); + await azdata.nb.showNotebookDocument(notebookUri); + should(azdata.nb.notebookDocuments.find(doc => doc.fileName === notebookUri.fsPath)).not.be.undefined(); + should(revealActiveDocumentInViewletSpy.calledTwice).be.true('revealActiveDocumentInViewlet should have been called twice'); + }); + + this.afterAll(async () => { + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + }); + + describe('providedBookTreeViewProvider', function (): void { + let providedbookTreeViewProvider: BookTreeViewProvider; + let book: BookTreeItem; + let notebook1: BookTreeItem; + + this.beforeAll(async () => { + providedbookTreeViewProvider = appContext.providedBookTreeViewProvider; + let errorCase = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000)); + await Promise.race([providedbookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('ProvidedBooksTreeViewProvider did not initialize in time'); })]); + await providedbookTreeViewProvider.openBook(bookFolderPath, undefined, false, false); + }); + + afterEach(function(): void { + sinon.restore(); + }); + + it('getChildren should return all book nodes when element is undefined', async function (): Promise { const children = await providedbookTreeViewProvider.getChildren(); should(children).be.Array(); should(children.length).equal(1); @@ -226,7 +288,7 @@ describe('BookTreeViewProviderTests', function () { should(book.title).equal(expectedBook.title); }); - it('providedBookTreeViewProvider should return all page nodes when element is a book', async function (): Promise { + it('getChildren should return all page nodes when element is a book', async function (): Promise { const children = await providedbookTreeViewProvider.getChildren(book); should(children).be.Array(); should(children.length).equal(3); @@ -238,7 +300,7 @@ describe('BookTreeViewProviderTests', function () { equalBookItems(externalLink, expectedExternalLink); }); - it('providedBookTreeViewProvider should return all sections when element is a notebook', async function (): Promise { + it('getChildren should return all sections when element is a notebook', async function (): Promise { const children = await providedbookTreeViewProvider.getChildren(notebook1); should(children).be.Array(); should(children.length).equal(2); @@ -248,7 +310,7 @@ describe('BookTreeViewProviderTests', function () { equalBookItems(notebook3, expectedNotebook3); }); - it('providedBookTreeViewProvider getNavigation should get previous and next urls correctly from the bookModel', async () => { + it('getNavigation should get previous and next urls correctly from the bookModel', async () => { let notebook1Path = path.join(rootFolderPath, 'Book', 'content', 'notebook1.ipynb'); let notebook2Path = path.join(rootFolderPath, 'Book', 'content', 'notebook2.ipynb'); let notebook3Path = path.join(rootFolderPath, 'Book', 'content', 'notebook3.ipynb'); @@ -258,6 +320,30 @@ describe('BookTreeViewProviderTests', function () { should(result.previous.fsPath).equal(notebook1Path, 'getNavigation failed to get the previous url'); }); + it('revealActiveDocumentInViewlet should return correct bookItem for highlight', async () => { + let notebook1Path = path.join(rootFolderPath, 'Book', 'content', 'notebook1.ipynb'); + let currentSelection = await providedbookTreeViewProvider.findAndExpandParentNode(notebook1Path); + equalBookItems(currentSelection, expectedNotebook1); + }); + + it('revealActiveDocumentInViewlet should be called on showNotebook', async () => { + const untitledNotebook1Uri = vscode.Uri.parse(`untitled:notebook1.ipynb`); + const untitledNotebook2Uri = vscode.Uri.parse(`untitled:notebook2.ipynb`); + + let revealActiveDocumentInViewletSpy = sinon.spy(providedbookTreeViewProvider, 'revealActiveDocumentInViewlet'); + await azdata.nb.showNotebookDocument(untitledNotebook1Uri); + should(azdata.nb.notebookDocuments.find(doc => doc.fileName === untitledNotebook1Uri.fsPath)).not.be.undefined(); + should(revealActiveDocumentInViewletSpy.calledOnce).be.true('revealActiveDocumentInViewlet should have been called'); + + await azdata.nb.showNotebookDocument(untitledNotebook2Uri); + should(azdata.nb.notebookDocuments.find(doc => doc.fileName === untitledNotebook2Uri.fsPath)).not.be.undefined(); + should(revealActiveDocumentInViewletSpy.calledTwice).be.true('revealActiveDocumentInViewlet should have been called twice'); + }); + + this.afterAll(async () => { + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + }); this.afterAll(async function (): Promise { @@ -338,13 +424,13 @@ describe('BookTreeViewProviderTests', function () { it('should show error message if config.yml file not found', async () => { await bookTreeViewProvider.currentBook.readBooks(); - should(bookTreeViewProvider.currentBook.errorMessage.toLocaleLowerCase()).equal(('Failed to read book '+ bookTreeViewProvider.currentBook.bookPath +': ENOENT: no such file or directory, open \'' + configFile + '\'').toLocaleLowerCase()); + should(bookTreeViewProvider.currentBook.errorMessage).equal(readBookError(bookTreeViewProvider.currentBook.bookPath, `ENOENT: no such file or directory, open '${configFile}'`)); }); it('should show error if toc.yml file format is invalid', async function (): Promise { await fs.writeFile(configFile, 'title: Test Book'); await bookTreeViewProvider.currentBook.readBooks(); - should(bookTreeViewProvider.currentBook.errorMessage.toLocaleLowerCase()).equal(('Failed to read book '+ bookTreeViewProvider.currentBook.bookPath +': Invalid toc file').toLocaleLowerCase()); + should(bookTreeViewProvider.currentBook.errorMessage).equal(readBookError(bookTreeViewProvider.currentBook.bookPath, `Invalid toc file`)); }); this.afterAll(async function (): Promise { @@ -433,19 +519,16 @@ describe('BookTreeViewProviderTests', function () { await Promise.race([bookTreeViewProvider.initialized, errorCase.then(() => { throw new Error('BookTreeViewProvider did not initialize in time'); })]); }); + afterEach(function(): void { + sinon.restore(); + }); + it('should add book and initialize book on openBook', async () => { should(bookTreeViewProvider.books.length).equal(0, 'Invalid books on initialize.'); await bookTreeViewProvider.openBook(rootFolderPath); should(bookTreeViewProvider.books.length).equal(1, 'Failed to initialize the book on open'); }); - it('should remove book on closeBook', async () => { - await bookTreeViewProvider.openBook(rootFolderPath); - should(bookTreeViewProvider.books.length).equal(1, 'Failed to initialize the book on open'); - await bookTreeViewProvider.closeBook(bookTreeViewProvider.books[0].bookItems[0]); - should(bookTreeViewProvider.books.length).equal(0, 'Failed to remove the book on close'); - }); - it('should add book when bookPath contains special characters on openBook', async () => { let rootFolderPath2 = path.join(os.tmpdir(), `BookTestData(1)_${uuid.v4()}`); let dataFolderPath2 = path.join(rootFolderPath2, '_data'); @@ -461,7 +544,9 @@ describe('BookTreeViewProviderTests', function () { await fs.writeFile(notebook2File2, ''); await bookTreeViewProvider.openBook(rootFolderPath2); - should(bookTreeViewProvider.books.length).equal(1, 'Failed to initialize the book on open'); + should(bookTreeViewProvider.books.length).equal(2, 'Failed to initialize the book on open'); + + await promisify(rimraf)(rootFolderPath2); }); it('should get notebook path with untitled schema on openNotebookAsUntitled', async () => { @@ -469,6 +554,45 @@ describe('BookTreeViewProviderTests', function () { should(notebookUri.scheme).equal('untitled', 'Failed to get untitled uri of the resource'); }); + it('openNotebookFolder should prompt for notebook path and invoke loadNotebooksInFolder', async () => { + sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([vscode.Uri.file(rootFolderPath)])); + let loadNotebooksSpy = sinon.spy(bookTreeViewProvider, 'loadNotebooksInFolder'); + await bookTreeViewProvider.openNotebookFolder(); + + should(loadNotebooksSpy.calledOnce).be.true('openNotebookFolder should have called loadNotebooksInFolder'); + }); + + it('openNewBook should prompt for notebook path and invoke openBook', async () => { + sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([vscode.Uri.file(rootFolderPath)])); + let openBookSpy = sinon.spy(bookTreeViewProvider, 'openBook'); + await bookTreeViewProvider.openNewBook(); + + should(openBookSpy.calledOnce).be.true('openNewBook should have called openBook'); + }); + + it('searchJupyterBooks should call command that opens Search view', async () => { + let executeCommandSpy = sinon.spy(vscode.commands, 'executeCommand'); + await bookTreeViewProvider.searchJupyterBooks(bookTreeViewProvider.books[0].bookItems[0]); + should(executeCommandSpy.calledWith('workbench.action.findInFiles')).be.true('searchJupyterBooks should have called command to open Search view'); + }); + + it('saveJupyterBooks should prompt location and openBook', async () => { + let saveFolderPath = path.join(os.tmpdir(), `Book_${uuid.v4()}`); + await fs.mkdir(saveFolderPath); + sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([vscode.Uri.file(saveFolderPath)])); + let executeCommandSpy = sinon.spy(vscode.commands, 'executeCommand'); + await bookTreeViewProvider.saveJupyterBooks(); + should(executeCommandSpy.calledWith('bookTreeView.openBook')).be.true('saveJupyterBooks should have called command openBook after saving'); + + await promisify(rimraf)(saveFolderPath); + }); + + it('should remove book on closeBook', async () => { + let length: number = bookTreeViewProvider.books.length; + await bookTreeViewProvider.closeBook(bookTreeViewProvider.books[0].bookItems[0]); + should(bookTreeViewProvider.books.length).equal(length-1, 'Failed to remove the book on close'); + }); + this.afterAll(async function (): Promise { if (await exists(rootFolderPath)) { await promisify(rimraf)(rootFolderPath); diff --git a/extensions/notebook/src/types.d.ts b/extensions/notebook/src/types.d.ts index bbf0a115e3..bcab72eca4 100644 --- a/extensions/notebook/src/types.d.ts +++ b/extensions/notebook/src/types.d.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { AppContext } from './common/appContext'; + /** * The API provided by this extension. * @@ -12,6 +14,7 @@ export interface IExtensionApi { getJupyterController(): IJupyterController; registerPackageManager(providerId: string, packageManagerProvider: IPackageManageProvider): void; getPackageManagers(): Map; + getAppContext(): AppContext; } /**