Incorporates files from retrolab PR 275 and fix rebase changes

pull/6487/head
Jason Weill 4 years ago committed by foo
parent e8e0f8f321
commit 73a64bbf12

@ -44,7 +44,17 @@ import { PromiseDelegate } from '@lumino/coreutils';
import { DisposableDelegate, DisposableSet } from '@lumino/disposable';
import { Widget } from '@lumino/widgets';
import { Menu, Widget } from '@lumino/widgets';
/**
* The default notebook factory.
*/
const NOTEBOOK_FACTORY = 'Notebook';
/**
* The editor factory.
*/
const EDITOR_FACTORY = 'Editor';
/**
* A regular expression to match path to notebooks and documents
@ -65,6 +75,11 @@ namespace CommandIDs {
*/
export const toggleTop = 'application:toggle-top';
/**
* Toggle sidebar visibility
*/
export const togglePanel = 'application:toggle-panel';
/**
* Toggle the Zen mode
*/
@ -91,6 +106,13 @@ namespace CommandIDs {
export const resolveTree = 'application:resolve-tree';
}
/**
* Are the left and right panels available on the current page?
*/
const sidePanelsEnabled: () => boolean = () => {
return PageConfig.getOption('notebookPage') === 'notebooks';
};
/**
* Check if the application is dirty before closing the browser tab.
*/
@ -153,10 +175,12 @@ const opener: JupyterFrontEndPlugin<void> = {
id: '@jupyter-notebook/application-extension:opener',
autoStart: true,
requires: [IRouter, IDocumentManager],
optional: [ISettingRegistry],
activate: (
app: JupyterFrontEnd,
router: IRouter,
docManager: IDocumentManager
docManager: IDocumentManager,
settingRegistry: ISettingRegistry | null
): void => {
const { commands } = app;
@ -171,12 +195,21 @@ const opener: JupyterFrontEndPlugin<void> = {
}
const file = decodeURIComponent(path);
const urlParams = new URLSearchParams(parsed.search);
const factory = urlParams.get('factory') ?? 'default';
const ext = PathExt.extname(file);
app.restored.then(async () => {
docManager.open(file, factory, undefined, {
ref: '_noref'
});
// TODO: get factory from file type instead?
if (ext === '.ipynb') {
// TODO: fix upstream?
await settingRegistry?.load('@jupyterlab/notebook-extension:panel');
await Promise.resolve();
docManager.open(file, NOTEBOOK_FACTORY, undefined, {
ref: '_noref'
});
} else {
docManager.open(file, EDITOR_FACTORY, undefined, {
ref: '_noref'
});
}
});
}
});
@ -198,7 +231,7 @@ const menus: JupyterFrontEndPlugin<void> = {
// always disable the Tabs menu
menu.tabsMenu.dispose();
const page = PageConfig.getOption('notebookPage');
const page = PageConfig.getOption('retroPage');
switch (page) {
case 'consoles':
case 'terminals':
@ -557,6 +590,135 @@ const topVisibility: JupyterFrontEndPlugin<void> = {
autoStart: true
};
/**
* Plugin to toggle the left or right sidebar's visibility.
*/
const sidebarVisibility: JupyterFrontEndPlugin<void> = {
id: '@jupyter-notebook/application-extension:sidebar',
requires: [INotebookShell, ITranslator],
optional: [IMainMenu, ISettingRegistry],
activate: (
app: JupyterFrontEnd<JupyterFrontEnd.IShell>,
notebookShell: INotebookShell,
translator: ITranslator,
menu: IMainMenu | null,
settingRegistry: ISettingRegistry | null
) => {
if (!sidePanelsEnabled()) {
return;
}
const trans = translator.load('notebook');
/* Arguments for togglePanel command:
* side, left or right area
* title, widget title to show in the menu
* id, widget ID to activate in the sidebar
*/
app.commands.addCommand(CommandIDs.togglePanel, {
label: args => args['title'] as string,
caption: args => {
// We do not substitute the parameter into the string because the parameter is not
// localized (e.g., it is always 'left') even though the string is localized.
if (args['side'] === 'left') {
return trans.__(
'Show %1 in the left sidebar',
args['title'] as string
);
} else if (args['side'] === 'right') {
return trans.__(
'Show %1 in the right sidebar',
args['title'] as string
);
}
return trans.__('Show %1 in the sidebar', args['title'] as string);
},
execute: args => {
switch (args['side'] as string) {
case 'left':
if (notebookShell.leftCollapsed) {
notebookShell.activateById(args['id'] as string);
notebookShell.expandLeft();
} else {
notebookShell.collapseLeft();
if (notebookShell.currentWidget) {
notebookShell.activateById(notebookShell.currentWidget.id);
}
}
break;
case 'right':
if (notebookShell.rightCollapsed) {
notebookShell.activateById(args['id'] as string);
notebookShell.expandRight();
} else {
notebookShell.collapseRight();
if (notebookShell.currentWidget) {
notebookShell.activateById(notebookShell.currentWidget.id);
}
}
break;
}
},
isToggled: args => {
if (notebookShell.leftCollapsed) {
return false;
}
const currentWidget = notebookShell.leftHandler.current;
if (!currentWidget) {
return false;
}
return currentWidget.id === (args['id'] as string);
}
});
const leftSidebarMenu = new Menu({ commands: app.commands });
leftSidebarMenu.title.label = trans.__('Show Left Sidebar');
const rightSidebarMenu = new Menu({ commands: app.commands });
rightSidebarMenu.title.label = trans.__('Show Right Sidebar');
app.restored.then(() => {
const leftWidgets = notebookShell.widgetsList('left');
leftWidgets.forEach(widget => {
leftSidebarMenu.addItem({
command: CommandIDs.togglePanel,
args: {
side: 'left',
title: widget.title.caption,
id: widget.id
}
});
});
const rightWidgets = notebookShell.widgetsList('right');
rightWidgets.forEach(widget => {
rightSidebarMenu.addItem({
command: CommandIDs.togglePanel,
args: {
side: 'right',
title: widget.title.caption,
id: widget.id
}
});
});
const menuItemsToAdd: Menu.IItemOptions[] = [];
if (leftWidgets.length > 0) {
menuItemsToAdd.push({ type: 'submenu', submenu: leftSidebarMenu });
}
if (rightWidgets.length > 0) {
menuItemsToAdd.push({ type: 'submenu', submenu: rightSidebarMenu });
}
if (menu && menuItemsToAdd) {
menu.viewMenu.addGroup(menuItemsToAdd, 2);
}
});
},
autoStart: true
};
/**
* The default tree route resolver plugin.
*/
@ -711,6 +873,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
router,
sessionDialogs,
shell,
sidebarVisibility,
status,
tabTitle,
title,

@ -2,18 +2,23 @@
// Distributed under the terms of the Modified BSD License.
import { JupyterFrontEnd } from '@jupyterlab/application';
import { PageConfig } from '@jupyterlab/coreutils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { ArrayExt } from '@lumino/algorithm';
import { ArrayExt, find } from '@lumino/algorithm';
import { Token } from '@lumino/coreutils';
import { Message, MessageLoop, IMessageHandler } from '@lumino/messaging';
import { Debouncer } from '@lumino/polling';
import { ISignal, Signal } from '@lumino/signaling';
import { Panel, Widget, BoxLayout } from '@lumino/widgets';
import {
BoxLayout,
Layout,
Panel,
SplitPanel,
StackedPanel,
Widget
} from '@lumino/widgets';
/**
* The Jupyter Notebook application shell token.
@ -40,39 +45,99 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
super();
this.id = 'main';
const rootLayout = new BoxLayout();
this._topHandler = new Private.PanelHandler();
this._menuHandler = new Private.PanelHandler();
this._leftHandler = new Private.SideBarHandler();
this._rightHandler = new Private.SideBarHandler();
this._main = new Panel();
const topWrapper = (this._topWrapper = new Panel());
const menuWrapper = (this._menuWrapper = new Panel());
this._topHandler.panel.id = 'top-panel';
this._menuHandler.panel.id = 'menu-panel';
this._main.id = 'main-panel';
this._spacer = new Widget();
this._spacer.id = 'spacer-widget';
// create wrappers around the top and menu areas
const topWrapper = (this._topWrapper = new Panel());
topWrapper.id = 'top-panel-wrapper';
topWrapper.addWidget(this._topHandler.panel);
const menuWrapper = (this._menuWrapper = new Panel());
menuWrapper.id = 'menu-panel-wrapper';
menuWrapper.addWidget(this._menuHandler.panel);
BoxLayout.setStretch(topWrapper, 0);
BoxLayout.setStretch(menuWrapper, 0);
BoxLayout.setStretch(this._topWrapper, 0);
BoxLayout.setStretch(this._menuWrapper, 0);
if (this.sidePanelsVisible()) {
this.layout = this.initLayoutWithSidePanels();
} else {
this.layout = this.initLayoutWithoutSidePanels();
}
}
initLayoutWithoutSidePanels(): Layout {
const rootLayout = new BoxLayout();
BoxLayout.setStretch(this._main, 1);
this._spacer = new Widget();
this._spacer.id = 'spacer-widget';
rootLayout.spacing = 0;
rootLayout.addWidget(topWrapper);
rootLayout.addWidget(menuWrapper);
rootLayout.addWidget(this._topWrapper);
rootLayout.addWidget(this._menuWrapper);
rootLayout.addWidget(this._spacer);
rootLayout.addWidget(this._main);
this.layout = rootLayout;
return rootLayout;
}
initLayoutWithSidePanels(): Layout {
const rootLayout = new BoxLayout();
const leftHandler = this._leftHandler;
const rightHandler = this._rightHandler;
const mainPanel = this._main;
this.leftPanel.id = 'jp-left-stack';
this.rightPanel.id = 'jp-right-stack';
// Hide the side panels by default.
leftHandler.hide();
rightHandler.hide();
// TODO: Consider storing this as an attribute this._hsplitPanel if saving/restoring layout needed
const hsplitPanel = new SplitPanel();
hsplitPanel.id = 'main-split-panel';
hsplitPanel.spacing = 1;
// Catch current changed events on the side handlers.
leftHandler.updated.connect(this._onLayoutModified, this);
rightHandler.updated.connect(this._onLayoutModified, this);
BoxLayout.setStretch(hsplitPanel, 1);
SplitPanel.setStretch(leftHandler.stackedPanel, 0);
SplitPanel.setStretch(rightHandler.stackedPanel, 0);
SplitPanel.setStretch(mainPanel, 1);
hsplitPanel.addWidget(leftHandler.stackedPanel);
hsplitPanel.addWidget(mainPanel);
hsplitPanel.addWidget(rightHandler.stackedPanel);
// Use relative sizing to set the width of the side panels.
// This will still respect the min-size of children widget in the stacked
// panel.
hsplitPanel.setRelativeSizes([1, 2.5, 1]);
rootLayout.spacing = 0;
rootLayout.addWidget(this._topWrapper);
rootLayout.addWidget(this._menuWrapper);
rootLayout.addWidget(this._spacer);
rootLayout.addWidget(hsplitPanel);
return rootLayout;
}
/**
@ -103,13 +168,62 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
return this._menuWrapper;
}
/**
* Get the left area handler
*/
get leftHandler(): Private.SideBarHandler {
return this._leftHandler;
}
/**
* Get the right area handler
*/
get rightHandler(): Private.SideBarHandler {
return this._rightHandler;
}
/**
* Shortcut to get the left area handler's stacked panel
*/
get leftPanel(): StackedPanel {
return this._leftHandler.stackedPanel;
}
/**
* Shortcut to get the right area handler's stacked panel
*/
get rightPanel(): StackedPanel {
return this._rightHandler.stackedPanel;
}
/**
* Is the left sidebar visible?
*/
get leftCollapsed(): boolean {
return !(this._leftHandler.isVisible && this.leftPanel.isVisible);
}
/**
* Is the right sidebar visible?
*/
get rightCollapsed(): boolean {
return !(this._rightHandler.isVisible && this.rightPanel.isVisible);
}
/**
* Activate a widget in its area.
*/
activateById(id: string): void {
const widget = this._main.widgets.find(w => w.id === id);
if (widget) {
widget.activate();
// Search all areas that can have widgets for this widget, starting with main.
for (const area of ['main', 'top', 'left', 'right', 'menu']) {
if ((area === 'left' || area === 'right') && !this.sidePanelsVisible()) {
continue;
}
const widget = find(this.widgets(area), w => w.id === id);
if (widget) {
widget.activate();
}
}
}
@ -126,24 +240,37 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
*/
add(
widget: Widget,
area?: Shell.Area,
area?: string,
options?: DocumentRegistry.IOpenOptions
): void {
const rank = options?.rank ?? DEFAULT_RANK;
if (area === 'top') {
return this._topHandler.addWidget(widget, rank);
}
if (area === 'menu') {
return this._menuHandler.addWidget(widget, rank);
}
if (area === 'main' || area === undefined) {
if (this._main.widgets.length > 0) {
// do not add the widget if there is already one
return;
}
this._main.addWidget(widget);
this._main.update();
this._currentChanged.emit(void 0);
switch (area) {
case 'top':
return this._topHandler.addWidget(widget, rank);
case 'menu':
return this._menuHandler.addWidget(widget, rank);
case 'main':
case undefined:
if (this._main.widgets.length > 0) {
// do not add the widget if there is already one
return;
}
this._main.addWidget(widget);
this._main.update();
this._currentChanged.emit(void 0);
break;
case 'left':
if (this.sidePanelsVisible()) {
return this._leftHandler.addWidget(widget, rank);
}
throw new Error(`${area} area is not available on this page`);
case 'right':
if (this.sidePanelsVisible()) {
return this._rightHandler.addWidget(widget, rank);
}
throw new Error(`${area} area is not available on this page`);
default:
throw new Error(`Cannot add widget to area: ${area}`);
}
}
@ -179,16 +306,126 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
case 'main':
yield* this._main.widgets;
return;
}
}
/**
* Expand the left panel to show the sidebar with its widget.
*/
expandLeft(): void {
if (!this.sidePanelsVisible()) {
throw new Error('Left panel is not available on this page');
}
this.leftPanel.show();
this._leftHandler.expand(); // Show the current widget, if any
this._onLayoutModified();
}
/**
* Collapse the left panel
*/
collapseLeft(): void {
if (!this.sidePanelsVisible()) {
throw new Error('Left panel is not available on this page');
}
this._leftHandler.collapse();
this.leftPanel.hide();
this._onLayoutModified();
}
/**
* Expand the right panel to show the sidebar with its widget.
*/
expandRight(): void {
if (!this.sidePanelsVisible()) {
throw new Error('Right panel is not available on this page');
}
this.rightPanel.show();
this._rightHandler.expand(); // Show the current widget, if any
this._onLayoutModified();
}
/**
* Collapse the right panel
*/
collapseRight(): void {
if (!this.sidePanelsVisible()) {
throw new Error('Right panel is not available on this page');
}
this._rightHandler.collapse();
this.rightPanel.hide();
this._onLayoutModified();
}
widgetsList(area?: string): readonly Widget[] {
switch (area ?? 'main') {
case 'top':
return this._topHandler.panel.widgets;
case 'menu':
return this._menuHandler.panel.widgets;
case 'main':
return this._main.widgets;
case 'left':
if (this.sidePanelsVisible()) {
return this._leftHandler.stackedPanel.widgets;
}
throw new Error(`Invalid area: ${area}`);
case 'right':
if (this.sidePanelsVisible()) {
return this._rightHandler.stackedPanel.widgets;
}
throw new Error(`Invalid area: ${area}`);
default:
console.error(`This shell has no area called "${area}"`);
return;
}
}
/**
* Return the list of widgets for the given area.
*
* @param area The area
*/
// widgets(area?: string): IIterator<Widget> {
// return iter(this.widgetsList(area));
// }
/**
* Is a particular area empty (no widgets)?
*
* @param area Named area in the application
* @returns true if area has no widgets, false if at least one widget is present
*/
isEmpty(area: Shell.Area): boolean {
return this.widgetsList(area).length === 0;
}
/**
* Can the shell display a left or right panel?
*
* @returns True if the left and right side panels could be shown, false otherwise
*/
sidePanelsVisible(): boolean {
return PageConfig.getOption('notebookPage') === 'notebooks';
}
/**
* Handle a change to the layout.
*/
private _onLayoutModified(): void {
void this._layoutDebouncer.invoke();
}
private _layoutModified = new Signal<this, void>(this);
private _layoutDebouncer = new Debouncer(() => {
this._layoutModified.emit(undefined);
}, 0);
private _topWrapper: Panel;
private _topHandler: Private.PanelHandler;
private _menuWrapper: Panel;
private _menuHandler: Private.PanelHandler;
private _leftHandler: Private.SideBarHandler;
private _rightHandler: Private.SideBarHandler;
private _spacer: Widget;
private _main: Panel;
private _currentChanged = new Signal<this, void>(this);
@ -201,7 +438,7 @@ export namespace Shell {
/**
* The areas of the application shell where widgets can reside.
*/
export type Area = 'main' | 'top' | 'menu';
export type Area = 'main' | 'top' | 'left' | 'right' | 'menu';
}
/**
@ -293,4 +530,175 @@ namespace Private {
private _items = new Array<Private.IRankItem>();
private _panel = new Panel();
}
/**
* A class which manages a side bar that can show at most one widget at a time.
*/
export class SideBarHandler {
/**
* Construct a new side bar handler.
*/
constructor() {
this._stackedPanel = new StackedPanel();
this._stackedPanel.hide();
this._current = null;
this._lastCurrent = null;
this._stackedPanel.widgetRemoved.connect(this._onWidgetRemoved, this);
}
get current(): Widget | null {
return (
this._current ||
this._lastCurrent ||
(this._items.length > 0 ? this._items[0].widget : null)
);
}
/**
* Whether the panel is visible
*/
get isVisible(): boolean {
return this._stackedPanel.isVisible;
}
/**
* Get the stacked panel managed by the handler
*/
get stackedPanel(): StackedPanel {
return this._stackedPanel;
}
/**
* Signal fires when the stacked panel changes
*/
get updated(): ISignal<SideBarHandler, void> {
return this._updated;
}
/**
* Expand the sidebar.
*
* #### Notes
* This will open the most recently used widget, or the first widget
* if there is no most recently used.
*/
expand(): void {
const visibleWidget = this.current;
if (visibleWidget) {
this._current = visibleWidget;
this.activate(visibleWidget.id);
}
}
/**
* Activate a widget residing in the stacked panel by ID.
*
* @param id - The widget's unique ID.
*/
activate(id: string): void {
const widget = this._findWidgetByID(id);
if (widget) {
this._current = widget;
widget.show();
widget.activate();
}
}
/**
* Test whether the sidebar has the given widget by id.
*/
has(id: string): boolean {
return this._findWidgetByID(id) !== null;
}
/**
* Collapse the sidebar so no items are expanded.
*/
collapse(): void {
this._current = null;
}
/**
* Add a widget and its title to the stacked panel.
*
* If the widget is already added, it will be moved.
*/
addWidget(widget: Widget, rank: number): void {
widget.parent = null;
widget.hide();
const item = { widget, rank };
const index = this._findInsertIndex(item);
ArrayExt.insert(this._items, index, item);
this._stackedPanel.insertWidget(index, widget);
// TODO: Update menu to include widget in appropriate position
this._refreshVisibility();
}
/**
* Hide the side panel
*/
hide(): void {
this._isHiddenByUser = true;
this._refreshVisibility();
}
/**
* Show the side panel
*/
show(): void {
this._isHiddenByUser = false;
this._refreshVisibility();
}
/**
* Find the insertion index for a rank item.
*/
private _findInsertIndex(item: Private.IRankItem): number {
return ArrayExt.upperBound(this._items, item, Private.itemCmp);
}
/**
* Find the index of the item with the given widget, or `-1`.
*/
private _findWidgetIndex(widget: Widget): number {
return ArrayExt.findFirstIndex(this._items, i => i.widget === widget);
}
/**
* Find the widget with the given id, or `null`.
*/
private _findWidgetByID(id: string): Widget | null {
const item = find(this._items, value => value.widget.id === id);
return item ? item.widget : null;
}
/**
* Refresh the visibility of the stacked panel.
*/
private _refreshVisibility(): void {
this._stackedPanel.setHidden(this._isHiddenByUser);
this._updated.emit();
}
/*
* Handle the `widgetRemoved` signal from the panel.
*/
private _onWidgetRemoved(sender: StackedPanel, widget: Widget): void {
if (widget === this._lastCurrent) {
this._lastCurrent = null;
}
ArrayExt.removeAt(this._items, this._findWidgetIndex(widget));
// TODO: Remove the widget from the menu
this._refreshVisibility();
}
private _isHiddenByUser = false;
private _items = new Array<Private.IRankItem>();
private _stackedPanel: StackedPanel;
private _current: Widget | null;
private _lastCurrent: Widget | null;
private _updated: Signal<SideBarHandler, void> = new Signal(this);
}
}

@ -80,3 +80,6 @@ body[data-notebook='notebooks'] #main-panel {
body[data-notebook='notebooks'] #spacer-widget {
min-height: unset;
}
/* Sibling imports */
@import './sidepanel.css';

@ -0,0 +1,37 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|
| Adapted from JupyterLab's packages/application/style/sidepanel.css.
|----------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------
| Variables
|----------------------------------------------------------------------------*/
:root {
--jp-private-sidebar-tab-width: 32px;
}
/*-----------------------------------------------------------------------------
| SideBar
|----------------------------------------------------------------------------*/
/* Left */
/* Right */
/* Stack panels */
#jp-left-stack > .lm-Widget,
#jp-right-stack > .lm-Widget {
min-width: var(--jp-sidebar-min-width);
}
#jp-right-stack {
border-left: var(--jp-border-width) solid var(--jp-border-color1);
}
#jp-left-stack {
border-right: var(--jp-border-width) solid var(--jp-border-color1);
}

@ -1,21 +1,32 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { NotebookShell, INotebookShell } from '@jupyter-notebook/application';
import {
INotebookShell,
NotebookShell,
Shell
} from '@jupyter-notebook/application';
import { JupyterFrontEnd } from '@jupyterlab/application';
import { Widget } from '@lumino/widgets';
describe('Shell', () => {
describe('Shell for notebooks', () => {
let shell: INotebookShell;
let sidePanelsVisibleSpy: jest.SpyInstance;
beforeEach(() => {
shell = new NotebookShell();
sidePanelsVisibleSpy = jest
.spyOn(shell, 'sidePanelsVisible')
.mockImplementation(() => {
return true;
});
Widget.attach(shell, document.body);
});
afterEach(() => {
sidePanelsVisibleSpy.mockRestore();
shell.dispose();
});
@ -23,10 +34,16 @@ describe('Shell', () => {
it('should create a LabShell instance', () => {
expect(shell).toBeInstanceOf(NotebookShell);
});
it('should make all areas empty initially', () => {
['main', 'top', 'left', 'right', 'menu'].forEach(area =>
expect(shell.isEmpty(area as Shell.Area)).toBe(true)
);
});
});
describe('#widgets()', () => {
it('should add widgets to existing areas', () => {
it('should add widgets to main area', () => {
const widget = new Widget();
shell.add(widget, 'main');
const widgets = Array.from(shell.widgets('main'));
@ -36,7 +53,7 @@ describe('Shell', () => {
it('should be empty and console.error if area does not exist', () => {
const spy = jest.spyOn(console, 'error');
const jupyterFrontEndShell = shell as JupyterFrontEnd.IShell;
expect(Array.from(jupyterFrontEndShell.widgets('left'))).toHaveLength(0);
expect(Array.from(jupyterFrontEndShell.widgets('fake'))).toHaveLength(0);
expect(spy).toHaveBeenCalled();
});
});
@ -82,4 +99,104 @@ describe('Shell', () => {
expect(widgets.length).toBeGreaterThan(0);
});
});
describe('#add(widget, "left")', () => {
it('should add a widget to the left area', () => {
const widget = new Widget();
widget.id = 'foo';
shell.add(widget, 'left');
expect(shell.isEmpty('left')).toBe(false);
});
});
describe('#add(widget, "right")', () => {
it('should add a widget to the right area', () => {
const widget = new Widget();
widget.id = 'foo';
shell.add(widget, 'right');
expect(shell.isEmpty('right')).toBe(false);
});
});
});
describe('Shell for tree view', () => {
let shell: INotebookShell;
let sidePanelsVisibleSpy: jest.SpyInstance;
beforeEach(() => {
shell = new NotebookShell();
sidePanelsVisibleSpy = jest
.spyOn(shell, 'sidePanelsVisible')
.mockImplementation(() => {
return false;
});
Widget.attach(shell, document.body);
});
afterEach(() => {
sidePanelsVisibleSpy.mockRestore();
shell.dispose();
});
describe('#constructor()', () => {
it('should create a LabShell instance', () => {
expect(shell).toBeInstanceOf(NotebookShell);
});
it('should make all areas empty initially', () => {
['main', 'top', 'menu'].forEach(area =>
expect(shell.isEmpty(area as Shell.Area)).toBe(true)
);
});
});
describe('#widgets()', () => {
it('should add widgets to existing areas', () => {
const widget = new Widget();
shell.add(widget, 'main');
const widgets = Array.from(shell.widgets('main'));
expect(widgets).toEqual([widget]);
});
it('should throw an exception if a fake area does not exist', () => {
const jupyterFrontEndShell = shell as JupyterFrontEnd.IShell;
expect(() => {
jupyterFrontEndShell.widgets('fake');
}).toThrow('Invalid area: fake');
});
it('should throw an exception if the left area does not exist', () => {
const jupyterFrontEndShell = shell as JupyterFrontEnd.IShell;
expect(() => {
jupyterFrontEndShell.widgets('left');
}).toThrow('Invalid area: left');
});
it('should throw an exception if the right area does not exist', () => {
const jupyterFrontEndShell = shell as JupyterFrontEnd.IShell;
expect(() => {
jupyterFrontEndShell.widgets('right');
}).toThrow('Invalid area: right');
});
});
describe('#add(widget, "left")', () => {
it('should fail to add a widget to the left area', () => {
const widget = new Widget();
widget.id = 'foo';
expect(() => {
shell.add(widget, 'left');
}).toThrow('left area is not available on this page');
});
});
describe('#add(widget, "right")', () => {
it('should fail to add a widget to the right area', () => {
const widget = new Widget();
widget.id = 'foo';
expect(() => {
shell.add(widget, 'right');
}).toThrow('right area is not available on this page');
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Loading…
Cancel
Save