Browse Source

wip update tests

Steven Silvester 7 years ago
parent
commit
8a9c295247

+ 10 - 3
packages/rendermime/src/rendermime.ts

@@ -88,14 +88,21 @@ class RenderMime {
    * [[preferredMimeType]].
    */
   createRenderer(mimeType: string, trusted: boolean): IRenderMime.IRendererWidget {
-    let rendererOptions = {
+    let factory = this._factories[mimeType];
+    if (!factory) {
+      throw new Error(`Unregistered mimeType: ${mimeType}`);
+    }
+    let options = {
       mimeType,
       trusted,
       resolver: this._resolver,
       sanitizer: this.sanitizer,
       linkHandler: this._handler
     };
-    return this._factories[mimeType].createRenderer(rendererOptions);
+    if (!factory.canCreateRenderer(options)) {
+      throw new Error('Cannot create renderer');
+    }
+    return factory.createRenderer(options);
   }
 
   /**
@@ -141,7 +148,7 @@ class RenderMime {
       linkHandler: this._handler
     });
     each(this._order, mimeType => {
-      rendermime.addFactory(this._factories[mimeType], mimeType);
+      rendermime.addFactory(this._factories[mimeType], mimeType, -1);
     });
     return rendermime;
   }

+ 88 - 89
test/src/index.ts

@@ -3,96 +3,95 @@
 
 import '@phosphor/widgets/style/index.css';
 
-import './application/layoutrestorer.spec';
-import './application/shell.spec';
-
-import './apputils/clientsession.spec';
-import './apputils/commandlinker.spec';
-import './apputils/dialog.spec';
-import './apputils/iframe.spec';
-import './apputils/instancetracker.spec';
-import './apputils/jsoneditor.spec';
-import './apputils/sanitizer.spec';
-import './apputils/statedb.spec';
-import './apputils/styling.spec';
-import './apputils/toolbar.spec';
-import './apputils/vdom.spec';
-
-import './cells/inputarea.spec';
-import './cells/model.spec';
-import './cells/widget.spec';
-
-import './codeeditor/editor.spec';
-import './codeeditor/widget.spec';
-
-import './codemirror/editor.spec';
-import './codemirror/factory.spec';
-
-import './completer/handler.spec';
-import './completer/model.spec';
-import './completer/widget.spec';
-
-import './console/foreign.spec';
-import './console/history.spec';
-import './console/panel.spec';
-import './console/widget.spec';
-
-import './coreutils/activitymonitor.spec';
-import './coreutils/modeldb.spec';
-import './coreutils/nbformat.spec';
-import './coreutils/observablejson.spec';
-import './coreutils/observablemap.spec';
-import './coreutils/observablestring.spec';
-import './coreutils/observablelist.spec';
-import './coreutils/pageconfig.spec';
-import './coreutils/path.spec';
-import './coreutils/settingregistry.spec';
-import './coreutils/time.spec';
-import './coreutils/undoablelist.spec';
-import './coreutils/url.spec';
-import './coreutils/uuid.spec';
-import './coreutils/markdowncodeblocks.spec';
-
-import './csvviewer/toolbar.spec';
-import './csvviewer/widget.spec';
-
-import './docmanager/manager.spec';
-import './docmanager/savehandler.spec';
-import './docmanager/widgetmanager.spec';
-
-import './docregistry/context.spec';
-import './docregistry/default.spec';
-import './docregistry/registry.spec';
-
-import './fileeditor/widget.spec';
-
-import './filebrowser/crumbs.spec';
-import './filebrowser/model.spec';
-
-import './imageviewer/widget.spec';
-
-import './inspector/inspector.spec';
-
-import './markdownviewer/widget.spec';
-
-import './notebook/actions.spec';
-import './notebook/celltools.spec';
-import './notebook/default-toolbar.spec';
-import './notebook/model.spec';
-import './notebook/modelfactory.spec';
-import './notebook/panel.spec';
-import './notebook/tracker.spec';
-import './notebook/widget.spec';
-import './notebook/widgetfactory.spec';
-
-import './outputarea/model.spec';
-import './outputarea/widget.spec';
-
-import './renderers/latex.spec';
-import './renderers/renderers.spec';
-
+// import './application/layoutrestorer.spec';
+// import './application/shell.spec';
+
+// import './apputils/clientsession.spec';
+// import './apputils/commandlinker.spec';
+// import './apputils/dialog.spec';
+// import './apputils/iframe.spec';
+// import './apputils/instancetracker.spec';
+// import './apputils/jsoneditor.spec';
+// import './apputils/sanitizer.spec';
+// import './apputils/statedb.spec';
+// import './apputils/styling.spec';
+// import './apputils/toolbar.spec';
+// import './apputils/vdom.spec';
+
+// import './cells/inputarea.spec';
+// import './cells/model.spec';
+// import './cells/widget.spec';
+
+// import './codeeditor/editor.spec';
+// import './codeeditor/widget.spec';
+
+// import './codemirror/editor.spec';
+// import './codemirror/factory.spec';
+
+// import './completer/handler.spec';
+// import './completer/model.spec';
+// import './completer/widget.spec';
+
+// import './console/foreign.spec';
+// import './console/history.spec';
+// import './console/panel.spec';
+// import './console/widget.spec';
+
+// import './coreutils/activitymonitor.spec';
+// import './coreutils/modeldb.spec';
+// import './coreutils/nbformat.spec';
+// import './coreutils/observablejson.spec';
+// import './coreutils/observablemap.spec';
+// import './coreutils/observablestring.spec';
+// import './coreutils/observablelist.spec';
+// import './coreutils/pageconfig.spec';
+// import './coreutils/path.spec';
+// import './coreutils/settingregistry.spec';
+// import './coreutils/time.spec';
+// import './coreutils/undoablelist.spec';
+// import './coreutils/url.spec';
+// import './coreutils/uuid.spec';
+// import './coreutils/markdowncodeblocks.spec';
+
+// import './csvviewer/toolbar.spec';
+// import './csvviewer/widget.spec';
+
+// import './docmanager/manager.spec';
+// import './docmanager/savehandler.spec';
+// import './docmanager/widgetmanager.spec';
+
+// import './docregistry/context.spec';
+// import './docregistry/default.spec';
+// import './docregistry/registry.spec';
+
+// import './fileeditor/widget.spec';
+
+// import './filebrowser/crumbs.spec';
+// import './filebrowser/model.spec';
+
+// import './imageviewer/widget.spec';
+
+// import './inspector/inspector.spec';
+
+// import './markdownviewer/widget.spec';
+
+// import './notebook/actions.spec';
+// import './notebook/celltools.spec';
+// import './notebook/default-toolbar.spec';
+// import './notebook/model.spec';
+// import './notebook/modelfactory.spec';
+// import './notebook/panel.spec';
+// import './notebook/tracker.spec';
+// import './notebook/widget.spec';
+// import './notebook/widgetfactory.spec';
+
+// import './outputarea/model.spec';
+// import './outputarea/widget.spec';
+
+import './rendermime/latex.spec';
+import './rendermime/factories.spec';
 import './rendermime/mimemodel.spec';
 import './rendermime/outputmodel.spec';
 import './rendermime/rendermime.spec';
 
-import './terminal/terminal.spec';
+// import './terminal/terminal.spec';

+ 0 - 447
test/src/renderers/renderers.spec.ts

@@ -1,447 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-
-import expect = require('expect.js');
-
-import {
-  JSONObject, JSONValue
-} from '@phosphor/coreutils';
-
-import {
-  Widget
-} from '@phosphor/widgets';
-
-import {
-  defaultSanitizer
-} from '@jupyterlab/apputils';
-
-import {
-  LatexRenderer, PDFRenderer, JavaScriptRenderer,
-  SVGRenderer, MarkdownRenderer, TextRenderer, HTMLRenderer, ImageRenderer
-} from '@jupyterlab/rendermime';
-
-import {
-  MimeModel, IRenderMime
-} from '@jupyterlab/rendermime';
-
-
-function runCanRunder(renderer: IRenderMime.IRenderer, trusted: boolean): boolean {
-  let canRender = true;
-  let data: JSONObject = Object.create(null);
-  for (let mimeType in renderer.mimeTypes) {
-    data[mimeType] = 'test';
-  }
-  data['fizz/buzz'] = 'test';
-  let model = new MimeModel({ data, trusted });
-
-  for (let mimeType of renderer.mimeTypes) {
-    let options = { model, mimeType, sanitizer };
-    if (!renderer.canRender(options)) {
-      canRender = false;
-    }
-  }
-  let options = { model, mimeType: 'fizz/buzz', sanitizer };
-  expect(renderer.canRender(options)).to.be(false);
-  return canRender;
-}
-
-
-function createModel(mimeType: string, source: JSONValue, trusted=false): IRenderMime.IMimeModel {
-  let data: JSONObject = {};
-  data[mimeType] = source;
-  return new MimeModel({ data, trusted });
-}
-
-const sanitizer = defaultSanitizer;
-
-
-describe('renderers', () => {
-
-  describe('TextRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should have text related mimeTypes', () => {
-        let mimeTypes = ['text/plain', 'application/vnd.jupyter.stdout',
-               'application/vnd.jupyter.stderr'];
-        let t = new TextRenderer();
-        expect(t.mimeTypes).to.eql(mimeTypes);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted and untrusted text data', () => {
-        let r = new TextRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(true);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should output the correct HTML', () => {
-        let t = new TextRenderer();
-        let mimeType = 'text/plain';
-        let model = createModel(mimeType, 'x = 2 ** a');
-        let widget = t.render({ mimeType, model, sanitizer });
-        expect(widget.node.innerHTML).to.be('<pre>x = 2 ** a</pre>');
-      });
-
-      it('should output the correct HTML with ansi colors', () => {
-        let t = new TextRenderer();
-        let source = 'There is no text but \x1b[01;41;32mtext\x1b[00m.\nWoo.';
-        let mimeType = 'application/vnd.jupyter.console-text';
-        let model = createModel(mimeType, source);
-        let widget = t.render({ mimeType, model, sanitizer });
-        expect(widget.node.innerHTML).to.be('<pre>There is no text but <span class="ansi-bright-green-fg ansi-red-bg">text</span>.\nWoo.</pre>');
-      });
-
-      it('should escape inline html', () => {
-        let t = new TextRenderer();
-        let source = 'There is no text <script>window.x=1</script> but \x1b[01;41;32mtext\x1b[00m.\nWoo.';
-        let mimeType = 'application/vnd.jupyter.console-text';
-        let model = createModel(mimeType, source);
-        let widget = t.render({ mimeType, model, sanitizer });
-        expect(widget.node.innerHTML).to.be('<pre>There is no text &lt;script&gt;window.x=1&lt;/script&gt; but <span class="ansi-bright-green-fg ansi-red-bg">text</span>.\nWoo.</pre>');
-      });
-
-    });
-
-  });
-
-  describe('LatexRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should have the text/latex mimeType', () => {
-        let t = new LatexRenderer();
-        expect(t.mimeTypes).to.eql(['text/latex']);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted and untrusted latex data', () => {
-        let r = new LatexRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(true);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should set the textContent of the widget', () => {
-        let source = '\sum\limits_{i=0}^{\infty} \frac{1}{n^2}';
-        let t = new LatexRenderer();
-        let mimeType = 'text/latex';
-        let model = createModel(mimeType, source);
-        let widget = t.render({ mimeType, model, sanitizer });
-        expect(widget.node.textContent).to.be(source);
-      });
-
-    });
-
-  });
-
-  describe('PDFRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should have the application/pdf mimeType', () => {
-        let t = new PDFRenderer();
-        expect(t.mimeTypes).to.eql(['application/pdf']);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted pdf data', () => {
-        let r = new PDFRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(false);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should render the correct HTML', () => {
-        let source = 'test';
-        let t = new PDFRenderer();
-        let mimeType = 'application/pdf';
-        let model = createModel(mimeType, source);
-        let w = t.render({ mimeType, model, sanitizer });
-        let node = w.node.firstChild as HTMLAnchorElement;
-        expect(node.localName).to.be('a');
-        expect(node.target).to.be('_blank');
-        expect(node.href).to.be('data:application/pdf;base64,test');
-      });
-
-    });
-
-  });
-
-  describe('JavaScriptRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should have the text/javascript mimeType', () => {
-        let mimeTypes = ['text/javascript', 'application/javascript'];
-        let t = new JavaScriptRenderer();
-        expect(t.mimeTypes).to.eql(mimeTypes);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted JavaScript data', () => {
-        let r = new JavaScriptRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(false);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should create a script tag', () => {
-        let t = new JavaScriptRenderer();
-        let source = 'window.x = 1';
-        let mimeType = 'text/javascript';
-        let model = createModel(mimeType, source);
-        let w = t.render({ mimeType, model, sanitizer });
-        let el = w.node.firstChild as HTMLElement;
-        expect(el.localName).to.be('script');
-        expect(el.textContent).to.be(source);
-
-        // Ensure script has not been run yet
-        expect((window as any).x).to.be(void 0);
-        // Put it on the DOM
-        Widget.attach(w, document.body);
-        // Should be evaluated now
-        expect((window as any).x).to.be(1);
-        w.dispose();
-      });
-
-    });
-
-  });
-
-  describe('SVGRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should have the image/svg+xml mimeType', () => {
-        let t = new SVGRenderer();
-        expect(t.mimeTypes).to.eql(['image/svg+xml']);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted SVG data', () => {
-        let r = new SVGRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(false);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should create an svg tag', () => {
-        const source = '<svg></svg>';
-        let t = new SVGRenderer();
-        let mimeType = 'image/svg+xml';
-        let model = createModel(mimeType, source);
-        let w = t.render({ mimeType, model, sanitizer });
-        let svgEl = w.node.getElementsByTagName('svg')[0];
-        expect(svgEl).to.be.ok();
-      });
-
-    });
-
-  });
-
-  describe('MarkdownRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should have the text/markdown mimeType', function() {
-        let t = new MarkdownRenderer();
-        expect(t.mimeTypes).to.eql(['text/markdown']);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted and untrusted markdown data', () => {
-        let r = new MarkdownRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(true);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should set the inner html', () => {
-        let r = new MarkdownRenderer();
-        let source = '<p>hello</p>';
-        let mimeType = 'text/markdown';
-        let model = createModel(mimeType, source);
-        let widget = r.render({ mimeType, model, sanitizer });
-        return widget.ready.then(() => {
-          expect(widget.node.innerHTML).to.be(source);
-        });
-      });
-
-      it('should add header anchors', () => {
-        let source = require('../../../examples/filebrowser/sample.md') as string;
-        let r = new MarkdownRenderer();
-        let mimeType = 'text/markdown';
-        let model = createModel(mimeType, source);
-        let widget = r.render({ mimeType, model, sanitizer });
-        return widget.ready.then(() => {
-          Widget.attach(widget, document.body);
-          let node = document.getElementById('Title-third-level');
-          expect(node.localName).to.be('h3');
-          let anchor = node.firstChild.nextSibling as HTMLAnchorElement;
-          expect(anchor.href).to.contain('#Title-third-level');
-          expect(anchor.target).to.be('_self');
-          expect(anchor.className).to.contain('jp-InternalAnchorLink');
-          expect(anchor.textContent).to.be('¶');
-          Widget.detach(widget);
-        });
-      });
-
-      it('should sanitize the html', () => {
-        let r = new MarkdownRenderer();
-        let source = '<p>hello</p><script>alert("foo")</script>';
-        let mimeType = 'text/markdown';
-        let model = createModel(mimeType, source);
-        let widget = r.render({ mimeType, model, sanitizer });
-        return widget.ready.then(() => {
-          expect(widget.node.innerHTML).to.not.contain('script');
-        });
-      });
-
-    });
-
-  });
-
-  describe('HTMLRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should have the text/html mimeType', () => {
-        let t = new HTMLRenderer();
-        expect(t.mimeTypes).to.eql(['text/html']);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted and untrusted html data', () => {
-        let r = new HTMLRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(true);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should set the inner HTML', () => {
-        let r = new HTMLRenderer();
-        const source = '<h1>This is great</h1>';
-        let mimeType = 'text/html';
-        let model = createModel(mimeType, source);
-        let w = r.render({ mimeType, model, sanitizer });
-        expect(w.node.innerHTML).to.be('<h1>This is great</h1>');
-      });
-
-      it('should execute a script tag when attached', () => {
-        const source = '<script>window.y=3;</script>';
-        let r = new HTMLRenderer();
-        let mimeType = 'text/html';
-        let model = createModel(mimeType, source, true);
-        let w = r.render({ mimeType, model, sanitizer });
-        expect((window as any).y).to.be(void 0);
-        Widget.attach(w, document.body);
-        expect((window as any).y).to.be(3);
-        w.dispose();
-      });
-
-      it('should sanitize when untrusted', () => {
-        const source = '<pre><script>window.y=3;</script></pre>';
-        let r = new HTMLRenderer();
-        let mimeType = 'text/html';
-        let model = createModel(mimeType, source);
-        let w = r.render({ mimeType, model, sanitizer });
-        expect(w.node.innerHTML).to.be('<pre></pre>');
-      });
-
-    });
-
-  });
-
-  describe('ImageRenderer', () => {
-
-    describe('#mimeTypes', () => {
-
-      it('should support multiple mimeTypes', () => {
-        let t = new ImageRenderer();
-        expect(t.mimeTypes).to.eql(['image/png', 'image/jpeg', 'image/gif']);
-      });
-
-    });
-
-    describe('#canRender()', () => {
-
-      it('should be able to render trusted and untrusted image data', () => {
-        let r = new ImageRenderer();
-        expect(runCanRunder(r, true)).to.be(true);
-        expect(runCanRunder(r, false)).to.be(true);
-      });
-
-    });
-
-    describe('#render()', () => {
-
-      it('should create an <img> with the right mimeType', () => {
-        let source = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
-        let r = new ImageRenderer();
-        let mimeType = 'image/png';
-        let model = createModel(mimeType, source);
-        let w = r.render({ mimeType, model, sanitizer });
-        let el = w.node.firstChild as HTMLImageElement;
-        expect(el.src).to.be('data:image/png;base64,' + source);
-        expect(el.localName).to.be('img');
-        expect(el.innerHTML).to.be('');
-
-        source = 'R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=';
-        mimeType = 'image/gif';
-        model = createModel(mimeType, source);
-        w = r.render({ mimeType, model, sanitizer });
-        el = w.node.firstChild as HTMLImageElement;
-        expect(el.src).to.be('data:image/gif;base64,' + source);
-        expect(el.localName).to.be('img');
-        expect(el.innerHTML).to.be('');
-      });
-
-    });
-
-  });
-
-});

+ 491 - 0
test/src/rendermime/factories.spec.ts

@@ -0,0 +1,491 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import expect = require('expect.js');
+
+import {
+  JSONObject, JSONValue
+} from '@phosphor/coreutils';
+
+import {
+  Widget
+} from '@phosphor/widgets';
+
+import {
+  defaultSanitizer
+} from '@jupyterlab/apputils';
+
+import {
+  LatexRendererFactory, PDFRendererFactory, JavaScriptRendererFactory,
+  SVGRendererFactory, MarkdownRendererFactory, TextRendererFactory, HTMLRendererFactory, ImageRendererFactory
+} from '@jupyterlab/rendermime';
+
+import {
+  MimeModel, IRenderMime
+} from '@jupyterlab/rendermime';
+
+
+function runCanCreateRenderer(renderer: IRenderMime.IRendererFactory, trusted: boolean): boolean {
+  let canCreateRenderer = true;
+
+  for (let mimeType of renderer.mimeTypes) {
+    let options = { mimeType, sanitizer, trusted };
+    if (!renderer.canCreateRenderer(options)) {
+      canCreateRenderer = false;
+    }
+  }
+  let options = { trusted, mimeType: 'fizz/buzz', sanitizer };
+  expect(renderer.canCreateRenderer(options)).to.be(false);
+  return canCreateRenderer;
+}
+
+
+function createModel(mimeType: string, source: JSONValue): IRenderMime.IMimeModel {
+  let data: JSONObject = {};
+  data[mimeType] = source;
+  return new MimeModel({ data });
+}
+
+const sanitizer = defaultSanitizer;
+
+
+describe('rendermime/factories', () => {
+
+  describe('TextRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should have text related mimeTypes', () => {
+        let mimeTypes = ['text/plain', 'application/vnd.jupyter.stdout',
+               'application/vnd.jupyter.stderr'];
+        let f = new TextRendererFactory();
+        expect(f.mimeTypes).to.eql(mimeTypes);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted and untrusted text data', () => {
+        let f = new TextRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(true);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should output the correct HTML', () => {
+        let f = new TextRendererFactory();
+        let mimeType = 'text/plain';
+        let model = createModel(mimeType, 'x = 2 ** a');
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          expect(w.node.innerHTML).to.be('<pre>x = 2 ** a</pre>');
+        });
+      });
+
+      it('should output the correct HTML with ansi colors', () => {
+        let f = new TextRendererFactory();
+        let source = 'There is no text but \x1b[01;41;32mtext\x1b[00m.\nWoo.';
+        let mimeType = 'application/vnd.jupyter.console-text';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          expect(w.node.innerHTML).to.be('<pre>There is no text but <span class="ansi-bright-green-fg ansi-red-bg">text</span>.\nWoo.</pre>');
+        });
+      });
+
+      it('should escape inline html', () => {
+        let f = new TextRendererFactory();
+        let source = 'There is no text <script>window.x=1</script> but \x1b[01;41;32mtext\x1b[00m.\nWoo.';
+        let mimeType = 'application/vnd.jupyter.console-text';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          expect(w.node.innerHTML).to.be('<pre>There is no text &lt;script&gt;window.x=1&lt;/script&gt; but <span class="ansi-bright-green-fg ansi-red-bg">text</span>.\nWoo.</pre>');
+        });
+      });
+
+    });
+
+  });
+
+  describe('LatexRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should have the text/latex mimeType', () => {
+        let t = new LatexRendererFactory();
+        expect(t.mimeTypes).to.eql(['text/latex']);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted and untrusted latex data', () => {
+        let f = new LatexRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(true);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should set the textContent of the widget', () => {
+        let source = '\sum\limits_{i=0}^{\infty} \frac{1}{n^2}';
+        let f = new LatexRendererFactory();
+        let mimeType = 'text/latex';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          expect(w.node.textContent).to.be(source);
+        });
+      });
+
+    });
+
+  });
+
+  describe('PDFRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should have the application/pdf mimeType', () => {
+        let f = new PDFRendererFactory();
+        expect(f.mimeTypes).to.eql(['application/pdf']);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted pdf data', () => {
+        let f = new PDFRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(false);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should render the correct HTML', () => {
+        let source = 'test';
+        let f = new PDFRendererFactory();
+        let mimeType = 'application/pdf';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          let node = w.node.firstChild as HTMLAnchorElement;
+          expect(node.localName).to.be('a');
+          expect(node.target).to.be('_blank');
+          expect(node.href).to.be('data:application/pdf;base64,test');
+        });
+      });
+
+    });
+
+  });
+
+  describe('JavaScriptRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should have the text/javascript mimeType', () => {
+        let mimeTypes = ['text/javascript', 'application/javascript'];
+        let f = new JavaScriptRendererFactory();
+        expect(f.mimeTypes).to.eql(mimeTypes);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted JavaScript data', () => {
+        let f = new JavaScriptRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(false);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should create a script tag', () => {
+        let f = new JavaScriptRendererFactory();
+        let source = 'window.x = 1';
+        let mimeType = 'text/javascript';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          let el = w.node.firstChild as HTMLElement;
+          expect(el.localName).to.be('script');
+          expect(el.textContent).to.be(source);
+
+          // Ensure script has not been run yet
+          expect((window as any).x).to.be(void 0);
+          // Put it on the DOM
+          Widget.attach(w, document.body);
+          // Should be evaluated now
+          expect((window as any).x).to.be(1);
+          w.dispose();
+        });
+      });
+
+    });
+
+  });
+
+  describe('SVGRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should have the image/svg+xml mimeType', () => {
+        let f = new SVGRendererFactory();
+        expect(f.mimeTypes).to.eql(['image/svg+xml']);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted SVG data', () => {
+        let f = new SVGRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(false);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should create an svg tag', () => {
+        const source = '<svg></svg>';
+        let f = new SVGRendererFactory();
+        let mimeType = 'image/svg+xml';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          let svgEl = w.node.getElementsByTagName('svg')[0];
+          expect(svgEl).to.be.ok();
+        });
+      });
+
+    });
+
+  });
+
+  describe('MarkdownRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should have the text/markdown mimeType', function() {
+        let f = new MarkdownRendererFactory();
+        expect(f.mimeTypes).to.eql(['text/markdown']);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted and untrusted markdown data', () => {
+        let f = new MarkdownRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(true);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should set the inner html', () => {
+        let f = new MarkdownRendererFactory();
+        let source = '<p>hello</p>';
+        let mimeType = 'text/markdown';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          expect(w.node.innerHTML).to.be(source);
+        });
+      });
+
+      it('should add header anchors', () => {
+        let source = require('../../../examples/filebrowser/sample.md') as string;
+        let f = new MarkdownRendererFactory();
+        let mimeType = 'text/markdown';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          Widget.attach(w, document.body);
+          let node = document.getElementById('Title-third-level');
+          expect(node.localName).to.be('h3');
+          let anchor = node.firstChild.nextSibling as HTMLAnchorElement;
+          expect(anchor.href).to.contain('#Title-third-level');
+          expect(anchor.target).to.be('_self');
+          expect(anchor.className).to.contain('jp-InternalAnchorLink');
+          expect(anchor.textContent).to.be('¶');
+          Widget.detach(w);
+        });
+      });
+
+      it('should sanitize the html', () => {
+        let f = new MarkdownRendererFactory();
+        let source = '<p>hello</p><script>alert("foo")</script>';
+        let mimeType = 'text/markdown';
+        let model = createModel(mimeType, source);
+        let trusted = false;
+        let w = f.createRenderer({ mimeType, trusted, sanitizer });
+        return w.render(model).then(() => {
+          expect(w.node.innerHTML).to.not.contain('script');
+        });
+      });
+
+    });
+
+  });
+
+  describe('HTMLRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should have the text/html mimeType', () => {
+        let f = new HTMLRendererFactory();
+        expect(f.mimeTypes).to.eql(['text/html']);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted and untrusted html data', () => {
+        let f = new HTMLRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(true);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should set the inner HTML', () => {
+        let f = new HTMLRendererFactory();
+        const source = '<h1>This is great</h1>';
+        let mimeType = 'text/html';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          expect(w.node.innerHTML).to.be('<h1>This is great</h1>');
+        });
+      });
+
+      it('should execute a script tag when attached', () => {
+        const source = '<script>window.y=3;</script>';
+        let f = new HTMLRendererFactory();
+        let mimeType = 'text/html';
+        let model = createModel(mimeType, source);
+        let trusted = true;
+        let w = f.createRenderer({ mimeType, sanitizer, trusted });
+        return w.render(model).then(() => {
+          expect((window as any).y).to.be(void 0);
+          Widget.attach(w, document.body);
+          expect((window as any).y).to.be(3);
+          w.dispose();
+        });
+      });
+
+      it('should sanitize when untrusted', () => {
+        const source = '<pre><script>window.y=3;</script></pre>';
+        let f = new HTMLRendererFactory();
+        let mimeType = 'text/html';
+        let model = createModel(mimeType, source);
+        let trusted = false;
+        let w = f.createRenderer({ mimeType, trusted, sanitizer });
+        return w.render(model).then(() => {
+          expect(w.node.innerHTML).to.be('<pre></pre>');
+        });
+      });
+
+    });
+
+    it('should sanitize html', () => {
+      let model = createModel('text/html', '<h1>foo <script>window.x=1></scrip></h1>');
+      let f = new HTMLRendererFactory();
+      let trusted = false;
+      let mimeType = 'text/html';
+      let w = f.createRenderer({ mimeType, sanitizer, trusted });
+      return w.render(model).then(() => {
+        expect(w.node.innerHTML).to.be('<h1>foo </h1>');
+      });
+    });
+
+  });
+
+  describe('ImageRendererFactory', () => {
+
+    describe('#mimeTypes', () => {
+
+      it('should support multiple mimeTypes', () => {
+        let f = new ImageRendererFactory();
+        expect(f.mimeTypes).to.eql(['image/png', 'image/jpeg', 'image/gif']);
+      });
+
+    });
+
+    describe('#canCreateRenderer()', () => {
+
+      it('should be able to render trusted and untrusted image data', () => {
+        let f = new ImageRendererFactory();
+        expect(runCanCreateRenderer(f, true)).to.be(true);
+        expect(runCanCreateRenderer(f, false)).to.be(true);
+      });
+
+    });
+
+    describe('#createRenderer()', () => {
+
+      it('should create an <img> with the right mimeType', () => {
+        let source = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
+        let f = new ImageRendererFactory();
+        let mimeType = 'image/png';
+        let model = createModel(mimeType, source);
+        let w = f.createRenderer({ mimeType, sanitizer, trusted: true });
+        let el = w.node.firstChild as HTMLImageElement;
+
+        return w.render(model).then(() => {
+          expect(el.src).to.be('data:image/png;base64,' + source);
+          expect(el.localName).to.be('img');
+          expect(el.innerHTML).to.be('');
+
+          source = 'R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=';
+          mimeType = 'image/gif';
+          model = createModel(mimeType, source);
+          let trusted = true;
+          w = f.createRenderer({ mimeType, sanitizer, trusted });
+          return w.render(model);
+        }).then(() => {
+          el = w.node.firstChild as HTMLImageElement;
+          expect(el.src).to.be('data:image/gif;base64,' + source);
+          expect(el.localName).to.be('img');
+          expect(el.innerHTML).to.be('');
+        });
+      });
+
+    });
+
+  });
+
+});

+ 0 - 0
test/src/renderers/latex.spec.ts → test/src/rendermime/latex.spec.ts


+ 48 - 93
test/src/rendermime/rendermime.spec.ts

@@ -20,7 +20,7 @@ import {
 } from '@phosphor/widgets';
 
 import {
-  TextRenderer
+  TextRendererFactory
 } from '@jupyterlab/rendermime';
 
 import {
@@ -42,13 +42,13 @@ function createModel(data: JSONObject, trusted=false): IRenderMime.IMimeModel {
 
 describe('rendermime/index', () => {
 
-  let r: IRenderMime;
+  let r: RenderMime;
 
   beforeEach(() => {
     r = defaultRenderMime();
   });
 
-  describe('IRenderMime', () => {
+  describe('RenderMime', () => {
 
     describe('#constructor()', () => {
 
@@ -81,86 +81,40 @@ describe('rendermime/index', () => {
 
     });
 
-    describe('#mimeTypes()', () => {
+    describe('#mimeTypes', () => {
 
       it('should get an iterator over the ordered list of mimeTypes', () => {
-        let mimeTypes = r.mimeTypes();
+        let mimeTypes = r.mimeTypes;
         expect(toArray(mimeTypes).length).to.be.above(0);
       });
 
     });
 
-    describe('#render()', () => {
+    describe('#createRenderer()', () => {
 
-      it('should render a mimebundle', () => {
-        let model = createModel({ 'text/plain': 'foo' });
-        let w = r.render(model);
+      it('should create a mime renderer', () => {
+        let w = r.createRenderer('text/plain', false);
         expect(w instanceof Widget).to.be(true);
       });
 
-      it('should return a placeholder for an unregistered mime type', () => {
-        let model = createModel({ 'text/fizz': 'buzz' });
-        let value = r.render(model);
-        expect(value).to.be.a(Widget);
-      });
-
-      it('should render with the mimeType of highest precidence', () => {
-        let model = createModel({
-          'text/plain': 'foo',
-          'text/html': '<h1>foo</h1>'
-        }, true);
-        let w = r.render(model);
-        let el = w.node.firstChild as HTMLElement;
-        expect(el.localName).to.be('h1');
-      });
-
-      it('should render the mimeType that is safe', () => {
-        let model = createModel({
-          'text/plain': 'foo',
-          'text/javascript': 'window.x = 1',
-          'image/png': 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
-        });
-        let w = r.render(model);
-        let el = w.node.firstChild as HTMLElement;
-        expect(el.localName).to.be('img');
-      });
-
-      it('should render the mimeType that is sanitizable', () => {
-        let model = createModel({
-          'text/plain': 'foo',
-          'text/html': '<h1>foo</h1>'
-        });
-        let w = r.render(model);
-        let el = w.node.firstChild as HTMLElement;
-        expect(el.localName).to.be('h1');
-      });
-
-      it('should sanitize html', () => {
-        let model = createModel({
-          'text/html': '<h1>foo <script>window.x=1></scrip></h1>'
-        });
-        let widget = r.render(model);
-        expect(widget.node.innerHTML).to.be('<h1>foo </h1>');
+      it('should raise an error for an unregistered mime type', () => {
+        expect(() => { r.createRenderer('text/fizz', false); }).to.throwError();
       });
 
       it('should render json data', () => {
         let model = createModel({
           'application/json': { 'foo': 1 }
         });
-        let widget = r.render(model);
-        expect(widget.node.textContent).to.be('{\n  "foo": 1\n}');
-      });
-
-      it('should handle an injection', () => {
-        let model = createModel({ 'test/injector': 'foo' });
-        r.render(model);
-        expect(model.data.get('test/injector')).to.be('foo');
+        let widget = r.createRenderer('application/json', false);
+        return widget.render(model).then(() => {
+          expect(widget.node.textContent).to.be('{\n  "foo": 1\n}');
+        });
       });
 
       it('should send a url resolver', (done) => {
         let model = createModel({
           'text/html': '<img src="./foo">foo</img>'
-        }, true);
+        });
         let called = false;
         r.resolver = {
           resolveUrl: (path: string) => {
@@ -173,13 +127,14 @@ describe('rendermime/index', () => {
             return Promise.resolve(path);
           }
         };
-        r.render(model);
+        let w = r.createRenderer('text/html', true);
+        w.render(model);
       });
 
       it('should send a link handler', (done) => {
         let model = createModel({
           'text/html': '<a href="./foo/bar.txt">foo</a>'
-        }, true);
+        });
         r.resolver = RESOLVER;
         r.linkHandler = {
           handleLink: (node: HTMLElement, url: string) => {
@@ -187,9 +142,9 @@ describe('rendermime/index', () => {
             done();
           }
         };
-        r.render(model);
+        let w = r.createRenderer('text/html', true);
+        w.render(model);
       });
-
     });
 
     describe('#preferredMimeType()', () => {
@@ -199,12 +154,12 @@ describe('rendermime/index', () => {
           'text/plain': 'foo',
           'text/html': '<h1>foo</h1>'
         });
-        expect(r.preferredMimeType(model)).to.be('text/html');
+        expect(r.preferredMimeType(model, true)).to.be('text/html');
       });
 
       it('should return `undefined` if there are no registered mimeTypes', () => {
         let model = createModel({ 'text/fizz': 'buzz' });
-        expect(r.preferredMimeType(model)).to.be(void 0);
+        expect(r.preferredMimeType(model, true)).to.be(void 0);
       });
 
       it('should select the mimeType that is safe', () => {
@@ -213,7 +168,7 @@ describe('rendermime/index', () => {
           'text/javascript': 'window.x = 1',
           'image/png': 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
         });
-        expect(r.preferredMimeType(model)).to.be('image/png');
+        expect(r.preferredMimeType(model, false)).to.be('image/png');
       });
 
       it('should render the mimeType that is sanitizable', () => {
@@ -221,7 +176,7 @@ describe('rendermime/index', () => {
           'text/plain': 'foo',
           'text/html': '<h1>foo</h1>'
         });
-        expect(r.preferredMimeType(model)).to.be('text/html');
+        expect(r.preferredMimeType(model, false)).to.be('text/html');
       });
     });
 
@@ -229,64 +184,64 @@ describe('rendermime/index', () => {
 
       it('should clone the rendermime instance with shallow copies of data', () => {
         let c = r.clone();
-        expect(toArray(c.mimeTypes())).to.eql(toArray(r.mimeTypes()));
-        let renderer = new TextRenderer();
-        c.addRenderer({ mimeType: 'text/foo', renderer });
+        expect(toArray(c.mimeTypes)).to.eql(r.mimeTypes);
+        let factory = new TextRendererFactory();
+        c.addFactory(factory, 'text/foo');
         expect(r).to.not.be(c);
       });
 
     });
 
-    describe('#addRenderer()', () => {
+    describe('#addFactory()', () => {
 
-      it('should add a renderer by mimeType', () => {
-        let renderer = new TextRenderer();
-        r.addRenderer({ mimeType: 'text/foo', renderer });
-        let index = toArray(r.mimeTypes()).indexOf('text/foo');
+      it('should add a factory', () => {
+        let factory = new TextRendererFactory();
+        r.addFactory(factory, 'text/foo');
+        let index = r.mimeTypes.indexOf('text/foo');
         expect(index).to.be(0);
       });
 
       it('should take an optional order index', () => {
-        let renderer = new TextRenderer();
-        let len = toArray(r.mimeTypes()).length;
-        r.addRenderer({ mimeType: 'text/foo', renderer }, 0);
-        let index = toArray(r.mimeTypes()).indexOf('text/foo');
+        let factory = new TextRendererFactory();
+        let len = r.mimeTypes.length;
+        r.addFactory(factory, 'text/foo', 0);
+        let index = r.mimeTypes.indexOf('text/foo');
         expect(index).to.be(0);
-        expect(toArray(r.mimeTypes()).length).to.be(len + 1);
+        expect(r.mimeTypes.length).to.be(len + 1);
       });
 
     });
 
-    describe('#removeRenderer()', () => {
+    describe('#removeFactory()', () => {
 
-      it('should remove a renderer by mimeType', () => {
-        r.removeRenderer('text/html');
+      it('should remove a factory by mimeType', () => {
+        r.removeFactory('text/html');
         let model = createModel({ 'text/html': '<h1>foo</h1>' });
-        expect(r.preferredMimeType(model)).to.be(void 0);
+        expect(r.preferredMimeType(model, false)).to.be(void 0);
       });
 
       it('should be a no-op if the mimeType is not registered', () => {
-        r.removeRenderer('text/foo');
+        r.removeFactory('text/foo');
       });
 
     });
 
-    describe('#getRenderer()', () => {
+    describe('#getFactory()', () => {
 
-      it('should get a renderer by mimeType', () => {
-        expect(r.getRenderer('text/plain')).to.be.a(TextRenderer);
+      it('should get a factory by mimeType', () => {
+        expect(r.getFactory('text/plain')).to.be.a(TextRendererFactory);
       });
 
       it('should return undefined for missing mimeType', () => {
-        expect(r.getRenderer('hello/world')).to.be(undefined);
+        expect(r.getFactory('hello/world')).to.be(undefined);
       });
 
     });
 
-    describe('#mimeTypes()', () => {
+    describe('#mimeTypes', () => {
 
       it('should get the ordered list of mimeTypes', () => {
-        expect(toArray(r.mimeTypes()).indexOf('text/html')).to.not.be(-1);
+        expect(r.mimeTypes.indexOf('text/html')).to.not.be(-1);
       });
 
     });

+ 12 - 25
test/src/utils.ts

@@ -28,7 +28,7 @@ import {
 } from '@jupyterlab/notebook';
 
 import {
-  IRenderMime, RenderMime, TextRenderer, HTMLRenderer
+  IRenderMime, RenderMime, TextRendererFactory, RenderedHTML
 } from '@jupyterlab/rendermime';
 
 
@@ -36,7 +36,7 @@ import {
  * Get a copy of the default rendermime instance.
  */
 export
-function defaultRenderMime(): IRenderMime {
+function defaultRenderMime(): RenderMime {
   return Private.rendermime.clone();
 }
 
@@ -152,34 +152,22 @@ namespace Private {
   const notebookFactory = new NotebookModelFactory({});
 
 
-  class JSONRenderer extends HTMLRenderer {
+  class JSONRenderer extends RenderedHTML {
 
-    mimeTypes = ['application/json'];
-
-
-    get ready(): Promise<void> {
-      return Promise.resolve(undefined);
-    }
-
-    render(options: IRenderMime.IRenderOptions): IRenderMime.IReadyWidget {
-      let source = options.model.data.get(options.mimeType);
-      options.model.data.set(options.mimeType, json2html(source));
-      return super.render(options);
+    render(model: IRenderMime.IMimeModel): Promise<void> {
+      let source = model.data.get('application/json');
+      model.data.set('text/html', json2html(source));
+      return super.render(model);
     }
   }
 
 
-  class InjectionRenderer extends TextRenderer {
-
-    mimeTypes = ['test/injector'];
+  class JSONRendererFactory extends TextRendererFactory {
 
-    get ready(): Promise<void> {
-      return Promise.resolve(undefined);
-    }
+    mimeTypes = ['application/json'];
 
-    render(options: IRenderMime.IRenderOptions): IRenderMime.IReadyWidget {
-      options.model.data.set('application/json', { 'foo': 1 } );
-      return super.render(options);
+    createRenderer(options: IRenderMime.IRendererOptions): IRenderMime.IRendererWidget {
+      return new JSONRenderer(options);
     }
   }
 
@@ -187,8 +175,7 @@ namespace Private {
   const rendermime = new RenderMime();
 
   RenderMime.addDefaultFactories(rendermime);
-  rendermime.addFactory(new JSONRenderer(), 'application/json');
-  rendermime.addFactory(new InjectionRenderer(), 'test/injector');
+  rendermime.addFactory(new JSONRendererFactory(), 'application/json');
 }