فهرست منبع

Merge pull request #1021 from blink1073/use-notebook-server

Use notebook server for tests
Afshin Darian 8 سال پیش
والد
کامیت
8c775c6303

+ 5 - 5
package.json

@@ -75,11 +75,11 @@
     "postinstall": "node scripts/dedupe.js",
     "prepublish": "npm run build",
     "test": "npm run test:firefox",
-    "test:chrome": "npm run build:test && karma start --browsers=Chrome test/karma.conf.js",
-    "test:coverage": "webpack --config test/webpack-cov.conf.js && karma start test/karma-cov.conf.js",
-    "test:debug": "npm run build:test && karma start --browsers=Chrome --singleRun=false --debug=true test/karma.conf.js",
-    "test:firefox": "npm run build:test && karma start --browsers=Firefox test/karma.conf.js",
-    "test:ie": "npm run build:test && karma start --browsers=IE test/karma.conf.js",
+    "test:chrome": "npm run build:test && cd test && python run-test.py --browsers=Chrome karma.conf.js",
+    "test:coverage": "webpack --config test/webpack-cov.conf.js && cd test && python run-test.py karma-cov.conf.js",
+    "test:debug": "npm run build:test && cd test && python run-test.py  --browsers=Chrome --singleRun=false --debug=true karma.conf.js",
+    "test:firefox": "npm run build:test && cd test && python run-test.py --browsers=Firefox karma.conf.js",
+    "test:ie": "npm run build:test && cd test && python run-test.py  --browsers=IE karma.conf.js",
     "watch": "watch \"npm run build:all\" src --wait 10",
     "watch:src": "watch \"npm run build\" src --wait 10",
     "watch:test": "watch \"npm run build && npm test\" src test/src --wait 10"

+ 7 - 5
scripts/travis_script.sh

@@ -7,15 +7,17 @@ set -ex
 export DISPLAY=:99.0
 sh -e /etc/init.d/xvfb start || true
 
-# Install in-place and enable the server extension
 export PATH="$HOME/miniconda/bin:$PATH"
-pip install -v .
-jupyter serverextension enable --py jupyterlab
 
 npm run clean
 npm run build
-npm test
-npm run test:coverage
+npm test || npm test
+npm run test:coverage || npm run test:coverage
+
+
+# Install in-place and enable the server extension
+pip install -v .
+jupyter serverextension enable --py jupyterlab
 
 # Run the python tests
 npm run build:serverextension

+ 3 - 0
src/completer/handler.ts

@@ -125,6 +125,9 @@ class CellCompleterHandler implements IDisposable {
     }
     let value = msg.content;
     let model = this._completer.model;
+    if (!model) {
+      return;
+    }
     // Completion request failures or negative results fail silently.
     if (value.status !== 'ok') {
       model.reset();

+ 11 - 10
src/docmanager/context.ts

@@ -54,7 +54,6 @@ class Context<T extends IDocumentModel> implements IDocumentContext<T> {
     this._model = this._factory.createNew(lang);
     manager.sessions.runningChanged.connect(this._onSessionsChanged, this);
     this._saver = new SaveHandler({ context: this, manager });
-    this._saver.start();
   }
 
   /**
@@ -237,7 +236,7 @@ class Context<T extends IDocumentModel> implements IDocumentContext<T> {
       contents.content = model.toString();
     }
     return this._manager.contents.save(path, contents).then(newContents => {
-      this._contentsModel = this._copyContentsModel(newContents);
+      this._updateContentsModel(contents);
       model.dirty = false;
       if (!this._isPopulated) {
         this._populate();
@@ -292,11 +291,7 @@ class Context<T extends IDocumentModel> implements IDocumentContext<T> {
       } else {
         model.fromString(contents.content);
       }
-      let contentsModel = this._copyContentsModel(contents);
-      this._contentsModel = contentsModel;
-      if (contentsModel.last_modified !== this._contentsModel.last_modified) {
-        this.contentsModelChanged.emit(contentsModel);
-      }
+      this._updateContentsModel(contents);
       model.dirty = false;
       if (!this._isPopulated) {
         this._populate();
@@ -405,10 +400,10 @@ class Context<T extends IDocumentModel> implements IDocumentContext<T> {
   }
 
   /**
-   * Copy the contents of a contents model, without the content.
+   * Update our contents model, without the content.
    */
-  private _copyContentsModel(model: IContents.IModel): IContents.IModel {
-    return {
+  private _updateContentsModel(model: IContents.IModel): void {
+    let newModel: IContents.IModel = {
       path: model.path,
       name: model.name,
       type: model.type,
@@ -418,6 +413,11 @@ class Context<T extends IDocumentModel> implements IDocumentContext<T> {
       mimetype: model.mimetype,
       format: model.format
     };
+    let prevModel = this._contentsModel;
+    this._contentsModel = newModel;
+    if (!prevModel || newModel.last_modified !== prevModel.last_modified) {
+      this.contentsModelChanged.emit(newModel);
+    }
   }
 
   /**
@@ -441,6 +441,7 @@ class Context<T extends IDocumentModel> implements IDocumentContext<T> {
    */
   private _populate(): void {
     this._isPopulated = true;
+    this._saver.start();
     // Add a checkpoint if none exists.
     this.listCheckpoints().then(checkpoints => {
       if (!checkpoints) {

+ 8 - 10
src/notebook/notebook/panel.ts

@@ -346,16 +346,12 @@ class NotebookPanel extends Widget {
       this._updateLanguage(kernel.info.language_info);
     } else {
       kernel.kernelInfo().then(msg => {
-        this._updateLanguage(msg.content.language_info);
-      });
-    }
-    if (kernel.spec) {
-      this._updateSpec(kernel);
-    } else {
-      kernel.getKernelSpec().then(spec => {
-        this._updateSpec(kernel);
+        if (this.model) {
+          this._updateLanguage(msg.content.language_info);
+        }
       });
     }
+    this._updateSpec(kernel);
   }
 
   /**
@@ -370,11 +366,13 @@ class NotebookPanel extends Widget {
    * Update the kernel spec.
    */
   private _updateSpec(kernel: IKernel): void {
+    let specs = this.context.kernelspecs;
+    let spec = specs.kernelspecs[kernel.name].spec;
     let specCursor = this.model.getMetadata('kernelspec');
     specCursor.setValue({
       name: kernel.name,
-      display_name: kernel.spec.display_name,
-      language: kernel.spec.language
+      display_name: spec.display_name,
+      language: spec.language
     });
   }
 

+ 2 - 0
src/notebook/notebook/widgetfactory.ts

@@ -78,6 +78,8 @@ class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookMod
     let rendermime = this._rendermime.clone();
     if (kernel) {
       context.changeKernel(kernel);
+    } else if (!context.kernel) {
+      context.changeKernel({ name: context.kernelspecs.default });
     }
     let panel = new NotebookPanel({
       rendermime,

+ 10 - 1
src/toolbar/kernel.ts

@@ -46,6 +46,7 @@ const TOOLBAR_INDICATOR_CLASS = 'jp-Kernel-toolbarKernelIndicator';
  */
 const TOOLBAR_BUSY_CLASS = 'jp-mod-busy';
 
+
 /**
  * A kernel owner interface.
  */
@@ -61,6 +62,7 @@ interface IKernelOwner {
   kernelChanged: ISignal<IKernelOwner, IKernel>;
 }
 
+
 /**
  * Create an interrupt toolbar item.
  */
@@ -77,6 +79,7 @@ function createInterruptButton(kernelOwner: IKernelOwner): ToolbarButton {
   });
 }
 
+
 /**
  * Create a restart toolbar item.
  */
@@ -91,6 +94,7 @@ function createRestartButton(kernelOwner: IKernelOwner, host?: HTMLElement): Too
   });
 }
 
+
 /**
  * Create a kernel name indicator item.
  *
@@ -110,6 +114,7 @@ function createKernelNameItem(kernelOwner: IKernelOwner): Widget {
   return widget;
 }
 
+
 /**
  * Update the text of the kernel name item.
  */
@@ -122,11 +127,14 @@ function updateKernelNameItem(widget: Widget, kernel: IKernel): void {
     widget.node.textContent = kernel.spec.display_name;
   } else {
     kernel.getKernelSpec().then(spec => {
-      widget.node.textContent = kernel.spec.display_name;
+      if (!widget.isDisposed) {
+        widget.node.textContent = kernel.spec.display_name;
+      }
     });
   }
 }
 
+
 /**
  * Create a kernel status indicator item.
  *
@@ -141,6 +149,7 @@ function createKernelStatusItem(kernelOwner: IKernelOwner): Widget {
   return new KernelIndicator(kernelOwner);
 }
 
+
 /**
  * A toolbar item that displays kernel status.
  */

+ 11 - 5
test/karma-cov.conf.js

@@ -2,24 +2,30 @@ var path = require('path');
 
 module.exports = function (config) {
   config.set({
-    basePath: '..',
+    basePath: '.',
     browsers: ['Firefox'],
     frameworks: ['mocha'],
+    client: {
+      mocha: {
+        timeout : 10000 // 10 seconds - upped from 2 seconds
+      }
+    },
     reporters: ['mocha', 'coverage', 'remap-coverage'],
     files: [
       'node_modules/es6-promise/dist/es6-promise.js',
-      'test/build/coverage.js'
+      'build/injector.js',
+      'build/coverage.js'
     ],
     preprocessors: {
-      'test/build/coverage.js': ['sourcemap']
+      'build/coverage.js': ['sourcemap']
     },
     coverageReporter: {
       type: 'in-memory'
     },
     remapCoverageReporter: {
       'text-summary': null, // to show summary in console
-      json: 'test/coverage/remapped.json',
-      html: 'test/coverage/html'
+      json: 'coverage/remapped.json',
+      html: 'coverage/html'
     },
     port: 9876,
     colors: true,

+ 9 - 3
test/karma.conf.js

@@ -1,11 +1,17 @@
 module.exports = function (config) {
   config.set({
-    basePath: '..',
+    basePath: '.',
     frameworks: ['mocha'],
     reporters: ['mocha'],
+    client: {
+      mocha: {
+        timeout : 10000 // 10 seconds - upped from 2 seconds
+      }
+    },
     files: [
-      'node_modules/es6-promise/dist/es6-promise.js',
-      'test/build/bundle.js'
+      '../node_modules/es6-promise/dist/es6-promise.js',
+      './build/injector.js',
+      './build/bundle.js'
     ],
     preprocessors: {
       'test/build/bundle.js': ['sourcemap']

+ 91 - 0
test/run-test.py

@@ -0,0 +1,91 @@
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import argparse
+import subprocess
+import sys
+import os
+import re
+import shutil
+import threading
+import tempfile
+
+
+# Set up the file structure
+root_dir = tempfile.mkdtemp(prefix='mock_contents')
+os.mkdir(os.path.join(root_dir, 'src'))
+with open(os.path.join(root_dir, 'src', 'temp.txt'), 'w') as fid:
+    fid.write('hello')
+
+
+HERE = os.path.dirname(__file__)
+
+shell = (sys.platform == 'win32')
+
+
+def start_notebook():
+    nb_command = [sys.executable, '-m', 'notebook', root_dir, '--no-browser',
+                  '--NotebookApp.allow_origin="*"']
+    nb_server = subprocess.Popen(nb_command, shell=shell,
+                                 stderr=subprocess.STDOUT,
+                                 stdout=subprocess.PIPE)
+
+    # wait for notebook server to start up
+    while 1:
+        line = nb_server.stdout.readline().decode('utf-8').strip()
+        if not line:
+            continue
+        print(line)
+        if 'Jupyter Notebook is running at:' in line:
+            base_url = re.search('(http.*?)$', line).groups()[0]
+            break
+
+    while 1:
+        line = nb_server.stdout.readline().decode('utf-8').strip()
+        if not line:
+            continue
+        print(line)
+        if 'Control-C' in line:
+            break
+
+    def print_thread():
+        while 1:
+            line = nb_server.stdout.readline().decode('utf-8').strip()
+            if not line:
+                continue
+            print(line)
+
+    thread = threading.Thread(target=print_thread)
+    thread.setDaemon(True)
+    thread.start()
+
+    return nb_server, base_url
+
+
+def run_karma(base_url):
+    with open(os.path.join(HERE, 'build', 'injector.js'), 'w') as fid:
+        fid.write("""
+        var node = document.createElement('script');
+        node.id = 'jupyter-config-data';
+        node.type = 'application/json';
+        node.textContent = '{"baseUrl": "%s"}';
+        document.body.appendChild(node);
+        """ % base_url)
+
+    cmd = ['karma', 'start'] + sys.argv[1:]
+    return subprocess.check_call(cmd, shell=shell, stderr=subprocess.STDOUT)
+
+
+if __name__ == '__main__':
+
+    nb_server, base_url = start_notebook()
+
+    try:
+        resp = run_karma(base_url)
+    except (subprocess.CalledProcessError, KeyboardInterrupt):
+        resp = 1
+    finally:
+        nb_server.kill()
+
+    shutil.rmtree(root_dir, True)
+    sys.exit(resp)

+ 19 - 12
test/src/completer/handler.spec.ts

@@ -4,13 +4,9 @@
 import expect = require('expect.js');
 
 import {
-  KernelMessage
+  KernelMessage, IKernel, startNewKernel
 } from 'jupyter-js-services';
 
-import {
-  MockKernel
-} from 'jupyter-js-services/lib/mockkernel';
-
 import {
   BaseCellWidget, CellModel
 } from '../../../lib/notebook/cells';
@@ -73,9 +69,24 @@ class TestCompleterHandler extends CellCompleterHandler {
   }
 }
 
+const kernelPromise = startNewKernel();
+
 
 describe('completer/handler', () => {
 
+  let kernel: IKernel;
+
+  beforeEach((done) => {
+    kernelPromise.then(k => {
+      kernel = k;
+      done();
+    });
+  });
+
+  after(() => {
+    kernel.shutdown();
+  });
+
   describe('CellCompleterHandler', () => {
 
     describe('#constructor()', () => {
@@ -96,10 +107,8 @@ describe('completer/handler', () => {
 
       it('should be settable', () => {
         let handler = new CellCompleterHandler(new CompleterWidget());
-        let kernel = new MockKernel();
         expect(handler.kernel).to.be(null);
         handler.kernel = kernel;
-        expect(handler.kernel).to.be.a(MockKernel);
         expect(handler.kernel).to.be(kernel);
       });
 
@@ -158,7 +167,6 @@ describe('completer/handler', () => {
 
       it('should dispose of the handler resources', () => {
         let handler = new CellCompleterHandler(new CompleterWidget());
-        let kernel = new MockKernel();
         handler.kernel = kernel;
         expect(handler.isDisposed).to.be(false);
         expect(handler.kernel).to.be.ok();
@@ -196,11 +204,10 @@ describe('completer/handler', () => {
         });
       });
 
-      // TODO: This test needs to be fixed when MockKernel is updated.
+      // TODO: This test needs to be updated to use a python kernel.
       it('should resolve if handler has a kernel', () => {
-        console.warn('This test needs to be fixed when MockKernel is updated.');
+        console.warn('This test needs to be updated to use a python kernel.');
         let handler = new TestCompleterHandler(new CompleterWidget());
-        let kernel = new MockKernel();
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,
@@ -400,7 +407,7 @@ describe('completer/handler', () => {
           renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
         });
 
-        handler.kernel = new MockKernel();
+        handler.kernel = kernel;
         handler.activeCell = cell;
         expect(handler.methods).to.not.contain('makeRequest');
         cell.editor.completionRequested.emit(request);

+ 20 - 17
test/src/console/history.spec.ts

@@ -4,13 +4,9 @@
 import expect = require('expect.js');
 
 import {
-  KernelMessage
+  KernelMessage, IKernel, startNewKernel
 } from 'jupyter-js-services';
 
-import {
-  MockKernel
-} from 'jupyter-js-services/lib/mockkernel';
-
 import {
   ConsoleHistory
 } from '../../../lib/console/history';
@@ -40,8 +36,24 @@ class TestHistory extends ConsoleHistory {
 }
 
 
+const kernelPromise = startNewKernel();
+
+
 describe('console/history', () => {
 
+  let kernel: IKernel;
+
+  beforeEach((done) => {
+    kernelPromise.then(k => {
+      kernel = k;
+      done();
+    });
+  });
+
+  after(() => {
+    kernel.shutdown();
+  });
+
   describe('ConsoleHistory', () => {
 
     describe('#constructor()', () => {
@@ -52,9 +64,7 @@ describe('console/history', () => {
       });
 
       it('should accept an options argument', () => {
-        let history = new ConsoleHistory({
-          kernel: new MockKernel({ name: 'python' })
-        });
+        let history = new ConsoleHistory({ kernel });
         expect(history).to.be.a(ConsoleHistory);
       });
 
@@ -74,13 +84,11 @@ describe('console/history', () => {
     describe('#kernel', () => {
 
       it('should return the kernel that was passed in', () => {
-        let kernel = new MockKernel({ name: 'python' });
         let history = new ConsoleHistory({ kernel });
         expect(history.kernel).to.be(kernel);
       });
 
       it('should be settable', () => {
-        let kernel = new MockKernel({ name: 'python' });
         let history = new ConsoleHistory();
         expect(history.kernel).to.be(null);
         history.kernel = kernel;
@@ -90,7 +98,6 @@ describe('console/history', () => {
       });
 
       it('should be safe to set multiple times', () => {
-        let kernel = new MockKernel({ name: 'python' });
         let history = new ConsoleHistory();
         history.kernel = kernel;
         history.kernel = kernel;
@@ -129,9 +136,7 @@ describe('console/history', () => {
       });
 
       it('should return previous items if they exist', (done) => {
-        let history = new TestHistory({
-          kernel: new MockKernel({ name: 'python' })
-        });
+        let history = new TestHistory({ kernel });
         history.onHistory(mockHistory);
         history.back('').then(result => {
           let index = mockHistory.content.history.length - 1;
@@ -154,9 +159,7 @@ describe('console/history', () => {
       });
 
       it('should return next items if they exist', (done) => {
-        let history = new TestHistory({
-          kernel: new MockKernel({ name: 'python' })
-        });
+        let history = new TestHistory({ kernel });
         history.onHistory(mockHistory);
         Promise.all([history.back(''), history.back('')]).then(() => {
           history.forward('').then(result => {

+ 0 - 154
test/src/docmanager/mockcontext.ts

@@ -1,154 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-
-import {
-  IContents, IKernel, ISession
-} from 'jupyter-js-services';
-
-import {
-  MockKernel, KERNELSPECS
-} from 'jupyter-js-services/lib/mockkernel';
-
-import {
-  IDisposable
-} from 'phosphor/lib/core/disposable';
-
-import {
-  defineSignal, ISignal
-} from 'phosphor/lib/core/signaling';
-
-import {
-  Widget
-} from 'phosphor/lib/ui/widget';
-
-import {
-  IDocumentContext, IDocumentModel
-} from '../../../lib/docregistry';
-
-
-export
-class MockContext<T extends IDocumentModel> implements IDocumentContext<T> {
-
-  methods: string[] = [];
-
-  constructor(model: T) {
-    this._model = model;
-  }
-
-  kernelChanged: ISignal<IDocumentContext<IDocumentModel>, IKernel>;
-
-  pathChanged: ISignal<IDocumentContext<IDocumentModel>, string>;
-
-  contentsModelChanged: ISignal<IDocumentContext<T>, IContents.IModel>;
-
-  populated: ISignal<IDocumentContext<IDocumentModel>, void>;
-
-  disposed: ISignal<IDocumentContext<T>, void>;
-
-  get id(): string {
-    return '';
-  }
-
-  get model(): T {
-    return this._model;
-  }
-
-  get kernel(): IKernel {
-    return this._kernel;
-  }
-
-  get path(): string {
-    return this._path;
-  }
-
-  get contentsModel(): IContents.IModel {
-    return void 0;
-  }
-
-  get kernelspecs(): IKernel.ISpecModels {
-    return KERNELSPECS;
-  }
-
-  get isPopulated(): boolean {
-    return true;
-  }
-
-  get isDisposed(): boolean {
-    return this._model === null;
-  }
-
-  dispose(): void {
-    this._model.dispose();
-    this._model = null;
-    this.methods.push('dispose');
-  }
-
-  changeKernel(options: IKernel.IModel): Promise<IKernel> {
-    if (!options) {
-      this._kernel = null;
-    } else {
-      this._kernel = new MockKernel(options);
-    }
-    this.kernelChanged.emit(this._kernel);
-    this.methods.push('changeKernel');
-    return Promise.resolve(this._kernel);
-  }
-
-  save(): Promise<void> {
-    this.methods.push('save');
-    return Promise.resolve(void 0);
-  }
-
-  saveAs(): Promise<void> {
-    this._path = 'foo';
-    this.pathChanged.emit(this._path);
-    this.methods.push('saveAs');
-    return Promise.resolve(void 0);
-  }
-
-  revert(): Promise<void> {
-    this.methods.push('revert');
-    return Promise.resolve(void 0);
-  }
-
-  createCheckpoint(): Promise<IContents.ICheckpointModel> {
-    return Promise.resolve(void 0);
-  }
-
-  deleteCheckpoint(checkpointID: string): Promise<void> {
-    return Promise.resolve(void 0);
-  }
-
-  restoreCheckpoint(checkpointID?: string): Promise<void> {
-    return Promise.resolve(void 0);
-  }
-
-  listCheckpoints(): Promise<IContents.ICheckpointModel[]> {
-    return Promise.resolve([]);
-  }
-
-  listSessions(): Promise<ISession.IModel[]> {
-    this.methods.push('listSessions');
-    return Promise.resolve([] as ISession.IModel[]);
-  }
-
-  resolveUrl(url: string): string {
-    return url;
-  }
-
-  addSibling(widget: Widget): IDisposable {
-    this.methods.push('addSibling');
-    return void 0;
-  }
-
-  private _model: T = null;
-  private _path = '';
-  private _kernel: IKernel = null;
-}
-
-
-defineSignal(MockContext.prototype, 'kernelChanged');
-defineSignal(MockContext.prototype, 'pathChanged');
-defineSignal(MockContext.prototype, 'contentsModelChanged');
-defineSignal(MockContext.prototype, 'populated');
-defineSignal(MockContext.prototype, 'disposed');

+ 19 - 4
test/src/docregistry/default.spec.ts

@@ -17,8 +17,12 @@ import {
 } from '../../../lib/docregistry';
 
 import {
-  MockContext
-} from '../docmanager/mockcontext';
+  Context
+} from '../../../lib/docmanager/context';
+
+import {
+  createFileContext
+} from '../utils';
 
 
 class WidgetFactory extends ABCWidgetFactory<Widget, IDocumentModel> {
@@ -31,6 +35,19 @@ class WidgetFactory extends ABCWidgetFactory<Widget, IDocumentModel> {
 
 describe('docmanager/default', () => {
 
+  let context: Context<IDocumentModel>;
+
+  beforeEach((done) => {
+    createFileContext().then(c => {
+      context = c;
+      done();
+    });
+  });
+
+  afterEach(() => {
+    context.dispose();
+  });
+
   describe('ABCWidgetFactory', () => {
 
     describe('#isDisposed', () => {
@@ -65,8 +82,6 @@ describe('docmanager/default', () => {
 
       it('should create a new widget given a document model and a context', () => {
         let factory = new WidgetFactory();
-        let model = new DocumentModel();
-        let context = new MockContext(model);
         let widget = factory.createNew(context);
         expect(widget).to.be.a(Widget);
       });

+ 8 - 6
test/src/filebrowser/model.spec.ts

@@ -4,8 +4,8 @@
 import expect = require('expect.js');
 
 import {
-  MockServiceManager
-} from 'jupyter-js-services/lib/mockmanager';
+  createServiceManager
+} from 'jupyter-js-services';
 
 import {
   FileBrowserModel
@@ -18,10 +18,12 @@ describe('filebrowser/model', () => {
 
     describe('#constructor()', () => {
 
-      it('should construct a new file browser model', () => {
-        let manager = new MockServiceManager();
-        let model = new FileBrowserModel({ manager });
-        expect(model).to.be.a(FileBrowserModel);
+      it('should construct a new file browser model', (done) => {
+        createServiceManager().then(manager => {
+          let model = new FileBrowserModel({ manager });
+          expect(model).to.be.a(FileBrowserModel);
+          done();
+        });
       });
 
     });

+ 17 - 11
test/src/markdownwidget/widget.spec.ts

@@ -20,17 +20,16 @@ import {
 } from '../../../lib/markdownwidget/widget';
 
 import {
-  DocumentModel
+  IDocumentModel
 } from '../../../lib/docregistry';
 
 import {
-  MockContext
-} from '../docmanager/mockcontext';
+  Context
+} from '../../../lib/docmanager/context';
 
 import {
-  defaultRenderMime
-} from '../rendermime/rendermime.spec';
-
+  createFileContext, defaultRenderMime
+} from '../utils';
 
 const RENDERMIME = defaultRenderMime();
 
@@ -50,14 +49,25 @@ class LogWidget extends MarkdownWidget {
 }
 
 
+const contextPromise = createFileContext();
+
+
 describe('markdownwidget/widget', () => {
 
+  let context: Context<IDocumentModel>;
+
+  beforeEach((done) => {
+    contextPromise.then(c => {
+      context = c;
+      done();
+    });
+  });
+
   describe('MarkdownWidgetFactory', () => {
 
     describe('#createNew()', () => {
 
       it('should require a context parameter', () => {
-        let context = new MockContext(new DocumentModel());
         let widgetFactory = new MarkdownWidgetFactory(RENDERMIME);
         expect(widgetFactory.createNew(context)).to.be.a(MarkdownWidget);
       });
@@ -71,7 +81,6 @@ describe('markdownwidget/widget', () => {
     describe('#constructor()', () => {
 
       it('should require a context parameter', () => {
-        let context = new MockContext(new DocumentModel());
         let widget = new MarkdownWidget(context, RENDERMIME);
         expect(widget).to.be.a(MarkdownWidget);
       });
@@ -81,7 +90,6 @@ describe('markdownwidget/widget', () => {
     describe('#onAfterAttach()', () => {
 
       it('should update the widget', () => {
-        let context = new MockContext(new DocumentModel());
         let widget = new LogWidget(context, RENDERMIME);
         expect(widget.methods).to.not.contain('onAfterAttach');
         Widget.attach(widget, document.body);
@@ -94,7 +102,6 @@ describe('markdownwidget/widget', () => {
     describe('#onUpdateRequest()', () => {
 
       it('should update rendered markdown', () => {
-        let context = new MockContext(new DocumentModel());
         let widget = new LogWidget(context, RENDERMIME);
         expect(widget.methods).to.not.contain('onUpdateRequest');
         context.model.contentChanged.emit(void 0);
@@ -104,7 +111,6 @@ describe('markdownwidget/widget', () => {
       });
 
       it('should replace children on subsequent updates', () => {
-        let context = new MockContext(new DocumentModel());
         let widget = new LogWidget(context, RENDERMIME);
         context.model.contentChanged.emit(void 0);
         sendMessage(widget, WidgetMessage.UpdateRequest);

+ 22 - 16
test/src/notebook/cells/widget.spec.ts

@@ -4,8 +4,8 @@
 import expect = require('expect.js');
 
 import {
-  MockKernel
-} from 'jupyter-js-services/lib/mockkernel';
+  startNewKernel
+} from 'jupyter-js-services';
 
 import {
   Message, sendMessage
@@ -48,7 +48,7 @@ import {
 
 import {
   defaultRenderMime
-} from '../../rendermime/rendermime.spec';
+} from '../../utils';
 
 
 const INPUT_CLASS = 'jp-InputArea';
@@ -584,23 +584,29 @@ describe('notebook/cells/widget', () => {
 
       it('should fulfill a promise if there is no code to execute', (done) => {
         let widget = new CodeCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
-        let kernel = new MockKernel();
-        widget.model = new CodeCellModel();
-        widget.execute(kernel).then(() => { done(); });
+        startNewKernel().then(kernel => {
+          widget.model = new CodeCellModel();
+          return widget.execute(kernel).then(() => {
+            kernel.shutdown();
+            done();
+          });
+        }).catch(done);
       });
 
       it('should fulfill a promise if there is code to execute', (done) => {
         let widget = new CodeCellWidget({ rendermime, renderer: CodeMirrorNotebookRenderer.defaultCodeCellRenderer });
-        let kernel = new MockKernel();
-        widget.model = new CodeCellModel();
-        widget.model.source = 'foo';
-
-        let originalCount = (widget.model).executionCount;
-        widget.execute(kernel).then(() => {
-          let executionCount = (widget.model).executionCount;
-          expect(executionCount).to.not.equal(originalCount);
-          done();
-        });
+        startNewKernel().then(kernel => {
+          widget.model = new CodeCellModel();
+          widget.model.source = 'foo';
+
+          let originalCount = (widget.model).executionCount;
+          return widget.execute(kernel).then(() => {
+            let executionCount = (widget.model).executionCount;
+            expect(executionCount).to.not.equal(originalCount);
+            kernel.shutdown();
+            done();
+          });
+        }).catch(done);
       });
 
     });

+ 25 - 13
test/src/notebook/notebook/actions.spec.ts

@@ -4,13 +4,9 @@
 import expect = require('expect.js');
 
 import {
-  IKernel
+  IKernel, startNewKernel
 } from 'jupyter-js-services';
 
-import {
-  MockKernel, ERROR_INPUT
-} from 'jupyter-js-services/lib/mockkernel';
-
 import {
   MimeData
 } from 'phosphor/lib/core/mimedata';
@@ -33,7 +29,7 @@ import {
 
 import {
   defaultRenderMime
-} from '../../rendermime/rendermime.spec';
+} from '../../utils';
 
 import {
   DEFAULT_CONTENT
@@ -45,6 +41,8 @@ import {
 
 
 const clipboard = new MimeData();
+const ERROR_INPUT = 'a = foo';
+const kernelPromise = startNewKernel();
 
 
 describe('notebook/notebook/actions', () => {
@@ -54,7 +52,7 @@ describe('notebook/notebook/actions', () => {
     let widget: Notebook;
     let kernel: IKernel;
 
-    beforeEach(() => {
+    beforeEach((done) => {
       widget = new Notebook({
         rendermime: defaultRenderMime(),
         renderer: CodeMirrorNotebookRenderer.defaultRenderer
@@ -63,16 +61,22 @@ describe('notebook/notebook/actions', () => {
       model.fromJSON(DEFAULT_CONTENT);
       widget.model = model;
 
-      kernel = new MockKernel({ name: 'python' });
+      kernelPromise.then(k => {
+        kernel = k;
+        done();
+      });
       widget.activeCellIndex = 0;
     });
 
     afterEach(() => {
       widget.dispose();
-      kernel.dispose();
       clipboard.clear();
     });
 
+    after(() => {
+      kernel.shutdown();
+    });
+
     describe('#splitCell()', () => {
 
       it('should split the active cell into two cells', () => {
@@ -450,7 +454,7 @@ describe('notebook/notebook/actions', () => {
 
     describe('#run()', () => {
 
-      it('should run the selected cells', (done) => {
+      it('should run the selected cells', function (done) {
         let next = widget.childAt(1) as MarkdownCellWidget;
         widget.select(next);
         let cell = widget.activeCell as CodeCellWidget;
@@ -461,7 +465,7 @@ describe('notebook/notebook/actions', () => {
           expect(cell.model.outputs.length).to.be.above(0);
           expect(next.rendered).to.be(true);
           done();
-        });
+        }).catch(done);
       });
 
       it('should be a no-op if there is no model', (done) => {
@@ -475,11 +479,12 @@ describe('notebook/notebook/actions', () => {
       it('should activate the last selected cell', (done) => {
         let other = widget.childAt(2);
         widget.select(other);
+        other.model.source = 'a = 1';
         NotebookActions.run(widget, kernel).then(result => {
           expect(result).to.be(true);
           expect(widget.activeCell).to.be(other);
           done();
-        });
+        }).catch(done);
       });
 
       it('should clear the selection', (done) => {
@@ -568,7 +573,7 @@ describe('notebook/notebook/actions', () => {
         let next = widget.childAt(2);
         widget.select(next);
         NotebookActions.runAndAdvance(widget, kernel).then(result => {
-          expect(result).to.be(true);
+          expect(result).to.be(false);
           expect(widget.isSelected(widget.childAt(0))).to.be(false);
           done();
         });
@@ -680,6 +685,7 @@ describe('notebook/notebook/actions', () => {
       it('should insert a new code cell in edit mode after the last selected cell', (done) => {
         let next = widget.childAt(2);
         widget.select(next);
+        next.model.source = 'a = 1';
         let count = widget.childCount();
         NotebookActions.runAndInsert(widget, kernel).then(result => {
           expect(result).to.be(true);
@@ -693,6 +699,7 @@ describe('notebook/notebook/actions', () => {
       it('should allow an undo of the cell insert', (done) => {
         let next = widget.childAt(2);
         widget.select(next);
+        next.model.source = 'a = 1';
         let count = widget.childCount();
         NotebookActions.runAndInsert(widget, kernel).then(result => {
           expect(result).to.be(true);
@@ -731,6 +738,11 @@ describe('notebook/notebook/actions', () => {
 
     describe('#runAll()', () => {
 
+      beforeEach(() => {
+        // Make sure all cells have valid code.
+        widget.childAt(2).model.source = 'a = 1';
+      });
+
       it('should run all of the cells in the notebok', (done) => {
         let next = widget.childAt(1) as MarkdownCellWidget;
         let cell = widget.activeCell as CodeCellWidget;

+ 106 - 49
test/src/notebook/notebook/default-toolbar.spec.ts

@@ -3,6 +3,10 @@
 
 import expect = require('expect.js');
 
+import {
+  utils, startNewSession
+} from 'jupyter-js-services';
+
 import {
   MimeData
 } from 'phosphor/lib/core/mimedata';
@@ -31,7 +35,7 @@ import {
 } from '../../../../lib/notebook/notebook/default-toolbar';
 
 import {
- NotebookModel
+ INotebookModel
 } from '../../../../lib/notebook/notebook/model';
 
 import {
@@ -39,12 +43,12 @@ import {
 } from '../../../../lib/notebook/notebook/panel';
 
 import {
-  MockContext
-} from '../../docmanager/mockcontext';
+  Context
+} from '../../../../lib/docmanager/context';
 
 import {
-  defaultRenderMime
-} from '../../rendermime/rendermime.spec';
+  createNotebookContext, defaultRenderMime
+} from '../../utils';
 
 import {
   DEFAULT_CONTENT
@@ -60,25 +64,41 @@ import {
  */
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
+const sessionPromise = startNewSession({ path: utils.uuid() });
 
 
 describe('notebook/notebook/default-toolbar', () => {
 
+  let context: Context<INotebookModel>;
+
+  beforeEach((done) => {
+    createNotebookContext().then(c => {
+      context = c;
+      done();
+    });
+  });
+
+  afterEach(() => {
+    if (context.kernel) {
+      context.kernel.dispose();
+    }
+    context.dispose();
+  });
+
   describe('ToolbarItems', () => {
 
     let panel: NotebookPanel;
-    let context: MockContext<NotebookModel>;
     const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
 
     beforeEach((done) => {
       panel = new NotebookPanel({ rendermime, clipboard, renderer });
-      let model = new NotebookModel();
-      model.fromJSON(DEFAULT_CONTENT);
-      context = new MockContext<NotebookModel>(model);
+      context.model.fromJSON(DEFAULT_CONTENT);
       panel.context = context;
-      context.changeKernel({ name: 'python' }).then(() => {
-        done();
-      }).catch(done);
+      sessionPromise.then(session => {
+        return context.changeKernel({ id: session.kernel.id });
+      }).then(() => {
+        return context.kernel.interrupt();
+      }).then(done, done);
     });
 
     afterEach(() => {
@@ -87,12 +107,14 @@ describe('notebook/notebook/default-toolbar', () => {
 
     describe('#createSaveButton()', () => {
 
-      it('should save when clicked', () => {
+      it('should save when clicked', (done) => {
         let button = ToolbarItems.createSaveButton(panel);
         Widget.attach(button, document.body);
+        context.contentsModelChanged.connect(() => {
+          button.dispose();
+          done();
+        });
         button.node.click();
-        expect(context.methods).to.contain('save');
-        button.dispose();
       });
 
       it('should have the `\'jp-Notebook-toolbarSave\'` class', () => {
@@ -184,7 +206,6 @@ describe('notebook/notebook/default-toolbar', () => {
 
       it('should run and advance when clicked', (done) => {
         let button = ToolbarItems.createRunButton(panel);
-
         let widget = panel.content;
         let next = widget.childAt(1) as MarkdownCellWidget;
         widget.select(next);
@@ -193,8 +214,7 @@ describe('notebook/notebook/default-toolbar', () => {
         next.rendered = false;
         Widget.attach(button, document.body);
         panel.kernel.statusChanged.connect((sender, status) => {
-          if (status === 'idle') {
-            expect(cell.model.outputs.length).to.be.above(0);
+          if (status === 'idle' && cell.model.outputs.length > 0) {
             expect(next.rendered).to.be(true);
             button.dispose();
             done();
@@ -215,12 +235,16 @@ describe('notebook/notebook/default-toolbar', () => {
       it('should interrupt the kernel when clicked', (done) => {
         let button = createInterruptButton(panel);
         Widget.attach(button, document.body);
-        button.node.click();
-        expect(panel.context.kernel.status).to.be('busy');
+        let clicked = false;
         panel.kernel.statusChanged.connect((sender, status) => {
           if (status === 'idle') {
-            button.dispose();
-            done();
+            if (!clicked) {
+              button.node.click();
+              clicked = true;
+            } else {
+              button.dispose();
+              done();
+            }
           }
         });
       });
@@ -271,11 +295,10 @@ describe('notebook/notebook/default-toolbar', () => {
 
       it('should handle a change in context', () => {
         let item = ToolbarItems.createCellTypeItem(panel);
-        let model = new NotebookModel();
-        model.fromJSON(DEFAULT_CONTENT);
-        context = new MockContext<NotebookModel>(model);
-        context.changeKernel({ name: 'python' });
-        panel.context = context;
+        context.model.fromJSON(DEFAULT_CONTENT);
+        let name = context.kernelspecs.default;
+        context.changeKernel({ name });
+        panel.context = null;
         panel.content.activeCellIndex++;
         let node = item.node.getElementsByTagName('select')[0];
         expect((node as HTMLSelectElement).value).to.be('markdown');
@@ -286,8 +309,8 @@ describe('notebook/notebook/default-toolbar', () => {
     describe('#createKernelNameItem()', () => {
 
       it('should display the `\'display_name\'` of the kernel', (done) => {
-        let item = createKernelNameItem(panel);
-        panel.kernel.getKernelSpec().then(spec => {
+        return panel.kernel.getKernelSpec().then(spec => {
+          let item = createKernelNameItem(panel);
           expect(item.node.textContent).to.be(spec.display_name);
           done();
         });
@@ -301,12 +324,13 @@ describe('notebook/notebook/default-toolbar', () => {
 
       it('should handle a change in kernel', (done) => {
         let item = createKernelNameItem(panel);
-        panel.context.changeKernel({ name: 'shell' }).then(kernel => {
-          kernel.getKernelSpec().then(spec => {
+        let name = context.kernelspecs.default;
+        panel.context.changeKernel({ name }).then(kernel => {
+          return kernel.getKernelSpec().then(spec => {
             expect(item.node.textContent).to.be(spec.display_name);
             done();
           });
-        });
+        }).catch(done);
       });
 
       it('should handle a change in context', (done) => {
@@ -323,14 +347,22 @@ describe('notebook/notebook/default-toolbar', () => {
 
       it('should display a busy status if the kernel status is not idle', (done) => {
         let item = createKernelStatusItem(panel);
-        expect(item.hasClass('jp-mod-busy')).to.be(false);
         panel.kernel.statusChanged.connect(() => {
+          if (!panel.kernel) {
+            return;
+          }
+          if (panel.kernel.status === 'idle') {
+            expect(item.hasClass('jp-mod-busy')).to.be(false);
+            panel.kernel.interrupt();
+          }
           if (panel.kernel.status === 'busy') {
             expect(item.hasClass('jp-mod-busy')).to.be(true);
             done();
           }
         });
-        panel.kernel.interrupt();
+        if (panel.kernel.status === 'idle') {
+          panel.kernel.interrupt();
+        }
       });
 
       it('should show the current status in the node title', (done) => {
@@ -338,25 +370,42 @@ describe('notebook/notebook/default-toolbar', () => {
         let status = panel.kernel.status;
         expect(item.node.title.toLowerCase()).to.contain(status);
         panel.kernel.statusChanged.connect(() => {
+          if (!panel.kernel) {
+            return;
+          }
+          if (panel.kernel.status === 'idle') {
+            panel.kernel.interrupt();
+          }
           if (panel.kernel.status === 'busy') {
             expect(item.node.title.toLowerCase()).to.contain('busy');
             done();
           }
         });
-        panel.kernel.interrupt();
+        if (panel.kernel.status === 'idle') {
+          panel.kernel.interrupt();
+        }
       });
 
       it('should handle a change to the kernel', (done) => {
         let item = createKernelStatusItem(panel);
-        panel.context.changeKernel({ name: 'shell' }).then(() => {
+        let name = context.kernelspecs.default;
+        panel.context.changeKernel({ name }).then(() => {
           panel.kernel.statusChanged.connect(() => {
+            if (!panel.kernel) {
+              return;
+            }
+            if (panel.kernel.status === 'idle') {
+              panel.kernel.interrupt();
+            }
             if (panel.kernel.status === 'busy') {
               expect(item.hasClass('jp-mod-busy')).to.be(true);
               done();
             }
           });
+        }).catch(done);
+        if (panel.kernel.status === 'idle') {
           panel.kernel.interrupt();
-        });
+        }
       });
 
       it('should handle a null kernel', (done) => {
@@ -369,19 +418,27 @@ describe('notebook/notebook/default-toolbar', () => {
 
       it('should handle a change to the context', (done) => {
         let item = createKernelStatusItem(panel);
-        let model = new NotebookModel();
-        model.fromJSON(DEFAULT_CONTENT);
-        context = new MockContext<NotebookModel>(model);
-        panel.context = context;
-        context.changeKernel({ name: 'python' }).then(() => {
-          panel.kernel.statusChanged.connect(() => {
-            if (panel.kernel.status === 'idle') {
-              expect(item.hasClass('jp-mod-busy')).to.be(false);
-              done();
-            }
+        createNotebookContext().then(c => {
+          context = c;
+          context.model.fromJSON(DEFAULT_CONTENT);
+          panel.context = c;
+          let name = context.kernelspecs.default;
+          return context.changeKernel({ name }).then(() => {
+            panel.kernel.statusChanged.connect(() => {
+              if (!panel.kernel) {
+                return;
+              }
+              if (panel.kernel.status === 'busy') {
+                expect(item.hasClass('jp-mod-busy')).to.be(true);
+                panel.kernel.interrupt();
+              }
+              if (panel.kernel.status === 'idle') {
+                expect(item.hasClass('jp-mod-busy')).to.be(false);
+                done();
+              }
+            });
           });
-          panel.kernel.interrupt();
-        });
+        }).catch(done);
       });
 
     });

+ 54 - 61
test/src/notebook/notebook/panel.spec.ts

@@ -3,10 +3,6 @@
 
 import expect = require('expect.js');
 
-import {
-  MockKernel
-} from 'jupyter-js-services/lib/mockkernel';
-
 import {
   MimeData
 } from 'phosphor/lib/core/mimedata';
@@ -24,7 +20,7 @@ import {
 } from '../../../../lib/completer';
 
 import {
-  INotebookModel, NotebookModel
+  INotebookModel
 } from '../../../../lib/notebook/notebook/model';
 
 import {
@@ -40,12 +36,12 @@ import {
 } from '../../../../lib/notebook/notebook/widget';
 
 import {
-  MockContext
-} from '../../docmanager/mockcontext';
+  Context
+} from '../../../../lib/docmanager/context';
 
 import {
-  defaultRenderMime
-} from '../../rendermime/rendermime.spec';
+  createNotebookContext, defaultRenderMime
+} from '../../utils';
 
 import {
   DEFAULT_CONTENT
@@ -62,6 +58,7 @@ import {
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
 const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
+const contextPromise = createNotebookContext();
 
 
 class LogNotebookPanel extends NotebookPanel {
@@ -90,11 +87,9 @@ class LogNotebookPanel extends NotebookPanel {
 }
 
 
-function createPanel(): LogNotebookPanel {
+function createPanel(context: Context<INotebookModel>): LogNotebookPanel {
   let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
-  let model = new NotebookModel();
-  model.fromJSON(DEFAULT_CONTENT);
-  let context = new MockContext<NotebookModel>(model);
+  context.model.fromJSON(DEFAULT_CONTENT);
   panel.context = context;
   return panel;
 }
@@ -102,6 +97,19 @@ function createPanel(): LogNotebookPanel {
 
 describe('notebook/notebook/panel', () => {
 
+  let context: Context<INotebookModel>;
+
+  beforeEach((done) => {
+    contextPromise.then(c => {
+      context = c;
+      done();
+    });
+  });
+
+  after(() => {
+    context.kernel.shutdown();
+  });
+
   describe('NotebookPanel', () => {
 
     describe('#constructor()', () => {
@@ -125,8 +133,6 @@ describe('notebook/notebook/panel', () => {
       it('should be emitted when the context on the panel changes', () => {
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let called = false;
-        let model = new NotebookModel();
-        let context = new MockContext<INotebookModel>(model);
         panel.contextChanged.connect((sender, args) => {
           expect(sender).to.be(panel);
           expect(args).to.be(void 0);
@@ -139,8 +145,6 @@ describe('notebook/notebook/panel', () => {
       it('should not be emitted if the context does not change', () => {
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let called = false;
-        let model = new NotebookModel();
-        let context = new MockContext<INotebookModel>(model);
         panel.context = context;
         panel.contextChanged.connect(() => { called = true; });
         panel.context = context;
@@ -151,16 +155,14 @@ describe('notebook/notebook/panel', () => {
 
     describe('#kernelChanged', () => {
 
-      it('should be emitted when the kernel on the panel changes', () => {
-        let panel = createPanel();
-        let called = false;
+      it('should be emitted when the kernel on the panel changes', (done) => {
+        let panel = createPanel(context);
         panel.kernelChanged.connect((sender, args) => {
           expect(sender).to.be(panel);
-          expect(args).to.be.a(MockKernel);
-          called = true;
+          expect(args.name).to.be(context.kernelspecs.default);
+          done();
         });
-        panel.context.changeKernel({ name: 'python' });
-        expect(called).to.be(true);
+        panel.context.changeKernel({ name: context.kernelspecs.default });
       });
 
     });
@@ -185,11 +187,13 @@ describe('notebook/notebook/panel', () => {
 
     describe('#kernel', () => {
 
-      it('should be the current kernel used by the panel', () => {
-        let panel = createPanel();
-        expect(panel.kernel.name).to.be('python');
-        panel.context.changeKernel({ name: 'shell' });
-        expect(panel.kernel.name).to.be('shell');
+      it('should be the current kernel used by the panel', (done) => {
+        let panel = createPanel(context);
+        context.changeKernel({ name: context.kernelspecs.default });
+        context.kernelChanged.connect(() => {
+          expect(panel.kernel.name).to.be(context.kernelspecs.default);
+          done();
+        });
       });
 
     });
@@ -227,11 +231,9 @@ describe('notebook/notebook/panel', () => {
       it('should be the model for the widget', () => {
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         expect(panel.model).to.be(null);
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         panel.context = context;
-        expect(panel.model).to.be(model);
-        expect(panel.content.model).to.be(model);
+        expect(panel.model).to.be(context.model);
+        expect(panel.content.model).to.be(context.model);
       });
 
     });
@@ -245,8 +247,6 @@ describe('notebook/notebook/panel', () => {
 
       it('should set the document context for the widget', () => {
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         panel.context = context;
         expect(panel.context).to.be(context);
       });
@@ -254,8 +254,6 @@ describe('notebook/notebook/panel', () => {
       it('should emit the `contextChanged` signal', () => {
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let called = false;
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         panel.contextChanged.connect(() => { called = true; });
         panel.context = context;
         expect(called).to.be(true);
@@ -267,8 +265,6 @@ describe('notebook/notebook/panel', () => {
 
       it('should dispose of the resources used by the widget', () => {
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         panel.context = context;
         panel.dispose();
         expect(panel.isDisposed).to.be(true);
@@ -287,8 +283,6 @@ describe('notebook/notebook/panel', () => {
 
       it('should be called when the context changes', () => {
         let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         panel.methods = [];
         panel.context = context;
         expect(panel.methods).to.contain('onContextChanged');
@@ -298,15 +292,18 @@ describe('notebook/notebook/panel', () => {
 
     describe('#onPopulated()', () => {
 
-      it('should initialize the model state', () => {
+      it('should initialize the model state', (done) => {
         let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
-        let model = new NotebookModel();
+        let model = context.model;
         model.fromJSON(DEFAULT_CONTENT);
         expect(model.cells.canUndo).to.be(true);
-        let context = new MockContext<NotebookModel>(model);
         panel.context = context;
-        expect(panel.methods).to.contain('onPopulated');
-        expect(model.cells.canUndo).to.be(false);
+        context.populated.connect(() => {
+          expect(panel.methods).to.contain('onPopulated');
+          expect(model.cells.canUndo).to.be(false);
+          done();
+        });
+        context.save();
       });
 
     });
@@ -314,14 +311,14 @@ describe('notebook/notebook/panel', () => {
     describe('#onModelStateChanged()', () => {
 
       it('should be called when the model state changes', () => {
-        let panel = createPanel();
+        let panel = createPanel(context);
         panel.methods = [];
         panel.model.dirty = false;
         expect(panel.methods).to.contain('onModelStateChanged');
       });
 
       it('should update the title className based on the dirty state', () => {
-        let panel = createPanel();
+        let panel = createPanel(context);
         panel.model.dirty = true;
         expect(panel.title.className).to.contain('jp-mod-dirty');
         panel.model.dirty = false;
@@ -332,28 +329,24 @@ describe('notebook/notebook/panel', () => {
 
     describe('#onPathChanged()', () => {
 
-      it('should be called when the path changes', () => {
-        let panel = createPanel();
-        panel.methods = [];
-        panel.context.saveAs();
-        expect(panel.methods).to.contain('onPathChanged');
-      });
+      // it('should be called when the path changes', () => {
+      //   let panel = createPanel(context);
+      //   panel.methods = [];
+      //   // TODO: add a saveAs mock
+      //   panel.context.setPath('');
+      //   expect(panel.methods).to.contain('onPathChanged');
+      // });
 
       it('should be called when the context changes', () => {
         let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         panel.methods = [];
         panel.context = context;
         expect(panel.methods).to.contain('onPathChanged');
       });
 
       it('should update the title label', () => {
-        let panel = createPanel();
-        panel.methods = [];
-        panel.context.saveAs();
-        expect(panel.methods).to.contain('onPathChanged');
-        expect(panel.title.label).to.be('foo');
+        let panel = createPanel(context);
+        expect(panel.title.label).to.be(context.path);
       });
 
     });

+ 1 - 1
test/src/notebook/notebook/widget.spec.ts

@@ -34,7 +34,7 @@ import {
 
 import {
   defaultRenderMime
-} from '../../rendermime/rendermime.spec';
+} from '../../utils';
 
 import {
   DEFAULT_CONTENT

+ 42 - 30
test/src/notebook/notebook/widgetfactory.spec.ts

@@ -8,7 +8,7 @@ import {
 } from 'phosphor/lib/core/mimedata';
 
 import {
-  NotebookModel
+  INotebookModel
 } from '../../../../lib/notebook/notebook/model';
 
 import {
@@ -20,12 +20,13 @@ import {
 } from '../../../../lib/notebook/notebook/widgetfactory';
 
 import {
-  MockContext
-} from '../../docmanager/mockcontext';
+  Context
+} from '../../../../lib/docmanager/context';
 
 import {
-  defaultRenderMime
-} from '../../rendermime/rendermime.spec';
+  createNotebookContext, defaultRenderMime
+} from '../../utils';
+
 
 import {
   CodeMirrorNotebookPanelRenderer
@@ -35,10 +36,24 @@ import {
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
 const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
+const contextPromise = createNotebookContext();
 
 
 describe('notebook/notebook/widgetfactory', () => {
 
+  let context: Context<INotebookModel>;
+
+  beforeEach((done) => {
+    contextPromise.then(c => {
+      context = c;
+      done();
+    });
+  });
+
+  after(() => {
+    context.kernel.shutdown();
+  });
+
   describe('NotebookWidgetFactory', () => {
 
     describe('#constructor()', () => {
@@ -81,50 +96,47 @@ describe('notebook/notebook/widgetfactory', () => {
     describe('#createNew()', () => {
 
       it('should create a new `NotebookPanel` widget', () => {
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         expect(panel).to.be.a(NotebookPanel);
       });
 
       it('should create a clone of the rendermime', () => {
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         expect(panel.rendermime).to.not.be(rendermime);
       });
 
-      it('should start a kernel if one is given', () => {
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
+      it('should start a kernel if one is given', (done) => {
         let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
-        let panel = factory.createNew(context, { name: 'shell' });
-        expect(panel.context.kernel.name).to.be('shell');
+        context.kernelChanged.connect((sender, kernel) => {
+          expect(kernel.name).to.be(context.kernelspecs.default);
+          done();
+        });
+        factory.createNew(context, { name: context.kernelspecs.default });
       });
 
-      it('should start a kernel given the default kernel language', () => {
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
+      it('should start a kernel given the default kernel language', (done) => {
         let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
-        let panel = factory.createNew(context);
-        expect(panel.context.kernel.name).to.be('python');
+        createNotebookContext().then(ctx => {
+          ctx.kernelChanged.connect((sender, kernel) => {
+            expect(kernel.name).to.be(ctx.kernelspecs.default);
+            done();
+          });
+          factory.createNew(ctx);
+        });
       });
 
-      it('should start a kernel based on default language of the model', () => {
-        let model = new NotebookModel();
-        let cursor = model.getMetadata('language_info');
-        cursor.setValue({ name: 'shell' });
-        let context = new MockContext<NotebookModel>(model);
-        let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
-        let panel = factory.createNew(context);
-        expect(panel.context.kernel.name).to.be('shell');
-      });
+      // it('should start a kernel based on default language of the model', () => {
+      //   // TODO: inject other kernelspecs
+      //   let cursor = context.model.getMetadata('language_info');
+      //   cursor.setValue({ name: 'shell' });
+      //   let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
+      //   let panel = factory.createNew(context);
+      //   expect(panel.context.kernel.name).to.be('shell');
+      // });
 
       it('should populate the default toolbar items', () => {
-        let model = new NotebookModel();
-        let context = new MockContext<NotebookModel>(model);
         let factory = new NotebookWidgetFactory(rendermime, clipboard, renderer );
         let panel = factory.createNew(context);
         let items = panel.toolbar.list();

+ 19 - 8
test/src/notebook/output-area/model.spec.ts

@@ -4,8 +4,8 @@
 import expect = require('expect.js');
 
 import {
-  MockKernel
-} from 'jupyter-js-services/lib/mockkernel';
+  IKernel, startNewKernel
+} from 'jupyter-js-services';
 
 import {
   deepEqual
@@ -203,29 +203,40 @@ describe('notebook/output-area/model', () => {
 
     describe('#execute()', () => {
 
+      let kernel: IKernel;
+
+      beforeEach((done) => {
+        startNewKernel().then(k => {
+          kernel = k;
+          return kernel.kernelInfo();
+        }).then(() => {
+          done();
+        }).catch(done);
+      });
+
       it('should execute code on a kernel and send outputs to the model', (done) => {
-        let kernel = new MockKernel();
         let model = new OutputAreaModel();
         expect(model.length).to.be(0);
-        model.execute('foo', kernel).then(reply => {
+        model.execute('print("hello")', kernel).then(reply => {
           expect(reply.content.execution_count).to.be(1);
           expect(reply.content.status).to.be('ok');
           expect(model.length).to.be(1);
+          kernel.shutdown();
           done();
-        });
+        }).catch(done);
       });
 
       it('should clear existing outputs', (done) => {
-        let kernel = new MockKernel();
         let model = new OutputAreaModel();
         for (let output of DEFAULT_OUTPUTS) {
           model.add(output);
         }
-        model.execute('foo', kernel).then(reply => {
+        return model.execute('print("hello")', kernel).then(reply => {
           expect(reply.content.execution_count).to.be(1);
           expect(model.length).to.be(1);
+          kernel.shutdown();
           done();
-        });
+        }).catch(done);
       });
 
     });

+ 1 - 1
test/src/notebook/output-area/widget.spec.ts

@@ -25,7 +25,7 @@ import {
 
 import {
   defaultRenderMime
-} from '../../rendermime/rendermime.spec';
+} from '../../utils';
 
 import {
   DEFAULT_OUTPUTS

+ 3 - 31
test/src/rendermime/rendermime.spec.ts

@@ -8,8 +8,7 @@ import {
 } from 'phosphor/lib/ui/widget';
 
 import {
-  LatexRenderer, PDFRenderer, JavascriptRenderer,
-  SVGRenderer, MarkdownRenderer, TextRenderer, HTMLRenderer, ImageRenderer
+   TextRenderer
 } from '../../../lib/renderers';
 
 import {
@@ -17,35 +16,8 @@ import {
 } from '../../../lib/rendermime';
 
 import {
-  defaultSanitizer
-} from '../../../lib/sanitizer';
-
-
-const TRANSFORMERS = [
-  new JavascriptRenderer(),
-  new MarkdownRenderer(),
-  new HTMLRenderer(),
-  new PDFRenderer(),
-  new ImageRenderer(),
-  new SVGRenderer(),
-  new LatexRenderer(),
-  new TextRenderer()
-];
-
-
-export
-function defaultRenderMime(): RenderMime {
-  let renderers: RenderMime.MimeMap<RenderMime.IRenderer> = {};
-  let order: string[] = [];
-  for (let t of TRANSFORMERS) {
-    for (let m of t.mimetypes) {
-      renderers[m] = t;
-      order.push(m);
-    }
-  }
-  let sanitizer = defaultSanitizer;
-  return new RenderMime({ renderers, order, sanitizer });
-}
+  defaultRenderMime
+} from '../utils';
 
 
 describe('rendermime/index', () => {

+ 107 - 0
test/src/utils.ts

@@ -5,6 +5,74 @@ import {
   simulate
 } from 'simulate-event';
 
+import {
+  createServiceManager, utils, IServiceManager
+} from 'jupyter-js-services';
+
+import {
+  TextModelFactory, IDocumentModel
+} from '../../lib/docregistry';
+
+import {
+  Context
+} from '../../lib/docmanager/context';
+
+import {
+  INotebookModel
+} from '../../lib/notebook/notebook/model';
+
+import {
+  NotebookModelFactory
+} from '../../lib/notebook/notebook/modelfactory';
+
+import {
+  LatexRenderer, PDFRenderer, JavascriptRenderer,
+  SVGRenderer, MarkdownRenderer, TextRenderer, HTMLRenderer, ImageRenderer
+} from '../../lib/renderers';
+
+import {
+  RenderMime
+} from '../../lib/rendermime';
+
+import {
+  defaultSanitizer
+} from '../../lib/sanitizer';
+
+
+/**
+ * Get a copy of the default rendermime instance.
+ */
+export
+function defaultRenderMime(): RenderMime {
+  return Private.rendermime.clone();
+}
+
+
+/**
+ * Create a context for a file.
+ */
+export
+function createFileContext(path?: string): Promise<Context<IDocumentModel>> {
+  return Private.servicePromise.then(manager => {
+    let factory = Private.textFactory;
+    path = path || utils.uuid() + '.txt';
+    return new Context({ manager, factory, path });
+  });
+}
+
+
+/**
+ * Create a context for a notebook.
+ */
+export
+function createNotebookContext(path?: string): Promise<Context<INotebookModel>> {
+  return Private.servicePromise.then(manager => {
+    let factory = Private.notebookFactory;
+    path = path || utils.uuid() + '.ipynb';
+    return new Context({ manager, factory, path });
+  });
+}
+
 
 /**
  * Wait for a dialog to be attached to an element.
@@ -51,3 +119,42 @@ function dismissDialog(host: HTMLElement = document.body): Promise<void> {
     }
   });
 }
+
+
+/**
+ * A namespace for private data.
+ */
+namespace Private {
+  export
+  const servicePromise: Promise<IServiceManager> = createServiceManager();
+
+  export
+  const textFactory = new TextModelFactory();
+
+  export
+  const notebookFactory = new NotebookModelFactory();
+
+  const TRANSFORMERS = [
+    new JavascriptRenderer(),
+    new MarkdownRenderer(),
+    new HTMLRenderer(),
+    new PDFRenderer(),
+    new ImageRenderer(),
+    new SVGRenderer(),
+    new LatexRenderer(),
+    new TextRenderer()
+  ];
+
+  let renderers: RenderMime.MimeMap<RenderMime.IRenderer> = {};
+  let order: string[] = [];
+  for (let t of TRANSFORMERS) {
+    for (let m of t.mimetypes) {
+      renderers[m] = t;
+      order.push(m);
+    }
+  }
+  let sanitizer = defaultSanitizer;
+
+  export
+  const rendermime = new RenderMime({ renderers, order, sanitizer });
+}