From 67b6f6ffcd4c99d69e67faeeb1280f6bebb5a9a7 Mon Sep 17 00:00:00 2001 From: Cory Rivera Date: Fri, 17 Apr 2020 16:27:00 -0700 Subject: [PATCH] Add tests for Open Notebook Folder functionality. (#10056) --- extensions/notebook/src/book/bookTreeView.ts | 11 +- .../src/jupyter/jupyterSettingWriter.ts | 74 ------------ .../notebook/src/test/book/book.test.ts | 110 +++++++++++++++++- 3 files changed, 110 insertions(+), 85 deletions(-) delete mode 100644 extensions/notebook/src/jupyter/jupyterSettingWriter.ts diff --git a/extensions/notebook/src/book/bookTreeView.ts b/extensions/notebook/src/book/bookTreeView.ts index 42869eb393..d8872a0776 100644 --- a/extensions/notebook/src/book/bookTreeView.ts +++ b/extensions/notebook/src/book/bookTreeView.ts @@ -79,15 +79,10 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { @@ -114,7 +109,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider 0) { this.currentBook = existingBook; } else { - await this.createAndAddBookModel(bookPath, isNotebook); + await this.createAndAddBookModel(bookPath, !!isNotebook); let bookViewer = vscode.window.createTreeView(this.viewId, { showCollapseAll: true, treeDataProvider: this }); this.currentBook = this.books.find(book => book.bookPath === bookPath); bookViewer.reveal(this.currentBook.bookItems[0], { expand: vscode.TreeItemCollapsibleState.Expanded, focus: true, select: true }); @@ -370,7 +365,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { if (element) { let parentPath; @@ -488,7 +482,6 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { return await this.prompter.promptSingle({ diff --git a/extensions/notebook/src/jupyter/jupyterSettingWriter.ts b/extensions/notebook/src/jupyter/jupyterSettingWriter.ts deleted file mode 100644 index b256590566..0000000000 --- a/extensions/notebook/src/jupyter/jupyterSettingWriter.ts +++ /dev/null @@ -1,74 +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 fs from 'fs-extra'; -import * as nls from 'vscode-nls'; -const localize = nls.loadMessageBundle(); - -import * as constants from '../common/constants'; - -export enum SettingType { - String, - Number, - Boolean, - Set -} -export class ISetting { - key: string; - value: string | number | boolean; - type: SettingType; -} - -export class JupyterSettingWriter { - private settings: ISetting[] = []; - - constructor(private baseFile: string) { - } - - public addSetting(setting: ISetting): void { - this.settings.push(setting); - } - - public async writeSettings(targetFile: string): Promise { - let settings = await this.printSettings(); - await fs.writeFile(targetFile, settings); - } - - public async printSettings(): Promise { - let content = ''; - let newLine = process.platform === constants.winPlatform ? '\r\n' : '\n'; - if (this.baseFile) { - let sourceContents = await fs.readFile(this.baseFile); - content += sourceContents.toString(); - } - - for (let setting of this.settings) { - content += newLine; - content += this.printSetting(setting); - } - return content; - } - - private printSetting(setting: ISetting): string { - let value: string; - switch (setting.type) { - case SettingType.Boolean: - value = setting.value ? 'True' : 'False'; - break; - case SettingType.String: - value = `'${setting.value}'`; - break; - case SettingType.Number: - value = `${setting.value}`; - break; - case SettingType.Set: - value = `set([${setting.value}])`; - break; - default: - throw new Error(localize('UnexpectedSettingType', "Unexpected setting type {0}", setting.type)); - } - return `c.${setting.key} = ${value}`; - } -} diff --git a/extensions/notebook/src/test/book/book.test.ts b/extensions/notebook/src/test/book/book.test.ts index 54a3bed7bc..626c4cf852 100644 --- a/extensions/notebook/src/test/book/book.test.ts +++ b/extensions/notebook/src/test/book/book.test.ts @@ -12,12 +12,13 @@ import * as rimraf from 'rimraf'; import * as os from 'os'; import * as uuid from 'uuid'; import { BookTreeViewProvider } from '../../book/bookTreeView'; -import { BookTreeItem } from '../../book/bookTreeItem'; +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'; export interface IExpectedBookItem { @@ -43,7 +44,6 @@ export function equalBookItems(book: BookTreeItem, expectedBook: IExpectedBookIt } describe('BookTreeViewProviderTests', function () { - describe('BookTreeViewProvider', () => { let mockExtensionContext: vscode.ExtensionContext; @@ -449,4 +449,110 @@ describe('BookTreeViewProviderTests', function () { } }); }); + + describe('BookTreeViewProvider.openNotebookFolder', function (): void { + let rootFolderPath: string; + let bookFolderPath: string; + let bookTitle: string; + let notebookFolderPath: string; + let tableOfContentsFile: string; + let standaloneNotebookTitle: string; + let standaloneNotebookFile: string; + let bookTreeViewProvider: BookTreeViewProvider; + let appContext: AppContext; + + this.beforeAll(async () => { + rootFolderPath = path.join(os.tmpdir(), `BookFolderTest_${uuid.v4()}`); + bookFolderPath = path.join(rootFolderPath, 'BookTestData'); + let dataFolderPath = path.join(bookFolderPath, '_data'); + let contentFolderPath = path.join(bookFolderPath, 'content'); + let configFile = path.join(bookFolderPath, '_config.yml'); + tableOfContentsFile = path.join(dataFolderPath, 'toc.yml'); + let bookNotebookFile = path.join(contentFolderPath, 'notebook1.ipynb'); + notebookFolderPath = path.join(rootFolderPath, 'NotebookTestData'); + standaloneNotebookTitle = 'notebook2'; + standaloneNotebookFile = path.join(notebookFolderPath, `${standaloneNotebookTitle}.ipynb`); + await fs.mkdir(rootFolderPath); + await fs.mkdir(bookFolderPath); + await fs.mkdir(dataFolderPath); + await fs.mkdir(contentFolderPath); + await fs.mkdir(notebookFolderPath); + bookTitle = 'Test Book'; + await fs.writeFile(configFile, `title: ${bookTitle}`); + await fs.writeFile(tableOfContentsFile, '- title: Notebook1\n url: /notebook1'); + await fs.writeFile(bookNotebookFile, ''); + await fs.writeFile(standaloneNotebookFile, ''); + + const mockExtensionContext = new MockExtensionContext(); + appContext = new AppContext(mockExtensionContext, new ApiWrapper()); + bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], mockExtensionContext, false, 'bookTreeView'); + 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 () => { + await bookTreeViewProvider.loadNotebooksInFolder(rootFolderPath); + should(bookTreeViewProvider.books.length).equal(2, 'Should have loaded a book and a notebook'); + + validateIsBook(bookTreeViewProvider.books[0]); + validateIsNotebook(bookTreeViewProvider.books[1]); + }); + + it('should include only books when opening books folder', async () => { + await bookTreeViewProvider.loadNotebooksInFolder(bookFolderPath); + should(bookTreeViewProvider.books.length).equal(1, 'Should have loaded only one book'); + + validateIsBook(bookTreeViewProvider.books[0]); + }); + + it('should include only notebooks when opening notebooks folder', async () => { + await bookTreeViewProvider.loadNotebooksInFolder(notebookFolderPath); + should(bookTreeViewProvider.books.length).equal(1, 'Should have loaded only one notebook'); + + validateIsNotebook(bookTreeViewProvider.books[0]); + }); + + this.afterEach(async function (): Promise { + let bookItems = await bookTreeViewProvider.getChildren(); + await Promise.all(bookItems.map(bookItem => bookTreeViewProvider.closeBook(bookItem))); + }); + + this.afterAll(async function (): Promise { + if (await exists(rootFolderPath)) { + await promisify(rimraf)(rootFolderPath); + } + }); + + let validateIsBook = (book: BookModel) => { + should(book.isNotebook).be.false(); + should(book.bookItems.length).equal(1); + + let bookItem = book.bookItems[0]; + + let bookDetails = bookItem.book; + should(bookDetails.type).equal(BookTreeItemType.Book); + should(bookDetails.title).equal(bookTitle); + should(bookDetails.contentPath).equal(tableOfContentsFile.replace(/\\/g, '/')); + should(bookDetails.root).equal(bookFolderPath.replace(/\\/g, '/')); + should(bookDetails.tableOfContents.sections).not.equal(undefined); + should(bookDetails.page).not.equal(undefined); + }; + + let validateIsNotebook = (book: BookModel) => { + should(book.isNotebook).be.true(); + should(book.bookItems.length).equal(1); + + let bookItem = book.bookItems[0]; + should(book.getAllNotebooks().get(vscode.Uri.file(standaloneNotebookFile).fsPath)).equal(bookItem); + + let bookDetails = bookItem.book; + should(bookDetails.type).equal(BookTreeItemType.Notebook); + should(bookDetails.title).equal(standaloneNotebookTitle); + should(bookDetails.contentPath).equal(standaloneNotebookFile.replace(/\\/g, '/')); + should(bookDetails.root).equal(notebookFolderPath.replace(/\\/g, '/')); + should(bookDetails.tableOfContents.sections).equal(undefined); + should(bookDetails.page.sections).equal(undefined); + }; + }); });