diff --git a/extensions/notebook/src/common/localizedConstants.ts b/extensions/notebook/src/common/localizedConstants.ts index 3362b5c007..1bda0eeac1 100644 --- a/extensions/notebook/src/common/localizedConstants.ts +++ b/extensions/notebook/src/common/localizedConstants.ts @@ -105,7 +105,4 @@ export const confirmOverwrite = localize('confirmOverwrite', "File already exist export const title = localize('title', "Title"); export const fileName = localize('fileName', "File Name"); export const msgInvalidSaveFolder = localize('msgInvalidSaveFolder', "Save location path is not valid."); -export function msgDuplicadFileName(file: string): string { return localize('msgDuplicadFileName', "File {0} already exists in the destination folder", file); } - - - +export function msgDuplicateFileName(file: string): string { return localize('msgDuplicateFileName', "File {0} already exists in the destination folder", file); } diff --git a/extensions/notebook/src/dialog/addFileDialog.ts b/extensions/notebook/src/dialog/addFileDialog.ts index 14ee420f9e..a192d6de6e 100644 --- a/extensions/notebook/src/dialog/addFileDialog.ts +++ b/extensions/notebook/src/dialog/addFileDialog.ts @@ -27,12 +27,16 @@ export class AddFileDialog { this._prompter = new CodeAdapter(); } + public get dialog(): azdata.window.Dialog | undefined { + return this._dialog; + } + public async validatePath(folderPath: string, fileBasename: string): Promise { const destinationUri = path.join(folderPath, fileBasename); if (await pathExists(destinationUri)) { const doOverwrite = await confirmMessageDialog(this._prompter, loc.confirmOverwrite); if (!doOverwrite) { - throw (new Error(loc.msgDuplicadFileName(destinationUri))); + throw (new Error(loc.msgDuplicateFileName(destinationUri))); } } if (!(await pathExists(folderPath))) { @@ -88,16 +92,16 @@ export class AddFileDialog { await this.view.initializeModel(this._formModel); }); this._dialog.okButton.label = loc.add; - this._dialog.registerCloseValidator(async () => await this.createFile()); + this._dialog.registerCloseValidator(async () => await this.createFile(this._fileNameInputBox.value, this._titleInputBox.value)); azdata.window.openDialog(this._dialog); } - private async createFile(): Promise { + public async createFile(fileName: string, titleName: string): Promise { try { const dirPath = this._bookItem.contextValue === BookTreeItemType.savedBook ? this._bookItem.rootContentPath : path.dirname(this._bookItem.book.contentPath); - const filePath = path.posix.join(dirPath, this._fileNameInputBox.value).concat(this._extension); - await this.validatePath(dirPath, this._fileNameInputBox.value.concat(this._extension)); - const pathDetails = new TocEntryPathHandler(filePath, this._bookItem.rootContentPath, this._titleInputBox.value); + const filePath = path.posix.join(dirPath, fileName).concat(this._extension); + await this.validatePath(dirPath, fileName.concat(this._extension)); + const pathDetails = new TocEntryPathHandler(filePath, this._bookItem.rootContentPath, titleName); await this._tocManager.addNewFile(pathDetails, this._bookItem); return true; } catch (error) { diff --git a/extensions/notebook/src/test/book/addFile.test.ts b/extensions/notebook/src/test/book/addFile.test.ts new file mode 100644 index 0000000000..afe017aaa6 --- /dev/null +++ b/extensions/notebook/src/test/book/addFile.test.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * 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 os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { AddFileDialog } from '../../dialog/addFileDialog'; +import { IBookTocManager } from '../../book/bookTocManager'; +import { BookTreeItem, BookTreeItemFormat, BookTreeItemType } from '../../book/bookTreeItem'; +import * as utils from '../../common/utils'; +import * as sinon from 'sinon'; +import { TocEntryPathHandler } from '../../book/tocEntryPathHandler'; + +describe('Add File Dialog', function () { + let bookTocManager: IBookTocManager; + let bookTreeItem: BookTreeItem; + const fileExtension = utils.FileExtension.Notebook; + let bookItemFormat: BookTreeItemFormat; + + beforeEach(() => { + let mockBookManager = TypeMoq.Mock.ofType(); + mockBookManager.setup(m => m.addNewFile(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve()); + bookTocManager = mockBookManager.object; + + let mockTreeItem = TypeMoq.Mock.ofType(); + mockTreeItem.setup(i => i.contextValue).returns(() => BookTreeItemType.savedBook); + mockTreeItem.setup(i => i.rootContentPath).returns(() => ''); + + let mockItemFormat = TypeMoq.Mock.ofType(); + mockItemFormat.setup(f => f.contentPath).returns(() => ''); + bookItemFormat = mockItemFormat.object; + + mockTreeItem.setup(i => i.book).returns(() => bookItemFormat); + bookTreeItem = mockTreeItem.object; + }); + + it('Create dialog', async () => { + let fileDialog = new AddFileDialog(bookTocManager, bookTreeItem, fileExtension); + await fileDialog.createDialog(); + should(fileDialog.dialog).not.be.undefined(); + should(fileDialog.dialog.message).be.undefined(); + }); + + it('Validate path', async () => { + let fileDialog = new AddFileDialog(bookTocManager, bookTreeItem, fileExtension); + await fileDialog.createDialog(); + + let tempDir = os.tmpdir(); + let testDir = path.join(tempDir, utils.generateGuid()); + let fileBasename = 'addFileDialogTest.ipynb'; + let testFilePath = path.join(testDir, fileBasename); + + // Folder doesn't exist + await should(fileDialog.validatePath(testDir, fileBasename)).be.rejected(); + + // Folder exists + await fs.mkdir(testDir); + await should(fileDialog.validatePath(testDir, fileBasename)).not.be.rejected(); + + // File Exists, but don't choose to overwrite + sinon.stub(utils, 'confirmMessageDialog').resolves(false); + await fs.createFile(testFilePath); + await should(fileDialog.validatePath(testDir, fileBasename)).be.rejected(); + sinon.restore(); + + // File exists, choose to overwrite + sinon.stub(utils, 'confirmMessageDialog').resolves(true); + await should(fileDialog.validatePath(testDir, fileBasename)).not.be.rejected(); + sinon.restore(); + }); + + it('Create file', async () => { + let tempDir = os.tmpdir(); + let testDir = path.join(tempDir, utils.generateGuid()); + let testFileName = 'addFileDialogTest'; + let posixFilePath = path.posix.join(testDir, testFileName).concat(fileExtension); + let testTitle = 'Test Title'; + + await fs.mkdir(testDir); + + // Error case + let mockBookManager = TypeMoq.Mock.ofType(); + mockBookManager.setup(m => m.addNewFile(TypeMoq.It.isAny(), TypeMoq.It.isAny())).throws(new Error('Expected test error.')); + + let fileDialog = new AddFileDialog(mockBookManager.object, bookTreeItem, fileExtension); + await fileDialog.createDialog(); + + await should(fileDialog.createFile(testFileName, testTitle)).be.resolvedWith(false); + should(fileDialog.dialog?.message).not.be.undefined(); + + sinon.restore(); + + // Success case + let testPathDetails: TocEntryPathHandler[] = []; + mockBookManager = TypeMoq.Mock.ofType(); + mockBookManager.setup(m => m.addNewFile(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((path, item) => { testPathDetails.push(path); return Promise.resolve(); }); + + let mockTreeItem = TypeMoq.Mock.ofType(); + mockTreeItem.setup(i => i.contextValue).returns(() => BookTreeItemType.savedBook); + mockTreeItem.setup(i => i.rootContentPath).returns(() => testDir); + + fileDialog = new AddFileDialog(mockBookManager.object, mockTreeItem.object, fileExtension); + await fileDialog.createDialog(); + + let createFileResult = await fileDialog.createFile(testFileName, testTitle); + should(fileDialog.dialog.message).be.undefined(); + should(createFileResult).be.true('createFile call should succeed.'); + + should(testPathDetails.length).eql(1, 'Should only create one TocEntryPathHandler on success.'); + should(testPathDetails[0]).be.deepEqual(new TocEntryPathHandler(posixFilePath, testDir, testTitle), 'Should get the expected TocEntryPathHandler info on success.'); + + sinon.restore(); + }); +});