[Backend] Editing and creating books (#13089)

* Add interface for modifying the table of contents of books

* Add logic for creating toc

* Fix issue with toc

* Add test for creating toc

* Delete bookTocManager.test.ts

* update allowed extensions

* Fix failing tests and add test

* Add tests for creating books

* Remove unused methods

* add section to section
This commit is contained in:
Barbara Valdez
2020-11-02 14:55:44 -08:00
committed by GitHub
parent 04117b2333
commit 036faeb06d
4 changed files with 583 additions and 2 deletions

View File

@@ -0,0 +1,181 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { BookTreeItem } from './bookTreeItem';
import * as yaml from 'js-yaml';
import * as fs from 'fs-extra';
import { IJupyterBookSectionV1, IJupyterBookSectionV2, JupyterBookSection } from '../contracts/content';
import { BookVersion } from './bookModel';
import * as vscode from 'vscode';
export interface IBookTocManager {
updateBook(element: BookTreeItem, book: BookTreeItem): Promise<void>;
createBook(bookContentPath: string, contentFolder: string): Promise<void>
}
const allowedFileExtensions: string[] = ['.md', '.ipynb'];
const initMarkdown: string[] = ['index.md', 'introduction.md', 'intro.md', 'readme.md'];
export function hasSections(node: JupyterBookSection): boolean {
return node.sections !== undefined && node.sections.length > 0;
}
export class BookTocManager implements IBookTocManager {
public tableofContents: IJupyterBookSectionV2[];
public newSection: JupyterBookSection = {};
constructor() {
}
async getAllFiles(toc: IJupyterBookSectionV2[], directory: string, filesInDir: string[], rootDirectory: string): Promise<IJupyterBookSectionV2[]> {
await Promise.all(filesInDir.map(async file => {
let isDirectory = (await fs.promises.stat(path.join(directory, file))).isDirectory();
if (isDirectory) {
let files = await fs.promises.readdir(path.join(directory, file));
let initFile: string = '';
//Add files named as readme or index within the directory as the first file of the section.
files.some((f, index) => {
if (initMarkdown.includes(f)) {
initFile = path.parse(f).name;
files.splice(index, 1);
}
});
let jupyterSection: IJupyterBookSectionV2 = {
title: file,
file: path.join(file, initFile),
expand_sections: true,
numbered: false,
sections: []
};
toc.push(jupyterSection);
await this.getAllFiles(toc, path.join(directory, file), files, rootDirectory);
} else if (allowedFileExtensions.includes(path.extname(file))) {
// if the file is in the book root we don't include the directory.
const filePath = directory === rootDirectory ? path.parse(file).name : path.join(path.basename(directory), path.parse(file).name);
const addFile: IJupyterBookSectionV2 = {
title: path.parse(file).name,
file: filePath
};
//find if the directory (section) of the file exists else just add the file at the end of the table of contents
let indexToc = toc.findIndex(parent => parent.title === path.basename(directory));
//if there is not init markdown file then add the first notebook or markdown file that is found
if (indexToc !== -1) {
if (toc[indexToc].file === '') {
toc[indexToc].file = addFile.file;
} else {
toc[indexToc].sections.push(addFile);
}
} else {
toc.push(addFile);
}
}
}));
return toc;
}
updateToc(tableOfContents: JupyterBookSection[], findSection: BookTreeItem, addSection: JupyterBookSection): JupyterBookSection[] {
for (const section of tableOfContents) {
if ((section as IJupyterBookSectionV1).url && path.dirname(section.url) === path.join(path.sep, path.dirname(findSection.uri)) || (section as IJupyterBookSectionV2).file && path.dirname((section as IJupyterBookSectionV2).file) === path.join(path.sep, path.dirname(findSection.uri))) {
if (tableOfContents[tableOfContents.length - 1].sections) {
tableOfContents[tableOfContents.length - 1].sections.push(addSection);
} else {
tableOfContents[tableOfContents.length - 1].sections = [addSection];
}
break;
}
else if (hasSections(section)) {
return this.updateToc(section.sections, findSection, addSection);
}
}
return tableOfContents;
}
/**
* Follows the same logic as the JupyterBooksCreate.ipynb. It receives a path that contains a notebooks and
* a path where it creates the book. It copies the contents from one folder to another and creates a table of contents.
* @param bookContentPath The path to the book folder, the basename of the path is the name of the book
* @param contentFolder The path to the folder that contains the notebooks and markdown files to be added to the created book.
*/
public async createBook(bookContentPath: string, contentFolder: string): Promise<void> {
await fs.promises.mkdir(bookContentPath);
await fs.copy(contentFolder, bookContentPath);
let filesinDir = await fs.readdir(bookContentPath);
this.tableofContents = await this.getAllFiles([], bookContentPath, filesinDir, bookContentPath);
await fs.writeFile(path.join(bookContentPath, '_config.yml'), yaml.safeDump({ title: path.basename(bookContentPath) }));
await fs.writeFile(path.join(bookContentPath, '_toc.yml'), yaml.safeDump(this.tableofContents, { lineWidth: Infinity }));
await vscode.commands.executeCommand('notebook.command.openNotebookFolder', bookContentPath, undefined, true);
}
async addSection(section: BookTreeItem, book: BookTreeItem, isSection: boolean): Promise<void> {
this.newSection.title = section.title;
//the book contentPath contains the first file of the section, we get the dirname to identify the section's root path
const rootPath = isSection ? path.dirname(book.book.contentPath) : book.rootContentPath;
// TODO: the uri contains the first notebook or markdown file in the TOC format. If we are in a section,
// we want to include the intermediary directories between the book's root and the section
const uri = isSection ? path.join(path.basename(rootPath), section.uri) : section.uri;
if (section.book.version === BookVersion.v1) {
this.newSection.url = uri;
let movedSections: IJupyterBookSectionV1[] = [];
const files = section.sections as IJupyterBookSectionV1[];
for (const elem of files) {
await fs.promises.mkdir(path.join(rootPath, path.dirname(elem.url)), { recursive: true });
await fs.move(path.join(path.dirname(section.book.contentPath), path.basename(elem.url)), path.join(rootPath, elem.url));
movedSections.push({ url: isSection ? path.join(path.basename(rootPath), elem.url) : elem.url, title: elem.title });
}
this.newSection.sections = movedSections;
} else if (section.book.version === BookVersion.v2) {
(this.newSection as IJupyterBookSectionV2).file = uri;
let movedSections: IJupyterBookSectionV2[] = [];
const files = section.sections as IJupyterBookSectionV2[];
for (const elem of files) {
await fs.promises.mkdir(path.join(rootPath, path.dirname(elem.file)), { recursive: true });
await fs.move(path.join(path.dirname(section.book.contentPath), path.basename(elem.file)), path.join(rootPath, elem.file));
movedSections.push({ file: isSection ? path.join(path.basename(rootPath), elem.file) : elem.file, title: elem.title });
}
this.newSection.sections = movedSections;
}
}
async addNotebook(notebook: BookTreeItem, book: BookTreeItem, isSection: boolean): Promise<void> {
//the book's contentPath contains the first file of the section, we get the dirname to identify the section's root path
const rootPath = isSection ? path.dirname(book.book.contentPath) : book.rootContentPath;
let notebookName = path.basename(notebook.book.contentPath);
await fs.move(notebook.book.contentPath, path.join(rootPath, notebookName));
if (book.book.version === BookVersion.v1) {
this.newSection = { url: notebookName, title: notebookName };
} else if (book.book.version === BookVersion.v2) {
this.newSection = { file: notebookName, title: notebookName };
}
}
/**
* Moves the element to the book's folder and adds it to the table of contents.
* @param element Notebook, Markdown File, or section that will be added to the book.
* @param book Book or a BookSection that will be modified.
*/
public async updateBook(element: BookTreeItem, book: BookTreeItem): Promise<void> {
if (element.contextValue === 'section' && book.book.version === element.book.version) {
if (book.contextValue === 'section') {
await this.addSection(element, book, true);
this.tableofContents = this.updateToc(book.tableOfContents.sections, book, this.newSection);
await fs.writeFile(book.tableOfContentsPath, yaml.safeDump(this.tableofContents, { lineWidth: Infinity }));
} else if (book.contextValue === 'savedBook') {
await this.addSection(element, book, false);
book.tableOfContents.sections.push(this.newSection);
await fs.writeFile(book.tableOfContentsPath, yaml.safeDump(book.tableOfContents, { lineWidth: Infinity }));
}
}
else if (element.contextValue === 'savedNotebook') {
if (book.contextValue === 'savedBook') {
await this.addNotebook(element, book, false);
book.tableOfContents.sections.push(this.newSection);
await fs.writeFile(book.tableOfContentsPath, yaml.safeDump(book.tableOfContents, { lineWidth: Infinity }));
} else if (book.contextValue === 'section') {
await this.addNotebook(element, book, true);
this.tableofContents = this.updateToc(book.tableOfContents.sections, book, this.newSection);
await fs.writeFile(book.tableOfContentsPath, yaml.safeDump(this.tableofContents, { lineWidth: Infinity }));
}
}
}
}

View File

@@ -40,6 +40,8 @@ export class BookTreeItem extends vscode.TreeItem {
public readonly version: string;
public command: vscode.Command;
public resourceUri: vscode.Uri;
private _rootContentPath: string;
private _tableOfContentsPath: string;
constructor(public book: BookTreeItemFormat, icons: any) {
super(book.title, book.treeItemCollapsibleState);
@@ -74,7 +76,9 @@ export class BookTreeItem extends vscode.TreeItem {
this.tooltip = `${this._uri}`;
}
else {
this.tooltip = this.book.type === BookTreeItemType.Book ? (this.book.version === BookVersion.v1 ? path.join(this.book.root, content) : this.book.root) : this.book.contentPath;
this._tableOfContentsPath = (this.book.type === BookTreeItemType.Book || this.contextValue === 'section') ? (this.book.version === BookVersion.v1 ? path.join(this.book.root, '_data', 'toc.yml') : path.join(this.book.root, '_toc.yml')) : undefined;
this._rootContentPath = this.book.version === BookVersion.v1 ? path.join(this.book.root, content) : this.book.root;
this.tooltip = this.book.type === BookTreeItemType.Book ? this._rootContentPath : this.book.contentPath;
this.resourceUri = vscode.Uri.file(this.book.root);
}
}
@@ -158,6 +162,14 @@ export class BookTreeItem extends vscode.TreeItem {
return this.book.root;
}
public get rootContentPath(): string {
return this._rootContentPath;
}
public get tableOfContentsPath(): string {
return this._tableOfContentsPath;
}
public get tableOfContents(): IJupyterBookToc {
return this.book.tableOfContents;
}
@@ -176,6 +188,10 @@ export class BookTreeItem extends vscode.TreeItem {
public readonly tooltip: string;
public set uri(uri: string) {
this._uri = uri;
}
/**
* Helper method to find a child section with a specified URL
* @param url The url of the section we're searching for
@@ -188,7 +204,7 @@ export class BookTreeItem extends vscode.TreeItem {
}
private findChildSectionRecur(section: JupyterBookSection, url: string): JupyterBookSection | undefined {
if (section.url && section.url === url) {
if ((section as IJupyterBookSectionV1).url && (section as IJupyterBookSectionV1).url === url || (section as IJupyterBookSectionV2).file && (section as IJupyterBookSectionV2).file === url) {
return section;
} else if (section.sections) {
for (const childSection of section.sections) {

View File

@@ -19,6 +19,7 @@ import * as glob from 'fast-glob';
import { IJupyterBookSectionV2, IJupyterBookSectionV1 } from '../contracts/content';
import { debounce, getPinnedNotebooks } from '../common/utils';
import { IBookPinManager, BookPinManager } from './bookPinManager';
import { BookTocManager, IBookTocManager } from './bookTocManager';
const content = 'content';
@@ -36,6 +37,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
private _openAsUntitled: boolean;
private _bookTrustManager: IBookTrustManager;
public bookPinManager: IBookPinManager;
public bookTocManager: IBookTocManager;
private _bookViewer: vscode.TreeView<BookTreeItem>;
public viewId: string;
@@ -47,6 +49,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
this._extensionContext = extensionContext;
this.books = [];
this.bookPinManager = new BookPinManager();
this.bookTocManager = new BookTocManager();
this.viewId = view;
this.initialize(workspaceFolders).catch(e => console.error(e));
this.prompter = new CodeAdapter();
@@ -131,6 +134,16 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
}
}
async createBook(bookPath: string, contentPath: string): Promise<void> {
bookPath = path.normalize(bookPath);
contentPath = path.normalize(contentPath);
await this.bookTocManager.createBook(bookPath, contentPath);
}
async editBook(book: BookTreeItem, section: BookTreeItem): Promise<void> {
await this.bookTocManager.updateBook(section, book);
}
async openBook(bookPath: string, urlToOpen?: string, showPreview?: boolean, isNotebook?: boolean): Promise<void> {
try {
// Convert path to posix style for easier comparisons

View File

@@ -0,0 +1,371 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import * as path from 'path';
import { BookTocManager, hasSections } from '../../book/bookTocManager';
import { BookTreeItem, BookTreeItemFormat, BookTreeItemType } from '../../book/bookTreeItem';
import * as yaml from 'js-yaml';
import * as sinon from 'sinon';
import { IJupyterBookSectionV1, IJupyterBookSectionV2, JupyterBookSection } from '../../contracts/content';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as uuid from 'uuid';
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) {
return false;
}
}
return true;
}
export function equalSections(actualSection: JupyterBookSection, expectedSection: JupyterBookSection): boolean {
let equalFiles = ((actualSection as IJupyterBookSectionV1).url === (expectedSection as IJupyterBookSectionV1).url || (actualSection as IJupyterBookSectionV2).file === (expectedSection as IJupyterBookSectionV2).file);
if (actualSection.title === expectedSection.title && equalFiles &&
hasSections(actualSection) && hasSections(expectedSection)) {
for (const [index, section] of actualSection.sections.entries()) {
equalSections(section, expectedSection.sections[index]);
}
} else {
return false;
}
return true;
}
describe('BookTocManagerTests', function () {
describe('CreatingBooks', () => {
let notebooks: string[];
let bookFolderPath: string;
let rootFolderPath: string;
let root2FolderPath: string;
const subfolder = 'Subfolder'
afterEach(function (): void {
sinon.restore();
});
beforeEach(async () => {
rootFolderPath = path.join(os.tmpdir(), `BookTestData_${uuid.v4()}`);
bookFolderPath = path.join(os.tmpdir(), `BookTestData_${uuid.v4()}`);
root2FolderPath = path.join(os.tmpdir(), `BookTestData_${uuid.v4()}`);
notebooks = ['notebook1.ipynb', 'notebook2.ipynb', 'notebook3.ipynb', 'index.md', 'readme.md'];
await fs.mkdir(rootFolderPath);
await fs.writeFile(path.join(rootFolderPath, notebooks[0]), '');
await fs.writeFile(path.join(rootFolderPath, notebooks[1]), '');
await fs.writeFile(path.join(rootFolderPath, notebooks[2]), '');
await fs.writeFile(path.join(rootFolderPath, notebooks[3]), '');
await fs.mkdir(root2FolderPath);
await fs.mkdir(path.join(root2FolderPath, subfolder));
await fs.writeFile(path.join(root2FolderPath, notebooks[0]), '');
await fs.writeFile(path.join(root2FolderPath, subfolder, notebooks[1]), '');
await fs.writeFile(path.join(root2FolderPath, subfolder, notebooks[2]), '');
await fs.writeFile(path.join(root2FolderPath, subfolder, notebooks[4]), '');
await fs.writeFile(path.join(root2FolderPath, notebooks[3]), '');
});
it('should create a table of contents with no sections if there are only notebooks in folder', async function (): Promise<void> {
let bookTocManager: BookTocManager = new BookTocManager();
await bookTocManager.createBook(bookFolderPath, rootFolderPath);
let listFiles = await fs.promises.readdir(bookFolderPath);
should(bookTocManager.tableofContents.length).be.equal(4);
should(listFiles.length).be.equal(6);
});
it('should create a table of contents with sections if folder contains submodules', async () => {
let bookTocManager: BookTocManager = new BookTocManager();
let expectedSection: IJupyterBookSectionV2[] = [{
title: 'notebook2',
file: path.join(subfolder, 'notebook2')
},
{
title: 'notebook3',
file: path.join(subfolder, 'notebook3')
}];
await bookTocManager.createBook(bookFolderPath, root2FolderPath);
should(equalTOC(bookTocManager.tableofContents[2].sections, expectedSection)).be.true;
should(bookTocManager.tableofContents[2].file).be.equal(path.join(subfolder, 'readme'));
});
it('should ignore invalid file extensions', async () => {
await fs.writeFile(path.join(rootFolderPath, 'test.txt'), '');
let bookTocManager: BookTocManager = new BookTocManager();
await bookTocManager.createBook(bookFolderPath, rootFolderPath);
let listFiles = await fs.promises.readdir(bookFolderPath);
should(bookTocManager.tableofContents.length).be.equal(4);
should(listFiles.length).be.equal(7);
});
});
describe('EditingBooks', () => {
let book: BookTreeItem;
let bookSection: BookTreeItem;
let bookSection2: BookTreeItem;
let notebook: BookTreeItem;
let rootBookFolderPath: string = path.join(os.tmpdir(), uuid.v4(), 'Book');
let rootSectionFolderPath: string = path.join(os.tmpdir(), uuid.v4(), 'BookSection');
let rootSection2FolderPath: string = path.join(os.tmpdir(), uuid.v4(), 'BookSection2');
let notebookFolder: string = path.join(os.tmpdir(), uuid.v4(), 'Notebook');
let bookTocManager: BookTocManager;
let runs = [
{
it: 'using the jupyter-book legacy version < 0.7.0',
version: 'v1',
url: 'file',
book: {
'rootBookFolderPath': rootBookFolderPath,
'bookContentFolderPath': path.join(rootBookFolderPath, 'content', 'sample'),
'bookDataFolderPath': path.join(rootBookFolderPath, '_data'),
'notebook1': path.join(rootBookFolderPath, 'content', 'notebook'),
'notebook2': path.join(rootBookFolderPath, 'content', 'notebook2'),
'tocPath': path.join(rootBookFolderPath, '_data', 'toc.yml')
},
bookSection1: {
'contentPath': path.join(rootSectionFolderPath, 'content', 'sample', 'readme.md'),
'sectionRoot': rootSectionFolderPath,
'sectionName': 'Sample',
'bookContentFolderPath': path.join(rootSectionFolderPath, 'content', 'sample'),
'bookDataFolderPath': path.join(rootSectionFolderPath, '_data'),
'notebook3': path.join(rootSectionFolderPath, 'content', 'sample', 'notebook3'),
'notebook4': path.join(rootSectionFolderPath, 'content', 'sample', 'notebook4'),
'tocPath': path.join(rootSectionFolderPath, '_data', 'toc.yml')
},
bookSection2: {
'contentPath': path.join(rootSection2FolderPath, 'content', 'test', 'readme.md'),
'sectionRoot': rootSection2FolderPath,
'sectionName': 'Test',
'bookContentFolderPath': path.join(rootSection2FolderPath, 'content', 'test'),
'bookDataFolderPath': path.join(rootSection2FolderPath, '_data'),
'notebook5': path.join(rootSection2FolderPath, 'content', 'test', 'notebook5'),
'notebook6': path.join(rootSection2FolderPath, 'content', 'test', 'notebook6'),
'tocPath': path.join(rootSection2FolderPath, '_data', 'toc.yml')
},
notebook: {
'contentPath': path.join(notebookFolder, 'test', 'readme.md')
},
section: [
{
'title': 'Notebook',
'url': path.join(path.sep, 'notebook')
},
{
'title': 'Notebook 2',
'url': path.join(path.sep, 'notebook2')
}
],
section1: [
{
'title': 'Notebook 3',
'url': path.join('sample', 'notebook3')
},
{
'title': 'Notebook 4',
'url': path.join('sample', 'notebook4')
}
],
section2: [
{
'title': 'Notebook 5',
'url': path.join(path.sep, 'test', 'notebook5')
},
{
'title': 'Notebook 6',
'url': path.join(path.sep, 'test', 'notebook6')
}
]
}, {
it: 'using jupyter-book versions >= 0.7.0',
version: 'v2',
url: 'file',
book: {
'bookContentFolderPath': path.join(rootBookFolderPath, 'sample'),
'rootBookFolderPath': rootBookFolderPath,
'notebook1': path.join(rootBookFolderPath, 'notebook'),
'notebook2': path.join(rootBookFolderPath, 'notebook2'),
'tocPath': path.join(rootBookFolderPath, '_toc.yml')
},
bookSection1: {
'bookContentFolderPath': path.join(rootSectionFolderPath, 'sample'),
'contentPath': path.join(rootSectionFolderPath, 'sample', 'readme.md'),
'sectionRoot': rootSectionFolderPath,
'sectionName': 'Sample',
'notebook3': path.join(rootSectionFolderPath, 'sample', 'notebook3'),
'notebook4': path.join(rootSectionFolderPath, 'sample', 'notebook4'),
'tocPath': path.join(rootSectionFolderPath, '_toc.yml')
},
bookSection2: {
'bookContentFolderPath': path.join(rootSection2FolderPath, 'test'),
'contentPath': path.join(rootSection2FolderPath, 'test', 'readme.md'),
'sectionRoot': rootSection2FolderPath,
'sectionName': 'Test',
'notebook5': path.join(rootSection2FolderPath, 'test', 'notebook5'),
'notebook6': path.join(rootSection2FolderPath, 'test', 'notebook6'),
'tocPath': path.join(rootSection2FolderPath, '_toc.yml')
},
notebook: {
'contentPath': path.join(notebookFolder, 'test', 'readme.md')
},
section: [
{
'title': 'Notebook',
'file': path.join(path.sep, 'notebook')
},
{
'title': 'Notebook 2',
'file': path.join(path.sep, 'notebook2')
}
],
section1: [
{
'title': 'Notebook 3',
'file': path.join('sample', 'notebook3')
},
{
'title': 'Notebook 4',
'file': path.join('sample', 'notebook4')
}
],
section2: [
{
'title': 'Notebook 5',
'file': path.join(path.sep, 'test', 'notebook5')
},
{
'title': 'Notebook 6',
'file': path.join(path.sep, 'test', 'notebook6')
}
]
}
];
runs.forEach(function (run) {
describe('Editing Books ' + run.it, function (): void {
beforeEach(async () => {
let bookTreeItemFormat1: BookTreeItemFormat = {
contentPath: run.version === 'v1' ? path.join(run.book.rootBookFolderPath, 'content', 'index.md') : path.join(run.book.rootBookFolderPath, 'index.md'),
root: run.book.rootBookFolderPath,
tableOfContents: {
sections: run.section
},
isUntitled: undefined,
title: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book,
version: run.version,
page: run.section
};
let bookTreeItemFormat2: BookTreeItemFormat = {
title: run.bookSection1.sectionName,
contentPath: run.bookSection1.contentPath,
root: run.bookSection1.sectionRoot,
tableOfContents: {
sections: run.section1
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book,
version: run.version,
page: run.section1
};
let bookTreeItemFormat3: BookTreeItemFormat = {
title: run.bookSection2.sectionName,
contentPath: run.bookSection2.contentPath,
root: run.bookSection2.sectionRoot,
tableOfContents: {
sections: run.section2
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book,
version: run.version,
page: run.section2
};
let bookTreeItemFormat4: BookTreeItemFormat = {
title: run.bookSection2.sectionName,
contentPath: run.notebook.contentPath,
root: run.bookSection2.sectionRoot,
tableOfContents: {
sections: undefined
},
isUntitled: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Notebook,
page: {
sections: undefined
}
};
book = new BookTreeItem(bookTreeItemFormat1, undefined);
bookSection = new BookTreeItem(bookTreeItemFormat2, undefined);
bookSection2 = new BookTreeItem(bookTreeItemFormat3, undefined);
notebook = new BookTreeItem(bookTreeItemFormat4, undefined);
bookTocManager = new BookTocManager();
bookSection.uri = path.join('sample', 'readme');
bookSection2.uri = path.join('test', 'readme');
book.contextValue = 'savedBook';
bookSection.contextValue = 'section';
bookSection2.contextValue = 'section';
notebook.contextValue = 'savedNotebook';
await fs.promises.mkdir(run.book.bookContentFolderPath, { recursive: true });
await fs.promises.mkdir(run.bookSection1.bookContentFolderPath, { recursive: true });
await fs.promises.mkdir(run.bookSection2.bookContentFolderPath, { recursive: true });
await fs.promises.mkdir(path.dirname(run.notebook.contentPath), { recursive: true });
if (run.book.bookDataFolderPath && run.bookSection1.bookDataFolderPath && run.bookSection2.bookDataFolderPath) {
await fs.promises.mkdir(run.book.bookDataFolderPath, { recursive: true });
await fs.promises.mkdir(run.bookSection1.bookDataFolderPath, { recursive: true });
await fs.promises.mkdir(run.bookSection2.bookDataFolderPath, { recursive: true });
}
await fs.writeFile(run.book.notebook1, '');
await fs.writeFile(run.book.notebook2, '');
await fs.writeFile(run.bookSection1.notebook3, '');
await fs.writeFile(run.bookSection1.notebook4, '');
await fs.writeFile(run.bookSection2.notebook5, '');
await fs.writeFile(run.bookSection2.notebook6, '');
await fs.writeFile(run.notebook.contentPath, '');
});
it('Add section to book', async () => {
await bookTocManager.updateBook(bookSection, book);
const listFiles = await fs.promises.readdir(run.book.bookContentFolderPath);
const tocFile = await fs.promises.readFile(run.book.tocPath, 'utf8');
let toc = yaml.safeLoad(tocFile);
should(JSON.stringify(listFiles)).be.equal(JSON.stringify(['notebook3', 'notebook4']), 'The files of the section should be moved to the books folder');
should(equalSections(toc.sections[2], bookTocManager.newSection)).be.true;
});
it('Add section to section', async () => {
await bookTocManager.updateBook(bookSection, bookSection2);
let listFiles = await fs.promises.readdir(path.join(run.bookSection2.bookContentFolderPath, 'sample'));
const tocFile = await fs.promises.readFile(path.join(run.bookSection2.tocPath), 'utf8');
let toc = yaml.safeLoad(tocFile);
should(JSON.stringify(listFiles)).be.equal(JSON.stringify(['notebook3', 'notebook4']), 'The files of the section should be moved to the books folder');
should(equalSections(toc[1].sections, bookTocManager.newSection)).be.true;
});
it('Add notebook to book', async () => {
await bookTocManager.updateBook(notebook, book);
const folder = run.version === 'v1' ? path.join(run.book.rootBookFolderPath, 'content') : path.join(run.book.rootBookFolderPath);
let listFiles = await fs.promises.readdir(folder);
const tocFile = await fs.promises.readFile(run.book.tocPath, 'utf8');
let toc = yaml.safeLoad(tocFile);
should(listFiles.findIndex(f => f === 'readme.md')).not.equal(-1);
should(equalSections(toc.sections[2], bookTocManager.newSection)).be.true;
});
});
});
});
});