// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import 'jest'; const sampleData = require('../../../examples/filebrowser/sample.md'); import { JSONObject, JSONValue } from '@lumino/coreutils'; import { Widget } from '@lumino/widgets'; import { defaultSanitizer } from '@jupyterlab/apputils'; import { latexRendererFactory, svgRendererFactory, markdownRendererFactory, textRendererFactory, htmlRendererFactory, imageRendererFactory } from '../src'; import { MimeModel, IRenderMime } from '../src'; function createModel( mimeType: string, source: JSONValue, trusted = false ): IRenderMime.IMimeModel { const data: JSONObject = {}; data[mimeType] = source; return new MimeModel({ data, trusted }); } const sanitizer = defaultSanitizer; const defaultOptions: any = { sanitizer, linkHandler: null, resolver: null }; describe('rendermime/factories', () => { describe('textRendererFactory', () => { describe('#mimeTypes', () => { it('should have text related mimeTypes', () => { const mimeTypes = [ 'text/plain', 'application/vnd.jupyter.stdout', 'application/vnd.jupyter.stderr' ]; expect(textRendererFactory.mimeTypes).toEqual(mimeTypes); }); }); describe('#safe', () => { it('should be safe', () => { expect(textRendererFactory.safe).toBe(true); }); }); describe('#createRenderer()', () => { it('should output the correct HTML', async () => { const f = textRendererFactory; const mimeType = 'text/plain'; const model = createModel(mimeType, 'x = 2 ** a'); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); expect(w.node.innerHTML).toBe('
x = 2 ** a'); }); it('should be re-renderable', async () => { const f = textRendererFactory; const mimeType = 'text/plain'; const model = createModel(mimeType, 'x = 2 ** a'); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); await w.renderModel(model); expect(w.node.innerHTML).toBe('
x = 2 ** a'); }); it('should output the correct HTML with ansi colors', async () => { const f = textRendererFactory; const source = 'There is no text but \x1b[01;41;32mtext\x1b[00m.\nWoo.'; const mimeType = 'application/vnd.jupyter.console-text'; const model = createModel(mimeType, source); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); expect(w.node.innerHTML).toBe( '
There is no text but text.\nWoo.
'
);
});
it('should escape inline html', async () => {
const f = textRendererFactory;
const source =
'There is no text but \x1b[01;41;32mtext\x1b[00m.\nWoo.';
const mimeType = 'application/vnd.jupyter.console-text';
const model = createModel(mimeType, source);
const w = f.createRenderer({ mimeType, ...defaultOptions });
await w.renderModel(model);
expect(w.node.innerHTML).toBe(
'There is no text <script>window.x=1</script> but text.\nWoo.
'
);
});
it('should autolink URLs', async () => {
const f = textRendererFactory;
const urls = [
'https://example.com',
'https://example.com/',
'https://example.com#anchor',
'http://localhost:9090/app',
'http://localhost:9090/app/',
'http://127.0.0.1/test?query=string'
];
await Promise.all(
urls.map(async url => {
const source = `Here is some text with an URL such as ${url} inside.`;
const mimeType = 'text/plain';
const model = createModel(mimeType, source);
const w = f.createRenderer({ mimeType, ...defaultOptions });
await w.renderModel(model);
expect(w.node.innerHTML).toBe(
`Here is some text with an URL such as ${url} inside.` ); }) ); }); }); }); describe('latexRendererFactory', () => { describe('#mimeTypes', () => { it('should have the text/latex mimeType', () => { expect(latexRendererFactory.mimeTypes).toEqual(['text/latex']); }); }); describe('#safe', () => { it('should be safe', () => { expect(latexRendererFactory.safe).toBe(true); }); }); describe('#createRenderer()', () => { it('should set the textContent of the widget', async () => { const source = 'sumlimits_{i=0}^{infty} \frac{1}{n^2}'; const f = latexRendererFactory; const mimeType = 'text/latex'; const model = createModel(mimeType, source); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); expect(w.node.textContent).toBe(source); }); it('should be re-renderable', async () => { const source = 'sumlimits_{i=0}^{infty} \frac{1}{n^2}'; const f = latexRendererFactory; const mimeType = 'text/latex'; const model = createModel(mimeType, source); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); await w.renderModel(model); expect(w.node.textContent).toBe(source); }); }); }); describe('svgRendererFactory', () => { describe('#mimeTypes', () => { it('should have the image/svg+xml mimeType', () => { expect(svgRendererFactory.mimeTypes).toEqual(['image/svg+xml']); }); }); describe('#safe', () => { it('should not be safe', () => { expect(svgRendererFactory.safe).toBe(false); }); }); describe('#createRenderer()', () => { it('should create an img element with the uri encoded svg inline', async () => { const source = ''; const displaySource = ''; const f = svgRendererFactory; const mimeType = 'image/svg+xml'; const model = createModel(mimeType, source, true); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); const imgEl = w.node.getElementsByTagName('img')[0]; expect(imgEl).toBeTruthy(); expect(imgEl.src).toContain(encodeURIComponent(displaySource)); }); }); }); describe('markdownRendererFactory', () => { describe('#mimeTypes', () => { it('should have the text/markdown mimeType', function() { expect(markdownRendererFactory.mimeTypes).toEqual(['text/markdown']); }); }); describe('#safe', () => { it('should be safe', () => { expect(markdownRendererFactory.safe).toBe(true); }); }); describe('#createRenderer()', () => { it('should set the inner html', async () => { const f = markdownRendererFactory; const source = '
hello
'; const mimeType = 'text/markdown'; const model = createModel(mimeType, source); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); expect(w.node.innerHTML).toBe(source); }); it('it should be re-renderable', async () => { const f = markdownRendererFactory; const source = 'hello
'; const mimeType = 'text/markdown'; const model = createModel(mimeType, source); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); await w.renderModel(model); expect(w.node.innerHTML).toBe(source); }); it('should add header anchors', async () => { const f = markdownRendererFactory; const mimeType = 'text/markdown'; const model = createModel(mimeType, sampleData); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); Widget.attach(w, document.body); const node = document.getElementById('Title-third-level')!; expect(node.localName).toBe('h3'); const anchor = node.firstChild!.nextSibling as HTMLAnchorElement; expect(anchor.href).toContain('#Title-third-level'); expect(anchor.target).toBe('_self'); expect(anchor.className).toContain('jp-InternalAnchorLink'); expect(anchor.textContent).toBe('ΒΆ'); Widget.detach(w); }); it('should sanitize the html', async () => { const f = markdownRendererFactory; const source = 'hello
'; const mimeType = 'text/markdown'; const model = createModel(mimeType, source); const w = f.createRenderer({ mimeType, ...defaultOptions }); await w.renderModel(model); expect(w.node.innerHTML).toEqual( expect.not.arrayContaining(['script']) ); }); }); }); describe('htmlRendererFactory', () => { describe('#mimeTypes', () => { it('should have the text/html mimeType', () => { expect(htmlRendererFactory.mimeTypes).toEqual(['text/html']); }); }); describe('#safe', () => { it('should be safe', () => { expect(htmlRendererFactory.safe).toBe(true); }); }); describe('#createRenderer()', () => { it('should set the inner HTML', async () => { const f = htmlRendererFactory; const source = '