Add new file UI (#14773)

Adds a notebook or markdown file to a book's top level or a section by right click on Book Tree View
This commit is contained in:
Barbara Valdez
2021-03-30 18:44:52 -07:00
committed by GitHub
parent b774f09b6c
commit 8aa222d1c8
13 changed files with 428 additions and 247 deletions

View File

@@ -22,7 +22,6 @@ import * as loc from '../../common/localizedConstants';
import { BookVersion } from '../../book/bookVersionHandler';
import * as yaml from 'js-yaml';
export function equalTOC(actualToc: IJupyterBookSectionV2[], expectedToc: IJupyterBookSectionV2[]): boolean {
for (let [i, section] of actualToc.entries()) {
if (section.title !== expectedToc[i].title || section.file !== expectedToc[i].file) {
@@ -45,6 +44,37 @@ export function equalSections(actualSection: JupyterBookSection, expectedSection
return true;
}
function BookModelStub(root: string, bookItem: BookTreeItem, extension: MockExtensionContext): BookModel {
const bookModel = new BookModel(root, false, false, extension, undefined);
sinon.stub(bookModel, 'bookItems').value([bookItem]);
sinon.stub(bookModel, 'unwatchTOC').returns();
sinon.stub(bookModel, 'reinitializeContents').resolves();
sinon.stub(bookModel, 'bookPath').value(root);
return bookModel;
}
function createBookTreeItemFormat(item: any, root: string, version: BookVersion): BookTreeItemFormat {
const pageFormat = item.type === BookTreeItemType.section ? {
title: item.sectionName,
file: item.uri,
sections: item.sectionFormat
} : item.sectionFormat;
const sections = item.type === BookTreeItemType.section ? item.sectionFormat : [item.sectionFormat];
return {
title: item.sectionName,
contentPath: item.contentPath,
root: root,
tableOfContents: {
sections: sections
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: item.type,
version: version,
page: pageFormat
};
}
describe('BookTocManagerTests', function () {
describe('CreatingBooks', () => {
let notebooks: string[];
@@ -145,12 +175,14 @@ describe('BookTocManagerTests', function () {
'title': 'Notebook 2',
'file': path.posix.join(path.posix.sep, 'sectionA', 'notebook2')
}
]
],
'type': BookTreeItemType.savedBook
},
sectionA: {
'contentPath': path.posix.join(sourceBookFolderPath, 'content', 'sectionA', 'readme.md'),
'sectionRoot': path.posix.join(sourceBookFolderPath, 'content', 'sectionA'),
'sectionName': 'Section A',
'uri': path.posix.join(path.posix.sep, 'sectionA', 'readme'),
'notebook1': path.posix.join(sourceBookFolderPath, 'content', 'sectionA', 'notebook1.ipynb'),
'notebook2': path.posix.join(sourceBookFolderPath, 'content', 'sectionA', 'notebook2.ipynb'),
'sectionFormat': [
@@ -162,12 +194,14 @@ describe('BookTocManagerTests', function () {
'title': 'Notebook 2',
'file': path.posix.join(path.posix.sep, 'sectionA', 'notebook2')
}
]
],
'type': BookTreeItemType.section
},
sectionB: {
'contentPath': path.posix.join(sourceBookFolderPath, 'content', 'sectionB', 'readme.md'),
'sectionRoot': path.posix.join(sourceBookFolderPath, 'content', 'sectionB'),
'sectionName': 'Section B',
'uri': path.posix.join(path.posix.sep, 'sectionB', 'readme'),
'notebook3': path.posix.join(sourceBookFolderPath, 'content', 'sectionB', 'notebook3.ipynb'),
'notebook4': path.posix.join(sourceBookFolderPath, 'content', 'sectionB', 'notebook4.ipynb'),
'sectionFormat': [
@@ -179,10 +213,24 @@ describe('BookTocManagerTests', function () {
'title': 'Notebook 4',
'file': path.posix.join(path.posix.sep, 'sectionB', 'notebook4')
}
]
],
'type': BookTreeItemType.section
},
notebook5: {
'contentPath': path.posix.join(sourceBookFolderPath, 'content', 'notebook5.ipynb')
'contentPath': path.posix.join(sourceBookFolderPath, 'content', 'notebook5.ipynb'),
'sectionFormat': {
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
},
'type': BookTreeItemType.Notebook
},
duplicatedNotebook: {
'contentPath': path.posix.join(duplicatedNotebookPath, 'notebook5.ipynb'),
'sectionFormat': {
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
},
'type': BookTreeItemType.Notebook
},
targetBook: {
'rootBookFolderPath': targetBookFolderPath,
@@ -204,19 +252,22 @@ describe('BookTocManagerTests', function () {
}
]
}
]
],
'type': BookTreeItemType.Book
},
sectionC: {
'contentPath': path.posix.join(targetBookFolderPath, 'content', 'sectionC', 'readme.md'),
'sectionRoot': path.posix.join(targetBookFolderPath, 'content', 'sectionC'),
'sectionName': 'Section C',
'uri': path.posix.join(path.posix.sep, 'sectionC', 'readme'),
'notebook6': path.posix.join(targetBookFolderPath, 'content', 'sectionC', 'notebook6.ipynb'),
'sectionFormat': [
{
'title': 'Notebook 6',
'file': path.posix.join(path.posix.sep, 'sectionC', 'notebook6')
}
]
],
'type': BookTreeItemType.section
}
}, {
it: 'using the jupyter-book legacy version >= 0.7.0',
@@ -231,6 +282,7 @@ describe('BookTocManagerTests', function () {
'contentPath': path.posix.join(sourceBookFolderPath, 'sectionA', 'readme.md'),
'sectionRoot': path.posix.join(sourceBookFolderPath, 'sectionA'),
'sectionName': 'Section A',
'uri': path.posix.join(path.posix.sep, 'sectionA', 'readme'),
'notebook1': path.posix.join(sourceBookFolderPath, 'sectionA', 'notebook1.ipynb'),
'notebook2': path.posix.join(sourceBookFolderPath, 'sectionA', 'notebook2.ipynb'),
'sectionFormat': [
@@ -242,12 +294,14 @@ describe('BookTocManagerTests', function () {
'title': 'Notebook 2',
'file': path.posix.join(path.posix.sep, 'sectionA', 'notebook2')
}
]
],
'type': BookTreeItemType.section
},
sectionB: {
'contentPath': path.posix.join(sourceBookFolderPath, 'sectionB', 'readme.md'),
'sectionRoot': path.posix.join(sourceBookFolderPath, 'sectionB'),
'sectionName': 'Section B',
'uri': path.posix.join(path.posix.sep, 'sectionB', 'readme'),
'notebook3': path.posix.join(sourceBookFolderPath, 'sectionB', 'notebook3.ipynb'),
'notebook4': path.posix.join(sourceBookFolderPath, 'sectionB', 'notebook4.ipynb'),
'sectionFormat': [
@@ -259,10 +313,24 @@ describe('BookTocManagerTests', function () {
'title': 'Notebook 4',
'file': path.posix.join(path.posix.sep, 'sectionB', 'notebook4')
}
]
],
'type': BookTreeItemType.section
},
notebook5: {
'contentPath': path.posix.join(sourceBookFolderPath, 'notebook5.ipynb')
'contentPath': path.posix.join(sourceBookFolderPath, 'notebook5.ipynb'),
'sectionFormat': {
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
},
'type': BookTreeItemType.Notebook
},
duplicatedNotebook: {
'contentPath': path.posix.join(duplicatedNotebookPath, 'notebook5.ipynb'),
'sectionFormat': {
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
},
'type': BookTreeItemType.Notebook
},
targetBook: {
'rootBookFolderPath': targetBookFolderPath,
@@ -284,19 +352,22 @@ describe('BookTocManagerTests', function () {
}
]
}
]
],
'type': BookTreeItemType.Book
},
sectionC: {
'contentPath': path.posix.join(targetBookFolderPath, 'sectionC', 'readme.md'),
'sectionRoot': path.posix.join(targetBookFolderPath, 'sectionC'),
'sectionName': 'Section C',
'uri': path.posix.join(path.posix.sep, 'sectionC', 'readme'),
'notebook6': path.posix.join(targetBookFolderPath, 'sectionC', 'notebook6.ipynb'),
'sectionFormat': [
{
'title': 'Notebook 6',
'file': path.posix.join(path.posix.sep, 'sectionC', 'notebook6')
}
]
],
'type': BookTreeItemType.section
}
}
];
@@ -317,103 +388,11 @@ describe('BookTocManagerTests', function () {
version: run.version,
page: run.targetBook.toc
};
let sectionCTreeItemFormat: BookTreeItemFormat = {
title: run.sectionC.sectionName,
contentPath: run.sectionC.contentPath,
root: run.targetBook.rootBookFolderPath,
tableOfContents: {
sections: run.sectionC.sectionFormat
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Markdown,
version: run.version,
page: run.sectionC.sectionFormat
};
// section A is from source book
let sectionATreeItemFormat: BookTreeItemFormat = {
title: run.sectionA.sectionName,
contentPath: run.sectionA.contentPath,
root: run.sourceBook.rootBookFolderPath,
tableOfContents: {
sections: run.sectionA.sectionFormat
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Markdown,
version: run.version,
page: {
title: run.sectionA.sectionName,
file: path.posix.join(path.posix.sep, 'sectionA', 'readme'),
sections: run.sectionA.sectionFormat
}
};
// section B is from source book
let sectionBTreeItemFormat: BookTreeItemFormat = {
title: run.sectionB.sectionName,
contentPath: run.sectionB.contentPath,
root: run.sourceBook.rootBookFolderPath,
tableOfContents: {
sections: run.sectionB.sectionFormat
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Markdown,
version: run.version,
page: {
title: run.sectionB.sectionName,
file: path.posix.join(path.posix.sep, 'sectionB', 'readme'),
sections: run.sectionB.sectionFormat
}
};
// notebook5 is from source book
let notebookTreeItemFormat: BookTreeItemFormat = {
title: '',
contentPath: run.notebook5.contentPath,
root: run.sourceBook.rootBookFolderPath,
tableOfContents: {
sections: [
{
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
}
]
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Notebook,
version: run.version,
page: {
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
}
};
let duplicatedNbTreeItemFormat: BookTreeItemFormat = {
title: 'Duplicated Notebook',
contentPath: path.posix.join(duplicatedNotebookPath, 'notebook5.ipynb'),
root: duplicatedNotebookPath,
tableOfContents: {
sections: [
{
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
}
]
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Notebook,
version: run.version,
page: {
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
}
};
const sectionCTreeItemFormat = createBookTreeItemFormat(run.sectionC, run.targetBook.rootBookFolderPath, run.version);
const sectionATreeItemFormat = createBookTreeItemFormat(run.sectionA, run.sourceBook.rootBookFolderPath, run.version);
const sectionBTreeItemFormat = createBookTreeItemFormat(run.sectionB, run.sourceBook.rootBookFolderPath, run.version);
const notebookTreeItemFormat = createBookTreeItemFormat(run.notebook5, run.sourceBook.rootBookFolderPath, run.version);
const duplicatedNbTreeItemFormat = createBookTreeItemFormat(run.duplicatedNotebook, duplicatedNotebookPath, undefined);
targetBook = new BookTreeItem(targetBookTreeItemFormat, undefined);
sectionC = new BookTreeItem(sectionCTreeItemFormat, undefined);
@@ -443,12 +422,7 @@ describe('BookTocManagerTests', function () {
sectionA.sections = run.sectionA.sectionFormat;
sectionB.sections = run.sectionB.sectionFormat;
sectionC.sections = run.sectionC.sectionFormat;
notebook.sections = [
{
'title': 'Notebook 5',
'file': path.posix.join(path.posix.sep, 'notebook5')
}
];
notebook.sections = [run.notebook5.sectionFormat];
duplicatedNotebook.sections = notebook.sections;
await fs.promises.mkdir(run.targetBook.bookContentFolderPath, { recursive: true });
@@ -480,16 +454,13 @@ describe('BookTocManagerTests', function () {
const mockExtensionContext = new MockExtensionContext();
sourceBookModel = new BookModel(run.sourceBook.rootBookFolderPath, false, false, mockExtensionContext, undefined);
targetBookModel = new BookModel(run.targetBook.rootBookFolderPath, false, false, mockExtensionContext, undefined);
// create book model mock objects
sinon.stub(sourceBookModel, 'bookItems').value([sectionA]);
sinon.stub(targetBookModel, 'bookItems').value([targetBook]);
sourceBookModel = BookModelStub(run.sourceBook.rootBookFolderPath, sectionA, mockExtensionContext);
targetBookModel = BookModelStub(run.targetBook.rootBookFolderPath, targetBook, mockExtensionContext);
});
it('Add section to book', async () => {
bookTocManager = new BookTocManager(targetBookModel, sourceBookModel);
bookTocManager = new BookTocManager(sourceBookModel, targetBookModel);
await bookTocManager.updateBook(sectionA, targetBook, undefined);
const listFiles = await fs.promises.readdir(path.join(run.targetBook.bookContentFolderPath, 'sectionA'));
const listSourceFiles = await fs.promises.readdir(path.join(run.sourceBook.bookContentFolderPath));
@@ -498,7 +469,7 @@ describe('BookTocManagerTests', function () {
});
it('Add section to section', async () => {
bookTocManager = new BookTocManager(targetBookModel, sourceBookModel);
bookTocManager = new BookTocManager(sourceBookModel, targetBookModel);
await bookTocManager.updateBook(sectionB, sectionC, {
'title': 'Notebook 6',
'file': path.posix.join(path.posix.sep, 'sectionC', 'notebook6')
@@ -510,7 +481,7 @@ describe('BookTocManagerTests', function () {
});
it('Add notebook to book', async () => {
bookTocManager = new BookTocManager(targetBookModel);
bookTocManager = new BookTocManager(undefined, targetBookModel);
await bookTocManager.updateBook(notebook, targetBook);
const listFiles = await fs.promises.readdir(run.targetBook.bookContentFolderPath);
should(JSON.stringify(listFiles).includes('notebook5.ipynb')).be.true('Notebook 5 should be under the target book content folder');
@@ -526,7 +497,7 @@ describe('BookTocManagerTests', function () {
});
should(notebookInToc).be.true('Verify the notebook is in toc before removing');
bookTocManager = new BookTocManager();
bookTocManager = new BookTocManager(sourceBookModel);
await bookTocManager.removeNotebook(notebook);
const listFiles = await fs.promises.readdir(run.sourceBook.bookContentFolderPath);
@@ -542,7 +513,7 @@ describe('BookTocManagerTests', function () {
});
it('Add duplicated notebook to book', async () => {
bookTocManager = new BookTocManager(targetBookModel);
bookTocManager = new BookTocManager(undefined, targetBookModel);
await bookTocManager.updateBook(notebook, targetBook);
await bookTocManager.updateBook(duplicatedNotebook, targetBook);
const listFiles = await fs.promises.readdir(run.targetBook.bookContentFolderPath);
@@ -553,7 +524,7 @@ describe('BookTocManagerTests', function () {
it('Recovery method is called after error', async () => {
const mockExtensionContext = new MockExtensionContext();
const recoverySpy = sinon.spy(BookTocManager.prototype, 'recovery');
sinon.stub(BookTocManager.prototype, 'updateBook').throws(new Error('Unexpected error.'));
sinon.stub(BookTocManager.prototype, 'updateTOC').throws(new Error('Unexpected error.'));
const bookTreeViewProvider = new BookTreeViewProvider([], mockExtensionContext, false, 'bookTreeView', NavigationProviders.NotebooksNavigator);
const results: quickPickResults = {
book: targetBook,