瀏覽代碼

WIP notebook tests.

Also delete the notebook panel’s .notebook attribute, in favor of .content.
Jason Grout 7 年之前
父節點
當前提交
0468ec25fb

+ 1 - 9
packages/notebook/src/panel.ts

@@ -78,6 +78,7 @@ class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
 
     // Set up things related to the context
     this.content.model = this.context.model;
+    // TODO: what happens when the context model changes? Can the context model change?
     this.context.session.kernelChanged.connect(this._onKernelChanged, this);
 
     this.context.ready.then(() => {
@@ -124,15 +125,6 @@ class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
     return this.context.session;
   }
 
-  /**
-   * A convenience method to access the notebook.
-   *
-   * TODO: deprecate this in favor of the .content attribute
-   */
-  get notebook(): Notebook {
-    return this.content;
-  }
-
   /**
    * The content factory for the notebook.
    *

+ 9 - 6
tests/notebook-utils.ts

@@ -18,7 +18,11 @@ import {
 } from '@jupyterlab/coreutils';
 
 import {
-  NotebookPanel, Notebook, NotebookModel, StaticNotebook
+  Context
+} from '@jupyterlab/docregistry';
+
+import {
+  INotebookModel, NotebookPanel, Notebook, NotebookModel, StaticNotebook
 } from '@jupyterlab/notebook';
 
 import {
@@ -108,7 +112,7 @@ function createNotebookPanelFactory(): NotebookPanel.IContentFactory {
 export
 function createNotebook(): Notebook {
   return new Notebook({
-    rendermime,
+    rendermime: defaultRenderMime(),
     contentFactory: createNotebookFactory(),
     mimeTypeService
   });
@@ -119,11 +123,10 @@ function createNotebook(): Notebook {
  * Create a notebook panel widget.
  */
 export
-function createNotebookPanel(): NotebookPanel {
+function createNotebookPanel(context: Context<INotebookModel>): NotebookPanel {
   return new NotebookPanel({
-    rendermime,
-    contentFactory: createNotebookPanelFactory(),
-    mimeTypeService
+    content: createNotebook(),
+    context
   });
 }
 

+ 2 - 8
tests/test-docregistry/src/default.spec.ts

@@ -21,15 +21,9 @@ import {
 } from '@jupyterlab/services';
 
 import {
-  createFileContext
+  createFileContext, sleep
 } from '../../utils';
 
-function asyncTimer(time: number, value: any) {
-  return new Promise((resolve, reject) => {
-    setTimeout(resolve(value), time);
-  });
-}
-
 
 class WidgetFactory extends ABCWidgetFactory<IDocumentWidget> {
 
@@ -612,7 +606,7 @@ describe('docregistry/default', () => {
 
       it('should resolve after the ready and context ready promises', async () => {
         let x = Object.create(null);
-        let ready = asyncTimer(300, x);
+        let ready = sleep(300, x);
         let contextReady = Promise.all([context.ready, x]);
         let widget = new DocumentWidget({ context, content, ready: ready});
         expect(widget.isReady).to.be(false);

+ 296 - 288
tests/test-notebook/src/celltools.spec.ts

@@ -31,6 +31,9 @@ import {
   createNotebookPanel, populateNotebook
 } from '../../notebook-utils';
 
+import {
+  createNotebookContext
+} from '../../utils';
 
 class LogTool extends CellTools.Tool {
 
@@ -89,419 +92,424 @@ class LogKeySelector extends CellTools.KeySelector {
   }
 }
 
+describe('@jupyterlab/notebook', () => {
+  describe('celltools', () => {
+
+    let celltools: CellTools;
+    let tabpanel: TabPanel;
+    let tracker: NotebookTracker;
+    let panel0: NotebookPanel;
+    let panel1: NotebookPanel;
+
+    beforeEach(async () => {
+      const context0 = await createNotebookContext();
+      await context0.initialize(true);
+      panel0 = createNotebookPanel(context0);
+      populateNotebook(panel0.content);
+
+      const context1 = await createNotebookContext();
+      await context1.initialize(true);
+      panel1 = createNotebookPanel(context1);
+      populateNotebook(panel1.content);
+
+      tracker = new NotebookTracker({ namespace: 'notebook' });
+      tracker.add(panel0);
+      tracker.add(panel1);
+      celltools = new CellTools({ tracker });
+      tabpanel = new TabPanel();
+      tabpanel.addWidget(panel0);
+      tabpanel.addWidget(panel1);
+      tabpanel.addWidget(celltools);
+      tabpanel.node.style.height = '800px';
+      Widget.attach(tabpanel, document.body);
+      // Give the posted messages a chance to be handled.
+      await undefined;
+    });
 
-describe('notebook/celltools', () => {
-
-  let celltools: CellTools;
-  let tabpanel: TabPanel;
-  let tracker: NotebookTracker;
-  let panel0: NotebookPanel;
-  let panel1: NotebookPanel;
-
-  beforeEach((done) => {
-    tracker = new NotebookTracker({ namespace: 'notebook' });
-    panel0 = createNotebookPanel();
-    populateNotebook(panel0.notebook);
-    panel1 = createNotebookPanel();
-    populateNotebook(panel1.notebook);
-    tracker.add(panel0);
-    tracker.add(panel1);
-    celltools = new CellTools({ tracker });
-    tabpanel = new TabPanel();
-    tabpanel.addWidget(panel0);
-    tabpanel.addWidget(panel1);
-    tabpanel.addWidget(celltools);
-    tabpanel.node.style.height = '800px';
-    Widget.attach(tabpanel, document.body);
-    // Wait for posted messages.
-    requestAnimationFrame(() => {
-      done();
+    afterEach(() => {
+      tabpanel.dispose();
+      celltools.dispose();
     });
-  });
 
-  afterEach(() => {
-    tabpanel.dispose();
-    celltools.dispose();
-  });
+    describe('CellTools', () => {
 
-  describe('CellTools', () => {
+      describe('#constructor()', () => {
 
-    describe('#constructor()', () => {
+        it('should create a celltools object', () => {
+          expect(celltools).to.be.a(CellTools);
+        });
 
-      it('should create a celltools object', () => {
-        expect(celltools).to.be.a(CellTools);
       });
 
-    });
+      describe('#activeCell', () => {
 
-    describe('#activeCell', () => {
+        it('should be the active cell', () => {
+          expect(celltools.activeCell).to.be(panel1.content.activeCell);
+          tabpanel.currentIndex = 0;
+          simulate(panel0.node, 'focus');
+          expect(celltools.activeCell).to.be(panel0.content.activeCell);
+        });
 
-      it('should be the active cell', () => {
-        expect(celltools.activeCell).to.be(panel1.notebook.activeCell);
-        tabpanel.currentIndex = 0;
-        simulate(panel0.node, 'focus');
-        expect(celltools.activeCell).to.be(panel0.notebook.activeCell);
       });
 
-    });
+      describe('#selectedCells', () => {
 
-    describe('#selectedCells', () => {
+        it('should be the currently selected cells', () => {
+          expect(celltools.selectedCells).to.eql([panel1.content.activeCell]);
+          tabpanel.currentIndex = 0;
+          simulate(panel0.node, 'focus');
+          expect(celltools.selectedCells).to.eql([panel0.content.activeCell]);
+          panel0.content.select(panel0.content.widgets[1]);
+          expect(celltools.selectedCells.length).to.be(2);
+        });
 
-      it('should be the currently selected cells', () => {
-        expect(celltools.selectedCells).to.eql([panel1.notebook.activeCell]);
-        tabpanel.currentIndex = 0;
-        simulate(panel0.node, 'focus');
-        expect(celltools.selectedCells).to.eql([panel0.notebook.activeCell]);
-        panel0.notebook.select(panel0.notebook.widgets[1]);
-        expect(celltools.selectedCells.length).to.be(2);
       });
 
-    });
+      describe('#addItem()', () => {
 
-    describe('#addItem()', () => {
+        it('should add a cell tool item', () => {
+          let tool = new CellTools.Tool();
+          celltools.addItem({ tool });
+          tool.dispose();
+        });
 
-      it('should add a cell tool item', () => {
-        let tool = new CellTools.Tool();
-        celltools.addItem({ tool });
-        tool.dispose();
-      });
+        it('should accept a rank', () => {
+          let tool = new CellTools.Tool();
+          celltools.addItem({ tool, rank: 100 });
+          tool.dispose();
+        });
 
-      it('should accept a rank', () => {
-        let tool = new CellTools.Tool();
-        celltools.addItem({ tool, rank: 100 });
-        tool.dispose();
       });
 
     });
 
-  });
+    describe('CellTools.Tool', () => {
 
-  describe('CellTools.Tool', () => {
+      describe('#constructor', () => {
 
-    describe('#constructor', () => {
+        it('should create a new base tool', () => {
+          let tool = new CellTools.Tool();
+          expect(tool).to.be.a(CellTools.Tool);
+        });
 
-      it('should create a new base tool', () => {
-        let tool = new CellTools.Tool();
-        expect(tool).to.be.a(CellTools.Tool);
       });
 
-    });
+      describe('#parent', () => {
 
-    describe('#parent', () => {
+        it('should be the celltools object used by the tool', () => {
+          let tool = new CellTools.Tool({ });
+          celltools.addItem({ tool });
+          expect(tool.parent).to.be(celltools);
+        });
 
-      it('should be the celltools object used by the tool', () => {
-        let tool = new CellTools.Tool({ });
-        celltools.addItem({ tool });
-        expect(tool.parent).to.be(celltools);
       });
 
-    });
+      describe('#onActiveCellChanged()', () => {
 
-    describe('#onActiveCellChanged()', () => {
+        it('should be called when the active cell changes', () => {
+          let tool = new LogTool({});
+          celltools.addItem({ tool });
+          tool.methods = [];
+          simulate(panel0.node, 'focus');
+          expect(tool.methods).to.contain('onActiveCellChanged');
+        });
 
-      it('should be called when the active cell changes', () => {
-        let tool = new LogTool({});
-        celltools.addItem({ tool });
-        tool.methods = [];
-        simulate(panel0.node, 'focus');
-        expect(tool.methods).to.contain('onActiveCellChanged');
       });
 
-    });
+      describe('#onSelectionChanged()', () => {
 
-    describe('#onSelectionChanged()', () => {
+        it('should be called when the selection changes', () => {
+          let tool = new LogTool({});
+          celltools.addItem({ tool });
+          tool.methods = [];
+          let current = tracker.currentWidget;
+          current.content.select(current.content.widgets[1]);
+          expect(tool.methods).to.contain('onSelectionChanged');
+        });
 
-      it('should be called when the selection changes', () => {
-        let tool = new LogTool({});
-        celltools.addItem({ tool });
-        tool.methods = [];
-        let current = tracker.currentWidget;
-        current.notebook.select(current.notebook.widgets[1]);
-        expect(tool.methods).to.contain('onSelectionChanged');
       });
 
-    });
+      describe('#onMetadataChanged()', () => {
 
-    describe('#onMetadataChanged()', () => {
+        it('should be called when the metadata changes', () => {
+          let tool = new LogTool({});
+          celltools.addItem({ tool });
+          tool.methods = [];
+          let metadata = celltools.activeCell.model.metadata;
+          metadata.set('foo', 1);
+          metadata.set('foo', 2);
+          expect(tool.methods).to.contain('onMetadataChanged');
+        });
 
-      it('should be called when the metadata changes', () => {
-        let tool = new LogTool({});
-        celltools.addItem({ tool });
-        tool.methods = [];
-        let metadata = celltools.activeCell.model.metadata;
-        metadata.set('foo', 1);
-        metadata.set('foo', 2);
-        expect(tool.methods).to.contain('onMetadataChanged');
       });
 
     });
 
-  });
+    describe('CellTools.ActiveCellTool', () => {
 
-  describe('CellTools.ActiveCellTool', () => {
+      it('should create a new active cell tool', () => {
+        let tool = new CellTools.ActiveCellTool();
+        celltools.addItem({ tool });
+        expect(tool).to.be.a(CellTools.ActiveCellTool);
+      });
 
-    it('should create a new active cell tool', () => {
-      let tool = new CellTools.ActiveCellTool();
-      celltools.addItem({ tool });
-      expect(tool).to.be.a(CellTools.ActiveCellTool);
-    });
+      it('should handle a change to the active cell', () => {
+        let tool = new CellTools.ActiveCellTool();
+        celltools.addItem({ tool });
+        let widget = tracker.currentWidget;
+        widget.content.activeCellIndex++;
+        widget.content.activeCell.model.metadata.set('bar', 1);
+        expect(tool.node.querySelector('.jp-InputArea-editor')).to.be.ok();
+      });
 
-    it('should handle a change to the active cell', () => {
-      let tool = new CellTools.ActiveCellTool();
-      celltools.addItem({ tool });
-      let widget = tracker.currentWidget;
-      widget.notebook.activeCellIndex++;
-      widget.notebook.activeCell.model.metadata.set('bar', 1);
-      expect(tool.node.querySelector('.jp-InputArea-editor')).to.be.ok();
     });
 
-  });
+    describe('CellTools.MetadataEditorTool', () => {
 
-  describe('CellTools.MetadataEditorTool', () => {
+      let editorServices = new CodeMirrorEditorFactory();
+      const editorFactory = editorServices.newInlineEditor.bind(editorServices);
 
-    let editorServices = new CodeMirrorEditorFactory();
-    const editorFactory = editorServices.newInlineEditor.bind(editorServices);
+      it('should create a new metadata editor tool', () => {
+        let tool = new CellTools.MetadataEditorTool({ editorFactory });
+        expect(tool).to.be.a(CellTools.MetadataEditorTool);
+      });
 
-    it('should create a new metadata editor tool', () => {
-      let tool = new CellTools.MetadataEditorTool({ editorFactory });
-      expect(tool).to.be.a(CellTools.MetadataEditorTool);
-    });
+      it('should handle a change to the active cell', () => {
+        let tool = new CellTools.MetadataEditorTool({ editorFactory });
+        celltools.addItem({ tool });
+        let model = tool.editor.model;
+        expect(JSON.stringify(model.value.text)).to.ok();
+        let widget = tracker.currentWidget;
+        widget.content.activeCellIndex++;
+        widget.content.activeCell.model.metadata.set('bar', 1);
+        expect(JSON.stringify(model.value.text)).to.contain('bar');
+      });
 
-    it('should handle a change to the active cell', () => {
-      let tool = new CellTools.MetadataEditorTool({ editorFactory });
-      celltools.addItem({ tool });
-      let model = tool.editor.model;
-      expect(JSON.stringify(model.value.text)).to.ok();
-      let widget = tracker.currentWidget;
-      widget.notebook.activeCellIndex++;
-      widget.notebook.activeCell.model.metadata.set('bar', 1);
-      expect(JSON.stringify(model.value.text)).to.contain('bar');
-    });
+      it('should handle a change to the metadata', () => {
+        let tool = new CellTools.MetadataEditorTool({ editorFactory });
+        celltools.addItem({ tool });
+        let model = tool.editor.model;
+        let previous = model.value.text;
+        let metadata = celltools.activeCell.model.metadata;
+        metadata.set('foo', 1);
+        expect(model.value.text).to.not.be(previous);
+      });
 
-    it('should handle a change to the metadata', () => {
-      let tool = new CellTools.MetadataEditorTool({ editorFactory });
-      celltools.addItem({ tool });
-      let model = tool.editor.model;
-      let previous = model.value.text;
-      let metadata = celltools.activeCell.model.metadata;
-      metadata.set('foo', 1);
-      expect(model.value.text).to.not.be(previous);
     });
 
-  });
+    describe('CellTools.KeySelector', () => {
 
-  describe('CellTools.KeySelector', () => {
+      let tool: LogKeySelector;
 
-    let tool: LogKeySelector;
+      beforeEach(() => {
+        tool = new LogKeySelector({
+          key: 'foo',
+          optionsMap: {
+            'bar': 1,
+            'baz': [1, 2, 'a']
+          }
+        });
+        celltools.addItem({ tool });
+        simulate(panel0.node, 'focus');
+        tabpanel.currentIndex = 2;
+      });
 
-    beforeEach(() => {
-      tool = new LogKeySelector({
-        key: 'foo',
-        optionsMap: {
-          'bar': 1,
-          'baz': [1, 2, 'a']
-        }
+      afterEach(() => {
+        tool.dispose();
       });
-      celltools.addItem({ tool });
-      simulate(panel0.node, 'focus');
-      tabpanel.currentIndex = 2;
-    });
 
-    afterEach(() => {
-      tool.dispose();
-    });
+      describe('#constructor()', () => {
 
-    describe('#constructor()', () => {
+        it('should create a new key selector', () => {
+          expect(tool).to.be.a(CellTools.KeySelector);
+        });
 
-      it('should create a new key selector', () => {
-        expect(tool).to.be.a(CellTools.KeySelector);
       });
 
-    });
+      describe('#key', () => {
 
-    describe('#key', () => {
+        it('should be the key used by the selector', () => {
+          expect(tool.key).to.be('foo');
+        });
 
-      it('should be the key used by the selector', () => {
-        expect(tool.key).to.be('foo');
       });
 
-    });
+      describe('#selectNode', () => {
 
-    describe('#selectNode', () => {
+        it('should be the select node', () => {
+          expect(tool.selectNode.localName).to.be('select');
+        });
 
-      it('should be the select node', () => {
-        expect(tool.selectNode.localName).to.be('select');
       });
 
-    });
+      describe('#handleEvent()', () => {
 
-    describe('#handleEvent()', () => {
+        context('change', () => {
 
-      context('change', () => {
+          it('should update the metadata', () => {
+            let select = tool.selectNode;
+            simulate(select, 'focus');
+            select.selectedIndex = 1;
+            simulate(select, 'change');
+            expect(tool.events).to.contain('change');
+            let metadata = celltools.activeCell.model.metadata;
+            expect(metadata.get('foo')).to.eql([1, 2, 'a']);
+          });
 
-        it('should update the metadata', () => {
+        });
+
+        context('focus', () => {
+
+          it('should add the focused class to the wrapper node', () => {
+            let select = tool.selectNode;
+            simulate(select, 'focus');
+            let selector = '.jp-mod-focused';
+            expect(tool.node.querySelector(selector)).to.be.ok();
+          });
+
+        });
+
+        context('blur', () => {
+
+          it('should remove the focused class from the wrapper node', () => {
+            let select = tool.selectNode;
+            simulate(select, 'focus');
+            simulate(select, 'blur');
+            let selector = '.jp-mod-focused';
+            expect(tool.node.querySelector(selector)).to.not.be.ok();
+          });
+
+        });
+
+      });
+
+      describe('#onAfterAttach()', () => {
+
+        it('should add event listeners', () => {
           let select = tool.selectNode;
+          expect(tool.methods).to.contain('onAfterAttach');
           simulate(select, 'focus');
-          select.selectedIndex = 1;
+          simulate(select, 'blur');
+          select.selectedIndex = 0;
           simulate(select, 'change');
-          expect(tool.events).to.contain('change');
-          let metadata = celltools.activeCell.model.metadata;
-          expect(metadata.get('foo')).to.eql([1, 2, 'a']);
+          expect(tool.events).to.eql(['change']);
         });
 
       });
 
-      context('focus', () => {
+      describe('#onBeforeDetach()', () => {
 
-        it('should add the focused class to the wrapper node', () => {
+        it('should remove event listeners', () => {
           let select = tool.selectNode;
+          celltools.dispose();
+          expect(tool.methods).to.contain('onBeforeDetach');
           simulate(select, 'focus');
-          let selector = '.jp-mod-focused';
-          expect(tool.node.querySelector(selector)).to.be.ok();
+          simulate(select, 'blur');
+          simulate(select, 'change');
+          expect(tool.events).to.eql([]);
         });
 
       });
 
-      context('blur', () => {
+      describe('#onValueChanged()', () => {
 
-        it('should remove the focused class from the wrapper node', () => {
+        it('should update the metadata', () => {
           let select = tool.selectNode;
           simulate(select, 'focus');
-          simulate(select, 'blur');
-          let selector = '.jp-mod-focused';
-          expect(tool.node.querySelector(selector)).to.not.be.ok();
+          select.selectedIndex = 1;
+          simulate(select, 'change');
+          expect(tool.methods).to.contain('onValueChanged');
+          let metadata = celltools.activeCell.model.metadata;
+          expect(metadata.get('foo')).to.eql([1, 2, 'a']);
         });
 
       });
 
-    });
+      describe('#onActiveCellChanged()', () => {
 
-    describe('#onAfterAttach()', () => {
+        it('should update the select value', () => {
+          let cell = panel0.content.model.cells.get(1);
+          cell.metadata.set('foo', 1);
+          panel0.content.activeCellIndex = 1;
+          expect(tool.methods).to.contain('onActiveCellChanged');
+          expect(tool.selectNode.value).to.be('1');
+        });
 
-      it('should add event listeners', () => {
-        let select = tool.selectNode;
-        expect(tool.methods).to.contain('onAfterAttach');
-        simulate(select, 'focus');
-        simulate(select, 'blur');
-        select.selectedIndex = 0;
-        simulate(select, 'change');
-        expect(tool.events).to.eql(['change']);
       });
 
-    });
+      describe('#onMetadataChanged()', () => {
 
-    describe('#onBeforeDetach()', () => {
+        it('should update the select value', () => {
+          let metadata = celltools.activeCell.model.metadata;
+          metadata.set('foo', 1);
+          expect(tool.methods).to.contain('onMetadataChanged');
+          expect(tool.selectNode.value).to.be('1');
+        });
 
-      it('should remove event listeners', () => {
-        let select = tool.selectNode;
-        celltools.dispose();
-        expect(tool.methods).to.contain('onBeforeDetach');
-        simulate(select, 'focus');
-        simulate(select, 'blur');
-        simulate(select, 'change');
-        expect(tool.events).to.eql([]);
       });
 
     });
 
-    describe('#onValueChanged()', () => {
+    describe('CellTools.createSlideShowSelector()', () => {
 
-      it('should update the metadata', () => {
+      it('should create a slide show selector', () => {
+        let tool = CellTools.createSlideShowSelector();
+        tool.selectNode.selectedIndex = -1;
+        celltools.addItem({ tool });
+        simulate(panel0.node, 'focus');
+        tabpanel.currentIndex = 2;
+        expect(tool).to.be.a(CellTools.KeySelector);
+        expect(tool.key).to.be('slideshow');
         let select = tool.selectNode;
+        expect(select.value).to.be('');
+        let metadata = celltools.activeCell.model.metadata;
+        expect(metadata.get('slideshow')).to.be(void 0);
         simulate(select, 'focus');
-        select.selectedIndex = 1;
+        tool.selectNode.selectedIndex = 1;
         simulate(select, 'change');
-        expect(tool.methods).to.contain('onValueChanged');
-        let metadata = celltools.activeCell.model.metadata;
-        expect(metadata.get('foo')).to.eql([1, 2, 'a']);
+        expect(metadata.get('slideshow')).to.eql({ 'slide_type': 'slide' });
       });
 
     });
 
-    describe('#onActiveCellChanged()', () => {
+    describe('CellTools.createNBConvertSelector()', () => {
 
-      it('should update the select value', () => {
-        let cell = panel0.notebook.model.cells.get(1);
-        cell.metadata.set('foo', 1);
-        panel0.notebook.activeCellIndex = 1;
-        expect(tool.methods).to.contain('onActiveCellChanged');
-        expect(tool.selectNode.value).to.be('1');
-      });
-
-    });
-
-    describe('#onMetadataChanged()', () => {
+      it('should create a raw mimetype selector', () => {
+        let tool = CellTools.createNBConvertSelector();
+        tool.selectNode.selectedIndex = -1;
+        celltools.addItem({ tool });
+        simulate(panel0.node, 'focus');
+        NotebookActions.changeCellType(panel0.content, 'raw');
+        tabpanel.currentIndex = 2;
+        expect(tool).to.be.a(CellTools.KeySelector);
+        expect(tool.key).to.be('raw_mimetype');
+        let select = tool.selectNode;
+        expect(select.value).to.be('');
 
-      it('should update the select value', () => {
         let metadata = celltools.activeCell.model.metadata;
-        metadata.set('foo', 1);
-        expect(tool.methods).to.contain('onMetadataChanged');
-        expect(tool.selectNode.value).to.be('1');
+        expect(metadata.get('raw_mimetype')).to.be(void 0);
+        simulate(select, 'focus');
+        tool.selectNode.selectedIndex = 2;
+        simulate(select, 'change');
+        expect(metadata.get('raw_mimetype')).to.be('text/restructuredtext');
       });
 
-    });
-
-  });
-
-  describe('CellTools.createSlideShowSelector()', () => {
-
-    it('should create a slide show selector', () => {
-      let tool = CellTools.createSlideShowSelector();
-      tool.selectNode.selectedIndex = -1;
-      celltools.addItem({ tool });
-      simulate(panel0.node, 'focus');
-      tabpanel.currentIndex = 2;
-      expect(tool).to.be.a(CellTools.KeySelector);
-      expect(tool.key).to.be('slideshow');
-      let select = tool.selectNode;
-      expect(select.value).to.be('');
-      let metadata = celltools.activeCell.model.metadata;
-      expect(metadata.get('slideshow')).to.be(void 0);
-      simulate(select, 'focus');
-      tool.selectNode.selectedIndex = 1;
-      simulate(select, 'change');
-      expect(metadata.get('slideshow')).to.eql({ 'slide_type': 'slide' });
-    });
-
-  });
+      it('should have no effect on a code cell', () => {
+        let tool = CellTools.createNBConvertSelector();
+        tool.selectNode.selectedIndex = -1;
+        celltools.addItem({ tool });
+        simulate(panel0.node, 'focus');
+        NotebookActions.changeCellType(panel0.content, 'code');
 
-  describe('CellTools.createNBConvertSelector()', () => {
-
-    it('should create a raw mimetype selector', () => {
-      let tool = CellTools.createNBConvertSelector();
-      tool.selectNode.selectedIndex = -1;
-      celltools.addItem({ tool });
-      simulate(panel0.node, 'focus');
-      NotebookActions.changeCellType(panel0.notebook, 'raw');
-      tabpanel.currentIndex = 2;
-      expect(tool).to.be.a(CellTools.KeySelector);
-      expect(tool.key).to.be('raw_mimetype');
-      let select = tool.selectNode;
-      expect(select.value).to.be('');
-
-      let metadata = celltools.activeCell.model.metadata;
-      expect(metadata.get('raw_mimetype')).to.be(void 0);
-      simulate(select, 'focus');
-      tool.selectNode.selectedIndex = 2;
-      simulate(select, 'change');
-      expect(metadata.get('raw_mimetype')).to.be('text/restructuredtext');
-    });
+        tabpanel.currentIndex = 2;
+        expect(tool).to.be.a(CellTools.KeySelector);
+        expect(tool.key).to.be('raw_mimetype');
+        let select = tool.selectNode;
+        expect(select.disabled).to.be(true);
+        expect(select.value).to.be('');
+      });
 
-    it('should have no effect on a code cell', () => {
-      let tool = CellTools.createNBConvertSelector();
-      tool.selectNode.selectedIndex = -1;
-      celltools.addItem({ tool });
-      simulate(panel0.node, 'focus');
-      NotebookActions.changeCellType(panel0.notebook, 'code');
-
-      tabpanel.currentIndex = 2;
-      expect(tool).to.be.a(CellTools.KeySelector);
-      expect(tool.key).to.be('raw_mimetype');
-      let select = tool.selectNode;
-      expect(select.disabled).to.be(true);
-      expect(select.value).to.be('');
     });
 
   });
-
 });

+ 30 - 69
tests/test-notebook/src/default-toolbar.spec.ts

@@ -3,10 +3,6 @@
 
 import expect = require('expect.js');
 
-import {
-  IClientSession
-} from '@jupyterlab/apputils';
-
 import {
   toArray
 } from '@phosphor/algorithm';
@@ -16,7 +12,7 @@ import {
 } from '@phosphor/widgets';
 
 import {
-  Context, DocumentRegistry
+  Context
 } from '@jupyterlab/docregistry';
 
 import {
@@ -44,55 +40,30 @@ import {
 } from '../../utils';
 
 import {
-  DEFAULT_CONTENT, createNotebookPanelFactory, rendermime, clipboard,
-  mimeTypeService
+  DEFAULT_CONTENT, clipboard, createNotebookPanel
 } from '../../notebook-utils';
 
 
 const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
 
 
-function startSession(context: DocumentRegistry.IContext<INotebookModel>): Promise<IClientSession> {
-  return (context as any).initialize(true).then(() => {
-    return context.ready;
-  }).then(() => {
-    return context.session.kernel.ready;
-  }).then(() => {
-    return context.session;
-  });
-}
-
-
 describe('@jupyterlab/notebook', () => {
 
-  let context: Context<INotebookModel>;
-
-  beforeEach(() => {
-    return createNotebookContext().then(c => {
-      context = c;
-      return context.initialize(true);
-    });
-  });
-
-  afterEach(() => {
-    return context.session.shutdown().then(() => {
-      context.dispose();
-    });
-  });
-
   describe('ToolbarItems', () => {
 
+    let context: Context<INotebookModel>;
     let panel: NotebookPanel;
-    const contentFactory = createNotebookPanelFactory();
 
-    beforeEach(() => {
-      panel = new NotebookPanel({ rendermime, contentFactory,
-                                  mimeTypeService });
+    beforeEach(async () => {
+      context = await createNotebookContext();
+      await context.initialize(true);
+      panel = createNotebookPanel(context);
       context.model.fromJSON(DEFAULT_CONTENT);
-      panel.context = context;
     });
 
-    afterEach(() => {
+    afterEach(async () => {
+      await context.session.shutdown();
+      context.dispose();
       panel.dispose();
     });
 
@@ -121,8 +92,8 @@ describe('@jupyterlab/notebook', () => {
         let button = ToolbarItems.createInsertButton(panel);
         Widget.attach(button, document.body);
         button.node.click();
-        expect(panel.notebook.activeCellIndex).to.be(1);
-        expect(panel.notebook.activeCell).to.be.a(CodeCell);
+        expect(panel.content.activeCellIndex).to.be(1);
+        expect(panel.content.activeCell).to.be.a(CodeCell);
         button.dispose();
       });
 
@@ -137,10 +108,10 @@ describe('@jupyterlab/notebook', () => {
 
       it('should cut when clicked', () => {
         let button = ToolbarItems.createCutButton(panel);
-        let count = panel.notebook.widgets.length;
+        let count = panel.content.widgets.length;
         Widget.attach(button, document.body);
         button.node.click();
-        expect(panel.notebook.widgets.length).to.be(count - 1);
+        expect(panel.content.widgets.length).to.be(count - 1);
         expect(clipboard.hasData(JUPYTER_CELL_MIME)).to.be(true);
         button.dispose();
       });
@@ -156,10 +127,10 @@ describe('@jupyterlab/notebook', () => {
 
       it('should copy when clicked', () => {
         let button = ToolbarItems.createCopyButton(panel);
-        let count = panel.notebook.widgets.length;
+        let count = panel.content.widgets.length;
         Widget.attach(button, document.body);
         button.node.click();
-        expect(panel.notebook.widgets.length).to.be(count);
+        expect(panel.content.widgets.length).to.be(count);
         expect(clipboard.hasData(JUPYTER_CELL_MIME)).to.be(true);
         button.dispose();
       });
@@ -173,17 +144,15 @@ describe('@jupyterlab/notebook', () => {
 
     describe('#createPasteButton()', () => {
 
-      it('should paste when clicked', (done) => {
+      it('should paste when clicked', async () => {
         let button = ToolbarItems.createPasteButton(panel);
-        let count = panel.notebook.widgets.length;
+        let count = panel.content.widgets.length;
         Widget.attach(button, document.body);
-        NotebookActions.copy(panel.notebook);
+        NotebookActions.copy(panel.content);
         button.node.click();
-        requestAnimationFrame(() => {
-          expect(panel.notebook.widgets.length).to.be(count + 1);
-          button.dispose();
-          done();
-        });
+        await 0;
+        expect(panel.content.widgets.length).to.be(count + 1);
+        button.dispose();
       });
 
       it('should have the `\'jp-PasteIcon\'` class', () => {
@@ -195,16 +164,17 @@ describe('@jupyterlab/notebook', () => {
 
     describe('#createRunButton()', () => {
 
-      it('should run and advance when clicked', (done) => {
+      it.skip('should run and advance when clicked', (done) => {
         let button = ToolbarItems.createRunButton(panel);
-        let widget = panel.notebook;
+        let widget = panel.content;
         let next = widget.widgets[1] as MarkdownCell;
         widget.select(next);
         let cell = widget.activeCell as CodeCell;
         cell.model.outputs.clear();
         next.rendered = false;
         Widget.attach(button, document.body);
-        startSession(panel.context).then(session => {
+        const session = panel.context.session;
+        panel.context.session.kernel.ready.then(() => {
           session.statusChanged.connect((sender, status) => {
             if (status === 'idle' && cell.model.outputs.length > 0) {
               expect(next.rendered).to.be(true);
@@ -213,7 +183,7 @@ describe('@jupyterlab/notebook', () => {
             }
           });
           button.node.click();
-        }).catch(done);
+        });
       });
 
       it('should have the `\'jp-RunIcon\'` class', () => {
@@ -229,7 +199,7 @@ describe('@jupyterlab/notebook', () => {
         let item = ToolbarItems.createCellTypeItem(panel);
         let node = item.node.getElementsByTagName('select')[0] as HTMLSelectElement;
         expect(node.value).to.be('code');
-        panel.notebook.activeCellIndex++;
+        panel.content.activeCellIndex++;
         expect(node.value).to.be('markdown');
       });
 
@@ -237,7 +207,7 @@ describe('@jupyterlab/notebook', () => {
         let item = ToolbarItems.createCellTypeItem(panel);
         let node = item.node.getElementsByTagName('select')[0] as HTMLSelectElement;
         expect(node.value).to.be('code');
-        panel.notebook.select(panel.notebook.widgets[1]);
+        panel.content.select(panel.content.widgets[1]);
         expect(node.value).to.be('-');
       });
 
@@ -247,19 +217,10 @@ describe('@jupyterlab/notebook', () => {
         expect(node.value).to.be('code');
         let cell = panel.model.contentFactory.createCodeCell({});
         panel.model.cells.insert(1, cell);
-        panel.notebook.select(panel.notebook.widgets[1]);
+        panel.content.select(panel.content.widgets[1]);
         expect(node.value).to.be('code');
       });
 
-      it('should handle a change in context', () => {
-        let item = ToolbarItems.createCellTypeItem(panel);
-        context.model.fromJSON(DEFAULT_CONTENT);
-        panel.context = null;
-        panel.notebook.activeCellIndex++;
-        let node = item.node.getElementsByTagName('select')[0];
-        expect((node as HTMLSelectElement).value).to.be('markdown');
-      });
-
     });
 
     describe('#populateDefaults()', () => {

+ 9 - 7
tests/test-notebook/src/model.spec.ts

@@ -27,8 +27,12 @@ import {
   DEFAULT_CONTENT
 } from '../../notebook-utils';
 
+import {
+  moment
+} from '../../utils';
+
 
-describe('notebook/notebook/model', () => {
+describe('@jupyterlab/notebook', () => {
 
   describe('NotebookModel', () => {
 
@@ -134,14 +138,12 @@ describe('notebook/notebook/model', () => {
           expect(model.dirty).to.be(true);
         });
 
-        it('should add a new code cell when cells are cleared', (done) => {
+        it('should add a new code cell when cells are cleared', async () => {
           let model = new NotebookModel();
           model.cells.clear();
-          requestAnimationFrame(() => {
-            expect(model.cells.length).to.be(1);
-            expect(model.cells.get(0)).to.be.a(CodeCellModel);
-            done();
-          });
+          await moment();
+          expect(model.cells.length).to.be(1);
+          expect(model.cells.get(0)).to.be.a(CodeCellModel);
         });
 
       });

+ 1 - 1
tests/test-notebook/src/modelfactory.spec.ts

@@ -16,7 +16,7 @@ import {
 } from '@jupyterlab/notebook';
 
 
-describe('notebook/notebook/modelfactory', () => {
+describe('@jupyterlab/notebook', () => {
 
   describe('NotebookModelFactory', () => {
 

+ 46 - 215
tests/test-notebook/src/panel.spec.ts

@@ -8,11 +8,7 @@ import {
 } from '@jupyterlab/services';
 
 import {
-  IChangedArgs, uuid
-} from '@jupyterlab/coreutils';
-
-import {
-  DocumentRegistry, Context
+  Context
 } from '@jupyterlab/docregistry';
 
 import {
@@ -29,7 +25,7 @@ import {
 
 import {
   DEFAULT_CONTENT, createNotebookPanelFactory, rendermime,
-  mimeTypeService, editorFactory
+  mimeTypeService, editorFactory, createNotebookPanel, createNotebook
 } from '../../notebook-utils';
 
 
@@ -40,117 +36,64 @@ const contentFactory = createNotebookPanelFactory();
 const options = { rendermime, mimeTypeService, contentFactory };
 
 
-class LogNotebookPanel extends NotebookPanel {
-
-  methods: string[] = [];
-
-  protected onContextChanged(oldValue: DocumentRegistry.IContext<INotebookModel>, newValue: DocumentRegistry.IContext<INotebookModel>): void {
-    super.onContextChanged(oldValue, newValue);
-    this.methods.push('onContextChanged');
-  }
-
-  protected onModelStateChanged(sender: INotebookModel, args: IChangedArgs<any>): void {
-    super.onModelStateChanged(sender, args);
-    this.methods.push('onModelStateChanged');
-  }
-
-  protected onPathChanged(sender: DocumentRegistry.IContext<INotebookModel>, path: string): void {
-    super.onPathChanged(sender, path);
-    this.methods.push('onPathChanged');
-  }
-}
-
-
-function createPanel(context: Context<INotebookModel>): LogNotebookPanel {
-  let panel = new LogNotebookPanel(options);
+function createPanel(context: Context<INotebookModel>): NotebookPanel {
+  const panel = createNotebookPanel(context);
   context.model.fromJSON(DEFAULT_CONTENT);
-  panel.context = context;
   return panel;
 }
 
 
 describe('@jupyterlab/notebook', () => {
 
-  let context: Context<INotebookModel>;
-  let manager: ServiceManager.IManager;
-
-  before(() => {
-    manager = new ServiceManager();
-    return manager.ready;
-  });
+  describe('NotebookPanel', () => {
 
-  beforeEach(() => {
-    return createNotebookContext('', manager).then(c => {
-      context = c;
+    let context: Context<INotebookModel>;
+    let manager: ServiceManager.IManager;
+  
+    before(async () => {
+      manager = new ServiceManager();
+      await manager.ready;
     });
-  });
-
-  afterEach(() => {
-    return context.session.shutdown().then(() => {
+  
+    after(() => {
+      manager.dispose();
+    });
+  
+    beforeEach(async () => {
+      context = await createNotebookContext('', manager);
+    });
+  
+    afterEach(async () => {
+      await context.session.shutdown();
       context.dispose();
     });
-  });
-
-  after(() => {
-    manager.dispose();
-  });
-
-  describe('NotebookPanel', () => {
 
     describe('#constructor()', () => {
 
       it('should create a notebook panel', () => {
-        let panel = new NotebookPanel(options);
+        const content = createNotebook();
+        const panel = new NotebookPanel({ context, content });
         expect(panel).to.be.a(NotebookPanel);
       });
 
-
-      it('should accept an optional content factory', () => {
-        let newFactory = createNotebookPanelFactory();
-        let panel = new NotebookPanel({
-          mimeTypeService, rendermime, contentFactory: newFactory
-        });
-        expect(panel.contentFactory).to.be(newFactory);
-      });
-
-    });
-
-    describe('#contextChanged', () => {
-
-      it('should be emitted when the context on the panel changes', () => {
-        let panel = new NotebookPanel(options);
-        let called = false;
-        panel.contextChanged.connect((sender, args) => {
-          expect(sender).to.be(panel);
-          expect(args).to.be(void 0);
-          called = true;
-        });
-        panel.context = context;
-        expect(called).to.be(true);
+      it('should initialize the model state', async () => {
+        const panel = createNotebookPanel(context);
+        const model = panel.content.model;
+        expect(model).to.be(context.model);
+        model.fromJSON(DEFAULT_CONTENT);
+        expect(model.cells.canUndo).to.be(true);
+        await context.initialize(true);
+        await context.ready;
+        expect(model.cells.canUndo).to.be(false);
       });
 
-      it('should not be emitted if the context does not change', () => {
-        let panel = new NotebookPanel(options);
-        let called = false;
-        panel.context = context;
-        panel.contextChanged.connect(() => { called = true; });
-        panel.context = context;
-        expect(called).to.be(false);
-      });
-
-    });
-
-    describe('#kernelChanged', () => {
-
-      it('should be emitted when the kernel on the panel changes', (done) => {
-        let panel = createPanel(context);
-        panel.session.kernelChanged.connect((sender, args) => {
-          expect(sender).to.be(panel.session);
-          expect(args.name).to.be.ok();
-          done();
-        });
-        panel.session.changeKernel({ name: 'echo' }).catch(done);
-        (panel.context as any).initialize(true).catch(done);
+      it('should change notebook to edit mode if we have a single empty code cell', async () => {
+        const panel = createNotebookPanel(context);
+        const model = panel.content.model;
+        expect(model).to.be(context.model);
+        await context.initialize(true);
+        await context.ready;
+        expect(panel.content.mode).to.equal('edit');
       });
 
     });
@@ -158,7 +101,7 @@ describe('@jupyterlab/notebook', () => {
     describe('#toolbar', () => {
 
       it('should be the toolbar used by the widget', () => {
-        let panel = new NotebookPanel(options);
+        let panel = createNotebookPanel(context);
         expect(panel.toolbar).to.be.a(Toolbar);
       });
 
@@ -166,55 +109,9 @@ describe('@jupyterlab/notebook', () => {
 
     describe('#content', () => {
 
-      it('should be the content area used by the widget', () => {
-        let panel = new NotebookPanel(options);
-        expect(panel.notebook).to.be.a(Notebook);
-      });
-
-    });
-
-    describe('#kernel', () => {
-
-      it('should be the current kernel used by the panel', (done) => {
-        let panel = createPanel(context);
-        context.initialize(true).catch(done);
-        context.session.kernelChanged.connect(() => {
-          expect(panel.session.kernel.name).to.be.ok();
-          done();
-        });
-        context.session.changeKernel({ name: 'test' }).catch(done);
-      });
-
-    });
-
-    describe('#rendermime', () => {
-
-      it('should be the rendermime instance used by the widget', () => {
-        let panel = new NotebookPanel(options);
-        expect(panel.rendermime).to.be(rendermime);
-      });
-
-    });
-
-    describe('#contentFactory', () => {
-
-      it('should be the contentFactory used by the widget', () => {
-        let r = createNotebookPanelFactory();
-        let panel = new NotebookPanel({
-          mimeTypeService, rendermime, contentFactory: r });
-        expect(panel.contentFactory).to.be(r);
-      });
-
-    });
-
-    describe('#model', () => {
-
-      it('should be the model for the widget', () => {
-        let panel = new NotebookPanel(options);
-        expect(panel.model).to.be(null);
-        panel.context = context;
-        expect(panel.model).to.be(context.model);
-        expect(panel.notebook.model).to.be(context.model);
+      it('should be the notebook content widget', () => {
+        let panel = createNotebookPanel(context);
+        expect(panel.content).to.be.a(Notebook);
       });
 
     });
@@ -222,38 +119,10 @@ describe('@jupyterlab/notebook', () => {
     describe('#context', () => {
 
       it('should get the document context for the widget', () => {
-        let panel = new NotebookPanel(options);
-        expect(panel.context).to.be(null);
-      });
-
-      it('should set the document context for the widget', () => {
-        let panel = new NotebookPanel(options);
-        panel.context = context;
+        let panel = createNotebookPanel(context);
         expect(panel.context).to.be(context);
       });
 
-      it('should emit the `contextChanged` signal', () => {
-        let panel = new NotebookPanel(options);
-        let called = false;
-        panel.contextChanged.connect(() => { called = true; });
-        panel.context = context;
-        expect(called).to.be(true);
-      });
-
-
-      it('should initialize the model state', (done) => {
-        let panel = new LogNotebookPanel(options);
-        let model = context.model;
-        model.fromJSON(DEFAULT_CONTENT);
-        expect(model.cells.canUndo).to.be(true);
-        panel.context = context;
-        context.ready.then(() => {
-          expect(model.cells.canUndo).to.be(false);
-          done();
-        });
-        context.initialize(true);
-      });
-
     });
 
     describe('#dispose()', () => {
@@ -274,16 +143,6 @@ describe('@jupyterlab/notebook', () => {
 
     });
 
-    describe('#onContextChanged()', () => {
-
-      it('should be called when the context changes', () => {
-        let panel = new LogNotebookPanel(options);
-        panel.methods = [];
-        panel.context = context;
-        expect(panel.methods).to.contain('onContextChanged');
-      });
-
-    });
 
     describe('#onModelStateChanged()', () => {
 
@@ -304,36 +163,8 @@ describe('@jupyterlab/notebook', () => {
 
     });
 
-    describe('#onPathChanged()', () => {
-
-      it('should be called when the path changes', (done) => {
-        let panel = createPanel(context);
-        panel.methods = [];
-        context.initialize(true).then(() => {
-          return manager.contents.rename(context.path, uuid() + '.ipynb');
-        }).catch(done);
-        context.pathChanged.connect(() => {
-          expect(panel.methods).to.contain('onPathChanged');
-          done();
-        });
-      });
-
-      it('should be called when the context changes', () => {
-        let panel = new LogNotebookPanel(options);
-        panel.methods = [];
-        panel.context = context;
-        expect(panel.methods).to.contain('onPathChanged');
-      });
-
-      it('should update the title label', () => {
-        let panel = createPanel(context);
-        expect(panel.title.label).to.be(context.path);
-      });
-
-    });
-
     describe('.ContentFactory', () => {
-
+      // TODO: make notebook panel still take a content factory for a notebook, and use that for a default notebook? This also moves all creation options to the notebook panel that are now just part of the content widget. That would be more backwards compatible, even if it departs a bit from our story about document widgets.
       describe('#constructor', () => {
 
         it('should create a new ContentFactory', () => {

+ 1 - 1
tests/test-notebook/src/widgetfactory.spec.ts

@@ -20,7 +20,7 @@ import {
 } from '@jupyterlab/notebook';
 
 import {
-  Context
+  Context, DocumentWidget
 } from '@jupyterlab/docregistry';
 
 import {

+ 24 - 7
tests/utils.ts

@@ -35,6 +35,24 @@ import {
 } from '@jupyterlab/rendermime';
 
 
+
+/**
+ * Return a promise that resolves in the given milliseconds with the given value.
+ */
+export
+function sleep<T>(milliseconds: number = 0, value?: T): Promise<T> {
+  return new Promise((resolve, reject) => {
+    setTimeout(() => { resolve(value); }, milliseconds);
+  });
+}
+
+export
+function moment<T>(value?: T): Promise<T> {
+  return new Promise((resolve, reject) => {
+    requestAnimationFrame(() => { resolve(value); });
+  });
+}
+
 /**
  * Get a copy of the default rendermime instance.
  */
@@ -82,14 +100,13 @@ function createFileContext(path?: string, manager?: ServiceManager.IManager): Co
  * Create a context for a notebook.
  */
 export
-function createNotebookContext(path?: string, manager?: ServiceManager.IManager): Promise<Context<INotebookModel>> {
+async function createNotebookContext(path?: string, manager?: ServiceManager.IManager): Promise<Context<INotebookModel>> {
   manager = manager || Private.manager;
-  return manager.ready.then(() => {
-    let factory = Private.notebookFactory;
-    path = path || uuid() + '.ipynb';
-    return new Context({
-      manager, factory, path, kernelPreference: { name: manager.specs.default }
-    });
+  await manager.ready;
+  const factory = Private.notebookFactory;
+  path = path || uuid() + '.ipynb';
+  return new Context({
+    manager, factory, path, kernelPreference: { name: manager.specs.default }
   });
 }