Browse Source

Run console tests in jest

wip fully testing console

cleanup

integrity

Remove console dependency in testutils

cleanup

convert from chai to jest using jest-codemods

Consolidate config

convert mocha tests to use chai and fix workflow target handling

Address CI failures

Refactor browser check to use asyncio

Integrity

reenable link check

Use an attached property for last message id

Use build mode for testutils

integrity

Fix package and add debug output to CI

Revert changes to browser check

Revert changes to example test

integrity

Properly suppress StreamClosedError

Revert browser and mocha changes

More reverts

Consolidate timeout config and skiplibchecks for now

integrity
Steven Silvester 5 years ago
parent
commit
ee03728830
57 changed files with 1294 additions and 646 deletions
  1. 1 1
      buildutils/test-template/package.json
  2. 1 2
      dev_mode/package.json
  3. 7 7
      package.json
  4. 1 0
      packages/console/babel.config.js
  5. 2 0
      packages/console/jest.config.js
  6. 8 0
      packages/console/package.json
  7. 20 12
      packages/console/src/panel.ts
  8. 68 64
      packages/console/test/foreign.spec.ts
  9. 30 22
      packages/console/test/history.spec.ts
  10. 21 25
      packages/console/test/panel.spec.ts
  11. 2 2
      packages/console/test/utils.ts
  12. 81 63
      packages/console/test/widget.spec.ts
  13. 31 0
      packages/console/tsconfig.test.json
  14. 2 1
      scripts/ci_install.sh
  15. 10 4
      scripts/ci_script.sh
  16. 1 1
      tests/karma.conf.js
  17. 2 2
      tests/test-application/package.json
  18. 2 2
      tests/test-apputils/package.json
  19. 2 2
      tests/test-cells/package.json
  20. 2 2
      tests/test-codeeditor/package.json
  21. 2 2
      tests/test-codemirror/package.json
  22. 2 2
      tests/test-completer/package.json
  23. 0 1
      tests/test-console/karma-cov.conf.js
  24. 0 1
      tests/test-console/karma.conf.js
  25. 0 41
      tests/test-console/package.json
  26. 0 10
      tests/test-console/run-test.py
  27. 0 33
      tests/test-console/tsconfig.json
  28. 2 2
      tests/test-coreutils/package.json
  29. 2 1
      tests/test-docmanager/tsconfig.json
  30. 2 2
      tests/test-docregistry/package.json
  31. 2 1
      tests/test-filebrowser/tsconfig.json
  32. 2 2
      tests/test-fileeditor/package.json
  33. 2 2
      tests/test-imageviewer/package.json
  34. 2 2
      tests/test-inspector/package.json
  35. 2 2
      tests/test-logconsole/package.json
  36. 2 2
      tests/test-mainmenu/package.json
  37. 2 2
      tests/test-nbformat/package.json
  38. 1 0
      tests/test-notebook/package.json
  39. 14 16
      tests/test-notebook/src/actions.spec.ts
  40. 2 4
      tests/test-notebook/src/default-toolbar.spec.ts
  41. 2 1
      tests/test-notebook/tsconfig.json
  42. 2 2
      tests/test-observables/package.json
  43. 2 2
      tests/test-outputarea/package.json
  44. 2 1
      tests/test-rendermime/tsconfig.json
  45. 2 2
      tests/test-services/package.json
  46. 2 2
      tests/test-settingregistry/package.json
  47. 2 2
      tests/test-statedb/package.json
  48. 2 2
      tests/test-statusbar/package.json
  49. 2 1
      tests/test-terminal/tsconfig.json
  50. 2 2
      tests/test-ui-components/package.json
  51. 6 2
      testutils/package.json
  52. 3 0
      testutils/src/index.ts
  53. 27 0
      testutils/src/jest-config-new.ts
  54. 1 2
      testutils/src/jest-script.ts
  55. 481 0
      testutils/src/mock.ts
  56. 1 2
      testutils/tsconfig.json
  57. 420 283
      yarn.lock

+ 1 - 1
buildutils/test-template/package.json

@@ -1,5 +1,5 @@
 {
-  "name": "@jupyterlab/template-for-tests",
+  "name": "template-for-tests",
   "version": "2.1.0",
   "private": true,
   "scripts": {

+ 1 - 2
dev_mode/package.json

@@ -362,7 +362,7 @@
       "@jupyterlab/example-services-outputarea": "../packages/services/examples/typescript-browser-with-output",
       "@jupyterlab/buildutils": "../buildutils",
       "@jupyterlab/template": "../buildutils/template",
-      "@jupyterlab/template-for-tests": "../buildutils/test-template",
+      "template-for-tests": "../buildutils/test-template",
       "@jupyterlab/test-root": "../tests",
       "@jupyterlab/test-application": "../tests/test-application",
       "@jupyterlab/test-apputils": "../tests/test-apputils",
@@ -370,7 +370,6 @@
       "@jupyterlab/test-codeeditor": "../tests/test-codeeditor",
       "@jupyterlab/test-codemirror": "../tests/test-codemirror",
       "@jupyterlab/test-completer": "../tests/test-completer",
-      "@jupyterlab/test-console": "../tests/test-console",
       "@jupyterlab/test-coreutils": "../tests/test-coreutils",
       "@jupyterlab/test-csvviewer": "../tests/test-csvviewer",
       "@jupyterlab/test-docmanager": "../tests/test-docmanager",

+ 7 - 7
package.json

@@ -70,15 +70,15 @@
     "remove:dependency": "node buildutils/lib/remove-dependency.js",
     "remove:package": "node buildutils/lib/remove-package.js",
     "remove:sibling": "node buildutils/lib/remove-package.js",
-    "test": "lerna run test --scope \"@jupyterlab/test-*\" --concurrency 1 --stream",
-    "test:all": "lerna run test --scope \"@jupyterlab/test-*\" --concurrency 1 --stream --no-bail",
-    "test:chrome": "lerna run test:chrome --scope \"@jupyterlab/test-*\" --concurrency 1 --stream",
-    "test:chrome-headless": "lerna run test:chrome-headless --scope \"@jupyterlab/test-*\" --concurrency 1 --stream",
+    "test": "lerna run test --scope \"@jupyterlab/*\" --concurrency 1 --stream",
+    "test:all": "lerna run test --scope \"@jupyterlab/*\" --concurrency 1 --stream --no-bail",
+    "test:chrome": "lerna run test:chrome --scope \"@jupyterlab/*\" --concurrency 1 --stream",
+    "test:chrome-headless": "lerna run test:chrome-headless --scope \"@jupyterlab/*\" --concurrency 1 --stream",
     "test:examples": "python examples/test_examples.py",
-    "test:firefox": "lerna run test:firefox --scope \"@jupyterlab/test-*\" --concurrency 1 --stream",
-    "test:ie": "lerna run test:ie --scope \"@jupyterlab/test-*\" --concurrency 1 --stream",
+    "test:firefox": "lerna run test:firefox --scope \"@jupyterlab/*\" --concurrency 1 --stream",
+    "test:ie": "lerna run test:ie --scope \"@jupyterlab/*\" --concurrency 1 --stream",
     "test:scope": "lerna run test --concurrency 1 --stream",
-    "test:summary": "lerna run test --scope \"@jupyterlab/test-*\" --parallel --no-bail | grep -Ei '.* test.*(failed|passed|total|completed|skipped)' | sort",
+    "test:summary": "lerna run test --scope \"@jupyterlab/*\" --parallel --no-bail | grep -Ei '.* test.*(failed|passed|total|completed|skipped)' | sort",
     "tslint": "tslint --fix -c tslint.json --project tsconfigbase.json '**/*{.ts,.tsx}'",
     "tslint:check": "tslint -c tslint.json --project tsconfigbase.json '**/*{.ts,.tsx}'",
     "update:dependency": "node buildutils/lib/update-dependency.js --lerna",

+ 1 - 0
packages/console/babel.config.js

@@ -0,0 +1 @@
+module.exports = require('@jupyterlab/testutils/lib/babel.config');

+ 2 - 0
packages/console/jest.config.js

@@ -0,0 +1,2 @@
+const func = require('@jupyterlab/testutils/lib/jest-config-new');
+module.exports = func('console', __dirname);

+ 8 - 0
packages/console/package.json

@@ -29,9 +29,13 @@
   },
   "scripts": {
     "build": "tsc -b",
+    "build:test": "tsc --build tsconfig.test.json",
     "clean": "rimraf lib",
     "docs": "typedoc src",
     "prepublishOnly": "npm run build",
+    "test": "jest",
+    "test:cov": "jest --collect-coverage",
+    "test:watch": "npm run test -- --watch",
     "watch": "tsc -b --watch"
   },
   "dependencies": {
@@ -53,7 +57,11 @@
     "@lumino/widgets": "^1.11.1"
   },
   "devDependencies": {
+    "@jupyterlab/testutils": "^2.1.0",
+    "@types/jest": "^24.0.23",
+    "jest": "^25.2.3",
     "rimraf": "~3.0.0",
+    "ts-jest": "^25.2.1",
     "typedoc": "^0.15.4",
     "typescript": "~3.7.3"
   },

+ 20 - 12
packages/console/src/panel.ts

@@ -45,7 +45,8 @@ export class ConsolePanel extends MainAreaWidget<Panel> {
       basePath,
       name,
       manager,
-      modelFactory
+      modelFactory,
+      sessionContext
     } = options;
     let contentFactory = (this.contentFactory =
       options.contentFactory || ConsolePanel.defaultContentFactory);
@@ -54,15 +55,17 @@ export class ConsolePanel extends MainAreaWidget<Panel> {
       path = `${basePath || ''}/console-${count}-${UUID.uuid4()}`;
     }
 
-    let sessionContext = (this._sessionContext = new SessionContext({
-      sessionManager: manager.sessions,
-      specsManager: manager.kernelspecs,
-      path,
-      name: name || `Console ${count}`,
-      type: 'console',
-      kernelPreference: options.kernelPreference,
-      setBusy: options.setBusy
-    }));
+    sessionContext = this._sessionContext =
+      sessionContext ||
+      new SessionContext({
+        sessionManager: manager.sessions,
+        specsManager: manager.kernelspecs,
+        path,
+        name: name || `Console ${count}`,
+        type: 'console',
+        kernelPreference: options.kernelPreference,
+        setBusy: options.setBusy
+      });
 
     let resolver = new RenderMimeRegistry.UrlResolver({
       session: sessionContext,
@@ -81,7 +84,7 @@ export class ConsolePanel extends MainAreaWidget<Panel> {
 
     void sessionContext.initialize().then(async value => {
       if (value) {
-        await sessionContextDialogs.selectKernel(sessionContext);
+        await sessionContextDialogs.selectKernel(sessionContext!);
       }
       this._connected = new Date();
       this._updateTitlePanel();
@@ -158,7 +161,7 @@ export class ConsolePanel extends MainAreaWidget<Panel> {
 
   private _executed: Date | null = null;
   private _connected: Date | null = null;
-  private _sessionContext: SessionContext;
+  private _sessionContext: ISessionContext;
 }
 
 /**
@@ -204,6 +207,11 @@ export namespace ConsolePanel {
      */
     kernelPreference?: ISessionContext.IKernelPreference;
 
+    /**
+     * An existing session context to use.
+     */
+    sessionContext?: SessionContext;
+
     /**
      * The model factory for the console widget.
      */

+ 68 - 64
tests/test-console/src/foreign.spec.ts → packages/console/test/foreign.spec.ts

@@ -1,28 +1,24 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import { expect } from 'chai';
+import 'jest';
 
-import { PromiseDelegate, UUID } from '@lumino/coreutils';
+import { UUID } from '@lumino/coreutils';
 
-import { KernelMessage, Session } from '@jupyterlab/services';
+import { KernelMessage } from '@jupyterlab/services';
 
 import { Signal } from '@lumino/signaling';
 
 import { Panel } from '@lumino/widgets';
 
-import { ForeignHandler } from '@jupyterlab/console';
-
 import { CodeCellModel, CodeCell } from '@jupyterlab/cells';
 
-import {
-  createSessionContext,
-  createSession,
-  defaultRenderMime,
-  NBTestUtils
-} from '@jupyterlab/testutils';
+import { defaultRenderMime, NBTestUtils, Mock } from '@jupyterlab/testutils';
+
 import { ISessionContext } from '@jupyterlab/apputils';
 
+import { ForeignHandler } from '../src';
+
 class TestParent extends Panel implements ForeignHandler.IReceiver {
   addCell(cell: CodeCell, msgId?: string): void {
     this.addWidget(cell);
@@ -100,19 +96,39 @@ const relevantTypes = [
 
 describe('@jupyterlab/console', () => {
   describe('ForeignHandler', () => {
-    let foreign: Session.ISessionConnection;
+    let foreign: ISessionContext;
     let handler: TestHandler;
     let sessionContext: ISessionContext;
 
-    before(async function() {
-      // tslint:disable-next-line:no-invalid-this
-      this.timeout(120000);
+    const streamMsg = KernelMessage.createMessage({
+      session: 'foo',
+      channel: 'iopub',
+      msgType: 'stream',
+      content: { name: 'stderr', text: 'foo' }
+    });
 
+    const clearMsg = KernelMessage.createMessage({
+      session: 'foo',
+      channel: 'iopub',
+      msgType: 'clear_output',
+      content: { wait: false }
+    });
+
+    beforeAll(async function() {
       const path = UUID.uuid4();
-      [sessionContext, foreign] = await Promise.all([
-        createSessionContext({ path, type: 'test' }),
-        createSession({ name: '', path, type: 'test' })
-      ]);
+      const kernel0 = new Mock.KernelMock({});
+      const kernel1 = Mock.cloneKernel(kernel0);
+      const connection0 = new Mock.SessionConnectionMock(
+        { model: { path, type: 'test' } },
+        kernel0
+      );
+      sessionContext = new Mock.SessionContextMock({}, connection0);
+      const connection1 = new Mock.SessionConnectionMock(
+        { model: { path, type: 'test2' } },
+        kernel1
+      );
+      foreign = new Mock.SessionContextMock({}, connection1);
+
       await sessionContext.initialize();
       await sessionContext.session!.kernel!.info;
     });
@@ -126,7 +142,7 @@ describe('@jupyterlab/console', () => {
       handler.dispose();
     });
 
-    after(async () => {
+    afterAll(async () => {
       foreign.dispose();
       await sessionContext.shutdown();
       sessionContext.dispose();
@@ -134,51 +150,49 @@ describe('@jupyterlab/console', () => {
 
     describe('#constructor()', () => {
       it('should create a new foreign handler', () => {
-        expect(handler).to.be.an.instanceof(ForeignHandler);
+        expect(handler).toBeInstanceOf(ForeignHandler);
       });
     });
 
     describe('#enabled', () => {
       it('should default to `false`', () => {
-        expect(handler.enabled).to.equal(false);
+        expect(handler.enabled).toBe(false);
       });
 
       it('should allow foreign cells to be injected if `true`', async () => {
-        const code = 'print("#enabled:true")';
         handler.enabled = true;
         let called = false;
         handler.injected.connect(() => {
           called = true;
         });
-        await foreign.kernel!.requestExecute({ code, stop_on_error: true })
-          .done;
-        expect(called).to.equal(true);
+        await foreign.session!.kernel!.requestExecute({ code: 'foo' }).done;
+        Mock.emitIopubMessage(foreign, streamMsg);
+        expect(called).toBe(true);
       });
 
       it('should reject foreign cells if `false`', async () => {
-        const code = 'print("#enabled:false")';
         handler.enabled = false;
         let called = false;
         handler.rejected.connect(() => {
           called = true;
         });
-        await foreign.kernel!.requestExecute({ code, stop_on_error: true })
-          .done;
-        expect(called).to.equal(true);
+        await foreign.session!.kernel!.requestExecute({ code: 'foo' }).done;
+        Mock.emitIopubMessage(foreign, streamMsg);
+        expect(called).toBe(true);
       });
     });
 
     describe('#isDisposed', () => {
       it('should indicate whether the handler is disposed', () => {
-        expect(handler.isDisposed).to.equal(false);
+        expect(handler.isDisposed).toBe(false);
         handler.dispose();
-        expect(handler.isDisposed).to.equal(true);
+        expect(handler.isDisposed).toBe(true);
       });
     });
 
     describe('#session', () => {
       it('should be a client session object', () => {
-        expect(handler.sessionContext.session!.path).to.be.ok;
+        expect(handler.sessionContext.session!.path).toBeTruthy();
       });
     });
 
@@ -189,77 +203,67 @@ describe('@jupyterlab/console', () => {
           sessionContext: handler.sessionContext,
           parent
         });
-        expect(handler.parent).to.equal(parent);
+        expect(handler.parent).toBe(parent);
       });
     });
 
     describe('#dispose()', () => {
       it('should dispose the resources held by the handler', () => {
-        expect(handler.isDisposed).to.equal(false);
+        expect(handler.isDisposed).toBe(false);
         handler.dispose();
-        expect(handler.isDisposed).to.equal(true);
+        expect(handler.isDisposed).toBe(true);
       });
 
       it('should be safe to call multiple times', () => {
-        expect(handler.isDisposed).to.equal(false);
+        expect(handler.isDisposed).toBe(false);
         handler.dispose();
         handler.dispose();
-        expect(handler.isDisposed).to.equal(true);
+        expect(handler.isDisposed).toBe(true);
       });
     });
 
     describe('#onIOPubMessage()', () => {
       it('should be called when messages come through', async () => {
-        const code = 'print("onIOPubMessage:disabled")';
-        const promise = new PromiseDelegate<void>();
         handler.enabled = false;
         let called = false;
         handler.received.connect(() => {
           called = true;
-          promise.resolve(void 0);
         });
-        await foreign.kernel!.requestExecute({ code, stop_on_error: true })
-          .done;
-        await promise.promise;
-        expect(called).to.equal(true);
+        await foreign.session!.kernel!.requestExecute({ code: 'foo' }).done;
+        Mock.emitIopubMessage(foreign, streamMsg);
+        expect(called).toBe(true);
       });
 
       it('should inject relevant cells into the parent', async () => {
-        const code = 'print("#onIOPubMessage:enabled")';
-        const promise = new PromiseDelegate<void>();
         handler.enabled = true;
         const parent = handler.parent as TestParent;
-        expect(parent.widgets.length).to.equal(0);
+        expect(parent.widgets.length).toBe(0);
         let called = false;
         handler.injected.connect(() => {
-          expect(parent.widgets.length).to.be.greaterThan(0);
+          expect(parent.widgets.length).toBeGreaterThan(0);
           called = true;
-          promise.resolve(void 0);
         });
-        await foreign.kernel!.requestExecute({ code, stop_on_error: true })
-          .done;
-        await promise.promise;
-        expect(called).to.equal(true);
+        await foreign.session!.kernel!.requestExecute({ code: 'foo' }).done;
+        Mock.emitIopubMessage(foreign, streamMsg);
+        expect(called).toBe(true);
       });
 
       it('should not reject relevant iopub messages', async () => {
-        const code = 'print("#onIOPubMessage:relevant")';
-        const promise = new PromiseDelegate<void>();
         let called = false;
+        let errored = false;
         handler.enabled = true;
         handler.rejected.connect(() => {
-          promise.reject('rejected relevant iopub message');
+          errored = true;
         });
-        handler.injected.connect((sender, msg) => {
-          if (KernelMessage.isStreamMsg(msg)) {
+        handler.received.connect((sender, msg) => {
+          if (KernelMessage.isClearOutputMsg(msg)) {
             called = true;
-            promise.resolve(void 0);
           }
         });
-        await foreign.kernel!.requestExecute({ code, stop_on_error: true })
-          .done;
-        await promise.promise;
-        expect(called).to.equal(true);
+        await foreign.session!.kernel!.requestExecute({ code: 'foo' }).done;
+        Mock.emitIopubMessage(foreign, clearMsg);
+        expect(called).toBe(true);
+        expect(errored).toBe(false);
       });
     });
   });

+ 30 - 22
tests/test-console/src/history.spec.ts → packages/console/test/history.spec.ts

@@ -1,7 +1,7 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import { expect } from 'chai';
+import 'jest';
 
 import { ISessionContext } from '@jupyterlab/apputils';
 
@@ -11,10 +11,10 @@ import { CodeEditor } from '@jupyterlab/codeeditor';
 
 import { CodeMirrorEditor } from '@jupyterlab/codemirror';
 
-import { ConsoleHistory } from '@jupyterlab/console';
-
 import { createSessionContext, signalToPromise } from '@jupyterlab/testutils';
 
+import { ConsoleHistory } from '../src';
+
 const mockHistory = ({
   header: null,
   parent_header: {
@@ -68,46 +68,46 @@ describe('console/history', () => {
     sessionContext = await createSessionContext();
   });
 
-  after(() => sessionContext.shutdown());
+  afterAll(() => sessionContext.shutdown());
 
   describe('ConsoleHistory', () => {
     describe('#constructor()', () => {
       it('should create a console history object', () => {
         const history = new ConsoleHistory({ sessionContext });
-        expect(history).to.be.an.instanceof(ConsoleHistory);
+        expect(history).toBeInstanceOf(ConsoleHistory);
       });
     });
 
     describe('#isDisposed', () => {
       it('should get whether the object is disposed', () => {
         const history = new ConsoleHistory({ sessionContext });
-        expect(history.isDisposed).to.equal(false);
+        expect(history.isDisposed).toBe(false);
         history.dispose();
-        expect(history.isDisposed).to.equal(true);
+        expect(history.isDisposed).toBe(true);
       });
     });
 
     describe('#session', () => {
       it('should be the client session object', () => {
         const history = new ConsoleHistory({ sessionContext });
-        expect(history.sessionContext).to.equal(sessionContext);
+        expect(history.sessionContext).toBe(sessionContext);
       });
     });
 
     describe('#dispose()', () => {
       it('should dispose the history object', () => {
         const history = new ConsoleHistory({ sessionContext });
-        expect(history.isDisposed).to.equal(false);
+        expect(history.isDisposed).toBe(false);
         history.dispose();
-        expect(history.isDisposed).to.equal(true);
+        expect(history.isDisposed).toBe(true);
       });
 
       it('should be safe to dispose multiple times', () => {
         const history = new ConsoleHistory({ sessionContext });
-        expect(history.isDisposed).to.equal(false);
+        expect(history.isDisposed).toBe(false);
         history.dispose();
         history.dispose();
-        expect(history.isDisposed).to.equal(true);
+        expect(history.isDisposed).toBe(true);
       });
     });
 
@@ -115,7 +115,7 @@ describe('console/history', () => {
       it('should return an empty string if no history exists', async () => {
         const history = new ConsoleHistory({ sessionContext });
         const result = await history.back('');
-        expect(result).to.equal('');
+        expect(result).toBe('');
       });
 
       it('should return previous items if they exist', async () => {
@@ -127,7 +127,7 @@ describe('console/history', () => {
         }
         const index = mockHistory.content.history.length - 1;
         const last = (mockHistory.content.history[index] as any)[2];
-        expect(result).to.equal(last);
+        expect(result).toBe(last);
       });
     });
 
@@ -135,7 +135,7 @@ describe('console/history', () => {
       it('should return an empty string if no history exists', async () => {
         const history = new ConsoleHistory({ sessionContext });
         const result = await history.forward('');
-        expect(result).to.equal('');
+        expect(result).toBe('');
       });
 
       it('should return next items if they exist', async () => {
@@ -148,7 +148,7 @@ describe('console/history', () => {
         }
         const index = mockHistory.content.history.length - 1;
         const last = (mockHistory.content.history[index] as any)[2];
-        expect(result).to.equal(last);
+        expect(result).toBe(last);
       });
     });
 
@@ -158,27 +158,33 @@ describe('console/history', () => {
         const item = 'foo';
         history.push(item);
         const result = await history.back('');
-        expect(result).to.equal(item);
+        expect(result).toBe(item);
       });
     });
 
     describe('#onTextChange()', () => {
       it('should be called upon an editor text change', () => {
         const history = new TestHistory({ sessionContext });
-        expect(history.methods).to.not.contain('onTextChange');
+        expect(history.methods).toEqual(
+          expect.not.arrayContaining(['onTextChange'])
+        );
         const model = new CodeEditor.Model();
         const host = document.createElement('div');
         const editor = new CodeMirrorEditor({ model, host });
         history.editor = editor;
         model.value.text = 'foo';
-        expect(history.methods).to.contain('onTextChange');
+        expect(history.methods).toEqual(
+          expect.arrayContaining(['onTextChange'])
+        );
       });
     });
 
     describe('#onEdgeRequest()', () => {
       it('should be called upon an editor edge request', async () => {
         const history = new TestHistory({ sessionContext });
-        expect(history.methods).to.not.contain('onEdgeRequest');
+        expect(history.methods).toEqual(
+          expect.not.arrayContaining(['onEdgeRequest'])
+        );
         const host = document.createElement('div');
         const model = new CodeEditor.Model();
         const editor = new CodeMirrorEditor({ model, host });
@@ -186,9 +192,11 @@ describe('console/history', () => {
         history.push('foo');
         const promise = signalToPromise(editor.model.value.changed);
         editor.edgeRequested.emit('top');
-        expect(history.methods).to.contain('onEdgeRequest');
+        expect(history.methods).toEqual(
+          expect.arrayContaining(['onEdgeRequest'])
+        );
         await promise;
-        expect(editor.model.value.text).to.equal('foo');
+        expect(editor.model.value.text).toBe('foo');
       });
     });
   });

+ 21 - 25
tests/test-console/src/panel.spec.ts → packages/console/test/panel.spec.ts

@@ -1,17 +1,15 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import { expect } from 'chai';
-
-import { ServiceManager } from '@jupyterlab/services';
+import 'jest';
 
 import { Message, MessageLoop } from '@lumino/messaging';
 
 import { Widget } from '@lumino/widgets';
 
-import { CodeConsole, ConsolePanel } from '@jupyterlab/console';
+import { CodeConsole, ConsolePanel } from '../src';
 
-import { dismissDialog, acceptDialog } from '@jupyterlab/testutils';
+import { dismissDialog, Mock } from '@jupyterlab/testutils';
 
 import {
   createConsolePanelFactory,
@@ -38,10 +36,10 @@ const contentFactory = createConsolePanelFactory();
 
 describe('console/panel', () => {
   let panel: TestPanel;
-  const manager = new ServiceManager({ standby: 'never' });
+  const manager = new Mock.ServiceManagerMock();
 
-  before(() => {
-    return manager.ready;
+  beforeAll(async () => {
+    return await manager.ready;
   });
 
   beforeEach(() => {
@@ -49,7 +47,8 @@ describe('console/panel', () => {
       manager,
       contentFactory,
       rendermime,
-      mimeTypeService
+      mimeTypeService,
+      sessionContext: Mock.createSimpleSessionContext()
     });
   });
 
@@ -60,36 +59,37 @@ describe('console/panel', () => {
   describe('ConsolePanel', () => {
     describe('#constructor()', () => {
       it('should create a new console panel', () => {
-        expect(panel).to.be.an.instanceof(ConsolePanel);
-        expect(Array.from(panel.node.classList)).to.contain('jp-ConsolePanel');
+        expect(panel).toBeInstanceOf(ConsolePanel);
+        expect(Array.from(panel.node.classList)).toEqual(
+          expect.arrayContaining(['jp-ConsolePanel'])
+        );
       });
     });
 
     describe('#console', () => {
       it('should be a code console widget created at instantiation', () => {
-        expect(panel.console).to.be.an.instanceof(CodeConsole);
+        expect(panel.console).toBeInstanceOf(CodeConsole);
       });
     });
 
     describe('#session', () => {
       it('should be a client session object', () => {
-        expect(panel.sessionContext.sessionChanged).to.be.ok;
+        expect(panel.sessionContext.kernelChanged).toBeTruthy();
       });
     });
 
     describe('#dispose()', () => {
       it('should dispose of the resources held by the panel', () => {
         panel.dispose();
-        expect(panel.isDisposed).to.equal(true);
+        expect(panel.isDisposed).toBe(true);
         panel.dispose();
-        expect(panel.isDisposed).to.equal(true);
+        expect(panel.isDisposed).toBe(true);
       });
     });
 
     describe('#onAfterAttach()', () => {
       it('should start the session', async () => {
         Widget.attach(panel, document.body);
-        await acceptDialog();
         await panel.sessionContext.ready;
         await panel.sessionContext.session!.kernel!.info;
       });
@@ -97,23 +97,19 @@ describe('console/panel', () => {
 
     describe('#onActivateRequest()', () => {
       it('should give the focus to the console prompt', () => {
-        expect(panel.methods).to.not.contain('onActivateRequest');
         Widget.attach(panel, document.body);
         MessageLoop.sendMessage(panel, Widget.Msg.ActivateRequest);
-        expect(panel.methods).to.contain('onActivateRequest');
-        expect(panel.console.promptCell!.editor.hasFocus()).to.equal(true);
+        expect(panel.console.promptCell!.editor.hasFocus()).toBe(true);
         return dismissDialog();
       });
     });
 
     describe('#onCloseRequest()', () => {
       it('should dispose of the panel resources after closing', () => {
-        expect(panel.methods).to.not.contain('onCloseRequest');
         Widget.attach(panel, document.body);
-        expect(panel.isDisposed).to.equal(false);
+        expect(panel.isDisposed).toBe(false);
         MessageLoop.sendMessage(panel, Widget.Msg.CloseRequest);
-        expect(panel.methods).to.contain('onCloseRequest');
-        expect(panel.isDisposed).to.equal(true);
+        expect(panel.isDisposed).toBe(true);
       });
     });
 
@@ -121,7 +117,7 @@ describe('console/panel', () => {
       describe('#constructor', () => {
         it('should create a new code console factory', () => {
           const factory = new ConsolePanel.ContentFactory({ editorFactory });
-          expect(factory).to.be.an.instanceof(ConsolePanel.ContentFactory);
+          expect(factory).toBeInstanceOf(ConsolePanel.ContentFactory);
         });
       });
 
@@ -133,7 +129,7 @@ describe('console/panel', () => {
             mimeTypeService,
             sessionContext: panel.sessionContext
           };
-          expect(contentFactory.createConsole(options)).to.be.an.instanceof(
+          expect(contentFactory.createConsole(options)).toBeInstanceOf(
             CodeConsole
           );
         });

+ 2 - 2
tests/test-console/src/utils.ts → packages/console/test/utils.ts

@@ -3,10 +3,10 @@
 
 import { editorServices } from '@jupyterlab/codemirror';
 
-import { CodeConsole, ConsolePanel } from '@jupyterlab/console';
-
 import { defaultRenderMime } from '@jupyterlab/testutils';
 
+import { CodeConsole, ConsolePanel } from '../src';
+
 export const editorFactory = editorServices.factoryService.newInlineEditor.bind(
   editorServices.factoryService
 );

+ 81 - 63
tests/test-console/src/widget.spec.ts → packages/console/test/widget.spec.ts

@@ -1,7 +1,7 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import { expect } from 'chai';
+import 'jest';
 
 import { Message, MessageLoop } from '@lumino/messaging';
 
@@ -9,7 +9,7 @@ import { Widget } from '@lumino/widgets';
 
 import { SessionContext } from '@jupyterlab/apputils';
 
-import { CodeConsole } from '@jupyterlab/console';
+import { CodeConsole } from '../src';
 
 import {
   CodeCell,
@@ -76,14 +76,16 @@ describe('console/widget', () => {
     describe('#constructor()', () => {
       it('should create a new console content widget', () => {
         Widget.attach(widget, document.body);
-        expect(widget).to.be.an.instanceof(CodeConsole);
-        expect(Array.from(widget.node.classList)).to.contain('jp-CodeConsole');
+        expect(widget).toBeInstanceOf(CodeConsole);
+        expect(Array.from(widget.node.classList)).toEqual(
+          expect.arrayContaining(['jp-CodeConsole'])
+        );
       });
     });
 
     describe('#cells', () => {
       it('should exist upon instantiation', () => {
-        expect(widget.cells).to.be.ok;
+        expect(widget.cells).toBeTruthy();
       });
 
       it('should reflect the contents of the widget', async () => {
@@ -91,9 +93,9 @@ describe('console/widget', () => {
         Widget.attach(widget, document.body);
         await (widget.sessionContext as SessionContext).initialize();
         await widget.execute(force);
-        expect(widget.cells.length).to.equal(1);
+        expect(widget.cells.length).toBe(1);
         widget.clear();
-        expect(widget.cells.length).to.equal(0);
+        expect(widget.cells.length).toBe(0);
       });
     });
 
@@ -107,14 +109,14 @@ describe('console/widget', () => {
         });
         await (widget.sessionContext as SessionContext).initialize();
         await widget.execute(force);
-        expect(called).to.be.an.instanceof(Date);
+        expect(called).toBeInstanceOf(Date);
       });
     });
 
     describe('#promptCell', () => {
       it('should be a code cell widget', () => {
         Widget.attach(widget, document.body);
-        expect(widget.promptCell).to.be.an.instanceof(CodeCell);
+        expect(widget.promptCell).toBeInstanceOf(CodeCell);
       });
 
       it('should be replaced after execution', async () => {
@@ -122,24 +124,24 @@ describe('console/widget', () => {
         Widget.attach(widget, document.body);
 
         const old = widget.promptCell;
-        expect(old).to.be.an.instanceof(CodeCell);
+        expect(old).toBeInstanceOf(CodeCell);
 
         await (widget.sessionContext as SessionContext).initialize();
         await widget.execute(force);
-        expect(widget.promptCell).to.be.an.instanceof(CodeCell);
-        expect(widget.promptCell).to.not.equal(old);
+        expect(widget.promptCell).toBeInstanceOf(CodeCell);
+        expect(widget.promptCell).not.toBe(old);
       });
     });
 
     describe('#session', () => {
       it('should be a client session object', () => {
-        expect(widget.sessionContext.sessionChanged).to.be.ok;
+        expect(widget.sessionContext.sessionChanged).toBeTruthy();
       });
     });
 
     describe('#contentFactory', () => {
       it('should be the content factory used by the widget', () => {
-        expect(widget.contentFactory).to.be.an.instanceof(
+        expect(widget.contentFactory).toBeInstanceOf(
           CodeConsole.ContentFactory
         );
       });
@@ -155,9 +157,9 @@ describe('console/widget', () => {
           rendermime
         }).initializeState();
         Widget.attach(widget, document.body);
-        expect(widget.cells.length).to.equal(0);
+        expect(widget.cells.length).toBe(0);
         widget.addCell(cell);
-        expect(widget.cells.length).to.equal(1);
+        expect(widget.cells.length).toBe(1);
       });
     });
 
@@ -167,27 +169,27 @@ describe('console/widget', () => {
         Widget.attach(widget, document.body);
         await (widget.sessionContext as SessionContext).initialize();
         await widget.execute(force);
-        expect(widget.cells.length).to.be.greaterThan(0);
+        expect(widget.cells.length).toBeGreaterThan(0);
         widget.clear();
-        expect(widget.cells.length).to.equal(0);
-        expect(widget.promptCell!.model.value.text).to.equal('');
+        expect(widget.cells.length).toBe(0);
+        expect(widget.promptCell!.model.value.text).toBe('');
       });
     });
 
     describe('#dispose()', () => {
       it('should dispose the content widget', () => {
         Widget.attach(widget, document.body);
-        expect(widget.isDisposed).to.equal(false);
+        expect(widget.isDisposed).toBe(false);
         widget.dispose();
-        expect(widget.isDisposed).to.equal(true);
+        expect(widget.isDisposed).toBe(true);
       });
 
       it('should be safe to dispose multiple times', () => {
         Widget.attach(widget, document.body);
-        expect(widget.isDisposed).to.equal(false);
+        expect(widget.isDisposed).toBe(false);
         widget.dispose();
         widget.dispose();
-        expect(widget.isDisposed).to.equal(true);
+        expect(widget.isDisposed).toBe(true);
       });
     });
 
@@ -195,10 +197,10 @@ describe('console/widget', () => {
       it('should execute contents of the prompt if forced', async () => {
         const force = true;
         Widget.attach(widget, document.body);
-        expect(widget.cells.length).to.equal(0);
+        expect(widget.cells.length).toBe(0);
         await (widget.sessionContext as SessionContext).initialize();
         await widget.execute(force);
-        expect(widget.cells.length).to.be.greaterThan(0);
+        expect(widget.cells.length).toBeGreaterThan(0);
       });
 
       it('should check if code is multiline and allow amending', async () => {
@@ -206,12 +208,12 @@ describe('console/widget', () => {
         const timeout = 9000;
         Widget.attach(widget, document.body);
         widget.promptCell!.model.value.text = 'for x in range(5):';
-        expect(widget.cells.length).to.equal(0);
+        expect(widget.cells.length).toBe(0);
         const session = widget.sessionContext as SessionContext;
         session.kernelPreference = { name: 'ipython' };
         await session.initialize();
         await widget.execute(force, timeout);
-        expect(widget.cells.length).to.equal(0);
+        expect(widget.cells.length).toBe(0);
       });
     });
 
@@ -219,9 +221,9 @@ describe('console/widget', () => {
       it('should add a code cell and execute it', async () => {
         const code = 'print("#inject()")';
         Widget.attach(widget, document.body);
-        expect(widget.cells.length).to.equal(0);
+        expect(widget.cells.length).toBe(0);
         await widget.inject(code);
-        expect(widget.cells.length).to.be.greaterThan(0);
+        expect(widget.cells.length).toBeGreaterThan(0);
       });
     });
 
@@ -230,9 +232,9 @@ describe('console/widget', () => {
         Widget.attach(widget, document.body);
 
         const model = widget.promptCell!.model;
-        expect(model.value.text).to.be.empty;
+        expect(model.value.text).toHaveLength(0);
         widget.insertLinebreak();
-        expect(model.value.text).to.equal('\n');
+        expect(model.value.text).toBe('\n');
       });
     });
 
@@ -242,58 +244,76 @@ describe('console/widget', () => {
         widget.promptCell!.model.value.text = 'foo';
 
         const serialized = widget.serialize();
-        expect(serialized).to.have.length(1);
-        expect(serialized[0].source).to.equal('foo');
+        expect(serialized).toHaveLength(1);
+        expect(serialized[0].source).toBe('foo');
       });
     });
 
     describe('#newPromptCell()', () => {
       it('should be called after attach, creating a prompt', () => {
-        expect(widget.promptCell).to.not.be.ok;
-        expect(widget.methods).to.not.contain('newPromptCell');
+        expect(widget.promptCell).toBeFalsy();
+        expect(widget.methods).toEqual(
+          expect.not.arrayContaining(['newPromptCell'])
+        );
         Widget.attach(widget, document.body);
-        expect(widget.methods).to.contain('newPromptCell');
-        expect(widget.promptCell).to.be.ok;
+        expect(widget.methods).toEqual(
+          expect.arrayContaining(['newPromptCell'])
+        );
+        expect(widget.promptCell).toBeTruthy();
       });
 
       it('should be called after execution, creating a prompt', async () => {
-        expect(widget.promptCell).to.not.be.ok;
-        expect(widget.methods).to.not.contain('newPromptCell');
+        expect(widget.promptCell).toBeFalsy();
+        expect(widget.methods).toEqual(
+          expect.not.arrayContaining(['newPromptCell'])
+        );
         Widget.attach(widget, document.body);
-        expect(widget.methods).to.contain('newPromptCell');
+        expect(widget.methods).toEqual(
+          expect.arrayContaining(['newPromptCell'])
+        );
 
         const old = widget.promptCell;
         const force = true;
-        expect(old).to.be.an.instanceof(CodeCell);
+        expect(old).toBeInstanceOf(CodeCell);
         widget.methods = [];
 
         await (widget.sessionContext as SessionContext).initialize();
         await widget.execute(force);
 
-        expect(widget.promptCell).to.be.an.instanceof(CodeCell);
-        expect(widget.promptCell).to.not.equal(old);
-        expect(widget.methods).to.contain('newPromptCell');
+        expect(widget.promptCell).toBeInstanceOf(CodeCell);
+        expect(widget.promptCell).not.toBe(old);
+        expect(widget.methods).toEqual(
+          expect.arrayContaining(['newPromptCell'])
+        );
       });
     });
 
     describe('#onActivateRequest()', () => {
       it('should focus the prompt editor', () => {
-        expect(widget.promptCell).to.not.be.ok;
-        expect(widget.methods).to.not.contain('onActivateRequest');
+        expect(widget.promptCell).toBeFalsy();
+        expect(widget.methods).toEqual(
+          expect.not.arrayContaining(['onActivateRequest'])
+        );
         Widget.attach(widget, document.body);
         MessageLoop.sendMessage(widget, Widget.Msg.ActivateRequest);
-        expect(widget.methods).to.contain('onActivateRequest');
-        expect(widget.promptCell!.editor.hasFocus()).to.equal(true);
+        expect(widget.methods).toEqual(
+          expect.arrayContaining(['onActivateRequest'])
+        );
+        expect(widget.promptCell!.editor.hasFocus()).toBe(true);
       });
     });
 
     describe('#onAfterAttach()', () => {
       it('should be called after attach, creating a prompt', () => {
-        expect(widget.promptCell).to.not.be.ok;
-        expect(widget.methods).to.not.contain('onAfterAttach');
+        expect(widget.promptCell).toBeFalsy();
+        expect(widget.methods).toEqual(
+          expect.not.arrayContaining(['onAfterAttach'])
+        );
         Widget.attach(widget, document.body);
-        expect(widget.methods).to.contain('onAfterAttach');
-        expect(widget.promptCell).to.be.ok;
+        expect(widget.methods).toEqual(
+          expect.arrayContaining(['onAfterAttach'])
+        );
+        expect(widget.promptCell).toBeTruthy();
       });
     });
 
@@ -301,7 +321,7 @@ describe('console/widget', () => {
       describe('#constructor', () => {
         it('should create a new ContentFactory', () => {
           const factory = new CodeConsole.ContentFactory({ editorFactory });
-          expect(factory).to.be.an.instanceof(CodeConsole.ContentFactory);
+          expect(factory).toBeInstanceOf(CodeConsole.ContentFactory);
         });
       });
 
@@ -313,7 +333,7 @@ describe('console/widget', () => {
             model,
             contentFactory
           });
-          expect(prompt).to.be.an.instanceof(CodeCell);
+          expect(prompt).toBeInstanceOf(CodeCell);
         });
       });
 
@@ -324,7 +344,7 @@ describe('console/widget', () => {
             model,
             contentFactory
           });
-          expect(prompt).to.be.an.instanceof(RawCell);
+          expect(prompt).toBeInstanceOf(RawCell);
         });
       });
     });
@@ -333,7 +353,7 @@ describe('console/widget', () => {
       describe('#constructor()', () => {
         it('should create a new model factory', () => {
           const factory = new CodeConsole.ModelFactory({});
-          expect(factory).to.be.an.instanceof(CodeConsole.ModelFactory);
+          expect(factory).toBeInstanceOf(CodeConsole.ModelFactory);
         });
 
         it('should accept a codeCellContentFactory', () => {
@@ -341,16 +361,14 @@ describe('console/widget', () => {
           const factory = new CodeConsole.ModelFactory({
             codeCellContentFactory
           });
-          expect(factory.codeCellContentFactory).to.equal(
-            codeCellContentFactory
-          );
+          expect(factory.codeCellContentFactory).toBe(codeCellContentFactory);
         });
       });
 
       describe('#codeCellContentFactory', () => {
         it('should be the code cell content factory used by the factory', () => {
           const factory = new CodeConsole.ModelFactory({});
-          expect(factory.codeCellContentFactory).to.equal(
+          expect(factory.codeCellContentFactory).toBe(
             CodeCellModel.defaultContentFactory
           );
         });
@@ -359,21 +377,21 @@ describe('console/widget', () => {
       describe('#createCodeCell()', () => {
         it('should create a code cell', () => {
           const factory = new CodeConsole.ModelFactory({});
-          expect(factory.createCodeCell({})).to.be.an.instanceof(CodeCellModel);
+          expect(factory.createCodeCell({})).toBeInstanceOf(CodeCellModel);
         });
       });
 
       describe('#createRawCell()', () => {
         it('should create a raw cell model', () => {
           const factory = new CodeConsole.ModelFactory({});
-          expect(factory.createRawCell({})).to.be.an.instanceof(RawCellModel);
+          expect(factory.createRawCell({})).toBeInstanceOf(RawCellModel);
         });
       });
     });
 
     describe('.defaultModelFactory', () => {
       it('should be a ModelFactory', () => {
-        expect(CodeConsole.defaultModelFactory).to.be.an.instanceof(
+        expect(CodeConsole.defaultModelFactory).toBeInstanceOf(
           CodeConsole.ModelFactory
         );
       });

+ 31 - 0
packages/console/tsconfig.test.json

@@ -0,0 +1,31 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "noImplicitAny": true,
+    "noEmitOnError": true,
+    "noUnusedLocals": true,
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "target": "es2015",
+    "outDir": "lib",
+    "lib": [
+      "es2015",
+      "es2015.collection",
+      "dom",
+      "es2015.iterable",
+      "es2017.object"
+    ],
+    "types": [],
+    "jsx": "react",
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "strictNullChecks": true,
+    "skipLibCheck": true
+  },
+  "include": ["src/*", "test/*"],
+  "references": [
+    {
+      "path": "../../testutils"
+    }
+  ]
+}

+ 2 - 1
scripts/ci_install.sh

@@ -16,7 +16,8 @@ mkdir ~/.jupyter
 # Install and enable the server extension
 pip install -q --upgrade pip
 pip --version
-pip install -e ".[test]"
+# Show a verbose install if the install fails, for debugging
+pip install -e ".[test]" || pip install -v -e ".[test]"
 jlpm versions
 jlpm config current
 jupyter serverextension enable --py jupyterlab

+ 10 - 4
scripts/ci_script.sh

@@ -23,8 +23,15 @@ if [[ $GROUP == js* ]]; then
         # extract the group name
         export PKG="${GROUP#*-}"
         jlpm run build:packages:scope --scope "@jupyterlab/$PKG"
-        jlpm run build:test:scope --scope "@jupyterlab/test-$PKG"
-        FORCE_COLOR=1 jlpm run test:scope --loglevel success --scope "@jupyterlab/test-$PKG"
+        here=$(pwd)
+        if [[ -d "${here}/tests/test-${GROUP}" ]];then
+            scope="@jupyterlab/test-${PKG}"
+        else
+            scope="@jupyterlab/${PKG}"
+            jlpm run build:testutils
+        fi
+        jlpm run build:test:scope --scope ${scope}
+        FORCE_COLOR=1 jlpm run test:scope --loglevel success --scope ${scope}
     else
         jlpm build:packages
         jlpm build:test
@@ -60,8 +67,7 @@ if [[ $GROUP == docs ]]; then
     # Remove internal sphinx files and use pytest-check-links on the generated html
     rm build/html/genindex.html
     rm build/html/search.html
-    # FIXME: re-enable pending https://github.com/minrk/pytest-check-links/pull/7
-    #py.test --check-links -k .html build/html || py.test --check-links -k .html --lf build/html
+    py.test --check-links -k .html build/html || py.test --check-links -k .html --lf build/html
 
     popd
 fi

+ 1 - 1
tests/karma.conf.js

@@ -11,7 +11,7 @@ module.exports = function(config) {
     client: {
       captureConsole: true,
       mocha: {
-        timeout: 30000, // 30 seconds - upped from 2 seconds
+        timeout: 120000, // 120 seconds - upped from 2 seconds
         retries: 3 // Allow for slow server on CI.
       }
     },

+ 2 - 2
tests/test-application/package.json

@@ -22,10 +22,10 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
     "simulate-event": "~1.4.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-apputils/package.json

@@ -22,11 +22,11 @@
     "@lumino/virtualdom": "^1.6.1",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
     "react": "~16.9.0",
     "simulate-event": "~1.4.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-cells/package.json

@@ -24,9 +24,9 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-codeeditor/package.json

@@ -19,10 +19,10 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
     "simulate-event": "~1.4.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-codemirror/package.json

@@ -17,10 +17,10 @@
     "@jupyterlab/testutils": "^2.1.0",
     "chai": "^4.2.0",
     "codemirror": "~5.49.2",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
     "simulate-event": "~1.4.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-completer/package.json

@@ -22,10 +22,10 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
     "simulate-event": "~1.4.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 0 - 1
tests/test-console/karma-cov.conf.js

@@ -1 +0,0 @@
-module.exports = require('../karma-cov.conf');

+ 0 - 1
tests/test-console/karma.conf.js

@@ -1 +0,0 @@
-module.exports = require('../karma.conf');

+ 0 - 41
tests/test-console/package.json

@@ -1,41 +0,0 @@
-{
-  "name": "@jupyterlab/test-console",
-  "version": "2.1.0",
-  "private": true,
-  "scripts": {
-    "build": "tsc -b",
-    "clean": "rimraf build && rimraf coverage",
-    "coverage": "python run-test.py --browsers=ChromeHeadless karma-cov.conf.js",
-    "test": "jlpm run test:firefox-headless",
-    "test:chrome": "python run-test.py --browsers=Chrome karma.conf.js",
-    "test:chrome-headless": "python run-test.py --browsers=ChromeHeadless karma.conf.js",
-    "test:debug": "python run-test.py  --browsers=Chrome --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000 karma.conf.js",
-    "test:firefox": "python run-test.py --browsers=Firefox karma.conf.js",
-    "test:firefox-headless": "python run-test.py --browsers=FirefoxHeadless karma.conf.js",
-    "test:ie": "python run-test.py  --browsers=IE karma.conf.js",
-    "watch": "tsc -b --watch",
-    "watch:src": "tsc -p src --watch"
-  },
-  "dependencies": {
-    "@jupyterlab/apputils": "^2.1.0",
-    "@jupyterlab/cells": "^2.1.0",
-    "@jupyterlab/codeeditor": "^2.1.0",
-    "@jupyterlab/codemirror": "^2.1.0",
-    "@jupyterlab/console": "^2.1.0",
-    "@jupyterlab/services": "^5.1.0",
-    "@jupyterlab/testutils": "^2.1.0",
-    "@lumino/coreutils": "^1.4.2",
-    "@lumino/messaging": "^1.3.3",
-    "@lumino/signaling": "^1.3.5",
-    "@lumino/widgets": "^1.11.1",
-    "chai": "^4.2.0"
-  },
-  "devDependencies": {
-    "@types/chai": "^4.2.7",
-    "karma": "^4.4.1",
-    "karma-chrome-launcher": "~3.1.0",
-    "puppeteer": "~2.0.0",
-    "rimraf": "~3.0.0",
-    "typescript": "~3.7.3"
-  }
-}

+ 0 - 10
tests/test-console/run-test.py

@@ -1,10 +0,0 @@
-# Copyright (c) Jupyter Development Team.
-# Distributed under the terms of the Modified BSD License.
-
-import os
-from jupyterlab.tests.test_app import run_karma
-
-HERE = os.path.realpath(os.path.dirname(__file__))
-
-if __name__ == '__main__':
-    run_karma(HERE)

+ 0 - 33
tests/test-console/tsconfig.json

@@ -1,33 +0,0 @@
-{
-  "extends": "../../tsconfigbase",
-  "compilerOptions": {
-    "outDir": "build",
-    "types": ["mocha"],
-    "composite": false,
-    "rootDir": "src"
-  },
-  "include": ["src/*"],
-  "references": [
-    {
-      "path": "../../packages/apputils"
-    },
-    {
-      "path": "../../packages/cells"
-    },
-    {
-      "path": "../../packages/codeeditor"
-    },
-    {
-      "path": "../../packages/codemirror"
-    },
-    {
-      "path": "../../packages/console"
-    },
-    {
-      "path": "../../packages/services"
-    },
-    {
-      "path": "../../testutils"
-    }
-  ]
-}

+ 2 - 2
tests/test-coreutils/package.json

@@ -16,9 +16,9 @@
     "@jupyterlab/testutils": "^2.1.0",
     "@lumino/signaling": "^1.3.5",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 1
tests/test-docmanager/tsconfig.json

@@ -4,7 +4,8 @@
     "outDir": "build",
     "types": ["mocha"],
     "composite": false,
-    "rootDir": "src"
+    "rootDir": "src",
+    "skipLibCheck": true
   },
   "include": ["src/*"],
   "references": [

+ 2 - 2
tests/test-docregistry/package.json

@@ -23,9 +23,9 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 1
tests/test-filebrowser/tsconfig.json

@@ -4,7 +4,8 @@
     "outDir": "build",
     "types": ["mocha"],
     "composite": false,
-    "rootDir": "src"
+    "rootDir": "src",
+    "skipLibCheck": true
   },
   "include": ["src/*"],
   "references": [

+ 2 - 2
tests/test-fileeditor/package.json

@@ -21,10 +21,10 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
     "simulate-event": "~1.4.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-imageviewer/package.json

@@ -20,9 +20,9 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-inspector/package.json

@@ -17,9 +17,9 @@
     "@lumino/signaling": "^1.3.5",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-logconsole/package.json

@@ -17,9 +17,9 @@
     "@jupyterlab/testutils": "^2.1.0",
     "@lumino/signaling": "^1.3.5",
     "@lumino/widgets": "^1.11.1",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-mainmenu/package.json

@@ -19,9 +19,9 @@
     "@lumino/commands": "^1.10.1",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-nbformat/package.json

@@ -15,9 +15,9 @@
     "@jupyterlab/nbformat": "^2.1.0",
     "@jupyterlab/testutils": "^2.1.0",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 1 - 0
tests/test-notebook/package.json

@@ -36,6 +36,7 @@
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",
+    "@types/mocha": "^7.0.2",
     "karma": "^4.4.1",
     "karma-chrome-launcher": "~3.1.0",
     "puppeteer": "~2.0.0",

+ 14 - 16
tests/test-notebook/src/actions.spec.ts

@@ -37,8 +37,6 @@ describe('@jupyterlab/notebook', () => {
     let ipySessionContext: ISessionContext;
 
     before(async function() {
-      // tslint:disable-next-line:no-invalid-this
-      this.timeout(100000);
       async function createContext(options?: Partial<SessionContext.IOptions>) {
         const context = await createSessionContext(options);
         await context.initialize();
@@ -549,7 +547,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(false);
         expect(cell.executionCount).to.be.null;
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should render all markdown cells on an error', async () => {
         const cell = widget.model!.contentFactory.createMarkdownCell({});
@@ -566,7 +564,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(false);
         expect(child.rendered).to.equal(true);
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(120000); // Allow for slower CI
+      });
     });
 
     describe('#runAndAdvance()', () => {
@@ -583,7 +581,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(true);
         expect(cell.model.outputs.length).to.be.above(0);
         expect(next.rendered).to.equal(true);
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should be a no-op if there is no model', async () => {
         widget.model = null;
@@ -604,7 +602,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(false);
         expect(widget.isSelected(widget.widgets[0])).to.equal(false);
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should change to command mode', async () => {
         widget.mode = 'edit';
@@ -664,7 +662,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(false);
         expect(cell.executionCount).to.be.null;
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should render all markdown cells on an error', async () => {
         widget.activeCell!.model.value.text = ERROR_INPUT;
@@ -683,7 +681,7 @@ describe('@jupyterlab/notebook', () => {
         expect(cell.rendered).to.equal(true);
         expect(widget.activeCellIndex).to.equal(2);
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(120000); // Allow for slower CI
+      });
     });
 
     describe('#runAndInsert()', () => {
@@ -763,7 +761,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(false);
         expect(cell.executionCount).to.be.null;
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should render all markdown cells on an error', async () => {
         widget.activeCell!.model.value.text = ERROR_INPUT;
@@ -782,7 +780,7 @@ describe('@jupyterlab/notebook', () => {
         expect(cell.rendered).to.equal(true);
         expect(widget.activeCellIndex).to.equal(2);
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(120000); // Allow for slower CI
+      });
     });
 
     describe('#runAll()', () => {
@@ -800,7 +798,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(true);
         expect(cell.model.outputs.length).to.be.above(0);
         expect(next.rendered).to.equal(true);
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should be a no-op if there is no model', async () => {
         widget.model = null;
@@ -813,7 +811,7 @@ describe('@jupyterlab/notebook', () => {
         const result = await NotebookActions.runAll(widget, sessionContext);
         expect(result).to.equal(true);
         expect(widget.mode).to.equal('command');
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should clear the existing selection', async () => {
         const next = widget.widgets[2];
@@ -821,12 +819,12 @@ describe('@jupyterlab/notebook', () => {
         const result = await NotebookActions.runAll(widget, sessionContext);
         expect(result).to.equal(true);
         expect(widget.isSelected(widget.widgets[2])).to.equal(false);
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should activate the last cell', async () => {
         await NotebookActions.runAll(widget, sessionContext);
         expect(widget.activeCellIndex).to.equal(widget.widgets.length - 1);
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should stop executing code cells on an error', async () => {
         widget.activeCell!.model.value.text = ERROR_INPUT;
@@ -837,7 +835,7 @@ describe('@jupyterlab/notebook', () => {
         expect(cell.executionCount).to.be.null;
         expect(widget.activeCellIndex).to.equal(widget.widgets.length - 1);
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(30000); // Allow for slower CI
+      });
 
       it('should render all markdown cells on an error', async () => {
         widget.activeCell!.model.value.text = ERROR_INPUT;
@@ -851,7 +849,7 @@ describe('@jupyterlab/notebook', () => {
         expect(result).to.equal(false);
         expect(cell.rendered).to.equal(true);
         await ipySessionContext.session!.kernel!.restart();
-      }).timeout(120000); // Allow for slower CI
+      });
     });
 
     describe('#selectAbove()', () => {

+ 2 - 4
tests/test-notebook/src/default-toolbar.spec.ts

@@ -232,8 +232,6 @@ describe('@jupyterlab/notebook', () => {
       let panel: NotebookPanel;
 
       beforeEach(async function() {
-        // tslint:disable-next-line:no-invalid-this
-        this.timeout(120000);
         context = await initNotebookContext({ startKernel: true });
         panel = NBTestUtils.createNotebookPanel(context);
         context.model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
@@ -272,7 +270,7 @@ describe('@jupyterlab/notebook', () => {
           await framePromise();
           simulate(button.node.firstChild as HTMLElement, 'mousedown');
           await p.promise;
-        }).timeout(30000); // Allow for slower CI
+        });
 
         it("should add an inline svg node with the 'run' icon", async () => {
           const button = ToolbarItems.createRunButton(panel);
@@ -314,7 +312,7 @@ describe('@jupyterlab/notebook', () => {
           simulate(button.node.firstChild as HTMLElement, 'mousedown');
           await acceptDialog();
           await p.promise;
-        }).timeout(30000); // Allow for slower CI
+        });
 
         it("should add an inline svg node with the 'fast-forward' icon", async () => {
           const button = ToolbarItems.createRestartRunAllButton(panel);

+ 2 - 1
tests/test-notebook/tsconfig.json

@@ -4,7 +4,8 @@
     "outDir": "build",
     "types": ["mocha"],
     "composite": false,
-    "rootDir": "src"
+    "rootDir": "src",
+    "skipLibCheck": true
   },
   "include": ["src/*"],
   "references": [

+ 2 - 2
tests/test-observables/package.json

@@ -17,9 +17,9 @@
     "@lumino/algorithm": "^1.2.3",
     "@lumino/coreutils": "^1.4.2",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-outputarea/package.json

@@ -20,9 +20,9 @@
     "@lumino/messaging": "^1.3.3",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 1
tests/test-rendermime/tsconfig.json

@@ -4,7 +4,8 @@
     "outDir": "build",
     "types": ["mocha", "node"],
     "composite": false,
-    "rootDir": "src"
+    "rootDir": "src",
+    "skipLibCheck": true
   },
   "include": ["src/*"],
   "references": [

+ 2 - 2
tests/test-services/package.json

@@ -19,11 +19,11 @@
     "@lumino/coreutils": "^1.4.2",
     "@lumino/signaling": "^1.3.5",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
     "node-fetch": "^2.6.0",
     "text-encoding": "^0.7.0",
-    "ts-jest": "^24.2.0",
+    "ts-jest": "^25.2.1",
     "ws": "^7.2.0"
   },
   "devDependencies": {

+ 2 - 2
tests/test-settingregistry/package.json

@@ -17,9 +17,9 @@
     "@jupyterlab/testutils": "^2.1.0",
     "@lumino/coreutils": "^1.4.2",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-statedb/package.json

@@ -18,9 +18,9 @@
     "@lumino/disposable": "^1.3.5",
     "@lumino/signaling": "^1.3.5",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 2
tests/test-statusbar/package.json

@@ -17,8 +17,8 @@
     "@lumino/signaling": "^1.3.5",
     "@lumino/widgets": "^1.11.1",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
-    "ts-jest": "^24.2.0"
+    "jest": "^25.2.3",
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 2 - 1
tests/test-terminal/tsconfig.json

@@ -4,7 +4,8 @@
     "outDir": "build",
     "types": ["mocha"],
     "composite": false,
-    "rootDir": "src"
+    "rootDir": "src",
+    "skipLibCheck": true
   },
   "include": ["src/*"],
   "references": [

+ 2 - 2
tests/test-ui-components/package.json

@@ -15,9 +15,9 @@
     "@jupyterlab/testutils": "^2.1.0",
     "@jupyterlab/ui-components": "^2.1.0",
     "chai": "^4.2.0",
-    "jest": "^24.9.0",
+    "jest": "^25.2.3",
     "jest-junit": "^10.0.0",
-    "ts-jest": "^24.2.0"
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.2.7",

+ 6 - 2
testutils/package.json

@@ -25,7 +25,7 @@
     "lib": "lib/"
   },
   "scripts": {
-    "build": "tsc",
+    "build": "tsc -b",
     "clean": "rimraf lib",
     "prepublishOnly": "npm run build",
     "watch": "tsc --watch"
@@ -41,16 +41,20 @@
     "@jupyterlab/rendermime": "^2.1.0",
     "@jupyterlab/services": "^5.1.0",
     "@lumino/coreutils": "^1.4.2",
+    "@lumino/properties": "^1.1.6",
     "@lumino/signaling": "^1.3.5",
     "fs-extra": "^8.1.0",
     "identity-obj-proxy": "^3.0.0",
+    "jest": "^25.2.3",
     "jest-raw-loader": "^1.0.1",
     "json-to-html": "~0.1.2",
     "node-fetch": "^2.6.0",
     "path": "~0.12.7",
-    "simulate-event": "~1.4.0"
+    "simulate-event": "~1.4.0",
+    "ts-jest": "^25.2.1"
   },
   "devDependencies": {
+    "@types/jest": "^24.0.23",
     "@types/node-fetch": "^2.5.4",
     "lighthouse": "5.6.0",
     "typescript": "~3.7.3"

+ 3 - 0
testutils/src/index.ts

@@ -20,6 +20,9 @@ import { INotebookModel, NotebookModelFactory } from '@jupyterlab/notebook';
 
 export { NBTestUtils } from './notebook-utils';
 
+import * as Mock from './mock';
+export { Mock };
+
 export { defaultRenderMime } from './rendermime';
 
 /**

+ 27 - 0
testutils/src/jest-config-new.ts

@@ -0,0 +1,27 @@
+import path = require('path');
+
+module.exports = function(name: string, baseDir: string) {
+  return {
+    preset: 'ts-jest/presets/js-with-babel',
+    moduleNameMapper: {
+      '\\.(css|less|sass|scss)$': 'identity-obj-proxy',
+      '\\.(gif|ttf|eot)$': '@jupyterlab/testutils/lib/jest-file-mock.js'
+    },
+    transform: {
+      '\\.svg$': 'jest-raw-loader'
+    },
+    setupFilesAfterEnv: ['@jupyterlab/testutils/lib/jest-script.js'],
+    setupFiles: ['@jupyterlab/testutils/lib/jest-shim.js'],
+    testPathIgnorePatterns: ['/lib/', '/node_modules/'],
+    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
+    reporters: ['default', 'jest-junit'],
+    coverageReporters: ['json', 'lcov', 'text', 'html'],
+    coverageDirectory: path.join(baseDir, 'coverage'),
+    testRegex: '/test/.*.spec.ts[x]?$',
+    globals: {
+      'ts-jest': {
+        tsConfig: `./tsconfig.test.json`
+      }
+    }
+  };
+};

+ 1 - 2
testutils/src/jest-script.ts

@@ -1,2 +1 @@
-declare var jest: any;
-jest.setTimeout(20000);
+// jest.setTimeout(20000);

+ 481 - 0
testutils/src/mock.ts

@@ -0,0 +1,481 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import 'jest';
+
+import { ISessionContext, SessionContext } from '@jupyterlab/apputils';
+
+import {
+  Kernel,
+  KernelMessage,
+  KernelSpec,
+  Session,
+  ServiceManager
+} from '@jupyterlab/services';
+
+import { AttachedProperty } from '@lumino/properties';
+
+import { UUID } from '@lumino/coreutils';
+
+import { Signal } from '@lumino/signaling';
+
+export const KERNELSPECS: KernelSpec.ISpecModel[] = [
+  {
+    argv: [
+      '/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
+      '-m',
+      'ipykernel_launcher',
+      '-f',
+      '{connection_file}'
+    ],
+    display_name: 'Python 3',
+    language: 'python',
+    metadata: {},
+    name: 'python3',
+    resources: {}
+  },
+  {
+    argv: [
+      '/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
+      '-m',
+      'ipykernel_launcher',
+      '-f',
+      '{connection_file}'
+    ],
+    display_name: 'R',
+    language: 'python',
+    metadata: {},
+    name: 'irkernel',
+    resources: {}
+  }
+];
+
+export const KERNEL_MODELS: Kernel.IModel[] = [
+  {
+    name: 'python3',
+    id: UUID.uuid4()
+  },
+  {
+    name: 'r',
+    id: UUID.uuid4()
+  },
+  {
+    name: 'python3',
+    id: UUID.uuid4()
+  }
+];
+
+// Notebook Paths for certain kernel name
+export const NOTEBOOK_PATHS: { [kernelName: string]: string[] } = {
+  python3: ['Untitled.ipynb', 'Untitled1.ipynb', 'Untitled2.ipynb'],
+  r: ['Visualization.ipynb', 'Analysis.ipynb', 'Conclusion.ipynb']
+};
+
+/**
+ * Forceably change the status of a session context.
+ * An iopub message is emitted for the change.
+ *
+ * @param sessionContext The session context of interest.
+ * @param newStatus The new kernel status.
+ */
+export function updateKernelStatus(
+  sessionContext: ISessionContext,
+  newStatus: KernelMessage.Status
+) {
+  const kernel = sessionContext.session!.kernel!;
+  (kernel as any).status = newStatus;
+  (sessionContext.statusChanged as any).emit(newStatus);
+  const msg = KernelMessage.createMessage({
+    session: kernel.clientId,
+    channel: 'iopub',
+    msgType: 'status',
+    content: { execution_state: newStatus }
+  });
+  emitIopubMessage(sessionContext, msg);
+}
+
+/**
+ * Emit an iopub message on a session context.
+ *
+ * @param sessionContext The session context
+ * @param msg Message created with `KernelMessage.createMessage`
+ */
+export function emitIopubMessage(
+  context: ISessionContext,
+  msg: KernelMessage.IIOPubMessage
+): void {
+  const kernel = context!.session!.kernel!;
+  const msgId = Private.lastMessageProperty.get(kernel);
+  (msg.parent_header as any).session = kernel.clientId;
+  (msg.parent_header as any).msg_id = msgId;
+  (kernel.iopubMessage as any).emit(msg);
+}
+
+/**
+ * Create a session context given a partial session model.
+ *
+ * @param model The session model to use.
+ */
+export function createSimpleSessionContext(
+  model: Private.RecursivePartial<Session.IModel> = {}
+): SessionContext {
+  const kernel = new KernelMock({ model: model?.kernel || {} });
+  const session = new SessionConnectionMock({ model }, kernel);
+  return new SessionContextMock({}, session);
+}
+
+/**
+ * Clone a kernel connection.
+ */
+export function cloneKernel(
+  kernel: Kernel.IKernelConnection
+): Kernel.IKernelConnection {
+  return (kernel as any).clone();
+}
+
+/**
+ * A mock kernel object.
+ *
+ * @param model The model of the kernel
+ */
+export const KernelMock = jest.fn<
+  Kernel.IKernelConnection,
+  [Private.RecursivePartial<Kernel.IKernelConnection.IOptions>]
+>(options => {
+  const model = options.model || {};
+  if (!model.id) {
+    (model! as any).id = 'foo';
+  }
+  if (!model.name) {
+    (model! as any).name = KERNEL_MODELS[0].name;
+  }
+  options = {
+    clientId: UUID.uuid4(),
+    username: UUID.uuid4(),
+    ...options,
+    model
+  };
+  let executionCount = 0;
+  const spec = Private.kernelSpecForKernelName(model!.name!)!;
+  const thisObject = {
+    ...jest.requireActual('@jupyterlab/services'),
+    ...options,
+    ...model,
+    status: 'idle',
+    spec: () => {
+      return Promise.resolve(spec);
+    },
+    dispose: jest.fn(),
+    clone: jest.fn(() => {
+      const newKernel = Private.cloneKernel(options);
+      newKernel.iopubMessage.connect((_, args) => {
+        iopubMessageSignal.emit(args);
+      });
+      newKernel.statusChanged.connect((_, args) => {
+        (thisObject as any).status = args;
+        statusChangedSignal.emit(args);
+      });
+      return newKernel;
+    }),
+    info: jest.fn(Promise.resolve),
+    shutdown: jest.fn(Promise.resolve),
+    requestHistory: jest.fn(() => {
+      const historyReply = KernelMessage.createMessage({
+        channel: 'shell',
+        msgType: 'history_reply',
+        session: options.clientId!,
+        username: options.username!,
+        content: {
+          history: [],
+          status: 'ok'
+        }
+      });
+      return Promise.resolve(historyReply);
+    }),
+    requestExecute: jest.fn(code => {
+      const msgId = UUID.uuid4();
+      executionCount++;
+      Private.lastMessageProperty.set(thisObject, msgId);
+      const msg = KernelMessage.createMessage({
+        channel: 'iopub',
+        msgType: 'execute_input',
+        session: thisObject.clientId,
+        username: thisObject.username,
+        msgId,
+        content: {
+          code,
+          execution_count: executionCount
+        }
+      });
+      iopubMessageSignal.emit(msg);
+      return new MockShellFuture();
+    })
+  };
+  // Add signals.
+  const iopubMessageSignal = new Signal<
+    Kernel.IKernelConnection,
+    KernelMessage.IIOPubMessage
+  >(thisObject);
+  const statusChangedSignal = new Signal<
+    Kernel.IKernelConnection,
+    Kernel.Status
+  >(thisObject);
+  (thisObject as any).statusChanged = statusChangedSignal;
+  (thisObject as any).iopubMessage = iopubMessageSignal;
+  return thisObject;
+});
+
+/**
+ * A mock session connection.
+ *
+ * @param options Addition session options to use
+ * @param model A session model to use
+ */
+export const SessionConnectionMock = jest.fn<
+  Session.ISessionConnection,
+  [
+    Private.RecursivePartial<Session.ISessionConnection.IOptions>,
+    Kernel.IKernelConnection | null
+  ]
+>((options, kernel) => {
+  const name = kernel?.name || options.model?.name || KERNEL_MODELS[0].name;
+  kernel = kernel || new KernelMock({ model: { name } });
+  const model = {
+    path: 'foo',
+    type: 'notebook',
+    name: 'foo',
+    ...options.model,
+    kernel: kernel!.model
+  };
+  const thisObject = {
+    ...jest.requireActual('@jupyterlab/services'),
+    id: UUID.uuid4(),
+    ...options,
+    model,
+    ...model,
+    kernel,
+    dispose: jest.fn(),
+    changeKernel: jest.fn(partialModel => {
+      return Private.changeKernel(kernel!, partialModel!);
+    }),
+    selectKernel: jest.fn(),
+    shutdown: jest.fn(() => {
+      return Promise.resolve();
+    })
+  };
+  const statusChangedSignal = new Signal<
+    Session.ISessionConnection,
+    Kernel.Status
+  >(thisObject);
+  const kernelChangedSignal = new Signal<
+    Session.ISessionConnection,
+    Session.ISessionConnection.IKernelChangedArgs
+  >(thisObject);
+  const iopubMessageSignal = new Signal<
+    Session.ISessionConnection,
+    KernelMessage.IIOPubMessage
+  >(thisObject);
+
+  kernel!.iopubMessage.connect((_, args) => {
+    iopubMessageSignal.emit(args);
+  }, thisObject);
+
+  kernel!.statusChanged.connect((_, args) => {
+    statusChangedSignal.emit(args);
+  }, thisObject);
+
+  (thisObject as any).statusChanged = statusChangedSignal;
+  (thisObject as any).kernelChanged = kernelChangedSignal;
+  (thisObject as any).iopubMessage = iopubMessageSignal;
+  return thisObject;
+});
+
+/**
+ * A mock session context.
+ *
+ * @param session The session connection object to use
+ */
+export const SessionContextMock = jest.fn<
+  SessionContext,
+  [Partial<SessionContext.IOptions>, Session.ISessionConnection | null]
+>((options, connection) => {
+  const session =
+    connection ||
+    new SessionConnectionMock(
+      {
+        model: {
+          path: options.path || '',
+          type: options.type || '',
+          name: options.name || ''
+        }
+      },
+      null
+    );
+  const thisObject = {
+    ...jest.requireActual('@jupyterlab/apputils'),
+    ...options,
+    path: session.path,
+    type: session.type,
+    name: session.name,
+    kernel: session.kernel,
+    session,
+    dispose: jest.fn(),
+    initialize: jest.fn(() => {
+      return Promise.resolve();
+    }),
+    ready: jest.fn(() => {
+      return Promise.resolve();
+    }),
+    changeKernel: jest.fn(partialModel => {
+      return Private.changeKernel(
+        session.kernel || Private.RUNNING_KERNELS_MOCKS[0],
+        partialModel!
+      );
+    }),
+    shutdown: jest.fn(() => {
+      return Promise.resolve();
+    })
+  };
+
+  const propertyChangedSignal = new Signal<
+    ISessionContext,
+    'path' | 'name' | 'type'
+  >(thisObject);
+
+  const statusChangedSignal = new Signal<ISessionContext, Kernel.Status>(
+    thisObject
+  );
+  const kernelChangedSignal = new Signal<
+    ISessionContext,
+    Session.ISessionConnection.IKernelChangedArgs
+  >(thisObject);
+
+  const iopubMessageSignal = new Signal<
+    ISessionContext,
+    KernelMessage.IIOPubMessage
+  >(thisObject);
+
+  session!.statusChanged.connect((_, args) => {
+    statusChangedSignal.emit(args);
+  }, thisObject);
+
+  session!.iopubMessage.connect((_, args) => {
+    iopubMessageSignal.emit(args);
+  });
+
+  session!.kernelChanged.connect((_, args) => {
+    kernelChangedSignal.emit(args);
+  });
+
+  (thisObject as any).statusChanged = statusChangedSignal;
+  (thisObject as any).kernelChanged = kernelChangedSignal;
+  (thisObject as any).iopubMessage = iopubMessageSignal;
+  (thisObject as any).propertyChanged = propertyChangedSignal;
+  (thisObject as any).session = session;
+
+  return thisObject;
+});
+
+/**
+ * A mock service manager.
+ */
+export const ServiceManagerMock = jest.fn<ServiceManager, []>(() => ({
+  ...jest.requireActual('@jupyterlab/services'),
+  ready: jest.fn(() => {
+    return Promise.resolve();
+  })
+}));
+
+/**
+ * A mock kernel shell future.
+ */
+export const MockShellFuture = jest.fn<Kernel.IShellFuture, []>(() => ({
+  ...jest.requireActual('@jupyterlab/services'),
+  done: jest.fn(() => {
+    return Promise.resolve();
+  })
+}));
+
+/**
+ * A namespace for module private data.
+ */
+namespace Private {
+  export function flattenArray<T>(arr: T[][]): T[] {
+    let result: T[] = [];
+
+    arr.forEach(innerArr => {
+      innerArr.forEach(elem => {
+        result.push(elem);
+      });
+    });
+
+    return result;
+  }
+
+  export type RecursivePartial<T> = {
+    [P in keyof T]?: RecursivePartial<T[P]>;
+  };
+
+  export function cloneKernel(
+    options: RecursivePartial<Kernel.IKernelConnection.IOptions>
+  ): Kernel.IKernelConnection {
+    return new KernelMock(options);
+  }
+
+  // Get the kernel spec for kernel name
+  export function kernelSpecForKernelName(name: string) {
+    return KERNELSPECS.find(val => {
+      return val.name === name;
+    });
+  }
+
+  export function changeKernel(
+    kernel: Kernel.IKernelConnection,
+    partialModel: Partial<Kernel.IModel>
+  ): Promise<Kernel.IModel> {
+    if (partialModel.id) {
+      let kernelIdx = KERNEL_MODELS.findIndex(model => {
+        return model.id === partialModel.id;
+      });
+      if (kernelIdx !== -1) {
+        (kernel.model as any) = RUNNING_KERNELS_MOCKS[kernelIdx].model;
+        (kernel.id as any) = partialModel.id;
+        return Promise.resolve(RUNNING_KERNELS_MOCKS[kernelIdx]);
+      } else {
+        throw new Error(
+          `Unable to change kernel to one with id: ${partialModel.id}`
+        );
+      }
+    } else if (partialModel.name) {
+      let kernelIdx = KERNEL_MODELS.findIndex(model => {
+        return model.name === partialModel.name;
+      });
+      if (kernelIdx !== -1) {
+        (kernel.model as any) = RUNNING_KERNELS_MOCKS[kernelIdx].model;
+        (kernel.id as any) = partialModel.id;
+        return Promise.resolve(RUNNING_KERNELS_MOCKS[kernelIdx]);
+      } else {
+        throw new Error(
+          `Unable to change kernel to one with name: ${partialModel.name}`
+        );
+      }
+    } else {
+      throw new Error(`Unable to change kernel`);
+    }
+  }
+
+  // This list of running kernels simply mirrors the KERNEL_MODELS and KERNELSPECS lists
+  export const RUNNING_KERNELS_MOCKS: Kernel.IKernelConnection[] = KERNEL_MODELS.map(
+    (model, _) => {
+      return new KernelMock({ model });
+    }
+  );
+
+  export const lastMessageProperty = new AttachedProperty<
+    Kernel.IKernelConnection,
+    string
+  >({
+    name: 'lastMessageId',
+    create: () => ''
+  });
+}

+ 1 - 2
testutils/tsconfig.json

@@ -3,8 +3,7 @@
   "compilerOptions": {
     "outDir": "lib",
     "rootDir": "src",
-    "module": "commonjs",
-    "types": ["node"]
+    "module": "commonjs"
   },
   "include": ["src/*"],
   "references": [

File diff suppressed because it is too large
+ 420 - 283
yarn.lock


Some files were not shown because too many files changed in this diff