Browse Source

Merge pull request #74 from blink1073/renderer-tests

Clean up rendermime interface and add rendermime and renderer tests
Dave Willmer 9 years ago
parent
commit
32c2de28b3

+ 53 - 0
examples/filebrowser/sample.md

@@ -0,0 +1,53 @@
+# Title first level
+## Title second Level
+### Title third level
+#### h4
+##### h5
+###### h6
+# h1
+## h2
+### h3
+#### h4
+##### h6
+This is just a sample paragraph
+You can look at different level of nested unorderd list ljbakjn arsvlasc asc asc awsc asc ascd ascd ascd asdc asc
+- level 1
+    - level 2
+    - level 2
+    - level 2
+        - level 3
+        - level 3
+            - level 4
+                - level 5
+                    - level 6
+    - level 2
+- level 1
+- level 1
+- level 1
+Ordered list
+1. level 1
+    2. level 1
+    3. level 1
+        4. level 1
+        1. level 1
+        2. level 1
+            2. level 1
+            3. level 1
+                4. level 1
+                1. level 1
+                2. level 1
+3. level 1
+4. level 1
+some Horizontal line
+***
+and another one
+---
+Colons can be used to align columns.
+| Tables        | Are           | Cool  |
+| ------------- |:-------------:| -----:|
+| col 3 is      | right-aligned | 1600  |
+| col 2 is      | centered      |   12  |
+| zebra stripes | are neat      |    1  |
+There must be at least 3 dashes separating each header cell.
+The outer pipes (|) are optional, and you don't need to make the
+raw Markdown line up prettily. You can also use inline Markdown.

+ 1 - 0
package.json

@@ -39,6 +39,7 @@
     "karma-mocha": "^0.2.1",
     "karma-mocha-reporter": "^1.1.5",
     "mocha": "^2.3.4",
+    "raw-loader": "^0.5.1",
     "rimraf": "^2.5.0",
     "style-loader": "^0.13.0",
     "typedoc": "^0.3.12",

+ 2 - 1
src/renderers/index.ts

@@ -54,7 +54,8 @@ export
 class LatexWidget extends Widget {
   constructor(text: string) {
     super();
-    this.node.textContent = text;
+    text = text.replace(/<br>|\$\$|^\$|\$$|\\\(|\\\)|\\\[|\\\]/g, '');
+    this.node.innerHTML = text;
   }
 
   /**

+ 41 - 7
src/rendermime/index.ts

@@ -47,7 +47,7 @@ class RenderMime<T> {
    * @param renderers - a map of mimetypes to renderers.
    * @param order - a list of mimetypes in order of precedence (earliest one has precedence).
    */
-  constructor(renderers: MimeMap<IRenderer<T>> = {}, order: string[] = []) {
+  constructor(renderers: MimeMap<IRenderer<T>>, order: string[]) {
     this._renderers = {};
     for (let i in renderers) {
       this._renderers[i] = renderers[i];
@@ -63,7 +63,7 @@ class RenderMime<T> {
   render(bundle: MimeMap<string>): T {
     let mimetype = this.preferredMimetype(bundle);
     if (mimetype) {
-        return this.renderers[mimetype].render(mimetype, bundle[mimetype]);
+        return this._renderers[mimetype].render(mimetype, bundle[mimetype]);
     }
   }
 
@@ -84,14 +84,41 @@ class RenderMime<T> {
    * Clone the rendermime instance with shallow copies of data.
    */
   clone(): RenderMime<T> {
-    return new RenderMime<T>(this.renderers, this.order);
+    return new RenderMime<T>(this._renderers, this.order);
   }
 
   /**
-   * Get the renderer map.
+   * Get a renderer by mimetype.
    */
-  get renderers() {
-      return this._renderers;
+  getRenderer(mimetype: string) {
+    return this._renderers[mimetype];
+  }
+
+  /**
+   * Add a renderer by mimetype.
+   *
+   * @param mimetype - The mimetype of the renderer.
+   * @param renderer - The renderer instance.
+   * @param index - The optional order index.
+   */
+  addRenderer(mimetype: string, renderer: IRenderer<T>, index = -1): void {
+    this._renderers[mimetype] = renderer;
+    if (index !== -1) {
+      this._order.splice(index, 0, mimetype);
+    } else {
+      this._order.push(mimetype);
+    }
+  }
+
+  /**
+   * Remove a renderer by mimetype.
+   */
+  removeRenderer(mimetype: string): void {
+    delete this._renderers[mimetype];
+    let index = this._order.indexOf(mimetype);
+    if (index !== -1) {
+      this._order.splice(index, 1);
+    }
   }
 
   /**
@@ -102,7 +129,14 @@ class RenderMime<T> {
    * mimetype is used.
    */
   get order() {
-      return this._order;
+    return this._order.slice();
+  }
+
+  /**
+   * Set the ordered list of mimetypes.
+   */
+  set order(value: string[]) {
+    this._order = value.slice();
   }
 
   private _renderers: MimeMap<IRenderer<T>>;

+ 2 - 10
test/src/index.ts

@@ -2,13 +2,5 @@
 // Distributed under the terms of the Modified BSD License.
 'use strict';
 
-import expect = require('expect.js');
-
-
-describe('jupyter-ui', () => {
-
-  it('should always pass', () => {
-
-  });
-
-});
+import './renderers/renderers.spec';
+import './rendermime/rendermime.spec';

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

@@ -0,0 +1,212 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import expect = require('expect.js');
+
+import {
+  ConsoleTextRenderer, LatexRenderer, PDFRenderer, JavascriptRenderer,
+  SVGRenderer, MarkdownRenderer, TextRenderer, HTMLRenderer, ImageRenderer
+} from '../../../lib/renderers';
+
+
+describe('jupyter-ui', () => {
+
+  describe('ConsoleTextRenderer', () => {
+
+    it('should have the jupyter/console-text mimetype', () => {
+      let mimetypes = ['application/vnd.jupyter.console-text'];
+      let t = new ConsoleTextRenderer();
+      expect(t.mimetypes).to.eql(mimetypes);
+    });
+
+    it('should output the correct HTML', () => {
+      let consoleText = 'x = 2 ** a';
+      let t = new ConsoleTextRenderer();
+      let w = t.render('application/vnd.jupyter.console-text', consoleText);
+      let el = w.node;
+      expect(el.innerHTML).to.be("<pre>x = 2 ** a</pre>");
+      expect(el.textContent).to.be(consoleText);
+    });
+
+    it('should output the correct HTML with ansi colors', () => {
+      let consoleText = '\x1b[01;41;32mtext \x1b[00m';
+      let t = new ConsoleTextRenderer();
+      let w = t.render('application/vnd.jupyter.console-text', consoleText);
+      let el = w.node;
+      expect(el.innerHTML).to.be('<pre><span class="ansi-bright-green-fg ansi-red-bg">text </span></pre>');
+      expect(el.textContent).to.be('text ');
+    });
+
+  });
+
+
+  describe('LatexRenderer', () => {
+
+    it('should have the text/latex mimetype', () => {
+      let t = new LatexRenderer();
+      expect(t.mimetypes).to.eql(['text/latex']);
+    });
+
+    it('should output the correct MathJax script', () => {
+      let latex = '\sum\limits_{i=0}^{\infty} \frac{1}{n^2}';
+      let mathJaxScript = '<script type="math/tex">\sum\limits_{i=0}^{\infty} \frac{1}{n^2}</script>';
+      let t = new LatexRenderer();
+      let w = t.render('text/latex', mathJaxScript);
+      expect(w.node.innerHTML).to.be(mathJaxScript);
+    });
+
+  });
+
+  describe('PDFRenderer', () => {
+
+  it('should have the application/pdf mimetype', () => {
+    let t = new PDFRenderer();
+    expect(t.mimetypes).to.eql(['application/pdf']);
+  });
+
+  it('should output the correct HTML', () => {
+    let base64PDF = "I don't have a b64'd PDF";
+    let t = new PDFRenderer();
+    let w = t.render('application/pdf', base64PDF);
+    expect(w.node.innerHTML).to.be('<a href="data:application/pdf;base64,I don\'t have a b64\'d PDF" target="_blank">View PDF</a>');
+  });
+
+  });
+
+  describe('JavascriptRenderer', () => {
+
+    it('should have the text/javascript mimetype', () => {
+      let mimetypes = ['text/javascript', 'application/javascript'];
+      let t = new JavascriptRenderer();
+      expect(t.mimetypes).to.eql(mimetypes);
+    });
+
+    it('should create a script tag', () => {
+      let t = new JavascriptRenderer();
+      let w = t.render('text/javascript', 'window.x = 1');
+      let el = w.node.firstChild as HTMLElement;
+      expect(el.localName).to.be("script");
+      expect(el.textContent).to.be("window.x = 1");
+
+      // Ensure script has not been run yet
+      expect((window as any).x).to.be(void 0);
+      // Put it on the DOM
+      w.attach(document.body);
+      // Should be evaluated now
+      expect((window as any).x).to.be(1);
+      w.dispose();
+    });
+
+  });
+
+
+  describe('SVGRenderer', () => {
+
+    it('should have the image/svg+xml mimetype', () => {
+      let t = new SVGRenderer();
+      expect(t.mimetypes).to.eql(['image/svg+xml']);
+    });
+
+    it('should create an svg tag', () => {
+      const svg = `
+          <?xml version="1.0" standalone="no"?>
+          <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+          SYSTEM "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+          <svg></svg>
+      `;
+      let t = new SVGRenderer();
+      let w = t.render('image/svg+xml', svg);
+    });
+
+  });
+
+  describe('MarkdownRenderer', () => {
+
+    it('should have the text/markdown mimetype', function() {
+      let t = new MarkdownRenderer();
+      expect(t.mimetypes).to.eql(["text/markdown"]);
+    });
+
+    it('should create nice markup', () => {
+      let md = require('../../../examples/filebrowser/sample.md');
+      let t = new MarkdownRenderer();
+      let w = t.render('text/markdown', md as string);
+      expect(w.node.innerHTML).to.be(`<h1 id="title-first-level">Title first level</h1>\n<h2 id="title-second-level">Title second Level</h2>\n<h3 id="title-third-level">Title third level</h3>\n<h4 id="h4">h4</h4>\n<h5 id="h5">h5</h5>\n<h6 id="h6">h6</h6>\n<h1 id="h1">h1</h1>\n<h2 id="h2">h2</h2>\n<h3 id="h3">h3</h3>\n<h4 id="h4">h4</h4>\n<h5 id="h6">h6</h5>\n<p>This is just a sample paragraph\nYou can look at different level of nested unorderd list ljbakjn arsvlasc asc asc awsc asc ascd ascd ascd asdc asc</p>\n<ul>\n<li>level 1<ul>\n<li>level 2</li>\n<li>level 2</li>\n<li>level 2<ul>\n<li>level 3</li>\n<li>level 3<ul>\n<li>level 4<ul>\n<li>level 5<ul>\n<li>level 6</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>level 2</li>\n</ul>\n</li>\n<li>level 1</li>\n<li>level 1</li>\n<li>level 1\nOrdered list</li>\n<li>level 1<ol>\n<li>level 1</li>\n<li>level 1<ol>\n<li>level 1</li>\n<li>level 1</li>\n<li>level 1<ol>\n<li>level 1</li>\n<li>level 1<ol>\n<li>level 1</li>\n<li>level 1</li>\n<li>level 1</li>\n</ol>\n</li>\n</ol>\n</li>\n</ol>\n</li>\n</ol>\n</li>\n<li>level 1</li>\n<li>level 1\nsome Horizontal line</li>\n</ul>\n<hr>\n<h2 id="and-another-one">and another one</h2>\n<p>Colons can be used to align columns.\n| Tables        | Are           | Cool  |\n| ------------- |:-------------:| -----:|\n| col 3 is      | right-aligned | 1600  |\n| col 2 is      | centered      |   12  |\n| zebra stripes | are neat      |    1  |\nThere must be at least 3 dashes separating each header cell.\nThe outer pipes (|) are optional, and you don\'t need to make the\nraw Markdown line up prettily. You can also use inline Markdown.</p>\n`);
+    });
+
+  });
+
+  describe('TextRenderer', () => {
+
+    it('should have the text/plain mimetype', () => {
+      let t = new TextRenderer();
+      expect(t.mimetypes).to.eql(['text/plain']);
+    });
+
+    it('should create a pre with all the passed in elements', () => {
+      const text = 'There is no text but text.\nWoo.';
+      let t = new TextRenderer();
+      let w = t.render('text/plain', text);
+      let el = w.node.firstChild as HTMLElement;
+      expect(el.innerHTML).to.be(text);
+      expect(el.textContent).to.be(text);
+      expect(el.localName).to.be('pre');
+    });
+
+  });
+
+  describe('HTMLRenderer', () => {
+
+    it('should have the text/html mimetype', () => {
+      let t = new HTMLRenderer();
+      expect(t.mimetypes).to.eql(['text/html']);
+    });
+
+    it('should create a div with all the passed in elements', () => {
+      let t = new HTMLRenderer();
+      const htmlText = '<h1>This is great</h1>';
+      let w = t.render('text/html', htmlText);
+      let el = w.node.firstChild as HTMLElement;
+      expect(el.innerHTML).to.be('This is great');
+    });
+
+    it('should execute a script tag when attached', () => {
+      const htmlText = '<script>window.y=3;</script>';
+      let t = new HTMLRenderer();
+      let w = t.render('text/html', htmlText);
+      expect((window as any).y).to.be(void 0);
+      w.attach(document.body);
+      expect((window as any).y).to.be(3);
+      w.dispose();
+    });
+
+  });
+
+  describe('ImageRenderer', () => {
+
+    it('should support multiple mimetypes', () => {
+      let t = new ImageRenderer();
+      expect(t.mimetypes).to.eql(['image/png', 'image/jpeg', 'image/gif']);
+    });
+
+    it('should create an <img> with the right mimetype', () => {
+      const imageData = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
+      let t = new ImageRenderer();
+      let w = t.render('image/png', imageData);
+      let el = w.node.firstChild as HTMLImageElement;
+      expect(el.src).to.be('data:image/png;base64,' + imageData);
+      expect(el.localName).to.be('img');
+      expect(el.innerHTML).to.be('');
+
+      const imageData2 = 'R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs='
+      w = t.render('image/gif', imageData2);
+      el = w.node.firstChild as HTMLImageElement;
+      expect(el.src).to.be('data:image/gif;base64,' + imageData2)
+      expect(el.localName).to.be('img');
+      expect(el.innerHTML).to.be('');
+    });
+
+  });
+
+});

+ 190 - 0
test/src/rendermime/rendermime.spec.ts

@@ -0,0 +1,190 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import expect = require('expect.js');
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  ConsoleTextRenderer, LatexRenderer, PDFRenderer, JavascriptRenderer,
+  SVGRenderer, MarkdownRenderer, TextRenderer, HTMLRenderer, ImageRenderer
+} from '../../../lib/renderers';
+
+import {
+  IRenderer, MimeMap, RenderMime
+} from '../../../lib/rendermime';
+
+
+const TRANSFORMERS = [
+  new JavascriptRenderer(),
+  new MarkdownRenderer(),
+  new HTMLRenderer(),
+  new ImageRenderer(),
+  new SVGRenderer(),
+  new LatexRenderer(),
+  new ConsoleTextRenderer(),
+  new TextRenderer()
+];
+
+
+function defaultRenderMime(): RenderMime<Widget> {
+  let renderers: MimeMap<IRenderer<Widget>> = {};
+  let order: string[] = [];
+  for (let t of TRANSFORMERS) {
+    for (let m of t.mimetypes) {
+      renderers[m] = t;
+      order.push(m);
+    }
+  }
+  return new RenderMime<Widget>(renderers, order);
+}
+
+
+describe('jupyter-ui', () => {
+
+  describe('RenderMime', () => {
+
+    describe('#constructor()', () => {
+
+      it('should accept a mapping and a default order', () => {
+        let r = defaultRenderMime();
+        expect(r instanceof RenderMime).to.be(true);
+      });
+
+    });
+
+    describe('#render()', () => {
+
+      it('should render a mimebundle', () => {
+        let r = defaultRenderMime();
+        let w = r.render({ 'text/plain': 'foo' });
+        expect(w instanceof Widget).to.be(true);
+      });
+
+      it('should return `undefined` for an unregistered mime type', () => {
+        let r = defaultRenderMime();
+        expect(r.render({ 'text/fizz': 'buzz' })).to.be(void 0);
+      });
+
+      it('should render with the mimetype of highest precidence', () => {
+        let bundle: MimeMap<string> = {
+          'text/plain': 'foo',
+          'text/html': '<h1>foo</h1>'
+        }
+        let r = defaultRenderMime();
+        let w = r.render(bundle);
+        let el = w.node.firstChild as HTMLElement;
+        expect(el.localName).to.be('h1');
+      });
+
+    });
+
+    describe('#preferredMimetype()', () => {
+
+      it('should find the preferred mimetype in a bundle', () => {
+        let bundle: MimeMap<string> = {
+          'text/plain': 'foo',
+          'text/html': '<h1>foo</h1>'
+        }
+        let r = defaultRenderMime();
+        expect(r.preferredMimetype(bundle)).to.be('text/html');
+      });
+
+      it('should return `undefined` if there are no registered mimetypes', () => {
+        let r = defaultRenderMime();
+        expect(r.preferredMimetype({ 'text/fizz': 'buzz' })).to.be(void 0);
+      });
+
+    });
+
+    describe('#clone()', () => {
+
+      it('should clone the rendermime instance with shallow copies of data', () => {
+        let r = defaultRenderMime();
+        let c = r.clone();
+        expect(c.order).to.eql(r.order);
+        expect(c.getRenderer('text/html')).to.be(r.getRenderer('text/html'));
+        let t = new TextRenderer();
+        c.addRenderer('text/foo', t);
+        expect(c.getRenderer('text/foo')).to.be(t);
+        expect(r.getRenderer('text/foo')).to.be(void 0);
+        expect(r).to.not.be(c);
+      });
+
+    });
+
+    describe('#getRenderer()', () => {
+
+      it('should get a renderer by mimetype', () => {
+        let r = defaultRenderMime();
+        let t = r.getRenderer('text/latex');
+        expect(t.mimetypes.indexOf('text/latex')).to.not.be(-1);
+      });
+
+      it('should return `undefined` for an unregistered type', () => {
+        let r = defaultRenderMime();
+        expect(r.getRenderer('text/foo')).to.be(void 0);
+      });
+
+    });
+
+    describe('#addRenderer()', () => {
+
+      it('should add a renderer by mimetype', () => {
+        let r = defaultRenderMime();
+        let t = new TextRenderer();
+        r.addRenderer('text/foo', t);
+        expect(r.getRenderer('text/foo')).to.be(t);
+        let index = r.order.indexOf('text/foo');
+        expect(index).to.be(r.order.length - 1);
+      });
+
+      it('should take an optional order index', () => {
+        let r = defaultRenderMime();
+        let t = new TextRenderer();
+        let len = r.order.length;
+        r.addRenderer('text/foo', t, 0);
+        let index = r.order.indexOf('text/foo');
+        expect(index).to.be(0);
+        expect(r.order.length).to.be(len + 1);
+      });
+
+    });
+
+    describe('#removeRenderer()', () => {
+
+      it('should remove a renderer by mimetype', () => {
+        let r = defaultRenderMime();
+        r.removeRenderer('text/html');
+        expect(r.getRenderer('text/html')).to.be(void 0);
+      });
+
+      it('should be a no-op if the mimetype is not registered', () => {
+        let r = defaultRenderMime();
+        r.removeRenderer('text/foo');
+      });
+
+    });
+
+    describe('#order', () => {
+
+      it('should get the ordered list of mimetypes', () => {
+        let r = defaultRenderMime();
+        expect(r.order.indexOf('text/html')).to.not.be(-1);
+      });
+
+      it('should set the ordered list of mimetypes', () => {
+        let r = defaultRenderMime();
+        let order = r.order.reverse();
+        r.order = order;
+        expect(r.order).to.eql(order);
+      });
+
+    });
+
+  });
+
+});

+ 2 - 6
test/src/tsconfig.json

@@ -6,9 +6,5 @@
         "moduleResolution": "node",
         "target": "ES5",
         "outDir": "../build"
-    },
-    "files": [
-        "index.ts",
-        "typings.d.ts"
-    ]
-}
+    }
+}

+ 1 - 0
test/src/typings.d.ts

@@ -1,2 +1,3 @@
 /// <reference path="../../typings/expect.js/expect.js.d.ts"/>
 /// <reference path="../../typings/mocha/mocha.d.ts"/>
+/// <reference path="../../typings/require/require.d.ts"/>

+ 1 - 0
test/webpack-cov.conf.js

@@ -9,6 +9,7 @@ module.exports = {
   module: {
     loaders: [
       { test: /\.css$/, loader: 'style-loader!css-loader' },
+      { test: /\.md$/, loader: 'raw-loader'}
     ],
     preLoaders: [
       // instrument only testing sources with Istanbul

+ 1 - 0
test/webpack.conf.js

@@ -9,6 +9,7 @@ module.exports = {
   module: {
     loaders: [
       { test: /\.css$/, loader: 'style-loader!css-loader' },
+      { test: /\.md$/, loader: 'raw-loader'}
     ],
   }
 }